diff --git a/configuration/aws-automation/README.txt b/configuration/aws-automation/README.txt deleted file mode 100644 index 36cd7a8e0cf..00000000000 --- a/configuration/aws-automation/README.txt +++ /dev/null @@ -1,169 +0,0 @@ -Beschreibung des Skripts: - -0. Voraussetzungen f�r Nutzung des Skripts - -- AWS CLI (https://aws.amazon.com/de/cli) -- Cygwin Packages: --- tmux --- jq --- openssh --- wget --- curl - -0.1 AWS CLI konfigurieren - -"aws configure" durchf�hren. -Falls noch kein Keypair vorhanden: - -IAM-Konsole >> Users >> dein Benutzername >> Security Credentials >> Create access key - -0.2 Default-Werte anpassen - -Default-Werte in "aws_variables.sh" anpassen (key_name, key_file), damit diese nicht -immer neu eingegeben werden m�ssen. Testweise erst einmal in Region "eu-west-2" bleiben. - -key_name: Name des Keypairs zur Verbindung zur Instanz -key_file: Pfad zur Datei -... - -0.3 Skript ausprobieren - -./aws-setup.sh --help -./aws-setup instance-with-load-balancer - -Falls Fehler auftaucht: -Skript mit "-d"-Parameter starten und in den Logs der Instanz nachsehen -(./aws-setup.sh --tail --public-dns-name ec2-x...amazon.com) - -1. Aufbau: - -1.1 Dateien und ihre Hauptaufgaben: - -aws-setup-sh: -Parameteraufnahme, Main-Funktion, Dokumentation der Bedienung, Sourcing von lib/utils.sh - -lib/aws-functions.sh: -Funktionen f�r AWS (z.B. Load Balancer erstellen) und App-Instanz (z.B. Event erstellen) - -lib/aws-variables.sh: -Festlegung von regionspezifischen Konstanten (z.B Hosted Zone ID) und Defaultwerten (z.B Region) - -lib/scenario_instance.sh: -Erstellung einer Instanz und anschlie�endes Hinzuf�gen zu einem Load Balancer der �ber einen Route53 Eintrag -mit einer bestimmten Domain (z.B. wcs17.sapsailing.com) verkn�pft wird. - -lib/scenario_tail.sh: -Automatische Anzeige der Log-Dateien einer Instanz mithilfe von tmux - -lib/tmux_functions.sh: -Oberfl�chenkonstruktion, Vorbedingungstest, Befehlsausf�hrung - -lib/util_functions.sh: -Erleichterung der Benutzereingabe, Validierungsfunktionen, sonstige potenziell relevante Hilfsfunktionen - -lib/util_variables.sh: -Sonstiges Hilfsvariablen (Zeitstempel, Skriptname, etc.) - -lib/utils.sh: -Ausgabe von farbigen Meldungen, Logging (unbenutzt), Sourcing der restlichen Bash-Dateien im Verzeichnis - -1.2 Szenarios - -Szenarios sind gekapselte Ausf�hrungseinheiten, die durch Orchestrierung von AWS-Funktionen oder Bereitstellung -eigener Funktionalit�t einen Mehrwert f�r die Automatisierung darstellen. - -Szenarios beinhalten folgende Funktionen: - -- Funktion zum Start des Ausf�hrung des Szenarios -- Funktion zur �berpr�fung von Vorbedingungen (Abh�ngigkeiten von Paketen oder Umgebungsvariablen) [optional] -- Funktion zur Sicherstellung der Initialisierung von ben�tigen Variablen -- Funktion zur Ausf�hrung der Programmlogik - -2. Funktionalit�t - -2.1 Initialisierung von ben�tigten Variablen - -Jedes Szenario ben�tigt f�r seine Ausf�hrung bestimmte inititalisierte Variablen. Die Zuweisung eines Werts zu einer Variable erfolgt -entweder beim Start des Skripts �ber die Mitgabe eines Parameters oder falls kein Parameter �bergeben worden ist, �ber die Eingabe des -Benutzers nach entsprechender Aufforderung. -Wenn der Benutzer zur Eingabe eines Werts f�r eine Variable aufgefordert wird, wird automatisch ein Default-Wert aus der -Datei "aws_variables.sh" vorgeschlagen. Dies erleichtert die Benutzereingabe, da sich bestimmte Werte, wie z.B. der Name -des Keypairs zur Verbindung einer Instanz selten �ndern. In der Datei werden au�erdem regionspezifische Variablenwerte -(z.B. IDs der Sicherheitsgruppen oder Images) festgelegt. - -2.2 Fehlerbehandlung - -Um die erfolgreiche Ausf�hrung einer Funktion zu �berpr�fen werden verschiedene Strategien verfolgt: - -1. �berpr�fung des R�ckgabewerts einer Funktion - -Diese Strategie wird bei AWS-spezifischen Funktionen angewandt. Bei Aufruf eines AWS-Kommandos (z.B. aws elb create-load-balancer) wird -bei Erfolg der R�ckgabewert 0 zur�ckgegeben und bei Vorkommen eines Fehlers der R�ckgabewert 1. - -2. �berpr�fen des HTTP-Codes - -Bei Aufruf eines curl-Kommandos wird die Option -w "\n%{http_code}" mit�bergeben. Dabei werden Response- und HTTP-Code auf zwei Zeilen -aufgeteilt und damit erm�glicht, dass nach Aufruf der Funktion die Zuordnung zu zwei verschiedenen Variablen erfolgt. - -2.3 Meldungsausgabe - -Innerhalb von Methoden wird der Nutzer �ber Erfolg oder Misserfolg benachrichtigt. Die Ausgabe erfolgt �ber den -Standardfehlerkanal "stderr" (Workaround). - -2.4 Tail mit tmux - -Beim Verwenden des Parameters --tail werden automatisch relevante Log-Dateien angezeigt. -Der Befehl ist in Kombination mit dem Parameter --public-dns-name oder auch mit dem Szenario ---instance-with-load-balancer verwendbar. Zur Ausf�hrung muss das Skript in einer tmux-Session -ausgef�hrt werden. - -Beispiele: -./aws-setup.sh --tail --public-dns-name ec2-xxx.eu-west-2.compute.amazonaws.com -Zeigt relevante Log-Dateien der Instanz mit dem DNS Namen "ec2-xxx.eu-west-2.compute.amazonaws.com" an. - -./aws-setup.sh --tail --instance-with-load-balancer -Zeigt relevante Log-Dateien der Instanz die gerade erstellt wird, ab dem Zeitpunkt der Aufnahme der SSH-Verbindung - -3. TODO - -3.1 Fehlerbehebungen - -- Nutzer erstellen scheitert bisher mit "Precondition failed" (Nutzer- - name erf�llt Richtlinie nicht) - -3.2 Verbesserungen - -- run-instance Funktion nach "aws_functions.sh" faktorisieren -- Anpassung der Dateierzeugung an Linux ("crlf" ersetzen) -- Momentan wird der Admin-Nutzer als Eventersteller verwendet. - Die Rechtevergabe an einen anderen Nutzer �ber die REST-API - muss noch implementiert werden. - -3.2 Erweiterungen - -- list-Funktion mit Anzeige aller Instanznamen und deren DNS, Instanz-ID, etc. damit - --tail nicht die Web-GUI erfordert -- weitere Szenarios hinzuf�gen (Redeployment, R�ckmigration in Archiv, etc.) -- regionspezifisches Sourcing von Dateien mit Variablen (Auskommentieren nicht mehr notwendig) -- automatische Kontext�bernahme nach tmux - - -Getestet unter Microsoft Windows [Version 10.0.10586]: -- Cygwin 2.8.2(0.313/5/3) -- aws-cli/1.11.129 -- tmux 2.8.2(0.313/5/3) -- openssh 7.5p1-1 -- wget 1.19.1 -- curl 7.53.0 - -Funktioniert (Route53-Eintrag Erstellung konnte nicht getestet werden) - -Getestet unter Microsoft Windows [Version 6.3.96001]: -- Cygwin 2.9.0(0.318/5/3) -- aws-cli/1.11.166 -- tmux 2.4 -- openssh 7.4p1 -- wget 1.19.1 -- curl 7.55.1-1 - -Funktioniert (Route53-Eintrag Erstellung konnte nicht getestet werden) diff --git a/configuration/aws-automation/README_en.txt b/configuration/aws-automation/README_en.txt new file mode 100644 index 00000000000..2bb0f9a2c98 --- /dev/null +++ b/configuration/aws-automation/README_en.txt @@ -0,0 +1,164 @@ +Description: + +0. Requirements for use + 0.1 Configure AWS CLI + 0.2 Adjust default values + 0.3 Show manual + +1. Structure + 1.1 Files and their tasks + 1.2 Scenarios + +2. Functionality + 2.1 Initialization of required variables + 2.2 Error handling + +3. TODO + 3.1 Bug fixes + 3.2 Improvements + 3.3 Extensions + +0. Requirements for use + +- AWS CLI (https://aws.amazon.com/de/cli) +- Cygwin Packages: +-- tmux +-- jq +-- openssh +-- wget +-- curl + +0.1 Configure AWS CLI + +Enter "aws configure" into the console. Enter your Access Key and Secret Access Key of your AWS user. +If not already available, a corresponding key can be generated via the AWS Web console. + +To do this, a privileged user navigates to the following entry within the AWS Web console: +IAM Console >> Users >> Your Username >> Security Credentials >> Create access key + +0.2 Adjust default values + +To avoid repeated input of user-specific information, the file aws_variables.sh contains default values ​​ +for variables. It is possible to set the default region and default key name for instance. + +0.3 Show manual + +Call ./aws-setup.sh or ./aws-setup.sh --help to display instructions for usage. + +1. Structure + +1.1 Files and their tasks + +aws-setup.sh: +Functions for parameter recording, sourcing and documentation. Entry point of program. + +lib/functions_app.sh: +Functions for modifying the state of the SAP Sailing Analytics instance (event creation, password change, apache configuration). + +lib/functions_ec2.sh: +Functions for creating EC2 instances and querying their attributes. + +lib/functions_elb.sh: +Functions for creating load balancers, listeners, rules and target groups. + +lib/functions_io.sh: +Functions for processing user input and initializing variables. + +lib/functions_route53.sh: +Functions for creating Route53 entries. + +lib/functions_tmux.sh: +Functions for executing commands via tmux and for constructing the user interface. + +lib/functions_wrapper.sh: +Functions for error handling of functions and simplification of repeated calls. + +lib/scenario_associate_alb.sh, +lib/scenario_associate_clb.sh, +lib/scenario_associate_elastic_ip.sh, +lib/scenario_instance.sh, +lib/scenarion_tail.sh: +See script-internal documentation + +lib/util_functions.sh: +Features to facilitate user input, validation features and other potentially relevant helper functions. + +lib/util_variables.sh: +Miscellaneous auxiliary variables (time stamp, script name, etc.) + +lib/utils.sh: +Functions for the output of colored messages, logging and sourcing of remaining bash files + +1.2 Scenarios + +Scenarios are encapsulated execution units that are orchestrated by AWS and instance-specific functions +to automate a specific use case. + +Scenarios include the following functions: + +- Function to start the execution of the scenario +- Function to check preconditions (dependencies of packages or environment variables) [optional] +- Function to ensure the initialization of variables needed +- Function to execute the program logic + +2. Functionality + +2.1 Initialization of required variables + +Each scenario requires certain initialized variables for its execution. The assignment of a value to a variable takes place +either at the start of the script via the input of a parameter or if no parameter has been transferred, by input of the user after being prompted. +When the user is prompted to enter a value for a variable, he is being offered a default value out of the aws_variables.sh file. +This facilitates user input because certain values, such as the standard key name to connect to an instance don't change very often. + +2.2 Error handling + +To check the successful execution of a function, various strategies are used: + +1. Checking the return value of a function + +This strategy is applied to AWS-specific functions. When calling an AWS command (e.g. aws elb create-load-balancer) +the return value is either 0 (error occured) or 1 (no error occured). + +2. Checking the HTTP status code + +When a curl command is called, the option -w "\ n% {http_code}" is also passed as a parameter. In doing so, response and HTTP code can be assigned +to two different variables, allowing separate processing. The HTTP status code can then be compared to the expected result, without loosing information +about the actual response. + +3. TODO + +3.1 Bug fixes + +- Creating user fails with message "Precondition failed (username does not meet policy)" + +3.2 Improvements + +- Factorize run-instance() function + +3.3 Extensions + +- list() function with output of all instance names and their DNS name, instance id, etc. so that --tail does not require the web GUI anymore +- More detailed output of intermediate results of the script via the console +- Add more scenarios (Multi-Instance-Setup, Shutdown, Replica Mgmt.) +- region-specific sourcing of files with variables, so that commenting out is no longer necessary + + +Tested under Microsoft Windows [Version 10.0.10586]: +- Cygwin 2.8.2(0.313/5/3) +- aws-cli/1.11.129 +- tmux 2.8.2(0.313/5/3) +- openssh 7.5p1-1 +- wget 1.19.1 +- curl 7.53.0 + +=> Works (Route53 entry creation could not be tested yet) + +Tested under Microsoft Windows [Version 6.3.96001]: +- Cygwin 2.9.0(0.318/5/3) +- aws-cli/1.11.166 +- tmux 2.4 +- openssh 7.4p1 +- wget 1.19.1 +- curl 7.55.1-1 + +=> Works (Route53 entry creation could not be tested yet) diff --git a/configuration/aws-automation/aws-setup.sh b/configuration/aws-automation/aws-setup.sh index 4c7abef6a1c..aebd8831965 100755 --- a/configuration/aws-automation/aws-setup.sh +++ b/configuration/aws-automation/aws-setup.sh @@ -27,7 +27,7 @@ function trapCleanup() { function safeExit() { deleteTemp trap - INT TERM EXIT - exit + exit 77 } function deleteTemp () { @@ -46,10 +46,10 @@ args=() # Create temp directory with three random numbers and the process ID # in the name. This directory is removed automatically at exit. -tmpDir="./tmp.$RANDOM.$RANDOM.$RANDOM.$$/" -(umask 000 && mkdir "${tmpDir}") || { - die "Could not create temporary directory! Exiting." -} +#tmpDir="/tmp/$RANDOM.$RANDOM.$RANDOM.$$/" +#(umask 000 && mkdir "${tmpDir}") || { +# die "Could not create temporary directory! Exiting." +#} # Logging (not used) # To never save a logfile change variable to '/dev/null' @@ -60,28 +60,44 @@ logFile="$HOME/Library/Logs/${scriptBasename}.log" function mainScript() { echo -n -if $tail; then - check_if_tmux_is_used +local config_file=~/.aws-automation/config + +if is_exists ${config_file}; then + source "${config_file}" fi -if $instance; then +require_region + +local resource_file=./lib/resources-$region.sh +local region_config_file=~/.aws-automation/config-$region + +if is_exists ${resource_file}; then + source "${resource_file}" +fi + +if is_exists ${region_config_file}; then + source "${region_config_file}" +fi + +init_resources + +if $instance_scenario; then instance_start - if $associate_clb; then - associate_clb_start - fi - if $associate_elastic_ip; then - associate_elastic_ip_start - fi - if $associate_alb; then - associate_alb_start - fi - confirm_reset_panes safeExit fi -if $tail ; then - tail_start - confirm_reset_panes +if $shared_instance_scenario; then + shared_instance_start + safeExit +fi + +if $master_instance_scenario; then + master_instance_start + safeExit +fi + +if $associate_alb_scenario; then + associate_alb_start safeExit fi @@ -105,18 +121,22 @@ usage() { -l, --instance-short-name Short name for instance (e.g. subdomain \"wcs17\") -a, --new-admin-password New password for the admin user -p, --public-dns-name Dns name of instance (e.g. \"ec2-35-176...amazonaws.com\") + -z, --super-instance Dns name of superior instance (e.g. base instance for sub instances) + -m, --event-name Name of event + -b, --build Build version to use (leave empty for latest) + -w, --description Description of sub instance + -c, --contact-person Contact person + -e, --contact-email Email of contact person -f, --force Skip user input and use default variables -d, --debug Debug mode ${bold}Scenarios:${reset} --instance Create instance + --shared-instance Create sub instance + --master-instance Create master instance --associate-alb Associate instance with existing application load balancer whos listener is defined in variables_aws.sh. Automatically create necessary target group and host name rule. - Currently *.dummy.sapsailing.com is used for test purposes. - --associate-clb Associate instance with new classic load balancer. - --asslociate-elastic-ip Associate instance with new elastic ip. - --tail Tail logs from instance using tmux ${bold}Other:${reset} --version Output version information and exit @@ -129,31 +149,15 @@ usage() { Associate application load balancer: > ./aws-setup.sh --instance --associate-alb - Associate instance with classic load balancer while - automatically tailing important log files (tmux required): - > ./aws-setup.sh --instance --associate-clb --tail - - Associate instance with elastic ip: - > ./aws-setup.sh --instance --associate-elastic-ip - - Tail logfiles of running instance with dns name: - > ./aws-setup.sh --tail --public-dns-name ec2-x.compute.amazonaws.com + Associate instance with application load balancer: + > ./aws-setup.sh --instance --associate-alb Create instance and use default values: > ./aws-setup.sh --instance --force Create instance and use default values except instance name - and instance short name: - > ./aws-setup.sh --instance --instance-name Test --instance-short-name t --force - - Associate instance with classic load balancer by - passing all relevant parameters to script. - Also use debug mode. - > ./aws-setup.sh --region eu-west-2 --instance-type t2.medium - --key-name leonradeck-keypair --key-file /cygdrive/c/Users/d069485/ - .ssh/leonradeck-keypair.pem --user-username test --user-password test - --instance-name \"WC Santander 2017\" --instance-short-name test - --new-admin-password admin -d --instance-with-load-balancer + and instance short name. Also activate debug mode. + > ./aws-setup.sh --instance --instance-name Test --instance-short-name t --force -d " @@ -200,11 +204,10 @@ unset options [[ $# -eq 0 ]] && set -- "--help" # Set default value of variable without parameter value to false -associate_clb=false -associate_alb=false -associate_elastic_ip=false -instance=false -tail=false +associate_alb_scenario=false +instance_scenario=false +shared_instance_scenario=false +master_instance_scenario=false # Read the options and set variables while [[ $1 = -?* ]]; do @@ -222,13 +225,18 @@ while [[ $1 = -?* ]]; do -l|--instance-short-name) shift; instance_short_name_param=${1} ;; -a|--new-admin-password) shift; new_admin_password_param=${1} ;; -p|--public-dns-name) shift; public_dns_name_param=${1} ;; + -b|--super-instance) shift; super_instance_param=${1} ;; + -m|--event-name) shift; event_name_param=${1} ;; + -z|--build) shift; build_version_param=${1} ;; + -w|--description) shift; description_param=${1} ;; + -c|--contact-person) shift; contact_person_param=${1} ;; + -e|--contact-email) shift; contact_email_param=${1} ;; -f|--force) force=true ;; -d|--debug) debug=true ;; - --associate-clb) associate_clb=true ;; - --associate-alb) associate_alb=true ;; - --associate-elastic-ip) associate_elastic_ip=true ;; - --instance) instance=true ;; - --tail) tail=true ;; + --instance) instance_scenario=true ;; + --shared-instance) shared_instance_scenario=true ;; + --master-instance) master_instance_scenario=true ;; + --associate-alb) associate_alb_scenario=true ;; --endopts) shift; break ;; *) die "invalid option: '$1'." ;; esac @@ -259,6 +267,9 @@ if ${strict}; then set -o nounset ; fi # This way you can catch the error in case mysqldump fails in `mysqldump |gzip`, for example. set -o pipefail +set -E +trap '[ "$?" -ne 77 ] || exit 77' ERR + # Invoke the checkDependenices function to test for Bash packages. Uncomment if needed. # checkDependencies diff --git a/configuration/aws-automation/launch_template.json b/configuration/aws-automation/launch_template.json new file mode 100644 index 00000000000..2301bce6354 --- /dev/null +++ b/configuration/aws-automation/launch_template.json @@ -0,0 +1 @@ +{"UserData":"HALLO","ImageId":"ami-39f3e25d","InstanceType":"t2.medium","TagSpecifications":[{"ResourceType":"instance","Tags":[{"Key":"Type","Value":"Replica"}]}]} \ No newline at end of file diff --git a/configuration/aws-automation/lib/build-config.sh b/configuration/aws-automation/lib/build-config.sh new file mode 100644 index 00000000000..e20dcf37bf2 --- /dev/null +++ b/configuration/aws-automation/lib/build-config.sh @@ -0,0 +1,247 @@ +#!/bin/bash +# +# https://github.com/brbsix/bash-config +# Read and write config files from Bash + + +readonly PROGRAM="${0##*/}" +readonly VERSION='0.1.dev1' + +readonly SEPARATOR_BASH='=' +readonly SEPARATOR_INI=' = ' + + +error(){ + echo "ERROR: $*" >&2 +} + +get_line_number(){ + set_mode + local output=$(sed -n "/^${parameter}${separator}/=" "$config" 2>/dev/null) + [[ ! -z ${output##*[!0-9]*} ]] && line_number="$output" +} + +main(){ + if echo "$1" | grep -Eq '^(-b|--bash)$'; then + option_mode=bash + elif echo "$1" | grep -Eq '^(-c|--custom)$'; then + option_mode=custom + elif echo "$1" | grep -Eq '^(-i|--ini)$'; then + option_mode=ini + fi + + local args + if [[ $option_mode = bash || $option_mode = ini ]]; then + args=$(($# - 1)) + config=$2 + parameter=$3 + value=$4 + elif [[ $option_mode = custom ]]; then + args=$(($# - 2)) + separator_custom=$2 + config=$3 + parameter=$4 + value=$5 + else + args=$# + config=$1 + parameter=$2 + value=$3 + fi + + if (( args == 0 )); then + error "No arguments" + see_help_msg + exit 1 + elif (( args == 1 )) && echo "$1" | grep -Eq '^(-h|--help|help)$'; then + usage + elif (( args == 1 )) && echo "$1" | grep -Eq '^(-v|--version|version)$'; then + echo "$PROGRAM $VERSION" + exit 0 + elif (( args == 2 )); then + if [[ $parameter = % ]]; then + # DELETE CONFIG + if [[ ! -f $config ]]; then + error "'$config' does not exist" + exit 1 + elif [[ ! -w $config ]]; then + error "Lacking write perms to '$config'" + exit 1 + else + remove_config + fi + else + # READ PARAMETER + if [[ ! -f $config ]]; then + error "'$config' does not exist" + exit 1 + elif [[ ! -r $config ]]; then + error "No read access to '$config'" + exit 1 + else + read_parameter + fi + fi + elif (( args == 3 )); then + if [[ $value = % ]]; then + # DELETE PARAMETER + if [[ ! -f $config ]]; then + error "'$config' does not exist" + exit 1 + elif [[ ! -w $config ]]; then + error "No write access to '$config'" + exit 1 + else + remove_parameter + fi + else + # WRITE PARAMETER + if [[ -f $config && ! -w $config ]]; then + error "No write access to '$config'" + exit 1 + else + write_parameter + fi + fi + else + error "Invalid arguments" + see_help_msg + exit 1 + fi +} + +read_parameter(){ + set_mode + + local output=$(sed -n "s/^${parameter}${separator}//p" "$config" 2>/dev/null) + + if [[ -n $output ]]; then + echo "$output" + else + if grep -q "^${parameter}${separator}$" "$config" 2>/dev/null; then + error "'$parameter' is set to null in '$config'" + exit 0 + else + error "No match for '$parameter' in '$config'" + exit 1 + fi + fi +} + +remove_config(){ + rm -f "$config" &>/dev/null + + if [[ -f $config ]]; then + error "Failed to remove '$config'" + exit 1 + fi +} + +remove_parameter(){ + get_line_number + [[ -n $line_number ]] && sed -i "${line_number}d" "$config" 2>/dev/null + if grep -q "^${parameter}${separator}" "$config" 2>/dev/null; then + error "Failed to remove parameter '$parameter' from '$config'" + exit 1 + elif [[ -f $config && ! -s $config ]]; then # remove config if empty + remove_config + fi +} + +sanitize_parameter(){ + if ! echo "$parameter" | grep -q '^[A-Za-z0-9_-]\+$'; then + error "Parameter may consist of A-Z, a-z, 0-9, underscores, and dashes (no spaces or special characters)" + exit 1 + fi +} + +see_help_msg(){ + echo "Try '$PROGRAM --help' for more information." >&2 +} + +set_mode(){ + grep -q "^[A-Za-z0-9_-]\+${SEPARATOR_BASH}" "$config" &>/dev/null && mode+=(bash) + + grep -q "^[A-Za-z0-9_-]\+${SEPARATOR_INI}" "$config" &>/dev/null && mode+=(ini) + + [[ $option_mode = custom ]] && grep -q "^[A-Za-z0-9_-]\+${separator_custom}" "$config" &>/dev/null && mode+=(custom) + + if (( ${#mode[@]} > 1 )); then + error "Unable to determine a consistent configuration file format. Are you sure '$config' is a config file?" + exit 1 + fi + + # OPTION IS SET + if [[ -n $option_mode ]]; then + + if [[ -n ${mode[0]} && ( ${mode[0]} != $option_mode ) ]]; then + # use detected mode in the event of a conflicting option + echo "WARNING: '$config' was intified as ${mode[0]} mode (ignoring $option_mode option)" >&2 + mode=${mode[0]} + else + # use optioned mode + mode=$option_mode + fi + + # NO OPTION IS SET and we have detected mode + elif [[ -n ${mode[0]} ]]; then + # use detected mode + mode=${mode[0]} + fi + + set_separator "$mode" +} + +set_separator(){ + if [[ $1 = custom ]]; then + separator=$separator_custom + elif [[ $1 = ini ]]; then + separator=$SEPARATOR_INI + else + # default to bash mode + separator=$SEPARATOR_BASH + fi +} + +usage(){ + cat <<-EOF + Usage: $PROGRAM [OPTION] [value] + Read and write config files from bash. + + -b, --bash use bash mode ex. '=' + -c, --custom use custom separator string + -i, --ini use ini mode ex. ' = ' + -v, --version show program's version number and exit + + * Enter a value to create or update an entry + * Specify only a parameter to print the currently set value (if any) + * Use '%' to delete the config file or a specific parameter + EOF + exit 0 +} + +verify_write(){ + local fullstring=${parameter}${separator}${value} + if ! grep -q "^${fullstring}$" "$config" &>/dev/null; then + error "Failed to write '${fullstring}' to '$config'" + exit 1 + fi +} + +write_parameter(){ + if [[ -f $config ]]; then + get_line_number + if [[ -n $line_number ]]; then + sed -i "${line_number}c${parameter}${separator}${value}" "$config" 2>/dev/null + else + echo "${parameter}${separator}${value}" >> "$config" + sort -o "$config" "$config" + fi + else + set_separator "$option_mode" + echo "${parameter}${separator}${value}" >> "$config" + fi + verify_write +} + +main "$@" diff --git a/configuration/aws-automation/lib/functions_app.sh b/configuration/aws-automation/lib/functions_app.sh index f2e26ddc68b..b790b003c72 100644 --- a/configuration/aws-automation/lib/functions_app.sh +++ b/configuration/aws-automation/lib/functions_app.sh @@ -27,90 +27,123 @@ function get_latest_release(){ # @param $1 admin username # @param $2 admin password # @param $3 dns name of instance +# @param $4 port # @return access token # ----------------------------------------------------------- function get_access_token(){ - local_echo "Getting access token..." - curl_wrapper -X GET "http://$1:$2@$3:8888/security/api/restsecurity/access_token" | get_attribute '.access_token' + local_echo -n "Getting access token..." + curl_until_response "200" http://$1:$2@$3:$4/security/api/restsecurity/access_token + curl_wrapper -X GET "http://$1:$2@$3:$4/security/api/restsecurity/access_token" | get_attribute '.access_token' } # ----------------------------------------------------------- # Creates a new event with event name = instance name, no regatta and venuename="Default" # @param $1 access token of privileged user # @param $2 dns name of instance -# @param $3 event name +# @param $3 port +# @param $4 event name # @return event_id of created event # ----------------------------------------------------------- function create_event(){ - local_echo "Creating event with name $3..." - curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:8888/sailingserver/api/v1/events/createEvent" --data "eventName=$3" --data "venuename=Default" --data "createregatta=false" | get_attribute '.eventid' + local_echo -n "Creating event with name $4..." + curl_until_response "401" http://$2:$3/sailingserver/api/v1/events/createEvent + curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:$3/sailingserver/api/v1/events/createEvent" --data "eventName=$4" --data "venuename=Default" --data "createregatta=false" | get_attribute '.eventid' } # ----------------------------------------------------------- # Changes password of user # @param $1 access token of privileged user # @param $2 dns name of instance -# @param $3 admin username -# @param $4 admin new password +# @param $3 port +# @param $4 admin username +# @param $5 admin new password # ----------------------------------------------------------- function change_admin_password(){ - local_echo "Changing password of user $3 to $4..." - curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:8888/security/api/restsecurity/change_password" --data "username=$3" --data "password=$4" + local_echo "Changing password of user $4 to $5..." + curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:$3/security/api/restsecurity/change_password" --data "username=$4" --data "password=$5" } # ----------------------------------------------------------- # Creates new user # @param $1 access token of privileged user # @param $2 dns name of instance -# @param $3 user username -# @param $4 user password +# @param $3 port +# @param $4 user username +# @param $5 user password # ----------------------------------------------------------- function create_new_user(){ - local_echo "Creating new user \"$3\" with password \"$4\"..." - curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:8888/security/api/restsecurity/create_user" --data "username=$3" --data "password=$4" + local_echo "Creating new user \"$4\" with password \"$5\"..." + curl_wrapper -X POST -H "Authorization: Bearer $1" "http://$2:$3/security/api/restsecurity/create_user?username=$4&password=$5" } # ----------------------------------------------------------- -# Wait until access token resource is available -# @param $1 admin username -# @param $2 admin password -# @param $3 public_dns_name +# Creates event, changes admin password, creates user +# @param $1 public dns name +# @param $2 port of application instance +# @param $3 event name +# @param $4 new admin password +# @param $5 user username +# @param $6 user password +# @return event id if event was created # ----------------------------------------------------------- -function wait_for_access_token_resource(){ - echo -n "Wait until resource \"/security/api/restsecurity/access_token\" is available..." - curl_until_http_200 http://$1:$2@$3:8888/security/api/restsecurity/access_token -} +function configure_application(){ + if [ -z "$3" ] && [ -z "$4" ] && [ -z "$5" ] && [ -z "$6" ]; then + return 0 + fi -# ----------------------------------------------------------- -# Wait until create event resource is available -# @param $1 public_dns_name -# ----------------------------------------------------------- -function wait_for_create_event_resource(){ - echo -n 'Wait until resource "/sailingserver/api/v1/events/createEvent" is available...' - curl_until_http_401 http://$1:8888/sailingserver/api/v1/events/createEvent + access_token=$(get_access_token "admin" "admin" $1 $2) + + if [ ! -z "$3" ]; then + event_id=$(create_event $access_token $1 $2 $3) + fi + + if [ ! -z "$4" ]; then + response=$(change_admin_password $access_token $1 $2 "admin" $4) + fi + + if [ ! -z "$5" ] && [ ! -z "$6" ]; then + user=$(create_new_user $access_token $1 $2 $5 $6) + fi + + echo ${$event_id:-""} } # ----------------------------------------------------------- # Patch 001-events.conf with ssl macros -# @param $1 dns name +# @param $1 domain # @param $2 event id # @param $3 ssh user # @param $4 public dns name -# @param $5 use ssl +# @param $5 server port # ----------------------------------------------------------- -function configure_apache(){ - wait_for_ssh_connection "$3" "$4" - wait_for_001_events_patch "$3" "$4" - local patched_content="Use Event-SSL $1 \"$2\" 127.0.0.1 8888" +function append_macro_to_001_events_conf(){ + wait_for_001_events_patch $3 $4 + + if [ -z "$2" ]; then + patched_content="Use Home-SSL $1 127.0.0.1 $5" + else + patched_content="Use Event-SSL $1 \\\"$2\\\" 127.0.0.1 $5" + fi + + local_echo "Appending´\"$patched_content\" to $events_conf..." ssh_wrapper $3@$4 echo "$patched_content >> $events_conf" - #ssh_wraper $3@$4 "/etc/init.d/httpd reload" } +function reload_httpd(){ + local_echo "Reloading httpd..." + ssh_wrapper $1@$2 "apachectl configtest >/dev/null 2>&1" + out=$(ssh_wrapper $1@$2 "/etc/init.d/httpd reload") +} + + + +# ----------------------------------------------------------- +# Wait until 001_events.conf file appears on instance +# @param $1 ssh user +# @param $2 dns name +# ----------------------------------------------------------- function wait_for_001_events_patch(){ - echo -n "Waiting for 001-events.conf to be created..." - while [[ $(ssh_wrapper $1@$2 test -f $events_conf && echo "ok") != "ok" ]]; do - echo -n . - sleep 2 - done - success "001-events.conf now exists. Appending macros to overwrite sailing script" + echo "Waiting for 001-events.conf to be created..." + wait_for_ssh_connection $1 $2 + do_until_true ssh_wrapper $1@$2 test -f $events_conf } diff --git a/configuration/aws-automation/lib/functions_ec2.sh b/configuration/aws-automation/lib/functions_ec2.sh index 0f48cbb2898..6ebcea62940 100644 --- a/configuration/aws-automation/lib/functions_ec2.sh +++ b/configuration/aws-automation/lib/functions_ec2.sh @@ -5,9 +5,27 @@ # @param $1 instance id # @return public dns name # ----------------------------------------------------------- -function query_public_dns_name(){ - local_echo "Querying for the instance public dns name..." - aws_wrapper --region "$region" ec2 describe-instances --instance-ids "$1" --output text --query 'Reservations[*].Instances[*].PublicDnsName' +function get_public_dns_name(){ + local public_dns_name=$(aws_wrapper ec2 describe-instances --instance-ids $1 --output text --query 'Reservations[*].Instances[*].PublicDnsName') + exit_on_fail validate_hostname $public_dns_name +} + + +# ----------------------------------------------------------- +# Get resources that should be automatically discovered +# @return array of resource ids +# ----------------------------------------------------------- +function get_resources(){ + aws resourcegroupstaggingapi get-resources | jq -c ".ResourceTagMappingList[] | select(.ResourceARN)" -r | sanitize +} + +# ----------------------------------------------------------- +# @param1 instance id +# @param2 tag key +# @return tag value +# ----------------------------------------------------------- +function get_tag_value_for_key(){ + aws_wrapper ec2 describe-instances --query "Reservations[*].Instances[*].Tags[?Key=='$2'].Value" --instance-ids $1 --output text } # ----------------------------------------------------------- @@ -15,10 +33,35 @@ function query_public_dns_name(){ # @return default vpc id # ----------------------------------------------------------- function get_default_vpc_id(){ - local_echo "Querying for the default vpc id..." aws_wrapper ec2 describe-vpcs --query 'Vpcs[?IsDefault==`true`].VpcId' --output text } +# ----------------------------------------------------------- +# Query for instance id by public dns name +# param $1 public dns name +# @return instance id +# ----------------------------------------------------------- +function get_instance_id(){ + local_echo "Querying for the instance id of $1..." + local instance_id=$(aws_wrapper ec2 describe-instances --query "Reservations[*].Instances[?PublicDnsName=='$1'].InstanceId" --output text) + exit_on_fail validate_instance_id $instance_id +} + +# ----------------------------------------------------------- +# Returns the instance id part of e.g. arn:aws:ec2:eu-west-2:017363970217:instance/i-096a32ca8c28bedbb +# param $1 resource arn +# @return id part of arn +# ----------------------------------------------------------- +function get_resource_id(){ + cut -d/ -f2 <<< $1 +} + +function get_public_ip(){ + local_echo "Querying for the public ip of $1..." + local ip_address=$(aws_wrapper ec2 describe-instances --query "Reservations[*].Instances[?PublicIpAddress=='$1'].InstanceId" --output text) + exit_on_fail validate_ipaddress $ip_address +} + # ----------------------------------------------------------- # Allocates an elastic ip address # @return elastic ip @@ -35,7 +78,7 @@ function allocate_address(){ # ----------------------------------------------------------- function associate_address(){ local_echo "Associating instance \"$1\" with elastic ip \"$2\"..." - aws_wrapper ec2 associate-address --instance-id "$1" --public-ip "$2" + aws_wrapper ec2 associate-address --instance-id $1 --public-ip $2 } # ----------------------------------------------------------- @@ -43,7 +86,7 @@ function associate_address(){ # @return availability zones array # ----------------------------------------------------------- function get_availability_zones(){ - aws_wrapper ec2 --region $region describe-availability-zones --query "AvailabilityZones[].ZoneName" + aws_wrapper ec2 describe-availability-zones --query "AvailabilityZones[].ZoneName" } # ----------------------------------------------------------- @@ -51,70 +94,49 @@ function get_availability_zones(){ # @param $1 instance_id # ----------------------------------------------------------- function wait_instance_exists(){ - local_echo "Wait until instance \"$1\" is recognized by AWS..." - aws_wrapper ec2 wait instance-exists --instance-ids $1 + aws_wrapper ec2 wait instance-exists --instance-ids $1 &> /dev/null } # ----------------------------------------------------------- # Wait until ssh connection is established -# @param $1 ssh user -# @param $2 dns name of instance +# @param $1 ssh_user +# @param $2 public dns name # ----------------------------------------------------------- function wait_for_ssh_connection(){ - echo -n "Connecting to $1@$2..." - while ! ssh_wrapper -q $1@$2 true 1>&2; do - echo -n . - sleep 2 - done - success "SSH Connection \"$1@$2\" is established." + local_echo -n "Connecting to $1@$2..." + do_until_true ssh_prewrapper -q $1@$2 true 1>&2 } - -# ----------------------------------------------------------- -# Creates $count instances inside $region with image $image_id and -# $instance_type and $user_data as well as according $tag_specifications, -# $security_group_ids and $key_name -# @return instance id -# ----------------------------------------------------------- function run_instance(){ - local_echo "Creating instance..." - write_user_data_to_file - - local command="aws_wrapper --region $region ec2 run-instances" - command+=$(add_param "region" $region) - command+=$(add_param "image-id" $image_id) - command+=$(add_param "count" $instance_count) - command+=$(add_param "instance-type" $instance_type) - command+=$(add_param "key-name" $key_name) - command+=$(add_param "security-group-ids" $instance_security_group_ids) - command+=$(add_param "user-data" 'file://${tmpDir}/$user_data_file') - command+=$(add_param "tag-specifications" $(printf $tag_specifications "$instance_name")) - - eval "$command" + local instance_id=$(aws_wrapper ec2 run-instances --image-id $(get_resource_id $image_id) --count 1 --instance-type $instance_type --key-name $key_name \ + --security-group-ids $(get_resource_id $instance_security_group_id) --user-data "$1" \ + --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=$instance_name}]" | get_attribute '.Instances[0].InstanceId') + exit_on_fail validate_instance_id $instance_id } # ----------------------------------------------------------- -# Creates a file with user data for instance creation +# Get user data of instance with specific instance id +# @param $1 instance id +# @return user data of instance # ----------------------------------------------------------- -function write_user_data_to_file(){ - local MONGODB_HOST="$mongodb_host" - local MONGODB_PORT="$mongodb_port" - local MONGODB_NAME="$(lower_trim $instance_name | only_letters_and_numbers)" - local REPLICATION_CHANNEL="$MONGODB_NAME" - local SERVER_NAME="$MONGODB_NAME" - local USE_ENVIRONMENT="live-server" - local INSTALL_FROM_RELEASE="$(get_latest_release)" - local SERVER_STARTUP_NOTIFY="$default_server_startup_notify" +function get_user_data_from_instance() { + aws_wrapper ec2 describe-instance-attribute --instance-id $1 --attribute userData --output text --query "UserData.Value" | sanitize +} - local content= - content+=$(add_user_data_variable "MONGODB_HOST" $MONGODB_HOST) - content+=$(add_user_data_variable "MONGODB_PORT" $MONGODB_PORT) - content+=$(add_user_data_variable "MONGODB_NAME" $MONGODB_NAME) - content+=$(add_user_data_variable "INSTALL_FROM_RELEASE" $INSTALL_FROM_RELEASE) - content+=$(add_user_data_variable "USE_ENVIRONMENT" $USE_ENVIRONMENT) - content+=$(add_user_data_variable "REPLICATION_CHANNEL" $REPLICATION_CHANNEL) - content+=$(add_user_data_variable "SERVER_NAME" $SERVER_NAME) - content+=$(add_user_data_variable "SERVER_STARTUP_NOTIFY" $SERVER_STARTUP_NOTIFY) +# ----------------------------------------------------------- +# Create instance and output response +# @param $1 user data +# ----------------------------------------------------------- +function create_instance(){ + local_echo -e "Creating instance with following specifications:\n\nRegion: $region\nName: $instance_name\nShort name: $instance_short_name\nType: $instance_type\nBuild: $build_version\n\nUser data:\n${1}\n" + instance_id=$(run_instance "$1") + wait_instance_exists $instance_id + print_instance_description $instance_id + echo $instance_id +} - echo "$content" > "${tmpDir}/$user_data_file" +function print_instance_description(){ + aws_wrapper ec2 describe-instances --instance-ids $1 \ + --query "Reservations[*].Instances[0].{InstanceId:InstanceId, ImageId:ImageId, Type:InstanceType, PublicDNS:PublicDnsName, KeyName:KeyName, PrivateDnsName:PrivateDnsName, PrivateIpAddress:PrivateIpAddress}"\ + --output table 1>&2 } diff --git a/configuration/aws-automation/lib/functions_elb.sh b/configuration/aws-automation/lib/functions_elb.sh index ae9e1711bf3..710681e88ee 100644 --- a/configuration/aws-automation/lib/functions_elb.sh +++ b/configuration/aws-automation/lib/functions_elb.sh @@ -1,15 +1,43 @@ #!/usr/bin/env bash - # ----------------------------------------------------------- # Create target group for load balancer # @param $1 target group name # @return json result # ----------------------------------------------------------- function create_target_group(){ - local_echo "Creating target group..." - local target_group_name=$(echo "$1" | only_letters_and_numbers) - aws_wrapper elbv2 create-target-group --name $target_group_name --protocol HTTPS --port 443 --vpc-id "$2" + local_echo "Creating target group $1..." + local target_group_name=$(only_letters_numbers_dash $1) + local vpc_id=$(get_default_vpc_id) + aws_wrapper elbv2 create-target-group --name $target_group_name --protocol "HTTPS" \ + --port "443" --vpc-id $vpc_id | get_attribute '.TargetGroups[0].TargetGroupArn' +} + +# ----------------------------------------------------------- +# Sets health check of target group +# @param $1 target group arn +# @param $2 health check protocol (e.g. "HTTP") +# @param $3 health check path (e.g. "/index.html") +# @param $4 health check port (e.g. "443") +# @param $5 health check interval seconds (e.g. "5") +# @param $6 health check timeouts seconds (e.g. "4") +# @param $7 healthy threshold count (e.g. "2") +# @param $8 unhealthy threshold count (e.g. "2") +# ----------------------------------------------------------- +function set_target_group_health_check(){ + local_echo "Setting target group health check (protocol: $2, path: $3, port: $4, interval seconds: $5, timeout seconds: $6, unhealthy-count: $7, healthy-count: $8)..." + aws_wrapper elbv2 modify-target-group --target-group-arn $1 --health-check-protocol $2 --health-check-path $3 --health-check-port $4\ + --health-check-interval-seconds $5 --health-check-timeout-seconds $6 --healthy-threshold-count $7 --unhealthy-threshold-count $8 +} + +# ----------------------------------------------------------- +# Add tag to target group +# @param $1 target group arn +# @param $2 key (e.g. "Description") +# @param $2 value (e.g. "This is an instance for ...") +# ----------------------------------------------------------- +function set_target_group_tag(){ + aws_wrapper elbv2 add-tags --resource-arns $1 --tags "Key=$2,Value=$3" } # ----------------------------------------------------------- @@ -19,9 +47,17 @@ function create_target_group(){ # ----------------------------------------------------------- function register_targets(){ local_echo "Register instance within target group..." - aws elbv2 register-targets --target-group-arn "$1" --targets Id="$2" + aws_wrapper elbv2 register-targets --target-group-arn $1 --targets Id=$2 } +# ----------------------------------------------------------- +# Returns first https listener of load balancer +# @param $1 load balancer arn +# @return https listener arn +# ----------------------------------------------------------- +function get_first_https_listener(){ + aws_wrapper elbv2 describe-listeners --load-balancer-arn $1 --query "Listeners[?Protocol=='HTTPS'].ListenerArn" --output text +} # ----------------------------------------------------------- # Create rule for listener # @param $1 listener arn @@ -31,11 +67,19 @@ function register_targets(){ # ----------------------------------------------------------- function create_rule(){ local_echo "Creating rule for listener..." - local subdomain=$(echo "$2" | only_letters_and_numbers) + local subdomain=$(alphanumeric "$2") local priority=$(($(get_rule_with_highest_priority $1) + 1)) - local domain="$subdomain.dummy.sapsailing.com" - aws_wrapper elbv2 create-rule --listener-arn $1 --priority $priority --conditions Field=host-header,Values="$domain" --actions Type=forward,TargetGroupArn=$3 1>&2 - echo "$domain" + + # For testing purpose + if [ $region == "eu-west-2" ]; then + local domain="$subdomain.dummy2.sapsailing.com" + else + local domain="$subdomain.sapsailing.com" + fi + + rule=$(aws_wrapper elbv2 create-rule --listener-arn $1 --priority $priority \ + --conditions Field=host-header,Values=$domain --actions Type=forward,TargetGroupArn=$3) + echo $domain } # ----------------------------------------------------------- @@ -44,70 +88,10 @@ function create_rule(){ # @return highest priority # ----------------------------------------------------------- function get_rule_with_highest_priority(){ - local max_priority=$(aws_wrapper elbv2 describe-rules --listener-arn $1 --query "sort_by(Rules, &Priority)[*].{P: Priority} | [-2:-1]" --output text) + local max_priority=$(aws --region=$region elbv2 describe-rules --listener-arn $1 --query "Rules[*].{P:Priority}" --output text | sanitize | sort -n | tail -1) if is_number $max_priority; then echo $max_priority else - echo 1 + echo 0 fi } - -# ----------------------------------------------------------- -# Create load balancer with HTTP rule -# @param $1 load_balancer_name -# @return json result -# ----------------------------------------------------------- -function create_load_balancer_http(){ - local load_balancer_name=$(echo "$1" | only_letters_and_numbers) - local_echo "Creating load balancer \"$load_balancer_name\"..." - aws_wrapper elb create-load-balancer --load-balancer-name $load_balancer_name --listeners "Protocol=HTTP,LoadBalancerPort=80,InstanceProtocol=HTTP,InstancePort=80" --availability-zones "$(get_availability_zones)" --security-groups "$elb_security_group_ids" -} - -# NOT TESTED -# ----------------------------------------------------------- -# Create load balancer with HTTPS rule -# @param $1 load_balancer_name -# @param $2 certificate arn -# @param $2 security group ids -# @return json result -# ----------------------------------------------------------- -function create_load_balancer_https(){ - local load_balancer_name=$(echo "$1" | only_letters_and_numbers) - local_echo "Creating load balancer \"$load_balancer_name\"..." - aws_wrapper elb create-load-balancer --load-balancer-name $load_balancer_name --listeners "Protocol=HTTP,LoadBalancerPort=80,InstanceProtocol=HTTP,InstancePort=80" "Protocol=HTTPS,LoadBalancerPort=443,InstanceProtocol=HTTP,InstancePort=80,SSLCertificateId=$2" --availability-zones "$(get_availability_zones)" --security-groups "$elb_security_group_ids" -} - -# ----------------------------------------------------------- -# Configure health check HTTP -# @param $1 load balancer name -# @return json result -# ----------------------------------------------------------- -function configure_health_check_http(){ - local load_balancer_name=$(echo "$1" | only_letters_and_numbers) - local_echo "Configuring load balancer health check..." - aws_wrapper elb configure-health-check --load-balancer-name "$load_balancer_name" --health-check Target=TCP:8888,Interval=15,UnhealthyThreshold=2,HealthyThreshold=3,Timeout=5 -} - -# NOT TESTED -# ----------------------------------------------------------- -# Configure health check HTTPS -# @param $1 load balancer name -# @return json result -# ----------------------------------------------------------- -function configure_health_check_https(){ - local_echo "Configuring load balancer health check..." - local load_balancer_name=$(echo "$1" | only_letters_and_numbers) - aws_wrapper elb configure-health-check --load-balancer-name "$load_balancer_name" --health-check Target=HTTPS:443/index.html,Interval=15,UnhealthyThreshold=2,HealthyThreshold=3,Timeout=5 -} - -# ----------------------------------------------------------- -# Add instance to load balancer -# @param $1 load balancer name -# @param $2 instance id -# @return json result -# ----------------------------------------------------------- -function add_instance_to_elb(){ - local_echo "Adding instance to elb..." - local load_balancer_name=$(echo "$1" | only_letters_and_numbers) - aws_wrapper elb register-instances-with-load-balancer --load-balancer-name $load_balancer_name --instances $2 -} diff --git a/configuration/aws-automation/lib/functions_io.sh b/configuration/aws-automation/lib/functions_io.sh index bcf02765e83..8cf20fbc65a 100644 --- a/configuration/aws-automation/lib/functions_io.sh +++ b/configuration/aws-automation/lib/functions_io.sh @@ -1,18 +1,78 @@ #!/usr/bin/env bash +CHOOSE_FROM_INSTANCES=":instance" +CHOOSE_FROM_SECURITY_GROUPS=":security-group" +CHOOSE_FROM_IMAGES=":image" +CHOOSE_FROM_CERTIFICATES=":certificate" +CHOOSE_FROM_LOAD_BALANCERS=":loadbalancer" + # ----------------------------------------------------------- -# As long as variable is empty, prompt again. +# Prompt user for input and save value to variable # @param $1 prompt message # @param $2 default value # @param $3 variable that is receiving the value +# @param $4 hide text +# @param $5 filter # ----------------------------------------------------------- -function ask(){ - while [[ -z "${!3}" ]] - do - require_input "$1" "$2" $3 - done +function require_input(){ + + if [ ! -z "$5" ]; then + declare -a option_keys=() + declare -a option_values=() + number_of_tagged_resources=0 + default_tagged_resource="" + o=0 + for key in "${!RESOURCE_MAP[@]}"; do + if [[ $key = *"$5"* ]]; then + # if resource is tagged + option_keys[o]=$key + local tagged=$(echo ${RESOURCE_MAP[$key]} | cut -d, -f2) + local name=$(echo ${RESOURCE_MAP[$key]} | cut -d, -f1) + if [ $tagged == "true" ]; then + option_values[o]="$(tput setaf 3)$name ($key)$(tput sgr0)" + default_tagged_resource=$key + ((number_of_tagged_resources++)) + else + option_values[o]="$name ($key)" + fi + fi + ((o++)) + done + + if [ $number_of_tagged_resources -eq 1 ] && [ "$force" == "true" ] || [ ${#option_keys[@]} -eq 1 ]; then + read -r $3 <<< $default_tagged_resource + return 0 + elif [ $number_of_tagged_resources -gt 0 ]; then + selectedValue="" + select option in ${option_values[@]}; do + selectedValue=$option + break + done + # workaround cant access option_keys from within select + read -r $3 <<< $(echo $selectedValue | sed 's/.*(\(.*\)).*/\1/') + return 0 + else + read_input_normal "$1" "$2" $3 $4 + return 0 + fi + + else + read_input_normal "$1" "$2" $3 $4 + fi } +function read_input_normal(){ + # if hide input + if [ "$4" == "true" ]; then + read -e -s -p "$1" -i "$2" $3 + # print new line after read -s + echo + else + read -e -p "$1" -i "$2" $3 + fi +} + + # ----------------------------------------------------------- # If the needed variable was not passed as a parameter and is not # already set, then prompt the user to input a value and also @@ -22,23 +82,44 @@ function ask(){ # @param $2 variable # @param $3 default value # @param $4 prompt message +# @param $5 true if optional +# @param $6 true if text input be hidden +# @param $7 filter # ----------------------------------------------------------- function require_variable(){ # if parameter is empty if [ -z "$1" ]; then - # if required variable is empty + # if required variable value is empty if [ -z "${!2}" ]; then # if force is enabled - if [ "$force" = true ]; then - # read default value into required variable - read -r "$2" <<< "$3" + if [ "$force" = true ] && [ -z "$7" ]; then + # if variable is optional + if [ "$5" == "true" ]; then + # variable should be empty + read -r "$2" <<< "" + return 0 + else + # read default value into required variable + read -r "$2" <<< "$3" + return 0 + fi + fi + # if force not enabled or filter is set + # if variable value is optional, then value of "" is possible + if [ "$5" == "true" ]; then + require_input "$4" "$3" $2 $6 $7 + else + # if variable value is not optional, repeat until value is set for variable + while [[ -z "${!2}" ]] + do + require_input "$4" "$3" $2 $6 $7 + done fi - # if force not enabled, ask user for input - ask "$4" "$3" $2 fi else # if parameter is not empty, read its value into required variable read -r "$2" <<< "$1" + return 0 fi } @@ -47,73 +128,90 @@ function require_variable(){ # @param $1 message # ----------------------------------------------------------- function local_echo(){ - echo "$1" >&2 + echo "$@" >&2 } -# ----------------------------------------------------------- -# Prompt user for input and save value to variable -# @param $1 prompt message -# @param $2 default value -# @param $3 variable that is receiving the value -# ----------------------------------------------------------- -function require_input(){ - read -e -p "$1" -i "$2" $3 +function update_resources(){ + local resources_file=./lib/resources-$region.sh + > $resources_file + + OLD_RESOURCE_JSON=$RESOURCE_JSON + typeset -p OLD_RESOURCE_JSON >> $resources_file + + RESOURCES_ARRAY=($(get_resources)) + + declare -A RESOURCE_MAP + + for resource in ${RESOURCES_ARRAY[@]}; do + local arn=$(echo $resource | jq -c ".ResourceARN" -r | sanitize) + local name=$(echo $resource | jq -c ".Tags[] | select(.Key==\"Name\") | .Value" -r | sanitize) + local tagged=$(echo $resource | jq -c ".Tags[] | select(.Key==\"AutoDiscover\") | .Value" -r | sanitize) + + if [ ! -z "$tagged" ] && [ "$tagged" == "true" ] || [ "$tagged" == "True" ]; then + tagged="true" + else + tagged="false" + fi + + RESOURCE_MAP[$arn]="${name:-"Unnamed"},$tagged" + done + + typeset -p RESOURCE_MAP >> $resources_file } -function require_region(){ - require_variable "$region_param" region "$default_region" "$region_ask_message" +function init_resources(){ + local_echo "Checking if aws resources with tag key 'AutoDiscover' and value 'true' were added or removed in region $region..." + RESOURCE_JSON=$(get_resources) + + if [ "$RESOURCE_JSON" != "$OLD_RESOURCE_JSON" ]; then + local_echo "Updating tagged aws resources..." + update_resources + else + local_echo "Stored aws resources are up to date." + fi + + if ! is_exists ~/.aws-automation/config-$region; then + local_echo "Creating ~/.aws-automation/config-$region..." + create_region_configuration + fi + + if ! is_exists ~/.aws-automation/config; then + local_echo "Creating ~/.aws-automation/config..." + create_global_configuration + fi } -function require_instance_type(){ - require_variable "$instance_type_param" instance_type "$default_instance_type" "$instance_type_ask_message" +function create_region_configuration(){ + mkdir -p ~/.aws-automation + touch ~/.aws-automation/config-$region -} -function require_instance_name(){ - require_variable "$instance_name_param" instance_name "$default_instance_name" "$instance_name_ask_message" + local config="" + config+="default_instance_type=\n" + config+="default_ssh_user=\n" + config+="default_key_name=\n" + config+="default_key_file=\n" + config+="default_mongodb_host=\n" + config+="default_mongodb_port=\n" + + echo -e $config > ~/.aws-automation/config-$region } -function require_instance_short_name(){ - require_variable "$instance_short_name_param" instance_short_name "$default_instance_short_name" "$instance_short_name_ask_message" +function create_global_configuration(){ + mkdir -p ~/.aws-automation + touch ~/.aws-automation/config + + local config="" + config+="default_region=\n" + config+="default_server_startup_notify=\n" + config+="default_build_complete_notify=\n" + + echo -e $config > ~/.aws-automation/config } -function require_key_name(){ - require_variable "$key_name_param" key_name "$default_key_name" "$key_name_ask_message" +function set_config_variable(){ + ./lib/build-config.sh --bash ~/.aws-automation/config-$region "$1" "$2" } -function require_key_file(){ - require_variable "$key_file_param" key_file "$default_key_file" "$key_file_ask_message" +function get_config_variable(){ + ./lib/build-config.sh --bash ~/.aws-automation/config-$region "$1" } - -function require_new_admin_password(){ - require_variable "$new_admin_password_param" new_admin_password "$default_new_admin_password" "$new_admin_password_ask_message" -} - -function require_user_username(){ - require_variable "$user_username_param" user_username "$default_user_username" "$user_username_ask_message" -} - -function require_user_password(){ - require_variable "$user_password_param" user_password "$default_user_password" "$user_password_ask_message" -} - -function require_public_dns_name(){ - require_variable "$public_dns_name_param" public_dns_name "" "$public_dns_name_ask_message" -} - -function require_ssh_user(){ - require_variable "$ssh_user_param" ssh_user "" "$ssh_user_ask_message" -} - -region_ask_message="Please enter the region for the instance: " -instance_type_ask_message="Please enter the instance type: " -key_name_ask_message="Please enter the name of your keypair to connect to the instance: " -instance_name_ask_message="Please enter a name for the instance: (e.g \"WC Santander 2017\"): " -instance_short_name_ask_message="Please enter a short name for the instance (e.g. \"wcs17\"): " -key_file_ask_message="Please enter the file path of the keypair or leave empty to use default ssh key: " -new_admin_password_ask_message="Please enter a new password for the admin user: " -mongo_db_host_ask_message="Please enter the ip adress of the mongo db server: " -mongo_db_port_ask_message="Please enter the port of the mongo db server: " -user_username_ask_message="Please enter the username of your new user: " -user_password_ask_message="Please enter the password of your new user: " -public_dns_name_ask_message="Please enter the public dns name: " -ssh_user_ask_message="Please enter the ssh user: " diff --git a/configuration/aws-automation/lib/functions_route53.sh b/configuration/aws-automation/lib/functions_route53.sh deleted file mode 100644 index e8a7e37f28a..00000000000 --- a/configuration/aws-automation/lib/functions_route53.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash - -# NOT TESTED - -# ----------------------------------------------------------- -# Create CNAME record for the hosted zone to forward to a load balancer -# @param $1 subdomain name -# @param $2 TTL -# @param $3 load balancer dns name -# @return json result -# ----------------------------------------------------------- -function route53_change_resource_record(){ - create_route53_record_file "$1" "$2" "$3" - local_echo "Creating Route53 record set (Name: $1.sapsailing.com Value: $3 Type: CNAME)..." - aws_wrapper route53 change-resource-record-sets --hosted-zone-id "$hosted_zone_id" --change-batch "file://${tmpDir}/$change_resource_record_set_file" -} - -# ----------------------------------------------------------- -# Creates a file which includes the record change as json -# @param $1 subdomain name -# @param $2 TTL -# @param $3 load balancer dns name -# ----------------------------------------------------------- -function create_route53_record_file(){ - local json=$(printf '{"Changes":[{"Action":"CREATE","ResourceRecordSet": {"Name": "%s.sapsailing.com","Type": "CNAME","TTL": %s,"ResourceRecords":[{"Value": "%s"}]}}]}' $1 $2 $3) - echo "$json" > "${tmpDir}/$change_resource_record_set_file" -} diff --git a/configuration/aws-automation/lib/functions_tmux.sh b/configuration/aws-automation/lib/functions_tmux.sh deleted file mode 100644 index dadb804ab8a..00000000000 --- a/configuration/aws-automation/lib/functions_tmux.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env bash - -# ----------------------------------------------------------- -# Construct tmux user interface -# ----------------------------------------------------------- -function construct_ui() { - # enable scrolling, clicking on panes etc. - tmux set -g mouse on - - # enable named border at the top of panes - tmux set -g pane-border-status top - tmux set -g pane-border-format " [#{pane_index}] - #T " - - # close all panes except one - reset_panes - sleep 1 - - # construct pane layout - tmux split-window -h -p 50 - tmux select-pane -t 0 - tmux split-window -v -p 50 - tmux select-pane -t 2 - tmux split-window -v -p 50 - tmux select-pane -t 0 -} - -# ----------------------------------------------------------- -# Opens ssh connections on all three panes -# @param $1 ssh user -# @param $2 dns name of instance -# ----------------------------------------------------------- -function open_connections() { - - # Can't use ssh wrapper at this point because function cannot be export within tmux environment. - if [ -z $key_file ]; then - command="ssh -o StrictHostKeyChecking=no $1@$2" - else - command="ssh -o StrictHostKeyChecking=no -i $key_file $1@$2" - fi - - # Send $command to all three panes and execute - tmux send-keys -t 1 $command C-m - tmux send-keys -t 2 $command C-m - tmux send-keys -t 3 $command C-m -} - -# ----------------------------------------------------------- -# Close all panes except first -# ----------------------------------------------------------- -function reset_panes(){ - if more_panes_are_open; then - tmux kill-pane -a -t 0 - fi -} - -function get_number_of_panes(){ - tmux display-message -p '#{window_panes}' -} - -function more_panes_are_open(){ - if inside_tmux_session; then - if [ "$(get_number_of_panes)" -gt 1 ]; then - return 0; - fi - fi - return 1; -} - -function check_if_tmux_is_used(){ - check_dependencies - check_environment -} - -function check_dependencies() { - if ! is_tmux_available; then - echo "The package \"tmux\" is required to run the script with this option" - safeExit - fi -} - -function check_environment() { - if ! inside_tmux_session; then - echo "To tail instance log files, please run this script inside a tmux session. To do so enter \"tmux\" into the console and start the script from there." - safeExit - fi -} - -function is_tmux_available(){ - type tmux >/dev/null 2>/dev/null -} - -function inside_tmux_session(){ - [ "$TERM" = "screen" ] && [ -n "$TMUX" ] -} - -# ----------------------------------------------------------- -# Prompts user if open panes should be closed -# ----------------------------------------------------------- -function confirm_reset_panes(){ - if more_panes_are_open; then - seek_confirmation "Do you want to close all open panes?" - if is_confirmed; then - reset_panes - else - safeExit - fi - fi -} diff --git a/configuration/aws-automation/lib/functions_wrapper.sh b/configuration/aws-automation/lib/functions_wrapper.sh index fcd2c18e6bf..37d267f22c5 100644 --- a/configuration/aws-automation/lib/functions_wrapper.sh +++ b/configuration/aws-automation/lib/functions_wrapper.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +declare -g DISPLAY_SUCCESS_FOR_THIS_COMMAND="true" +HTTP_RETRY_INTERVAL=5 +TIMEOUT=300 + # ----------------------------------------------------------- # Does the error checking for curl commands. # Captures response and http code of curl command into variable $out. @@ -18,11 +22,11 @@ function curl_wrapper(){ local message=$(get_http_code_message $status_code) if is_http_ok $status_code; then - success "[ OK ]" + success ${response:-"[ OK ]"} echo $response else error "[ ERROR ]" - notice "Function (${FUNCNAME[1]}): [$status_code] $message\n\n$command" + notice "Function (${FUNCNAME[1]}): [$status_code] $message $response\n\n$command" return 1 fi } @@ -33,20 +37,27 @@ function curl_wrapper(){ # ----------------------------------------------------------- function aws_wrapper(){ local out; - out=$(aws "$@") + out=$(aws --region $region "$@" 2>&1) if command_was_successful $?; then - success "[ OK ]" + if [ "$DISPLAY_SUCCESS_FOR_THIS_COMMAND" != "false" ]; then + success ${out:-"[ OK ]"} + fi echo $out | sanitize else - error "[ ERROR ]" - return 1 + error "[ ERROR ] $out" + safeExit fi } -# ----------------------------------------------------------- -# Automatically uses key file for ssh if possible. -# ----------------------------------------------------------- -function ssh_wrapper(){ +function disable_aws_success_output(){ + DISPLAY_SUCCESS_FOR_THIS_COMMAND="false" +} + +function enable_aws_success_output(){ + DISPLAY_SUCCESS_FOR_THIS_COMMAND="true" +} + +function ssh_prewrapper(){ if [ -z $key_file ]; then ssh -o StrictHostKeyChecking=no "$@" else @@ -55,28 +66,55 @@ function ssh_wrapper(){ } # ----------------------------------------------------------- -# Executes curl command until status code is 200 +# Automatically uses key file for ssh if possible. # ----------------------------------------------------------- -function curl_until_http_200(){ - while [[ $(curl -s -o /dev/null -w ''%{http_code}'' --connect-timeout $http_retry_interval "$@") != "200" ]]; - do - echo -n "." - sleep $http_retry_interval; - done - echo "" - success "[ OK ]" +function ssh_wrapper(){ + out=$(ssh_prewrapper "$@") + if command_was_successful $?; then + success ${out:-"[ OK ]"} + echo $out + else + error "[ ERROR ] $out" + return 1 + fi } # ----------------------------------------------------------- -# Executes curl command until status code is 401. -# TODO: Fix code duplication (curl_until_http_200, curl_until_http_401) +# Executes curl command until status code is 200 # ----------------------------------------------------------- -function curl_until_http_401(){ - while [[ $(curl -s -o /dev/null -w ''%{http_code}'' --connect-timeout $http_retry_interval "$@") != "401" ]]; +function curl_until_response(){ + response_code=$1 + shift + start_time="$(date -u +%s)" + while [[ $(curl -s -o /dev/null -w ''%{http_code}'' --connect-timeout $HTTP_RETRY_INTERVAL "$@") != "$response_code" ]]; do - echo -n "." - sleep $http_retry_interval; + fail_on_timeout $start_time + + local_echo -n "." + sleep $HTTP_RETRY_INTERVAL; done - echo "" - success "[ OK ]" + local_echo "" +} + +function do_until_true(){ + start_time="$(date -u +%s)" + until "$@" + do + fail_on_timeout $start_time + + echo -n . + sleep $HTTP_RETRY_INTERVAL + done +} + +function fail_on_timeout(){ + end_time="$(date -u +%s)" + if [ "$(($end_time-$1))" -gt $TIMEOUT ]; then + error "TIMEOUT" + safeExit + fi +} + +function exit_on_fail(){ + "$@" || { safeExit; } } diff --git a/configuration/aws-automation/lib/require_variables.sh b/configuration/aws-automation/lib/require_variables.sh new file mode 100644 index 00000000000..5e93cc784c9 --- /dev/null +++ b/configuration/aws-automation/lib/require_variables.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash + +instance_ask_message="Please enter an instance to use: " +event_name_ask_message="Please enter an event name (leave blank to skip event creation):" +instance_security_group_id_ask_message="Please select the security group for the instance: " +load_balancer_ask_message="Please select/enter the load balancer dns name: " +region_ask_message="Please enter the region you want to use (e.g. eu-west-2): " +instance_type_ask_message="Please enter the instance type (e.g. t2.medium): " +key_name_ask_message="Please enter the name of your keypair to user for the instance (e.g. leon-keypair). " +instance_name_ask_message="Please enter a name for the instance: (e.g \"WC Santander 2017\"): " +instance_short_name_ask_message="Please enter a short name for the instance (e.g. \"wcs17\"): " +key_file_ask_message="Please enter the file path of the keypair or leave empty to use ~/.ssh key: " +new_admin_password_ask_message="Please enter a new password for the admin user (leave blank to skip password change): " +mongo_db_host_ask_message="Please enter the ip adress of the mongo db server: " +mongo_db_port_ask_message="Please enter the port of the mongo db server: " +user_username_ask_message="Please enter the username of your new user (leave blank to skip user creation): " +user_password_ask_message="Please enter the password of your new user (leave blank to skip user creation): " +ssh_user_ask_message="Please enter the ssh user to connect to the instance: " +super_instance_message="Please select/enter the dns name of the superior instance: " +description_message="Please enter a description for the server (leave blank to skip): " +contact_person_message="Please enter the name of a contact person (leave blank to skip): " +contact_email_message="Please enter the email of the contact person (leave blank to skip): " +build_version_message="Please enter a build version to use (releases.sapsailing.com): " + + +function require_image_id(){ + require_variable "" image_id "" "$image_id_ask_message" "false" "false" $CHOOSE_FROM_IMAGES +} + +function require_event_name(){ + require_variable "$event_name_param" event_name "" "$event_name_ask_message" "true" "false" +} + +function require_instance_security_group_id(){ + require_variable "" instance_security_group_id "" "$instance_security_group_id_ask_message" "false" "false" $CHOOSE_FROM_SECURITY_GROUPS +} + +function require_load_balancer(){ + require_variable "" load_balancer "" "$load_balancer_ask_message" "false" "false" $CHOOSE_FROM_LOAD_BALANCERS +} + +function require_instance(){ + require_variable "" instance "" "$instance_ask_message" "false" "false" $CHOOSE_FROM_INSTANCES +} + +function require_region(){ + require_variable "$region_param" region "$default_region" "$region_ask_message" "false" "false" +} + +function require_instance_type(){ + require_variable "$instance_type_param" instance_type "$default_instance_type" "$instance_type_ask_message" "false" "false" + +} +function require_instance_name(){ + require_variable "$instance_name_param" instance_name "$script_start_time" "$instance_name_ask_message" "false" "false" +} + +function require_instance_short_name(){ + require_variable "$instance_short_name_param" instance_short_name "$timestamp" "$instance_short_name_ask_message" "false" "false" +} + +function require_key_name(){ + require_variable "$key_name_param" key_name "$default_key_name" "$key_name_ask_message" "false" "false" +} + +function require_key_file(){ + require_variable "$key_file_param" key_file "$default_key_file" "$key_file_ask_message" "true" "false" +} + +function require_new_admin_password(){ + require_variable "$new_admin_password_param" new_admin_password "$default_new_admin_password" "$new_admin_password_ask_message" "true" "true" +} + +function require_user_username(){ + if [ ! -z ${user_password+x} ] || [ ! -z "$user_password" ]; then + require_variable "$user_username_param" user_username "$default_user_username" "$user_username_ask_message" "true" "false" + fi +} + +function require_user_password(){ + if [ ! -z ${user_name+x} ] || [ ! -z "$user_name" ]; then + require_variable "$user_password_param" user_password "$default_user_password" "$user_password_ask_message" "true" "true" + fi +} + +function require_ssh_user(){ + require_variable "$ssh_user_param" ssh_user "$default_ssh_user" "$ssh_user_ask_message" "false" "false" +} + +function require_super_instance(){ + require_variable "$super_instance_param" super_instance "$default_super_instance" "$super_instance_message" "false" "false" $CHOOSE_FROM_INSTANCES +} + +function require_description(){ + require_variable "$description_param" description "" "$description_message" "true" "false" +} + +function require_contact_person(){ + require_variable "$contact_person_param" contact_person "$default_contact_person" "$contact_person_message" "true" "false" +} + +function require_contact_email(){ + require_variable "$contact_email_param" contact_email "$default_contact_email" "$contact_email_message" "true" "false" +} + +function require_build_version(){ + require_variable "$build_version_param" build_version "$latest_release" "$build_version_message" "false" "false" +} diff --git a/configuration/aws-automation/lib/resources-eu-west-1.sh b/configuration/aws-automation/lib/resources-eu-west-1.sh new file mode 100644 index 00000000000..0d489cd3af1 --- /dev/null +++ b/configuration/aws-automation/lib/resources-eu-west-1.sh @@ -0,0 +1,11 @@ +declare -- OLD_RESOURCES="{ \"ResourceTagMappingList\": [ { \"ResourceARN\": \"arn:aws:acm:eu-west-1:017363970217:certificate/67a1515a-81be-472d-9459-85746d030564\", \"Tags\": [ { \"Value\": \"true\", \"Key\": \"AutoDiscover\" }, { \"Value\": \"sapsailing.com-2017\", \"Key\": \"Name\" } ] }, { \"ResourceARN\": \"arn:aws:ec2:eu-west-1:017363970217:instance/i-07344e3c868f46f85\", \"Tags\": [ { \"Value\": \"true\", \"Key\": \"AutoDiscover\" }, { \"Value\": \"SL Multi-Instance Sailling Server\", \"Key\": \"Name\" }, { \"Value\": \"Sailing\", \"Key\": \"function\" }, { \"Value\": \"sailing\", \"Key\": \"ssh_user\" } ] }, { \"ResourceARN\": \"arn:aws:ec2:eu-west-1:017363970217:image/ami-8ce485f5\", \"Tags\": [ { \"Value\": \"true\", \"Key\": \"AutoDiscover\" } ] }, { \"ResourceARN\": \"arn:aws:ec2:eu-west-1:017363970217:security-group/sg-eaf31e85\", \"Tags\": [ { \"Value\": \"true\", \"Key\": \"AutoDiscover\" } ] }, { \"ResourceARN\": \"arn:aws:elasticloadbalancing:eu-west-1:017363970217:loadbalancer/app/Sailing-eu-west-1/32b89dbfe7f75097\", \"Tags\": [ { \"Value\": \"true\", \"Key\": \"AutoDiscover\" } ] } ] }" +declare -a TAGGED_SECURITY_GROUP_NAMES=([0]="[Unnamed]") +declare -a TAGGED_SECURITY_GROUP_ARNS=([0]="arn:aws:ec2:eu-west-1:017363970217:security-group/sg-eaf31e85") +declare -a TAGGED_IMAGE_NAMES=([0]="[Unnamed]") +declare -a TAGGED_IMAGE_ARNS=([0]="arn:aws:ec2:eu-west-1:017363970217:image/ami-8ce485f5") +declare -a TAGGED_INSTANCE_NAMES=([0]="SL" [1]="Multi-Instance" [2]="Sailling" [3]="Server") +declare -a TAGGED_INSTANCE_ARNS=([0]="arn:aws:ec2:eu-west-1:017363970217:instance/i-07344e3c868f46f85") +declare -a TAGGED_CERTIFICATE_NAMES=([0]="sapsailing.com-2017") +declare -a TAGGED_CERTIFICATE_ARNS=([0]="arn:aws:acm:eu-west-1:017363970217:certificate/67a1515a-81be-472d-9459-85746d030564") +declare -a TAGGED_LOADBALANCER_NAMES=([0]="[Unnamed]") +declare -a TAGGED_LOADBALANCER_ARNS=([0]="arn:aws:elasticloadbalancing:eu-west-1:017363970217:loadbalancer/app/Sailing-eu-west-1/32b89dbfe7f75097") diff --git a/configuration/aws-automation/lib/resources-eu-west-2.sh b/configuration/aws-automation/lib/resources-eu-west-2.sh new file mode 100644 index 00000000000..bb4afed6857 --- /dev/null +++ b/configuration/aws-automation/lib/resources-eu-west-2.sh @@ -0,0 +1,20 @@ +declare -- OLD_RESOURCE_JSON="{\"ResourceARN\":\"arn:aws:acm:eu-west-2:017363970217:certificate/d2ae17b0-75ed-4b56-ac12-a80f9e5c493d\",\"Tags\":[{\"Value\":\"true\",\"Key\":\"AutoDiscover\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:image/ami-39f3e25d\",\"Tags\":[{\"Value\":\"true\",\"Key\":\"AutoDiscover\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:instance/i-02abdf10071ce9ce6\",\"Tags\":[{\"Value\":\"2018-02-14 06:57:30\",\"Key\":\"Name\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:instance/i-04eeb1346463540c5\",\"Tags\":[{\"Value\":\"123\",\"Key\":\"Name\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:instance/i-05f2fda5ce72c2b36\",\"Tags\":[{\"Value\":\"2018-02-14 07:22:33\",\"Key\":\"Name\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:instance/i-068b644ad5fbe6e5f\",\"Tags\":[{\"Value\":\"12\",\"Key\":\"Name\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:instance/i-08803ecb16560b18a\",\"Tags\":[{\"Value\":\"2018-02-14 07:15:26\",\"Key\":\"Name\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:instance/i-08e4a99e69f6d8dce\",\"Tags\":[{\"Value\":\"2018-02-14 07:07:20\",\"Key\":\"Name\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:instance/i-09330d7744ebef9b3\",\"Tags\":[{\"Value\":\"123\",\"Key\":\"Name\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:instance/i-096a32ca8c28bedbb\",\"Tags\":[{\"Value\":\"MongoDB\",\"Key\":\"Name\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:instance/i-0a65849491dfc8562\",\"Tags\":[{\"Value\":\"yxc\",\"Key\":\"Name\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:instance/i-0c57f03afc68de24c\",\"Tags\":[{\"Value\":\"true\",\"Key\":\"AutoDiscover\"},{\"Value\":\"Multi Instance\",\"Key\":\"Name\"},{\"Value\":\"root\",\"Key\":\"ssh_user\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:instance/i-0f2e26f9a7860e680\",\"Tags\":[{\"Value\":\"123\",\"Key\":\"Name\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:instance/i-0f5308bf09c42d7f9\",\"Tags\":[{\"Value\":\"123\",\"Key\":\"Name\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:security-group/sg-74c92d1c\",\"Tags\":[{\"Value\":\"true\",\"Key\":\"AutoDiscover\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:security-group/sg-7f0c2916\",\"Tags\":[{\"Value\":\"Database and Messaging\",\"Key\":\"Name\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:security-group/sg-871732ee\",\"Tags\":[{\"Value\":\"true\",\"Key\":\"AutoDiscover\"},{\"Value\":\"appSecurityGroup\",\"Key\":\"Name\"}]} +{\"ResourceARN\":\"arn:aws:ec2:eu-west-2:017363970217:security-group/sg-9732d6ff\",\"Tags\":[{\"Value\":\"test\",\"Key\":\"Name\"}]} +{\"ResourceARN\":\"arn:aws:elasticloadbalancing:eu-west-2:017363970217:loadbalancer/app/DummyALB/da70f61a914fc50a\",\"Tags\":[{\"Value\":\"true\",\"Key\":\"AutoDiscover\"}]}" +declare -A RESOURCE_MAP=([arn:aws:ec2:eu-west-2:017363970217:instance/i-096a32ca8c28bedbb]="MongoDB,false" [arn:aws:ec2:eu-west-2:017363970217:instance/i-08803ecb16560b18a]="2018-02-14 07:15:26,false" [arn:aws:ec2:eu-west-2:017363970217:instance/i-04eeb1346463540c5]="123,false" [arn:aws:ec2:eu-west-2:017363970217:instance/i-09330d7744ebef9b3]="123,false" [arn:aws:ec2:eu-west-2:017363970217:instance/i-02abdf10071ce9ce6]="2018-02-14 06:57:30,false" [arn:aws:ec2:eu-west-2:017363970217:instance/i-0f2e26f9a7860e680]="123,false" [arn:aws:acm:eu-west-2:017363970217:certificate/d2ae17b0-75ed-4b56-ac12-a80f9e5c493d]="Unnamed,true" [arn:aws:elasticloadbalancing:eu-west-2:017363970217:loadbalancer/app/DummyALB/da70f61a914fc50a]="Unnamed,true" [arn:aws:ec2:eu-west-2:017363970217:image/ami-39f3e25d]="Unnamed,true" [arn:aws:ec2:eu-west-2:017363970217:instance/i-05f2fda5ce72c2b36]="2018-02-14 07:22:33,false" [arn:aws:ec2:eu-west-2:017363970217:security-group/sg-74c92d1c]="Unnamed,true" [arn:aws:ec2:eu-west-2:017363970217:security-group/sg-871732ee]="appSecurityGroup,true" [arn:aws:ec2:eu-west-2:017363970217:instance/i-0a65849491dfc8562]="yxc,false" [arn:aws:ec2:eu-west-2:017363970217:security-group/sg-9732d6ff]="test,false" [arn:aws:ec2:eu-west-2:017363970217:instance/i-068b644ad5fbe6e5f]="12,false" [arn:aws:ec2:eu-west-2:017363970217:instance/i-0f5308bf09c42d7f9]="123,false" [arn:aws:ec2:eu-west-2:017363970217:instance/i-0c57f03afc68de24c]="Multi Instance,true" [arn:aws:ec2:eu-west-2:017363970217:instance/i-08e4a99e69f6d8dce]="2018-02-14 07:07:20,false" [arn:aws:ec2:eu-west-2:017363970217:security-group/sg-7f0c2916]="Database and Messaging,false" ) diff --git a/configuration/aws-automation/lib/scenario_associate_alb.sh b/configuration/aws-automation/lib/scenario_associate_alb.sh index 59a5345e566..2b32dc9e51a 100644 --- a/configuration/aws-automation/lib/scenario_associate_alb.sh +++ b/configuration/aws-automation/lib/scenario_associate_alb.sh @@ -14,23 +14,33 @@ function associate_alb_start(){ # the user will be prompted to enter a value # ----------------------------------------------------------- function associate_alb_require(){ - require_region - require_instance_type - require_instance_name - require_instance_short_name - require_key_name + # will only be executed after instance creation + require_instance + require_instance_short_name + require_load_balancer require_key_file - require_new_admin_password - require_user_username - require_user_password + require_ssh_user + + disable_aws_success_output + instance_id=$(get_resource_id $instance) + public_dns_name=$(get_public_dns_name $instance_id) + enable_aws_success_output } function associate_alb_execute() { - local vpc_id=$(get_default_vpc_id) - local target_group_arn=$(create_target_group $instance_name $vpc_id | get_attribute '.TargetGroups[0].TargetGroupArn') - register_targets $target_group_arn $instance_id - local domain=$(create_rule $listener_arn $instance_short_name $target_group_arn) + # create target group for instance + local target_group_arn=$(exit_on_fail create_target_group "S-dedicated-$instance_short_name") + exit_on_fail set_target_group_health_check "$target_group_arn" "HTTP" "/index.html" "80" "5" "4" "2" "2" + + listener_arn=$(exit_on_fail get_first_https_listener $load_balancer) + exit_on_fail register_targets $target_group_arn $instance_id + + # create rule for host-based forwarding within alb + local domain=$(exit_on_fail create_rule $listener_arn $instance_short_name $target_group_arn) header "Apache configuration" - configure_apache "$domain" "$event_id" "$ssh_user" "$public_dns_name" "ssl" + + # configure apache with event macro + exit_on_fail append_macro_to_001_events_conf $domain "" $ssh_user $public_dns_name "8888" + exit_on_fail reload_httpd $ssh_user $public_dns_name } diff --git a/configuration/aws-automation/lib/scenario_associate_clb.sh b/configuration/aws-automation/lib/scenario_associate_clb.sh deleted file mode 100644 index ba1932b307d..00000000000 --- a/configuration/aws-automation/lib/scenario_associate_clb.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash - -# Create classic load balancer for instance -# ------------------------------------------------------ - -function associate_clb_start(){ - associate_clb_require - associate_clb_execute -} - -# ----------------------------------------------------------- -# All these variables are needed for this scenario -# If one variable is not assigned or passed by parameter -# the user will be prompted to enter a value -# ----------------------------------------------------------- -function associate_clb_require(){ - require_region - require_instance_type - require_instance_name - require_instance_short_name - require_key_name - require_key_file - require_new_admin_password - require_user_username - require_user_password -} - -function associate_clb_execute() { - header "Associating classic load balancer" - - local load_balancer_dns_name=$(create_load_balancer_https "$instance_name" "$certificate_arn" | get_attribute '.DNSName') - local json_health_check=$(configure_health_check_https "$instance_name") - local json_added_instance_response=$(add_instance_to_elb "$instance_name" "$instance_id") - - header "Apache configuration" - configure_apache "$public_dns_name" "$event_id" "$ssh_user" "$public_dns_name" "ssl" -} diff --git a/configuration/aws-automation/lib/scenario_associate_elastic_ip.sh b/configuration/aws-automation/lib/scenario_associate_elastic_ip.sh deleted file mode 100644 index 554bd280db3..00000000000 --- a/configuration/aws-automation/lib/scenario_associate_elastic_ip.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash - -# Create elastic ip and associate it with instance -# ------------------------------------------------------ - -function associate_elastic_ip_start(){ - associate_elastic_ip_require - associate_elastic_ip_execute -} - -# ----------------------------------------------------------- -# All these variables are needed for this scenario -# If one variable is not assigned or passed by parameter -# the user will be prompted to enter a value -# ----------------------------------------------------------- -function associate_elastic_ip_require(){ - require_region - require_instance_type - require_instance_name - require_instance_short_name - require_key_name - require_key_file - require_new_admin_password - require_user_username - require_user_password -} - -function associate_elastic_ip_execute() { - header "Elastic ip creation" - - local elastic_ip=$(allocate_address) - associate_address "$instance_id" "$elastic_ip" - - header "Apache configuration" - configure_apache "$elastic_ip" "$event_id" "$ssh_user" "$elastic_ip" "ssl" -} diff --git a/configuration/aws-automation/lib/scenario_instance.sh b/configuration/aws-automation/lib/scenario_instance.sh index 95b9e7a5c0a..7cd5d1255e1 100644 --- a/configuration/aws-automation/lib/scenario_instance.sh +++ b/configuration/aws-automation/lib/scenario_instance.sh @@ -5,6 +5,7 @@ function instance_start(){ instance_require + instance_preview instance_execute } @@ -16,42 +17,57 @@ function instance_start(){ function instance_require(){ require_region require_instance_type + require_instance_security_group_id + require_image_id require_instance_name require_instance_short_name + require_ssh_user + require_build_version require_key_name require_key_file + require_event_name require_new_admin_password require_user_username require_user_password } +function instance_preview(){ + header "Preview" + user_data=$(build_user_data) + local_echo "An instance will be created with the following specifications: " + local_echo "To be implemented..." + seek_confirmation "Do you want to continue?" + if is_confirmed; then + break + else + break + fi +} + +# ----------------------------------------------------------- +# Execute instance scenario +# @param $1 user data +# ----------------------------------------------------------- function instance_execute() { + header "Instance Initialization" - local json_instance=$(run_instance) - instance_id=$(echo "$json_instance" | get_attribute '.Instances[0].InstanceId') - instance_private_ip=$(echo "$json_instance" | get_attribute '.Instances[0].PrivateIpAddress') - - wait_instance_exists "$instance_id" - - public_dns_name=$(query_public_dns_name "$instance_id") - - header "SSH Connection" - - wait_for_ssh_connection "$ssh_user" "$public_dns_name" - - if $tail; then - tail_start - fi + instance_id=$(create_instance "$user_data") + public_dns_name=$(get_public_dns_name $instance_id) + wait_for_ssh_connection $ssh_user $public_dns_name header "Event and user creation" - wait_for_access_token_resource "$admin_username" "$admin_password" "$public_dns_name" - access_token=$(get_access_token "$admin_username" "$admin_password" "$public_dns_name") + local port="8888" + configure_application $public_dns_name $port $event_name $new_admin_password $user_username $user_pass - wait_for_create_event_resource "$public_dns_name" - event_id=$(create_event "$access_token" "$public_dns_name" "$instance_name") + header "Conclusion" - change_admin_password "$access_token" "$public_dns_name" "$admin_username" "$new_admin_password" - create_new_user "$access_token" "$public_dns_name" "$user_username" "$user_password" + success "Instance should be reachable through $public_dns_name." +} + +function build_user_data(){ + build_configuration "MONGODB_HOST=$default_mongodb_host" "MONGODB_PORT=$default_mongodb_port" "MONGODB_NAME=$(alphanumeric $instance_name)" \ + "REPLICATION_CHANNEL=$(alphanumeric $instance_name)" "SERVER_NAME=$(alphanumeric $instance_name)" "USE_ENVIRONMENT=live-server" \ + "INSTALL_FROM_RELEASE=$build_version" "SERVER_STARTUP_NOTIFY=$default_server_startup_notify" } diff --git a/configuration/aws-automation/lib/scenario_master_instance.sh b/configuration/aws-automation/lib/scenario_master_instance.sh new file mode 100644 index 00000000000..a075900f6d1 --- /dev/null +++ b/configuration/aws-automation/lib/scenario_master_instance.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash + +#!/usr/bin/env bash + +# Scenario for creating an instance +# ------------------------------------------------------ + +function master_instance_start(){ + master_instance_require + master_instance_execute +} + +# ----------------------------------------------------------- +# All these variables are needed for this scenario +# If one variable is not assigned or passed by parameter +# the user will be prompted to enter a value +# ----------------------------------------------------------- +function master_instance_require(){ + require_region + require_instance_type + require_instance_name + require_instance_short_name + require_ssh_user + require_build_version + require_key_name + require_key_file + require_new_admin_password + require_user_username + require_user_password +} + +# ----------------------------------------------------------- +# Execute instance scenario +# @param $1 user data +# ----------------------------------------------------------- +function master_instance_execute() { + header "Master Instance Initialization" + + ssh_user=$image_ssh_user + + local user_data_master=$(build_configuration "MONGODB_HOST=$mongodb_host" "MONGODB_PORT=$mongodb_port" "MONGODB_NAME=$(alphanumeric $instance_name)" \ + "REPLICATION_CHANNEL=$(alphanumeric $instance_name)" "SERVER_NAME=$(alphanumeric $instance_name)" "USE_ENVIRONMENT=live-master-server" \ + "INSTALL_FROM_RELEASE=$build_version" "SERVER_STARTUP_NOTIFY=$default_server_startup_notify") + + instance_id=$(exit_on_fail create_instance "$user_data_master") + public_dns_name=$(get_public_dns_name $instance_id) + + wait_for_ssh_connection $ssh_user $public_dns_name + + header "Event and user creation" + + port="8888" + configure_application $public_dns_name $port $event_name $new_admin_password $user_username $user_password + + public_ip=$(get_public_ip $public_dns_name) + + local user_data_replica=$(build_configuration "MONGODB_HOST=$mongodb_host" "MONGODB_PORT=$mongodb_port" "MONGODB_NAME=$(alphanumeric $instance_name)-replica" \ + "REPLICATION_CHANNEL=$(alphanumeric $instance_name)" "SERVER_NAME=$(alphanumeric $instance_name)" "USE_ENVIRONMENT=live-replica-server" \ + "REPLICATE_MASTER_SERVLET_HOST=$public_dns_name" "REPLICATE_MASTER_EXCHANGE_HOST=$(alphanumeric $instance_name)" "EVENT_ID=$event_id" "INSTALL_FROM_RELEASE=$build_version" "SERVER_STARTUP_NOTIFY=$default_server_startup_notify") + + local base64_user_data=$(echo "$user_data_replica" | base64 -w 0) + local launch_template_for_replica=$(printf '{"UserData":"%s","ImageId":"%s","InstanceType":"%s","TagSpecifications":[{"ResourceType":"instance","Tags":[{"Key":"Name","Value":"%s"}]}], "SecurityGroupIds": ["%s"], "KeyName": "%s"}'\ + "${base64_user_data}" "$image_id" "$instance_type" "$instance_name (Replica)" "$instance_security_group_id" "$key_name") + + aws_wrapper ec2 create-launch-template --launch-template-name "Replica_$instance_short_name" --version-description "Replica for $instance_name." --launch-template-data $launch_template_for_replica + +} diff --git a/configuration/aws-automation/lib/scenario_shared_instance.sh b/configuration/aws-automation/lib/scenario_shared_instance.sh new file mode 100644 index 00000000000..78bd09482ec --- /dev/null +++ b/configuration/aws-automation/lib/scenario_shared_instance.sh @@ -0,0 +1,180 @@ +#!/usr/bin/env bash + +# Scenario for creating a shared instance +# ------------------------------------------------------ + +function shared_instance_start(){ + shared_instance_require + shared_instance_check_preconditions + shared_instance_execute +} + +# ----------------------------------------------------------- +# All these variables are needed for this scenario +# If one variable is not assigned or passed by parameter +# the user will be prompted to enter a value +# ----------------------------------------------------------- +function shared_instance_require(){ + require_super_instance + require_load_balancer + + require_instance_name + require_instance_short_name + require_build_version + require_key_file + require_event_name + require_new_admin_password + require_user_username + require_user_password + require_ssh_user + require_contact_person + require_contact_email + require_description + + disable_aws_success_output + local instance_id=$(get_resource_id $super_instance) + super_instance=$(get_public_dns_name $instance_id) + enable_aws_success_output +} + +function shared_instance_check_preconditions(){ + sailing_dir="/home/sailing" + servers_dir="$sailing_dir/servers" + server_dir="$servers_dir/$instance_short_name" + + header "Checking preconditions" + + local instance_id=$(get_instance_id $super_instance) + + # echo "Checking if instance has tag: 'ssh_user'." + # ssh_user=$(exit_on_fail get_tag_value_for_key $instance_id "ssh_user") + + echo "Checking if directory $server_dir exists already..." + exit_on_fail execute_remote "[ ! -d $server_dir ]" + + echo "Checking if ssh connection $ssh_user@$super_instance is working..." + exit_on_fail execute_remote ls + + # echo "Checking if super instance has user data.\ + # The instance should not have any user data set, otherwise the refreshInstance script will not work properly." + # exit_on_fail [ $(get_user_data_from_instance $instance_id) == "None" ] +} + +function shared_instance_execute() { + local server_env_file="$server_dir/env.sh" + local readme_file="$servers_dir/README" + local comment_out_line_in_env_with_pattern='JAVA_HOME=\/opt\/jdk1.8.0_20' + local comment_in_line_in_env_with_pattern='sapjvm_gc' + local refreshInstance_file='/home/sailing/code/java/target/refreshInstance.sh' + + header "Sub instance setup" + + # create sub instance server directory + local_echo "Creating directory $server_dir ..." + exit_on_fail execute_remote mkdir $server_dir + + local_echo "Getting next unused SERVER_PORT..." + local server_port=$(find_first_unused_port "SERVER_PORT" "8888") + + local_echo "Getting next unused TELNET_PORT..." + local telnet_port=$(find_first_unused_port "TELNET_PORT" "14900") + + local_echo "Getting next unused EXPEDITION_PORT..." + local expedition_port=$(find_first_unused_port "EXPEDITION_PORT" "2000") + + # copy refreshInstance.sh from /servers/server to sub instance directory + local_echo "Copying $refreshInstance_file to $server_dir..." + exit_on_fail execute_remote cp $refreshInstance_file $server_dir + + # Execute refreshInstance.sh + local_echo "Executing refreshInstance.sh with build version $build_version ..." + exit_on_fail execute_remote "set -o pipefail;export DEPLOY_TO=$instance_short_name;cd $server_dir;./refreshInstance.sh install-release $build_version > /dev/null 2>&1;" + + # uncommenting lines containing pattern + local_echo "Commenting in $comment_in_line_in_env_with_pattern inside $server_env_file..." + execute_remote "sed -i '/$comment_in_line_in_env_with_pattern/s/^#//g' $server_env_file" + + # commenting out lines containing pattern + local_echo "Commenting out $comment_out_line_in_env_with_pattern inside $server_env_file..." + execute_remote "sed -i '/$comment_out_line_in_env_with_pattern/s/^/#/g' $server_env_file" + + local_echo "Configuring $server_env_file..." + environment="live-server" + env_content=$(wget -qO- http://releases.sapsailing.com/environments/$environment) + + exit_on_fail execute_remote "echo -e \"# START Environment: $environment \" >> $server_env_file" + exit_on_fail execute_remote "echo -e \"$env_content\" >> $server_env_file" + exit_on_fail execute_remote "echo -e \"# END Environment: $environment \" >> $server_env_file" + + local env_patch=$(build_configuration "# START Script $script_start_time" "SERVER_NAME=$(alphanumeric $instance_name)" "TELNET_PORT=$telnet_port" \ + "SERVER_PORT=$server_port" "EXPEDITION_PORT=$expedition_port" "MONGODB_NAME=$(alphanumeric $instance_name)" "MONGODB_HOST=$default_mongodb_host" \ + "MONGODB_PORT=$default_mongodb_port" "DEPLOY_TO=$instance_short_name" "SERVER_STARTUP_NOTIFY=${default_server_startup_notify:-" "}" \ + "BUILD_COMPLETE_NOTIFY=${default_build_complete_notifiy:-" "}" "# END Script $script_start_time") + + exit_on_fail execute_remote "echo -e \"$env_patch\" >> $server_env_file" + + local memory="10000m" + exit_on_fail execute_remote "echo -e \"MEMORY=$memory\" >> $server_env_file" + + local_echo "Creating README file if it does not exist already..." + execute_remote touch $readme_file + + local_echo "Appending patch to README file..." + execute_remote "echo -e \"\n# $instance_short_name (${description:-"Unknown"}, ${contact_person:-"Unknown"},${contact_email:-"Unknown"})\n$env_patch\" >> $readme_file" + + # start server and redirect both stderr and stdout to /dev/null (&>/dev/null). Send command to background by &. Do this to avoid blocking. + local_echo "Starting server..." + exit_on_fail execute_remote -f "set -o pipefail;sh -c \"cd $server_dir; nohup ./start > /dev/null 2>&1 &\"" + + header "Event and user creation" + + event_id=$(configure_application $super_instance $server_port $event_name $new_admin_password $user_username $user_password) + + header "Configuring ALB" + + listener_arn=$(get_first_https_listener $load_balancer) + local target_group_arn=$(exit_on_fail create_target_group "S-shared-$instance_short_name") + set_target_group_health_check "$target_group_arn" "HTTP" "/index.html" "$server_port" "5" "4" "2" "2" + + register_targets $target_group_arn $(get_instance_id $super_instance) + + local domain=$(create_rule $listener_arn $instance_short_name $target_group_arn) + + header "Configuring Apache" + + execute_remote_root "echo -e \"\n# $instance_short_name (${description:-"Unknown"}, ${contact_person:-"Unknown"}, ${contact_email:-"Unknown"})\" >> $events_conf" + append_macro_to_001_events_conf "$domain" "$event_id" "root" "$super_instance" "$server_port" + + header "Conclusion" + + success "Sub instance should be reachable through https://$domain." +} + +# ----------------------------------------------------------- +# For a given variable name, find first number that is not already used within all env.sh files inside /home/sailing/servers. +# If no number is found, return $2 +# @param $1 variable name (e.g. SERVER_PORT) +# @param $2 default number (e.g. 8880) +# ----------------------------------------------------------- +function find_first_unused_port(){ + last_port=($(get_last_used_port "$1")) + if [ $? -ne 0 ]; then + port_to_use=$2 + else + port_to_use=$(($last_port+1)) + fi + success $port_to_use + echo $port_to_use +} + +function execute_remote(){ + ssh_wrapper $ssh_user@$super_instance "$@" +} + +function execute_remote_root(){ + ssh_wrapper "root"@$super_instance "$@" +} + +function get_last_used_port(){ + ssh_prewrapper $ssh_user@$super_instance "set -o pipefail; for i in /home/sailing/servers/*/env.sh; do cat \$i | grep '^ *$1=' | tr -d '$1='; done | sort -n | tail -1" +} diff --git a/configuration/aws-automation/lib/scenario_tail.sh b/configuration/aws-automation/lib/scenario_tail.sh deleted file mode 100644 index c4d7068a548..00000000000 --- a/configuration/aws-automation/lib/scenario_tail.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env bash - -function tail_start(){ - tail_precondition - tail_user_input - tail_execute -} - -function tail_precondition(){ - check_if_tmux_is_used -} - -# ----------------------------------------------------------- -# All these variables are needed for this scenario -# If one variable is not assigned or passed by parameter -# the user will be prompted to enter a value -# ----------------------------------------------------------- -function tail_user_input(){ - require_key_file - require_ssh_user - require_public_dns_name -} - -function tail_execute() { - echo "Open tmux panes and start tailing log files..." - construct_ui - wait_for_ssh_connection "$ssh_user" "$public_dns_name" - open_connections "$ssh_user" "$public_dns_name" - tail_logfiles -} - -function tail_logfiles(){ - tmux send-keys -t 1 "clear;echo \"Waiting for file $sailing_0 to appear...\";touch $sailing_0;cat $sailing_0;tail -F -v $sailing_0" C-m - tmux send-keys -t 2 "clear;cat $sailing_out;tail -F -v $sailing_out" C-m - tmux send-keys -t 3 "clear;cat $sailing_err;tail -F -v $sailing_err" C-m -} diff --git a/configuration/aws-automation/lib/util_functions.sh b/configuration/aws-automation/lib/util_functions.sh index 3168b45d9e0..91d1cf6c944 100644 --- a/configuration/aws-automation/lib/util_functions.sh +++ b/configuration/aws-automation/lib/util_functions.sh @@ -1,37 +1,43 @@ #!/usr/bin/env bash # ----------------------------------------------------------- -# Creates a parameter from a key and value -# @param $1 key -# @param $2 value -# @return result ("--key value") +# Appends multiple parameters (e.g. "SERVER_NAME=Test") +# Also includes parameters starting with "#" as comments. +# @param $@ variable key and value (e.g. "MONGODB_HOST=123.123.123.123") # ----------------------------------------------------------- -function add_param() { - if [ ! -z "$2" ]; then - local result=" --$1 $2" - fi - echo "$result" -} - -# ----------------------------------------------------------- -# Constructs string of user data variable name and value plus linebreak -# @param $1 user data variable name -# @param $2 user data variable value -# @return name=value (linebreak) if value not empty else nothing -# ----------------------------------------------------------- -function add_user_data_variable(){ - if ! [ -z "$2" ]; then - local CR_LF=$'\r'$'\n' - local content="$1=$2" - content+=$CR_LF - echo "$content" +function build_configuration(){ + for var in "$@" + do + # if parameters starts with #, add parameters to content + if [[ $var == \#* ]]; then + content+="$var\n" + continue fi + + key=${var%=*} + value=${var#*=} + + if [[ ! -z "$key" && ! -z "$value" ]]; then + content+="$key=$value\n" + fi + done + echo -e $content } function command_was_successful(){ [ $1 -eq 0 ] } +# ----------------------------------------------------------- +# Uncomments a line starting with a specific pattern +# @param $1 pattern +# @param $2 file +# ----------------------------------------------------------- +function uncomment_line_starting_with(){ + sed -i '/$1/s/^#//g' $2 +} + + # ----------------------------------------------------------- # Checks if variable is a number # @param $1 returnvariablevalue @@ -41,6 +47,8 @@ function is_number(){ [[ $1 =~ ^-?[0-9]+$ ]] } + + # ----------------------------------------------------------- # Check if variable is a number and its value is 200 # @param $1 variable @@ -66,6 +74,15 @@ function sanitize(){ tr -d '\r' } +function alphanumeric(){ + lower_trim $1 | only_letters_and_numbers +} + +function only_letters_numbers_dash(){ + echo $1 | trim | tr -d -c '[:alnum:]-' +} + + # ------------------------------------------------------ # The following functions were part of a bash template # and could be useful in the future @@ -234,41 +251,6 @@ function is_not_empty() { return 1 } -# Test whether a command exists -# ------------------------------------------------------ -# Usage: -# if type_exists 'git'; then -# some action -# else -# some other action -# fi -# ------------------------------------------------------ - -function type_exists() { - if [ "$(type -P "$1")" ]; then - return 0 - fi - return 1 -} - -function type_not_exists() { - if [ ! "$(type -P "$1")" ]; then - return 0 - fi - return 1 -} - -# Test which OS the user runs -# $1 = OS to test -# Usage: if is_os 'darwin'; then - -function is_os() { - if [[ "${OSTYPE}" == $1* ]]; then - return 0 - fi - return 1 -} - # SEEKING CONFIRMATION # ------------------------------------------------------ @@ -364,16 +346,6 @@ function pauseScript() { fi } -function in_array() { - # Determine if a value is in an array. - # Usage: if in_array "VALUE" "${ARRAY[@]}"; then ... - local value="$1"; shift - for arrayItem in "$@"; do - [[ "${arrayItem}" == "${value}" ]] && return 0 - done - return 1 -} - # Text Transformations # ----------------------------------- # Transform text using these functions. @@ -387,13 +359,6 @@ lower() { tr '[:upper:]' '[:lower:]' } -upper() { - # Convert stdin to uppercase. - # usage: text=$(upper <<<"$1") - # echo "MAKETHISUPPERCASE" | upper - tr '[:lower:]' '[:upper:]' -} - function lower_trim(){ echo $1 | lower | trim } @@ -403,242 +368,10 @@ trim() { tr -d '[:space:]' } -ltrim() { - # Removes all leading whitespace (from the left). - local char=${1:-[:space:]} - sed "s%^[${char//%/\\%}]*%%" -} - -rtrim() { - # Removes all trailing whitespace (from the right). - local char=${1:-[:space:]} - sed "s%[${char//%/\\%}]*$%%" -} - -lttrim() { - # Removes all leading/trailing whitespace - # Usage examples: - # echo " foo bar baz " | trim #==> "foo bar baz" - ltrim "$1" | rtrim "$1" -} - only_letters_and_numbers(){ tr -d -c '[:alnum:]' } -squeeze() { - # Removes leading/trailing whitespace and condenses all other consecutive - # whitespace into a single space. - # - # Usage examples: - # echo " foo bar baz " | squeeze #==> "foo bar baz" - - local char=${1:-[[:space:]]} - sed "s%\(${char//%/\\%}\)\+%\1%g" | trim "$char" -} - -squeeze_lines() { - # {{{ - # - # Removes all leading/trailing blank lines and condenses all other - # consecutive blank lines into a single blank line. - # - # }}} - - sed '/^[[:space:]]\+$/s/.*//g' | cat -s | trim_lines -} - -progressBar() { - # progressBar - # ----------------------------------- - # Prints a progress bar within a for/while loop. - # To use this function you must pass the total number of - # times the loop will run to the function. - # - # usage: - # for number in $(seq 0 100); do - # sleep 1 - # progressBar 100 - # done - # ----------------------------------- - if [[ "${quiet}" = "true" ]] || [ "${quiet}" == "1" ]; then - return - fi - - local width - width=30 - bar_char="#" - - # Don't run this function when scripts are running in verbose mode - if ${verbose}; then return; fi - - # Reset the count - if [ -z "${progressBarProgress}" ]; then - progressBarProgress=0 - fi - - # Do nothing if the output is not a terminal - if [ ! -t 1 ]; then - echo "Output is not a terminal" 1>&2 - return - fi - # Hide the cursor - tput civis - trap 'tput cnorm; exit 1' SIGINT - - if [ ! "${progressBarProgress}" -eq $(( $1 - 1 )) ]; then - # Compute the percentage. - perc=$(( progressBarProgress * 100 / $1 )) - # Compute the number of blocks to represent the percentage. - num=$(( progressBarProgress * width / $1 )) - # Create the progress bar string. - bar= - if [ ${num} -gt 0 ]; then - bar=$(printf "%0.s${bar_char}" $(seq 1 ${num})) - fi - # Print the progress bar. - progressBarLine=$(printf "%s [%-${width}s] (%d%%)" "Running Process" "${bar}" "${perc}") - echo -en "${progressBarLine}\r" - progressBarProgress=$(( progressBarProgress + 1 )) - else - # Clear the progress bar when complete - echo -ne "${width}%\033[0K\r" - unset progressBarProgress - fi - - tput cnorm -} - -htmlDecode() { - # Decode HTML characters with sed - # Usage: htmlDecode - echo "${1}" | sed -f "${SOURCEPATH}/htmlDecode.sed" -} - -htmlEncode() { - # Encode HTML characters with sed - # Usage: htmlEncode - echo "${1}" | sed -f "${SOURCEPATH}/htmlEncode.sed" -} - -urlencode() { - # URL encoding/decoding from: https://gist.github.com/cdown/1163649 - # Usage: urlencode - - local length="${#1}" - for (( i = 0; i < length; i++ )); do - local c="${1:i:1}" - case $c in - [a-zA-Z0-9.~_-]) printf "%s" "$c" ;; - *) printf '%%%02X' "'$c" - esac - done -} - -urldecode() { - # Usage: urldecode - - local url_encoded="${1//+/ }" - printf '%b' "${url_encoded//%/\x}" -} - -parse_yaml() { - # Function to parse YAML files and add values to variables. Send it to a temp file and source it - # https://gist.github.com/DinoChiesa/3e3c3866b51290f31243 which is derived from - # https://gist.github.com/epiloque/8cf512c6d64641bde388 - # - # Usage: - # $ parse_yaml sample.yml > /some/tempfile - # - # parse_yaml accepts a prefix argument so that imported settings all have a common prefix - # (which will reduce the risk of name-space collisions). - # - # $ parse_yaml sample.yml "CONF_" - - local prefix=$2 - local s - local w - local fs - s='[[:space:]]*' - w='[a-zA-Z0-9_]*' - fs="$(echo @|tr @ '\034')" - sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \ - -e "s|^\($s\)\($w\)$s[:-]$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$1" | - awk -F"$fs" '{ - indent = length($1)/2; - if (length($2) == 0) { conj[indent]="+";} else {conj[indent]="";} - vname[indent] = $2; - for (i in vname) {if (i > indent) {delete vname[i]}} - if (length($3) > 0) { - vn=""; for (i=0; i> "${csvFile}" - IFS=$saveIFS - -} - -function json2yaml() { - # convert json files to yaml using python and PyYAML - python -c 'import sys, yaml, json; yaml.safe_dump(json.load(sys.stdin), sys.stdout, default_flow_style=False)' < "$1" -} - -function yaml2json() { - # convert yaml files to json using python and PyYAML - python -c 'import sys, yaml, json; json.dump(yaml.load(sys.stdin), sys.stdout, indent=4)' < "$1" -} diff --git a/configuration/aws-automation/lib/util_variables.sh b/configuration/aws-automation/lib/util_variables.sh deleted file mode 100644 index 2c02cadd048..00000000000 --- a/configuration/aws-automation/lib/util_variables.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -# ------------------------------------------------------ -# The following variables were part of a bash template -# and could be useful in the future -# ------------------------------------------------------ - -# SCRIPTNAME -scriptName=`basename $0` #Set Script Name variable -scriptBasename="$(basename ${scriptName} .sh)" # Strips '.sh' from scriptName - -# TIMESTAMPS - -now=$(LC_ALL=C date +"%Y-%m-%d %H:%M:%S") # Returns: 2015-06-15 22:34:40 -datestamp=$(LC_ALL=C date +%Y-%m-%d) # Returns: 2015-06-14 -hourstamp=$(LC_ALL=C date +%r) # Returns: 10:34:40 PM -timestamp=$(LC_ALL=C date +%Y%m%d_%H%M%S) # Returns: 20150614_223440 -today=$(LC_ALL=C date +"%m-%d-%Y") # Returns: 06-14-2015 -longdate=$(LC_ALL=C date +"%a, %d %b %Y %H:%M:%S %z") # Returns: Sun, 10 Jan 2016 20:47:53 -0500 -gmtdate=$(LC_ALL=C date -u -R | sed 's/\+0000/GMT/') # Returns: Wed, 13 Jan 2016 15:55:29 GMT - -# THISHOST -thisHost=$(hostname) diff --git a/configuration/aws-automation/lib/utils.sh b/configuration/aws-automation/lib/utils.sh index f6e44d89b48..d0e53877efe 100644 --- a/configuration/aws-automation/lib/utils.sh +++ b/configuration/aws-automation/lib/utils.sh @@ -82,10 +82,22 @@ fi for utility_file in "${SOURCEPATH}"/*.sh do if [ -e "${utility_file}" ]; then + # Don't source self if [[ "${utility_file}" == *"utils.sh"* ]]; then continue fi + + # Don't source configurator + if [[ "${utility_file}" == *"build-config.sh"* ]]; then + continue + fi + + # Don't source resources file (will be sourced later) + if [[ "${utility_file}" == *"resources"* ]]; then + continue + fi + source "$utility_file" fi done diff --git a/configuration/aws-automation/lib/validation.sh b/configuration/aws-automation/lib/validation.sh new file mode 100644 index 00000000000..0ee358773e2 --- /dev/null +++ b/configuration/aws-automation/lib/validation.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +ValidHostnameRegex='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$' +ValidIpAddressRegex='^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$' +ValidInstanceIdRegex='i-[a-zA-Z0-9]{17}' +ValidResourceARNRegex="arn:.+" + +function validate_resource_arn(){ + [[ "$1" =~ $ValidResourceARNRegex ]] && echo "$1" || error_message "ResourceARN \"$1\" not valid." +} + +function validate_instance_id(){ + [[ "$1" =~ $ValidInstanceIdRegex ]] && echo "$1" || error_message "Instance id \"$1\" not valid." +} + +function validate_hostname(){ + [[ "$1" =~ $ValidHostnameRegex ]] && echo "$1" || error_message "Hostname \"$1\" not valid." +} + +function validate_ipaddress(){ + [[ "$1" =~ $ValidIpAddressRegex ]] && echo "$1" || error_message "Ip address \"$1\" not valid." +} + +function error_message(){ + error "[ ERROR ] $1 (${FUNCNAME[@]})" + return 1 +} diff --git a/configuration/aws-automation/lib/variables.sh b/configuration/aws-automation/lib/variables.sh new file mode 100644 index 00000000000..976004b10bf --- /dev/null +++ b/configuration/aws-automation/lib/variables.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +latest_release=$(get_latest_release) +events_conf='/etc/httpd/conf.d/001-events.conf' +scriptName=`basename $0` #Set Script Name variable +scriptBasename="$(basename ${scriptName} .sh)" # Strips '.sh' from scriptName +declare -r script_start_time=$(LC_ALL=C date +"%Y-%m-%d %H:%M:%S") # Returns: 2015-06-15 22:34:40 +declare -r timestamp=$(LC_ALL=C date +%Y%m%d_%H%M%S) # Returns: 20150614_223440 +today=$(LC_ALL=C date +"%m-%d-%Y") # Returns: 06-14-2015 +longdate=$(LC_ALL=C date +"%a, %d %b %Y %H:%M:%S %z") # Returns: Sun, 10 Jan 2016 20:47:53 -0500 +gmtdate=$(LC_ALL=C date -u -R | sed 's/\+0000/GMT/') # Returns: Wed, 13 Jan 2016 15:55:29 GMT +thisHost=$(hostname) diff --git a/configuration/aws-automation/lib/variables_aws.sh b/configuration/aws-automation/lib/variables_aws.sh deleted file mode 100644 index b89802d8baa..00000000000 --- a/configuration/aws-automation/lib/variables_aws.sh +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env bash - -# Set variables here -# ------------------------------------------------------ - -# Please comment out the variables of the region you are not using - -# Default region unspecific variables - -default_region=eu-west-2 -default_key_name=leonradeck-keypair -default_key_file='/cygdrive/c/Users/d069485/.ssh/leonradeck-keypair.pem' -default_instance_type=t2.medium -default_server_startup_notify=leon.radeck@sap.com -default_new_admin_password=admin -default_user_username=newuser -default_user_password=newpassword -hosted_zone_id=Z1R8UBAEXAMPLE - -# Variables for region "eu-west-2" (London) - -instance_security_group_ids=sg-871732ee -image_id=ami-39f3e25d -certificate_arn='arn:aws:acm:eu-west-2:017363970217:certificate/d2ae17b0-75ed-4b56-ac12-a80f9e5c493d' -elb_security_group_ids=sg-871732ee -mongodb_host=35.176.143.232 -mongodb_port=27017 -listener_arn='arn:aws:elasticloadbalancing:eu-west-2:017363970217:listener/app/DummyALB/da70f61a914fc50a/22ab628fba30d0e7' - -# Variables for region "eu-west-1" (Ireland) - -# instance_security_group_ids=sg-eaf31e85 -# image_id= -# certificate_arn='' -# elb_security_group_ids= -# mongodb_host=54.76.64.42 -# mongodb_port=27017 -# listener_arn='arn:aws:elasticloadbalancing:eu-west-1:017363970217:listener/app/DummyALBIreland/a86d2ab6ddfc4f18/bcbbd28a0ca75e54' - - -# Other Variables - -ssh_retry_interval=2 -http_retry_interval=5 -user_data_file=".userdata.txt" -change_resource_record_set_file=".change-resource-record-set.json" -tag_specifications="\'ResourceType=instance,Tags=[{Key=Name,Value=%s}]\'" -admin_username=admin -admin_password=admin -instance_count=1 -ssh_user=root -default_instance_name="$now" -default_instance_short_name="$timestamp" -sailing_0='/home/sailing/servers/server/logs/sailing0.log.0' -sailing_err='/var/log/sailing.err' -sailing_out='/var/log/sailing.out' -events_conf='/etc/httpd/conf.d/001-events.conf' diff --git a/configuration/uniqueIpsPerYear b/configuration/uniqueIpsPerYear new file mode 100755 index 00000000000..de17e124c8b --- /dev/null +++ b/configuration/uniqueIpsPerYear @@ -0,0 +1,10 @@ +#!/bin/sh +year=$1 +read -s -p "Password for user awstats on awstats.sapsailing.com: " passwd +if [ "$year" = "" ]; then + year=`date +%Y` +fi +for i in Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec; do + echo -n "$i: " + wget -O - "http://awstats:$passwd@awstats.sapsailing.com/unique-visitors/unique-ips-days-useragents-per-event-$i-$year" 2>/dev/null | awk '{ sum=sum+$2; } END { print sum; }' +done | awk '{ print $0; total=total+$2; } END { print "Total: " total; }' diff --git a/configuration/unique_ips_per_referrer_fast b/configuration/unique_ips_per_referrer_fast index 0f017614ddd..eb09a917247 100755 --- a/configuration/unique_ips_per_referrer_fast +++ b/configuration/unique_ips_per_referrer_fast @@ -8,4 +8,9 @@ CACHE=$VARLOGOLD/cache/unique-ips-per-referrer JAR_FILE=$VARLOGOLD/com.sap.sse.jar -$JAVA_HOME/bin/java -jar $JAR_FILE $CACHE $* +$JAVA_HOME/bin/java -Xmx8G -jar $JAR_FILE $CACHE $* + +# Do the sorting of all .unique files now: +for i in `find $CACHE -name '*.unique'`; do + cat $i | awk '{ print $2 " " $1; }' | sort -rn >${i}.sorted +done diff --git a/home.md b/home.md index 6dc5626ac91..72eef793135 100644 --- a/home.md +++ b/home.md @@ -35,6 +35,7 @@ SAP is at the center of today’s technology revolution, developing innovations * [[Creating an EC2 image from scratch|wiki/info/landscape/creating-ec2-image-from-scratch]] * [[Creating a webserver EC2 image from scratch|wiki/info/landscape/creating-ec2-image-for-webserver-from-scratch]] * [[EC2 mail relaying|wiki/info/landscape/mail-relaying]] + * [[Setting up dedicated S3 buckets|wiki/info/landscape/s3-bucket-setup]] * [[Building and Deploying|wiki/info/landscape/building-and-deploying]] * [[Data Mining Architecture|wiki/info/landscape/data-mining-architecture]] * [[Typical Data Mining Scenarios|wiki/info/landscape/typical-data-mining-scenarios]] diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/BravoFixWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/BravoFixWithContext.java index 5fc563fb059..3864d8144e0 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/BravoFixWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/BravoFixWithContext.java @@ -4,7 +4,6 @@ import com.sap.sailing.datamining.data.HasBravoFixContext; import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.Bearing; -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; @@ -64,8 +63,8 @@ public class BravoFixWithContext implements HasBravoFixContext { } @Override - public Bearing getAbsoluteTrueWindAngle() throws NoWindException { - return getTrackedRace().getTrackedLeg(getCompetitor(), getTimePoint()).getBeatAngle(getTimePoint()).abs(); + public Bearing getAbsoluteTrueWindAngle() { + return getTrackedRace().getTWA(getCompetitor(), getTimePoint()).abs(); } private Competitor getCompetitor() { diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/FoilingSegmentWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/FoilingSegmentWithContext.java index 402d123aec7..c3e38b5e479 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/FoilingSegmentWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/FoilingSegmentWithContext.java @@ -107,7 +107,8 @@ public class FoilingSegmentWithContext implements HasFoilingSegmentContext { } private Bearing getAbsoluteTrueWindAngle(final TimePoint timePoint) throws NoWindException { - return getTrackedRace().getTrackedLeg(getCompetitor(), timePoint).getBeatAngle(timePoint).abs(); + Bearing twa = getTrackedRace().getTWA(getCompetitor(), timePoint); + return twa == null ? null:twa.abs(); } @Override diff --git a/java/com.sap.sailing.domain.common/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.domain.common/.settings/org.eclipse.core.resources.prefs index f96e639d740..f45ef9dc346 100755 --- a/java/com.sap.sailing.domain.common/.settings/org.eclipse.core.resources.prefs +++ b/java/com.sap.sailing.domain.common/.settings/org.eclipse.core.resources.prefs @@ -1,5 +1,6 @@ eclipse.preferences.version=1 encoding//src/com/sap/sailing/domain/common/AbstractBearing.java=UTF-8 +encoding//src/com/sap/sailing/domain/common/AbstractPosition.java=UTF-8 encoding//src/com/sap/sailing/domain/common/LegType.java=UTF-8 encoding//src/com/sap/sailing/domain/common/confidence/impl/ConfidenceBasedAveragerImpl.java=utf-8 encoding//src/com/sap/sailing/domain/common/impl/AbstractSpeedWithAbstractBearingImpl.java=UTF-8 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 f90d64762b1..aa519f62ff6 100644 --- a/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.common/META-INF/MANIFEST.MF @@ -24,7 +24,8 @@ Export-Package: com.sap.sailing.domain.common, com.sap.sailing.domain.common.sensordata, com.sap.sailing.domain.common.trackfiles, com.sap.sailing.domain.common.tracking, - com.sap.sailing.domain.common.tracking.impl + com.sap.sailing.domain.common.tracking.impl, + com.sap.sailing.domain.common.windfinder Require-Bundle: com.sap.sse.common, com.sap.sse.security.common, com.sap.sse.datamining.annotations;bundle-version="1.0.0" 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 6e81590d215..60df2d1d938 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 @@ -5,6 +5,8 @@ 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.impl.RadianPosition; +import com.sap.sse.common.Util; +import com.sap.sse.common.util.RoundingUtil; public class AbstractPosition implements Position { private static final long serialVersionUID = -3057027562787541064L; @@ -243,4 +245,27 @@ public class AbstractPosition implements Position { return greatCircle; } + @Override + public String getAsDegreesAndDecimalMinutesWithCardinalPoints() { + final String lat = (getLatDeg()>=0 ? "N" : "S") + getDegreesAndDecimalMinutesOfNonNegativeAngle(Math.abs(getLatDeg()), /* degreePlaces */ 2, /* minuteDecimals */ 3); + final String lng = (getLngDeg()>=0 ? "E" : "W") + getDegreesAndDecimalMinutesOfNonNegativeAngle(Math.abs(getLngDeg()), /* degreePlaces */ 3, /* minuteDecimals */ 3); + return lat+" "+lng; + } + + private String getDegreesAndDecimalMinutesOfNonNegativeAngle(double nonNegativeAngle, int degreePlaces, int minuteDecimals) { + final double abs = Math.abs(nonNegativeAngle); + int integerDegrees = (int) nonNegativeAngle; + double minutes = RoundingUtil.format((abs-(int) abs)*60.0, 3); + if (minutes >= 60.0) { + minutes -= 60.0; + integerDegrees++; + } + return Util.padPositiveValue(integerDegrees, degreePlaces, 0, /* round */ true) + "°"+ + Util.padPositiveValue(minutes, 2, minuteDecimals, /* round */ true)+"'"; + } + + @Override + public String getAsSignedDecimalDegrees() { + return "("+getLatDeg()+", "+getLngDeg()+")"; + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Position.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Position.java index c4ef50b51aa..2d0d97b7bd2 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Position.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Position.java @@ -11,6 +11,16 @@ public interface Position extends Serializable { double getLatDeg(); double getLngDeg(); + + /** + * Generates a string representation of the form S03°45.235' E003°22.837' + */ + String getAsDegreesAndDecimalMinutesWithCardinalPoints(); + + /** + * Generates a string representation of the form (-3.7539166666, 3.3806166666) + */ + String getAsSignedDecimalDegrees(); /** * Central angle between this position and the other position, in radians diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindFinderReviewedSpotsCollectionIdProvider.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindFinderReviewedSpotsCollectionIdProvider.java new file mode 100755 index 00000000000..86980e5f00b --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindFinderReviewedSpotsCollectionIdProvider.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.common; + +/** + * Something that can provide IDs of WindFinder (https://www.windfinder.com) spot collections + * that have been reviewed by WindFinder and have been released for use. Typically, an event + * would specify zero or more such {@link String} IDs, the {@code RacingEventService} would + * expose the result of collecting those over all events through this interface, and the + * WindFinder adapter that is responsible for receiving wind data through the WindFinder API + * would request them through this interface that it expects to discover through the OSGi + * registry. + * + * @author Axel Uhl (D043530) + * + */ +public interface WindFinderReviewedSpotsCollectionIdProvider { + Iterable getWindFinderReviewedSpotsCollectionIds(); +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindSourceType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindSourceType.java index b4246a1c5d3..594763771de 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindSourceType.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/WindSourceType.java @@ -51,7 +51,14 @@ public enum WindSourceType { * Like {@link #COMBINED}, only that when combined with an ID into a {@link WindSourceWithAdditionalID} then the * position for the wind is taken to be the middle of the tracked leg whose number is identified by the ID. */ - LEG_MIDDLE(/* can be stored */ false, /* base confidence */ 0.9, /* useSpeed */ true); + LEG_MIDDLE(/* can be stored */ false, /* base confidence */ 0.9, /* useSpeed */ true), + + /** + * A wind measurement spot as provided by https://www.windfinder.com; when used with a + * {@link WindSourceWithAdditionalID}, the {@link WindSourceWithAdditionalID#getId() ID} is + * the WindFinder's "Spot ID" such as "10044N" for Kiel Lighthouse. + */ + WINDFINDER(/* can be stored */ true, /* base confidence */ 0.3, /* useSpeed */ true); private final boolean canBeStored; 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 index 9f18093d622..3da451a8474 100644 --- 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 @@ -27,4 +27,8 @@ public enum RaceLogTrackingState { public boolean isForTracking() { return this != NOT_A_RACELOG_TRACKED_RACE; } + + public boolean isTracking() { + return this == TRACKING; + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoExtendedSensorDataMetadata.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoExtendedSensorDataMetadata.java index efd9c9a863d..9cc436c2cdd 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoExtendedSensorDataMetadata.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoExtendedSensorDataMetadata.java @@ -20,10 +20,13 @@ import com.sap.sailing.domain.common.tracking.DoubleVectorFix; * into the {@link DoubleVectorFix}. */ public enum BravoExtendedSensorDataMetadata implements ColumnMetadata { + // START ----- DONT CHANGE UNLESS BravoSensorDataMetadata CHANGES TOO ------ RIDE_HEIGHT_PORT_HULL("RideHeightPort", /* expectedInLogfile */ true), // RIDE_HEIGHT_STBD_HULL("RideHeightStbd", /* expectedInLogfile */ true), // HEEL("Heel", /* expectedInLogfile */ true), // PITCH("PitchRate", /* expectedInLogfile */ true), // + // END ----- DONT CHANGE UNLESS BravoSensorDataMetadata CHANGES TOO ------ + DB_RAKE_PORT("DaggerBoardRakeAnglePort", /* expectedInLogfile */ true), // DB_RAKE_STBD("DaggerBoardRakeAngleStbd", /* expectedInLogfile */ true), // RUDDER_RAKE_PORT("RudderRakeAnglePort", /* expectedInLogfile */ true), // diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/SensorFix.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/SensorFix.java index 0eba757cef5..ea79e71c685 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/SensorFix.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/SensorFix.java @@ -21,4 +21,10 @@ public interface SensorFix extends Timed { */ double get(String valueName) throws IllegalArgumentException; + /** + * Obtains the data array containing the raw values making this sensor fix. + * The array returned is a copy, and writing to it will not affect this fix + * in any way. + */ + Double[] get(); } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoFixImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoFixImpl.java index 7db57751513..1d980ff8b2a 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoFixImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoFixImpl.java @@ -27,15 +27,15 @@ public class BravoFixImpl extends SensorFixImpl implements BravoFix { @Override public Distance getRideHeight() { - Double rideHeightPortHullasDouble = fix.get(BravoSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()); + Double rideHeightPortHullAsDouble = fix.get(BravoSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()); Double rideHeightStarboardHullAsDouble = fix .get(BravoSensorDataMetadata.RIDE_HEIGHT_STBD_HULL.getColumnIndex()); - return rideHeightPortHullasDouble == null ? + return rideHeightPortHullAsDouble == null ? rideHeightStarboardHullAsDouble == null ? null : new MeterDistance(rideHeightStarboardHullAsDouble) : rideHeightStarboardHullAsDouble == null ? - new MeterDistance(rideHeightPortHullasDouble) : - new MeterDistance(Math.min(rideHeightPortHullasDouble, rideHeightStarboardHullAsDouble)); + new MeterDistance(rideHeightPortHullAsDouble) : + new MeterDistance(Math.min(rideHeightPortHullAsDouble, rideHeightStarboardHullAsDouble)); } @Override diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/SensorFixImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/SensorFixImpl.java index b25d68eda28..581ebcf5759 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/SensorFixImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/SensorFixImpl.java @@ -26,6 +26,13 @@ public abstract class SensorFixImpl implements SensorFix { return fix.getTimePoint(); } + @Override + public Double[] get() { + Double[] result = new Double[fix.get().length]; + System.arraycopy(fix.get(), 0, result, 0, result.length); + return result; + } + @Override public double get(String valueName) { final ColumnMetadata columnMetadata = resolveMetadataFromValueName(valueName); diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/windfinder/SpotBase.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/windfinder/SpotBase.java new file mode 100755 index 00000000000..54aa1b46e64 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/windfinder/SpotBase.java @@ -0,0 +1,57 @@ +package com.sap.sailing.domain.common.windfinder; + +import java.net.URL; + +import com.sap.sailing.domain.common.Positioned; +import com.sap.sse.common.NamedWithID; +import com.sap.sse.common.TimePoint; + +/** + * A measurement spot for which a report and / or a forecast may exist. Equality and + * hash code are based on the {@link #getId() ID} only. + * + * @author Axel Uhl (D043530) + * + */ +public interface SpotBase extends NamedWithID, Positioned { + /** + * A spot's ID is always a {@link String} + */ + @Override + String getId(); + + /** + * A keyword that is used, in particular, when constructing the URLs for {@link #getReportUrl() report} and + * {@link #getForecastUrl() forecast} web pages. + */ + String getKeyword(); + + /** + * Name of the country this spot is in, provided in the English language + */ + String getEnglishCountryName(); + + /** + * @return the URL of a web page showing a current weather report for this spot + */ + String getReportUrl(); + + /** + * @return the URL of a web page showing a weather forecast for this spot + */ + String getForecastUrl(); + + /** + * @return the URL of a web page showing general wind statistics for this spot + */ + String getStatisticsUrl(); + + /** + * For this spot provides a {@link URL} that a web UI can use to link + * to the WindFinder web site with the content most appropriate given the time point. + * This could be the report page if the time is about now; the forecast page if the time point + * is up to ten days in the future, or the statistics page if the time point is out of any + * of the scopes above. + */ + String getCurrentlyMostAppropriateUrl(TimePoint timePoint); +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/windfinder/SpotDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/windfinder/SpotDTO.java new file mode 100755 index 00000000000..d3474074fa4 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/windfinder/SpotDTO.java @@ -0,0 +1,118 @@ +package com.sap.sailing.domain.common.windfinder; + +import com.sap.sailing.domain.common.Position; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.impl.NamedImpl; + +public class SpotDTO extends NamedImpl implements SpotBase { + private static final long serialVersionUID = 1642900468710612984L; + private static final String BASE_URL = "https://www.windfinder.com"; + private static final String BASE_REPORT_URL = BASE_URL + "/report"; + private static final String BASE_FORECAST_URL = BASE_URL + "/forecast"; + private static final String BASE_STATISTICS_URL = BASE_URL + "/windstatistics"; + + private static final Duration FORECAST_DURATION = Duration.ONE_DAY.times(9); + private static final Duration REPORT_LOOKBACK = Duration.ONE_DAY.times(7); + + private final String id; + private final String keyword; + private final String englishCountryName; + private final Position position; + + public SpotDTO(String name, String id, String keyword, String englishCountryName, Position position) { + super(name); + this.id = id; + this.keyword = keyword; + this.englishCountryName = englishCountryName; + this.position = position; + } + + /** + * Copy constructor + */ + public SpotDTO(SpotBase spot) { + this(spot.getName(), spot.getId(), spot.getKeyword(), spot.getEnglishCountryName(), spot.getPosition()); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.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; + SpotDTO other = (SpotDTO) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + + @Override + public Position getPosition() { + return position; + } + + @Override + public String getId() { + return id; + } + + @Override + public String getKeyword() { + return keyword; + } + + @Override + public String getEnglishCountryName() { + return englishCountryName; + } + + @Override + public String getReportUrl() { + return BASE_REPORT_URL+"/"+getKeyword(); + } + + @Override + public String getForecastUrl() { + return BASE_FORECAST_URL+"/"+getKeyword(); + } + + @Override + public String getStatisticsUrl() { + return BASE_STATISTICS_URL+"/"+getKeyword(); + } + + @Override + public String getCurrentlyMostAppropriateUrl(TimePoint timePoint) { + final String result; + final TimePoint now = MillisecondsTimePoint.now(); + if (timePoint.compareTo(now.plus(Duration.ONE_MINUTE))<=0) { // up to one minute into the future + if (timePoint.compareTo(now.minus(REPORT_LOOKBACK))>=0) { // and up to seven days back + result = getReportUrl(); // is covered by the report page; + } else { // we're looking further into the past; so it's the statistics page + result = getStatisticsUrl(); + } + } else { // we're looking into the future + if (timePoint.compareTo(now.plus(FORECAST_DURATION))<=0) { // and it's still covered by the forecast + result = getForecastUrl(); + } else { // otherwise we again resort to the stats page + result = getStatisticsUrl(); + } + } + return result; + } +} 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 3753f48e93e..817a62fd46e 100644 --- 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 @@ -42,7 +42,6 @@ import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState; import com.sap.sailing.domain.abstractlog.race.CompetitorResults; import com.sap.sailing.domain.abstractlog.race.RaceLog; -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent; @@ -65,7 +64,6 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultImpl; import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultsImpl; -import com.sap.sailing.domain.abstractlog.race.impl.RaceLogCourseAreaChangeEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogCourseDesignChangedEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogDependentStartTimeEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImpl; @@ -1058,6 +1056,11 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { Venue venue = loadVenue((DBObject) eventDBObject.get(FieldNames.VENUE.name())); Event result = new EventImpl(name, startDate, endDate, venue, isPublic, id); result.setDescription(description); + @SuppressWarnings("unchecked") + final List windFinderReviewedSpotCollectionIds = (List) eventDBObject.get(FieldNames.EVENT_WINDFINDER_SPOT_COLLECTION_IDS.name()); + if (windFinderReviewedSpotCollectionIds != null) { + result.setWindFinderReviewedSpotsCollection(windFinderReviewedSpotCollectionIds); + } String officialWebSiteURLAsString = (String) eventDBObject.get(FieldNames.EVENT_OFFICIAL_WEBSITE_URL.name()); if (officialWebSiteURLAsString != null) { try { @@ -1400,8 +1403,6 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { return loadRaceLogFlagEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogPassChangeEvent.class.getSimpleName())) { return loadRaceLogPassChangeEvent(createdAt, author, logicalTimePoint, id, passId, competitors); - } else if (eventClass.equals(RaceLogCourseAreaChangedEvent.class.getSimpleName())) { - return loadRaceLogCourseAreaChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogCourseDesignChangedEvent.class.getSimpleName())) { return loadRaceLogCourseDesignChangedEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogFinishPositioningListChangedEvent.class.getSimpleName())) { @@ -1563,11 +1564,6 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { return new RaceLogCourseDesignChangedEventImpl(createdAt, logicalTimePoint, author, id, passId, courseData, courseDesignerMode); } - private RaceLogCourseAreaChangedEvent loadRaceLogCourseAreaChangedEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { - Serializable courseAreaId = (Serializable) dbObject.get(FieldNames.COURSE_AREA_ID.name()); - return new RaceLogCourseAreaChangeEventImpl(createdAt, logicalTimePoint, author, id, passId, courseAreaId); - } - private RaceLogEvent loadRaceLogFixedMarkPassingEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, Integer passId, List competitors, DBObject dbObject) { TimePoint ofFixedPassing = loadTimePoint(dbObject, FieldNames.TIMEPOINT_OF_FIXED_MARKPASSING); 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 a3dfe0b6343..54efc610203 100644 --- 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 @@ -26,6 +26,7 @@ public enum FieldNames { EVENT_SAILORS_INFO_WEBSITE_URL, EVENT_SAILORS_INFO_WEBSITES, EVENT_LOGO_IMAGE_URL, + EVENT_WINDFINDER_SPOT_COLLECTION_IDS, REGATTA_NAME, REGATTA_ID, 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 68de78cdd2b..bdf4558bd65 100644 --- 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 @@ -28,7 +28,6 @@ import com.mongodb.util.JSON; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; import com.sap.sailing.domain.abstractlog.race.CompetitorResult; import com.sap.sailing.domain.abstractlog.race.CompetitorResults; -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent; @@ -594,6 +593,11 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { storeTimePoint(event.getStartDate(), eventDBObject, FieldNames.EVENT_START_DATE); storeTimePoint(event.getEndDate(), eventDBObject, FieldNames.EVENT_END_DATE); eventDBObject.put(FieldNames.EVENT_IS_PUBLIC.name(), event.isPublic()); + BasicDBList windFinderSpotCollectionIds = new BasicDBList(); + for (final String windFinderSpotCollectionId : event.getWindFinderReviewedSpotsCollectionIds()) { + windFinderSpotCollectionIds.add(windFinderSpotCollectionId); + } + eventDBObject.put(FieldNames.EVENT_WINDFINDER_SPOT_COLLECTION_IDS.name(), windFinderSpotCollectionIds); DBObject venueDBObject = getVenueAsDBObject(event.getVenue()); eventDBObject.put(FieldNames.VENUE.name(), venueDBObject); BasicDBList images = new BasicDBList(); @@ -841,13 +845,6 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { return result; } - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCourseAreaChangedEvent courseAreaChangedEvent) { - BasicDBObject result = new BasicDBObject(); - storeRaceLogIdentifier(raceLogIdentifier, result); - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCourseAreaChangedEvent(courseAreaChangedEvent)); - return result; - } - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCourseDesignChangedEvent courseDesignChangedEvent) { BasicDBObject result = new BasicDBObject(); storeRaceLogIdentifier(raceLogIdentifier, result); @@ -1175,14 +1172,6 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { return result; } - private DBObject storeRaceLogCourseAreaChangedEvent(RaceLogCourseAreaChangedEvent courseAreaChangedEvent) { - DBObject result = new BasicDBObject(); - storeRaceLogEventProperties(courseAreaChangedEvent, result); - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCourseAreaChangedEvent.class.getSimpleName()); - result.put(FieldNames.COURSE_AREA_ID.name(), courseAreaChangedEvent.getCourseAreaId()); - return result; - } - private DBObject storeRaceLogCourseDesignChangedEvent(RaceLogCourseDesignChangedEvent courseDesignChangedEvent) { DBObject result = new BasicDBObject(); storeRaceLogEventProperties(courseDesignChangedEvent, result); 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 a65b20e14ad..4ba962744a7 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 @@ -3,7 +3,6 @@ package com.sap.sailing.domain.persistence.impl; import java.util.logging.Logger; import com.mongodb.DBObject; -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent; @@ -68,12 +67,6 @@ public class MongoRaceLogStoreVisitor implements RaceLogEventVisitor { 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); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreAndLoadTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreAndLoadTest.java index 383919426b2..1ec8c9a5c6e 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreAndLoadTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreAndLoadTest.java @@ -505,6 +505,11 @@ public class SensorFixStoreAndLoadTest { return fix.get(TEST_COLUMN_INDEX); } + @Override + public Double[] get() { + return Arrays.copyOf(fix.get(), fix.get().length); + } + } public class TestFixTrackImpl extends SensorFixTrackImpl { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogCourseAreaChangedEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogCourseAreaChangedEvent.java deleted file mode 100644 index b63b82fd432..00000000000 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogCourseAreaChangedEvent.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.sap.sailing.domain.abstractlog.race; - -import java.io.Serializable; - -public interface RaceLogCourseAreaChangedEvent extends RaceLogEvent { - - Serializable getCourseAreaId(); - -} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventVisitor.java index 572f66310d7..a8d8af89858 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventVisitor.java @@ -18,8 +18,6 @@ public interface RaceLogEventVisitor { public void visit(RaceLogStartTimeEvent event); - public void visit(RaceLogCourseAreaChangedEvent event); - public void visit(RaceLogCourseDesignChangedEvent event); public void visit(RaceLogFinishPositioningListChangedEvent event); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/AbstractRaceLogChangedVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/AbstractRaceLogChangedVisitor.java index 8407d6f2d86..55f3e0be220 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/AbstractRaceLogChangedVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/AbstractRaceLogChangedVisitor.java @@ -1,6 +1,5 @@ package com.sap.sailing.domain.abstractlog.race.impl; -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent; @@ -46,10 +45,6 @@ public abstract class AbstractRaceLogChangedVisitor implements RaceLogEventVisit notifyListenerAboutEventAdded(event); } - public void visit(RaceLogCourseAreaChangedEvent event) { - notifyListenerAboutEventAdded(event); - } - public void visit(RaceLogCourseDesignChangedEvent event) { notifyListenerAboutEventAdded(event); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/BaseRaceLogEventVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/BaseRaceLogEventVisitor.java index bff059adf00..9aa980bb7d9 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/BaseRaceLogEventVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/BaseRaceLogEventVisitor.java @@ -1,6 +1,5 @@ package com.sap.sailing.domain.abstractlog.race.impl; -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent; @@ -51,11 +50,6 @@ public abstract class BaseRaceLogEventVisitor implements RaceLogEventVisitor { } - @Override - public void visit(RaceLogCourseAreaChangedEvent event) { - - } - @Override public void visit(RaceLogCourseDesignChangedEvent event) { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogCourseAreaChangeEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogCourseAreaChangeEventImpl.java deleted file mode 100644 index 92ea38896d4..00000000000 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogCourseAreaChangeEventImpl.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.sap.sailing.domain.abstractlog.race.impl; - -import java.io.Serializable; - -import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor; -import com.sap.sse.common.TimePoint; - -public class RaceLogCourseAreaChangeEventImpl extends RaceLogEventImpl implements RaceLogCourseAreaChangedEvent { - private static final long serialVersionUID = -3943108136024977860L; - - private final Serializable courseAreaId; - - public RaceLogCourseAreaChangeEventImpl(TimePoint createdAt, TimePoint pTimePoint, AbstractLogEventAuthor author, - Serializable pId, int pPassId, Serializable courseAreaId) { - super(createdAt, pTimePoint, author, pId, pPassId); - this.courseAreaId = courseAreaId; - } - - public RaceLogCourseAreaChangeEventImpl(TimePoint logicalTimePoint, AbstractLogEventAuthor author, int pPassId, - Serializable courseAreaId) { - this(now(), logicalTimePoint, author, randId(), pPassId, courseAreaId); - } - - @Override - public Serializable getCourseAreaId() { - return courseAreaId; - } - - @Override - public void accept(RaceLogEventVisitor visitor) { - visitor.visit(this); - } - -} diff --git a/java/com.sap.sailing.domain.test/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.domain.test/.settings/org.eclipse.core.resources.prefs index c3595ce3c47..cd07ac442b4 100755 --- a/java/com.sap.sailing.domain.test/.settings/org.eclipse.core.resources.prefs +++ b/java/com.sap.sailing.domain.test/.settings/org.eclipse.core.resources.prefs @@ -1,3 +1,4 @@ eclipse.preferences.version=1 +encoding//src/com/sap/sailing/domain/test/PositionTest.java=UTF-8 encoding//src/com/sap/sailing/domain/test/WindTest.java=UTF-8 encoding//src/com/sap/sailing/domain/test/markpassing/StarIDM2013MarkPassingTest.java=UTF-8 diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PositionTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PositionTest.java index 385f92a4a8e..00105e03aa1 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PositionTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PositionTest.java @@ -360,4 +360,64 @@ public class PositionTest { assertEquals(-Math.sqrt(1./2.), rotatedBy90DegreesCounterClockWise.getLatDeg(), 0.001); assertEquals(Math.sqrt(1./2.), rotatedBy90DegreesCounterClockWise.getLngDeg(), 0.001); } + + @Test + public void testStringRepresentation1() { + Position p = new DegreePosition(0, 0); + assertEquals("N00°00.000' E000°00.000'", p.getAsDegreesAndDecimalMinutesWithCardinalPoints()); + } + + @Test + public void testStringRepresentation2() { + Position p = new DegreePosition(1, 1); + assertEquals("N01°00.000' E001°00.000'", p.getAsDegreesAndDecimalMinutesWithCardinalPoints()); + } + + @Test + public void testStringRepresentation3() { + Position p = new DegreePosition(-1, -1); + assertEquals("S01°00.000' W001°00.000'", p.getAsDegreesAndDecimalMinutesWithCardinalPoints()); + } + + @Test + public void testStringRepresentation4() { + Position p = new DegreePosition(-11, -11); + assertEquals("S11°00.000' W011°00.000'", p.getAsDegreesAndDecimalMinutesWithCardinalPoints()); + } + + @Test + public void testStringRepresentation5() { + Position p = new DegreePosition(-11, -111); + assertEquals("S11°00.000' W111°00.000'", p.getAsDegreesAndDecimalMinutesWithCardinalPoints()); + } + + @Test + public void testStringRepresentation6() { + Position p = new DegreePosition(-11.0 - 1.05 / 60.0, -111.0 - 1.05 / 60.0); + assertEquals("S11°01.050' W111°01.050'", p.getAsDegreesAndDecimalMinutesWithCardinalPoints()); + } + + @Test + public void testStringRepresentation7() { + Position p = new DegreePosition(1.5, 1.5); + assertEquals("N01°30.000' E001°30.000'", p.getAsDegreesAndDecimalMinutesWithCardinalPoints()); + } + + @Test + public void testStringRepresentation8() { + Position p = new DegreePosition(59.999999999/60.0, 59.999999999/60.0); + assertEquals("N01°00.000' E001°00.000'", p.getAsDegreesAndDecimalMinutesWithCardinalPoints()); + } + + @Test + public void testStringRepresentation9() { + Position p = new DegreePosition(54.569283, 10.005400); + assertEquals("N54°34.157' E010°00.324'", p.getAsDegreesAndDecimalMinutesWithCardinalPoints()); + } + + @Test + public void testStringRepresentation10() { + Position p = new DegreePosition(53.569127762780646, 10.004243641469529); + assertEquals("N53°34.148' E010°00.255'", p.getAsDegreesAndDecimalMinutesWithCardinalPoints()); + } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BeatAngleTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TWATest.java similarity index 76% rename from java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BeatAngleTest.java rename to java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TWATest.java index a3d2ac886fc..c4c079c5d46 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BeatAngleTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TWATest.java @@ -8,6 +8,7 @@ import static org.mockito.Mockito.when; import org.junit.Before; import org.junit.Test; +import org.mockito.Matchers; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.impl.CompetitorImpl; @@ -32,7 +33,7 @@ import com.sap.sailing.domain.tracking.impl.TrackedLegOfCompetitorImpl; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; -public class BeatAngleTest { +public class TWATest { private Competitor competitor; private GPSFixTrack competitorTrack; private TrackedRace trackedRace; @@ -61,62 +62,63 @@ public class BeatAngleTest { when(trackedRace.getWind((Position) anyObject(), (TimePoint) anyObject())).thenReturn( new WindImpl(new DegreePosition(0, 0), MillisecondsTimePoint.now(), new KnotSpeedWithBearingImpl(12, windToBearing))); - when(trackedLegOfCompetitor.getSpeedOverGround((TimePoint) anyObject())).thenReturn(new KnotSpeedWithBearingImpl(10, boatToBearing)); - when(trackedLegOfCompetitor.getBeatAngle((TimePoint) anyObject())).thenCallRealMethod(); - when(trackedLegOfCompetitor.getBeatAngle((TimePoint) anyObject(), (WindLegTypeAndLegBearingCache) anyObject())).thenCallRealMethod(); + + when(competitorTrack.getEstimatedSpeed((TimePoint) anyObject())).thenReturn(new KnotSpeedWithBearingImpl(10, boatToBearing)); + when(trackedRace.getTWA(Matchers.eq(competitor), (TimePoint) anyObject())).thenCallRealMethod(); + when(trackedRace.getTWA(Matchers.eq(competitor), (TimePoint) anyObject(), (WindLegTypeAndLegBearingCache) anyObject())).thenCallRealMethod(); } @Test - public void testBeatAngleUpwindPort() throws NoWindException { + public void testTWAUpwindPort() throws NoWindException { final Bearing windToBearing = new DegreeBearingImpl(123); final Bearing boatToBearing = new DegreeBearingImpl(348); final Bearing legBearing = new DegreeBearingImpl(303); setUp(windToBearing, boatToBearing, legBearing, LegType.UPWIND); - assertEquals(-45., trackedLegOfCompetitor.getBeatAngle(MillisecondsTimePoint.now()).getDegrees(), 0.00001); + assertEquals(-45., trackedRace.getTWA(competitor, MillisecondsTimePoint.now()).getDegrees(), 0.00001); } @Test - public void testBeatAngleUpwindStarboard() throws NoWindException { + public void testTWAUpwindStarboard() throws NoWindException { final Bearing windToBearing = new DegreeBearingImpl(123); final Bearing boatToBearing = new DegreeBearingImpl(258); final Bearing legBearing = new DegreeBearingImpl(303); setUp(windToBearing, boatToBearing, legBearing, LegType.UPWIND); - assertEquals(45., trackedLegOfCompetitor.getBeatAngle(MillisecondsTimePoint.now()).getDegrees(), 0.00001); + assertEquals(45., trackedRace.getTWA(competitor, MillisecondsTimePoint.now()).getDegrees(), 0.00001); } @Test - public void testBeatAngleDownwindPort() throws NoWindException { + public void testTWADownwindPort() throws NoWindException { final Bearing windToBearing = new DegreeBearingImpl(123); final Bearing boatToBearing = new DegreeBearingImpl(120); final Bearing legBearing = new DegreeBearingImpl(123); setUp(windToBearing, boatToBearing, legBearing, LegType.DOWNWIND); - assertEquals(-177., trackedLegOfCompetitor.getBeatAngle(MillisecondsTimePoint.now()).getDegrees(), 0.00001); + assertEquals(-177., trackedRace.getTWA(competitor, MillisecondsTimePoint.now()).getDegrees(), 0.00001); } @Test - public void testBeatAngleDownwindStarboard() throws NoWindException { + public void testTWADownwindStarboard() throws NoWindException { final Bearing windToBearing = new DegreeBearingImpl(123); final Bearing boatToBearing = new DegreeBearingImpl(126); final Bearing legBearing = new DegreeBearingImpl(123); setUp(windToBearing, boatToBearing, legBearing, LegType.DOWNWIND); - assertEquals(177., trackedLegOfCompetitor.getBeatAngle(MillisecondsTimePoint.now()).getDegrees(), 0.00001); + assertEquals(177., trackedRace.getTWA(competitor, MillisecondsTimePoint.now()).getDegrees(), 0.00001); } @Test - public void testBeatAngleReachingPort() throws NoWindException { + public void testTWAReachingPort() throws NoWindException { final Bearing windToBearing = new DegreeBearingImpl(123); final Bearing boatToBearing = new DegreeBearingImpl(33); final Bearing legBearing = new DegreeBearingImpl(33); setUp(windToBearing, boatToBearing, legBearing, LegType.REACHING); - assertEquals(-90., trackedLegOfCompetitor.getBeatAngle(MillisecondsTimePoint.now()).getDegrees(), 0.00001); + assertEquals(-90., trackedRace.getTWA(competitor, MillisecondsTimePoint.now()).getDegrees(), 0.00001); } @Test - public void testBeatAngleReachingStarboard() throws NoWindException { + public void testTWAReachingStarboard() throws NoWindException { final Bearing windToBearing = new DegreeBearingImpl(303); final Bearing boatToBearing = new DegreeBearingImpl(33); final Bearing legBearing = new DegreeBearingImpl(33); setUp(windToBearing, boatToBearing, legBearing, LegType.REACHING); - assertEquals(90., trackedLegOfCompetitor.getBeatAngle(MillisecondsTimePoint.now()).getDegrees(), 0.00001); + assertEquals(90., trackedRace.getTWA(competitor, MillisecondsTimePoint.now()).getDegrees(), 0.00001); } } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/ReceiverType.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/ReceiverType.java index 3062fbcb2ca..983bb116c3b 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/ReceiverType.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/ReceiverType.java @@ -1,5 +1,5 @@ package com.sap.sailing.domain.tractracadapter; public enum ReceiverType { - MARKPASSINGS, MARKPOSITIONS, RACECOURSE, RACESTARTFINISH, RAWPOSITIONS + MARKPASSINGS, MARKPOSITIONS, RACECOURSE, RACESTARTFINISH, RAWPOSITIONS, SENSORDATA } 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 093d59e7c46..c65b8bd83cd 100755 --- 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 @@ -430,7 +430,11 @@ public class DomainFactoryImpl implements DomainFactory { result.add(new RaceStartedAndFinishedReceiver( trackedRegatta, tractracEvent, simulator, this, eventSubscriber, raceSubscriber, timeoutInMilliseconds)); break; - } + case SENSORDATA: + result.add(new SensorDataReceiver( + trackedRegatta, tractracEvent, simulator, this, eventSubscriber, raceSubscriber, timeoutInMilliseconds)); + break; + } } return result; } @@ -446,7 +450,8 @@ public class DomainFactoryImpl implements DomainFactory { raceDefinitionSetToUpdate, trackedRegattaRegistry, raceLogResolver, courseDesignUpdateURI, tracTracUsername, tracTracPassword, eventSubscriber, raceSubscriber, useInternalMarkPassingAlgorithm, timeoutInMilliseconds, ReceiverType.RACECOURSE, - ReceiverType.MARKPASSINGS, ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS); + ReceiverType.MARKPASSINGS, ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS, + ReceiverType.SENSORDATA); } @Override 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 651c3830da8..ab24e3bed53 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 @@ -48,27 +48,27 @@ public class RawPositionReceiver extends AbstractReceiverWithQueue event) { - if (!event.getA().getCompetitor().isNonCompeting()) { - if (received++ % 1000 == 0) { - System.out.print("P"); - if ((received / 1000 + 1) % 80 == 0) { - System.out.println(); - } - } - IRace race = event.getA().getRace(); - DynamicTrackedRace trackedRace = getTrackedRace(race); - if (trackedRace != null) { - final GPSFixMoving fix = getDomainFactory().createGPSFixMoving(event.getB()); - Competitor competitor = getDomainFactory().getOrCreateCompetitor(event.getA().getCompetitor()); - if (getSimulator() != null) { - getSimulator().scheduleCompetitorPosition(competitor, fix); - } else { - trackedRace.recordFix(competitor, fix); - } - } else { - logger.warning("Couldn't find tracked race for race " + race.getName() - + ". Dropping raw position event " + event); - } - } + if (!event.getA().getCompetitor().isNonCompeting()) { + if (received++ % 1000 == 0) { + System.out.print("P"); + if ((received / 1000 + 1) % 80 == 0) { + System.out.println(); + } + } + IRace race = event.getA().getRace(); + DynamicTrackedRace trackedRace = getTrackedRace(race); + if (trackedRace != null) { + final GPSFixMoving fix = getDomainFactory().createGPSFixMoving(event.getB()); + Competitor competitor = getDomainFactory().getOrCreateCompetitor(event.getA().getCompetitor()); + if (getSimulator() != null) { + getSimulator().scheduleCompetitorPosition(competitor, fix); + } else { + trackedRace.recordFix(competitor, fix); + } + } else { + logger.warning("Couldn't find tracked race for race " + race.getName() + + ". Dropping raw position event " + event); + } + } } } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/SensorDataReceiver.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/SensorDataReceiver.java new file mode 100755 index 00000000000..495a4f3d708 --- /dev/null +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/SensorDataReceiver.java @@ -0,0 +1,121 @@ +package com.sap.sailing.domain.tractracadapter.impl; + +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata; +import com.sap.sailing.domain.common.tracking.BravoFix; +import com.sap.sailing.domain.common.tracking.impl.BravoFixImpl; +import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl; +import com.sap.sailing.domain.tracking.BravoFixTrack; +import com.sap.sailing.domain.tracking.DynamicBravoFixTrack; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; +import com.sap.sailing.domain.tracking.impl.CompetitorBravoFixTrackImpl; +import com.sap.sailing.domain.tractracadapter.DomainFactory; +import com.sap.sse.common.Util.Triple; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.tractrac.model.lib.api.event.IEvent; +import com.tractrac.model.lib.api.event.IRace; +import com.tractrac.model.lib.api.event.IRaceCompetitor; +import com.tractrac.model.lib.api.sensor.ISensorData; +import com.tractrac.subscription.lib.api.IEventSubscriber; +import com.tractrac.subscription.lib.api.IRaceSubscriber; +import com.tractrac.subscription.lib.api.competitor.ICompetitorSensorDataListener; + +/** + * Subscribes an + * + * @author Axel Uhl (d043530) + * + */ +public class SensorDataReceiver extends AbstractReceiverWithQueue { + private static final Logger logger = Logger.getLogger(SensorDataReceiver.class.getName()); + private final ICompetitorSensorDataListener listener; + private int received; + + public SensorDataReceiver(DynamicTrackedRegatta trackedRegatta, IEvent tractracEvent, + Simulator simulator, DomainFactory domainFactory, IEventSubscriber eventSubscriber, + IRaceSubscriber raceSubscriber, long timeoutInMilliseconds) { + super(domainFactory, tractracEvent, trackedRegatta, simulator, eventSubscriber, raceSubscriber, timeoutInMilliseconds); + listener = new ICompetitorSensorDataListener() { + @Override + public void gotSensorData(IRaceCompetitor raceCompetitor, ISensorData sensorData) { + enqueue(new Triple<>(raceCompetitor, sensorData, null)); + } + }; + } + + @Override + public void subscribe() { + getRaceSubscriber().subscribeCompetitorSensorData(listener); + startThread(); + } + + @Override + protected void unsubscribe() { + getRaceSubscriber().unsubscribeCompetitorSensorData(listener); + } + + /** + * Looks for valid, non-{@code null} components for ride height port/starboard, heel and trim + * in the {@link ISensorData} object. All those values are copied into a {@link BravoFix} and + * inserted into the competitor's bravo fix track. + */ + @Override + protected void handleEvent(Triple event) { + if (received++ % 1000 == 0) { + System.out.print("S"); + if ((received / 1000 + 1) % 80 == 0) { + System.out.println(); + } + } + final IRace race = event.getA().getRace(); + DynamicTrackedRace trackedRace = getTrackedRace(race); + if (trackedRace != null) { + final ISensorData sensorData = event.getB(); + if (sensorData != null) { + final Float rideHeightPortInMeters = sensorData.getRideHeightPort(); + final Float rideHeightStarboardInMeters = sensorData.getRideHeightStarboard(); + final Float heelInDegrees = sensorData.getHeel(); + final Float trimInDegrees = sensorData.getTrim(); + final Double[] fixData = new Double[BravoSensorDataMetadata.getTrackColumnCount()]; + boolean create = false; // only create fix if at least one metric is present + // fill the port/starboard columns as well because their minimum defines the true ride height + if (rideHeightPortInMeters != null) { + fixData[BravoSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()] = Double.valueOf(rideHeightPortInMeters); + create = true; + } + if (rideHeightStarboardInMeters != null) { + fixData[BravoSensorDataMetadata.RIDE_HEIGHT_STBD_HULL.getColumnIndex()] = Double.valueOf(rideHeightStarboardInMeters); + create = true; + } + if (heelInDegrees != null) { + fixData[BravoSensorDataMetadata.HEEL.getColumnIndex()] = Double.valueOf(heelInDegrees); + create = true; + } + if (trimInDegrees != null) { + fixData[BravoSensorDataMetadata.PITCH.getColumnIndex()] = Double.valueOf(trimInDegrees); + create = true; + } + if (create) { + final BravoFix fix = new BravoFixImpl(new DoubleVectorFixImpl(new MillisecondsTimePoint(sensorData.getTimestamp()), fixData)); + final Competitor competitor = getDomainFactory().getOrCreateCompetitor(event.getA().getCompetitor()); + DynamicBravoFixTrack bravoFixTrack = + trackedRace.getOrCreateSensorTrack(competitor, BravoFixTrack.TRACK_NAME, + () -> new CompetitorBravoFixTrackImpl(competitor, BravoFixTrack.TRACK_NAME, /* hasExtendedFixes */ false, + trackedRace.getTrack(competitor))); + if (getSimulator() != null) { + getSimulator().scheduleCompetitorSensorData(bravoFixTrack, fix); + } else { + bravoFixTrack.add(fix); + } + } + } + } else { + logger.warning("Couldn't find tracked race for race " + race.getName() + + ". Dropping sensor event " + event); + } + } + +} diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/Simulator.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/Simulator.java index d26d28b1909..b8110005f68 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/Simulator.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/Simulator.java @@ -14,8 +14,12 @@ import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; import com.sap.sailing.domain.common.impl.WindImpl; +import com.sap.sailing.domain.common.tracking.BravoFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.common.tracking.impl.BravoFixImpl; +import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl; import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.domain.tracking.DynamicBravoFixTrack; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.RaceListener; @@ -272,20 +276,40 @@ public class Simulator { scheduleFixRecording(mark, markFix, recorder); } - private void scheduleFixRecording(final T object, GPSFixMoving fix, final RecordGPSFix recorder) { + public void scheduleCompetitorSensorData(DynamicBravoFixTrack bravoFixTrack, BravoFix fix) { final TimePoint transformedTimepoint = advance(fix.getTimePoint()); - final GPSFixMoving transformedMarkFix = new GPSFixMovingImpl(fix.getPosition(), transformedTimepoint, fix.getSpeed()); - long waitTime = getWaitTimeInMillisUntil(transformedMarkFix.getTimePoint()); + final BravoFix transformedFix = new BravoFixImpl(new DoubleVectorFixImpl(transformedTimepoint, fix.get())); + long waitTime = getWaitTimeInMillisUntil(transformedFix.getTimePoint()); if (waitTime <= 0) { - recorder.recordFix(object, transformedMarkFix); + bravoFixTrack.add(transformedFix); } else { timer.schedule(new TimerTask() { @Override public void run() { try { - recorder.recordFix(object, transformedMarkFix); + bravoFixTrack.add(transformedFix); } catch (Exception e) { - logger.throwing(Simulator.class.getName(), "scheduleMarkPosition", e); + logger.throwing(Simulator.class.getName(), "scheduleSensorData", e); + } + } + }, transformedTimepoint.asDate()); + } + } + + private void scheduleFixRecording(final T object, GPSFixMoving fix, final RecordGPSFix recorder) { + final TimePoint transformedTimepoint = advance(fix.getTimePoint()); + final GPSFixMoving transformedFix = new GPSFixMovingImpl(fix.getPosition(), transformedTimepoint, fix.getSpeed()); + long waitTime = getWaitTimeInMillisUntil(transformedFix.getTimePoint()); + if (waitTime <= 0) { + recorder.recordFix(object, transformedFix); + } else { + timer.schedule(new TimerTask() { + @Override + public void run() { + try { + recorder.recordFix(object, transformedFix); + } catch (Exception e) { + logger.throwing(Simulator.class.getName(), "schedulePosition", e); } } }, transformedTimepoint.asDate()); @@ -297,4 +321,5 @@ public class Simulator { timer.cancel(); stopped = true; } + } 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 6deab6e2ad5..9da600daf5d 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 @@ -317,21 +317,21 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl } // check if there is a directory configured where stored data files can be cached // only cache files for races in REPLAY state - final URI effectifeStoredURI; // may be altered by legacy caching mechanism; won't affect this tracker's ID + final URI effectiveStoredURI; // may be altered by legacy caching mechanism; won't affect this tracker's ID if ( (raceStatus != null && raceStatus.equals(TracTracConnectionConstants.REPLAY_STATUS)) || (raceVisibility != null && raceVisibility.equals(TracTracConnectionConstants.REPLAY_VISIBILITY)) ) { - effectifeStoredURI = checkForCachedStoredData(storedURI); + effectiveStoredURI = checkForCachedStoredData(storedURI); } else { - effectifeStoredURI = storedURI; + effectiveStoredURI = storedURI; } logger.info("Starting race tracker: " + tractracRace.getName() + " " + paramURL + " " + liveURI + " " - + effectifeStoredURI + " startOfTracking:" + + effectiveStoredURI + " startOfTracking:" + (startOfTracking != null ? startOfTracking.asMillis() : "n/a") + " endOfTracking:" + (endOfTracking != null ? endOfTracking.asMillis() : "n/a")); // Initialize data controller using live and stored data sources ISubscriberFactory subscriberFactory = SubscriptionLocator.getSusbcriberFactory(); - eventSubscriber = subscriberFactory.createEventSubscriber(tractracEvent, liveURI, effectifeStoredURI); + eventSubscriber = subscriberFactory.createEventSubscriber(tractracEvent, liveURI, effectiveStoredURI); competitorsListener = new ICompetitorsListener() { @Override public void updateCompetitor(ICompetitor competitor) { @@ -375,7 +375,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl eventSubscriber.subscribeRaces(racesListener); // Start live and stored data streams final Regatta effectiveRegatta; - raceSubscriber = subscriberFactory.createRaceSubscriber(tractracRace, liveURI, effectifeStoredURI); + raceSubscriber = subscriberFactory.createRaceSubscriber(tractracRace, liveURI, effectiveStoredURI); raceSubscriber.subscribeConnectionStatus(this); // Try to find a pre-associated event based on the Race ID if (regatta == null) { diff --git a/java/com.sap.sailing.domain.windfinderadapter.test/.classpath b/java/com.sap.sailing.domain.windfinderadapter.test/.classpath new file mode 100755 index 00000000000..110168ffa19 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter.test/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/java/com.sap.sailing.domain.windfinderadapter.test/.project b/java/com.sap.sailing.domain.windfinderadapter.test/.project new file mode 100755 index 00000000000..f4e90b744ba --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter.test/.project @@ -0,0 +1,28 @@ + + + com.sap.sailing.domain.windfinderadapter.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.windfinderadapter.test/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.domain.windfinderadapter.test/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..0c68a61dca8 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter.test/.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.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sailing.domain.windfinderadapter.test/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.domain.windfinderadapter.test/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..f29e940a005 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter.test/.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.windfinderadapter.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.windfinderadapter.test/META-INF/MANIFEST.MF new file mode 100755 index 00000000000..bcb83ed79e7 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter.test/META-INF/MANIFEST.MF @@ -0,0 +1,12 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Test +Bundle-SymbolicName: com.sap.sailing.domain.windfinderadapter.test +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Fragment-Host: com.sap.sailing.domain.windfinderadapter +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Require-Bundle: org.junit, + org.mockito.mockito-core;bundle-version="1.10.14", + org.objenesis;bundle-version="2.1.0", + org.hamcrest;bundle-version="1.1.0" diff --git a/java/com.sap.sailing.domain.windfinderadapter.test/build.properties b/java/com.sap.sailing.domain.windfinderadapter.test/build.properties new file mode 100755 index 00000000000..f11210d3f2c --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter.test/build.properties @@ -0,0 +1,5 @@ +source.. = src/,\ + resources/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sailing.domain.windfinderadapter.test/pom.xml b/java/com.sap.sailing.domain.windfinderadapter.test/pom.xml new file mode 100755 index 00000000000..f5a28c8a5ac --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter.test/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sailing.domain.windfinderadapter.test + eclipse-test-plugin + diff --git a/java/com.sap.sailing.domain.windfinderadapter.test/resources/sap_schilksee_10044N.json b/java/com.sap.sailing.domain.windfinderadapter.test/resources/sap_schilksee_10044N.json new file mode 100755 index 00000000000..862c78b5292 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter.test/resources/sap_schilksee_10044N.json @@ -0,0 +1 @@ +[{"wg":16,"wd":331,"dtl":"2017-11-13T15:16:00+01:00","dtl_s":"2017-11-13T15:15:30+01:00","ws":12,"at":7.0},{"wg":14,"wd":333,"dtl":"2017-11-13T15:32:00+01:00","dtl_s":"2017-11-13T15:31:30+01:00","ws":12,"at":7.0},{"wg":16,"wd":332,"dtl":"2017-11-13T15:40:00+01:00","dtl_s":"2017-11-13T15:39:30+01:00","ws":12,"at":7.0}] \ No newline at end of file diff --git a/java/com.sap.sailing.domain.windfinderadapter.test/resources/schilksee_nearby.json b/java/com.sap.sailing.domain.windfinderadapter.test/resources/schilksee_nearby.json new file mode 100755 index 00000000000..b3f1e6c9cf2 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter.test/resources/schilksee_nearby.json @@ -0,0 +1,17 @@ +[{ + "c": "Germany", + "lon": 10.15, + "n": "Kiel-Holtenau Airport", + "kw": "kiel-holtenau", + "lat": 54.38, + "has": "1111000", + "id": "de15" +},{ + "c": "Germany", + "lon": 10.28, + "n": "Kiel/Leuchtturm", + "kw": "kiel_leuchtturm", + "lat": 54.47, + "has": "1111010", + "id": "10044N" +}] diff --git a/java/com.sap.sailing.domain.windfinderadapter.test/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderPollerTest.java b/java/com.sap.sailing.domain.windfinderadapter.test/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderPollerTest.java new file mode 100755 index 00000000000..6785021b74d --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter.test/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderPollerTest.java @@ -0,0 +1,67 @@ +package com.sap.sailing.domain.windfinderadapter.impl; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; +import com.sap.sailing.domain.tracking.WindTracker; +import com.sap.sse.common.Util.Pair; + +public class WindFinderPollerTest { + @Test + public void testPolling() throws Exception { + final Object monitor = new Object(); + WindFinderTrackerFactoryImpl factory = new WindFinderTrackerFactoryImpl(); + factory.addReviewedSpotCollection(new ReviewedSpotsCollectionImpl("schilksee")); + final List> wind = new ArrayList<>(); + final RaceDefinition mockedRaceDefinition = mock(RaceDefinition.class); + final DynamicTrackedRegatta mockedTrackedRegatta = mock(DynamicTrackedRegatta.class); + final DynamicTrackedRace mockedTrackedRace = mock(DynamicTrackedRace.class); + when(mockedTrackedRegatta.getExistingTrackedRace(mockedRaceDefinition)).thenReturn(mockedTrackedRace); + when(mockedTrackedRegatta.getTrackedRace(mockedRaceDefinition)).thenReturn(mockedTrackedRace); + when(mockedTrackedRace.recordWind(any(Wind.class), any(WindSourceWithAdditionalID.class))).thenAnswer(new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + wind.add(new Pair<>(invocation.getArgumentAt(0, Wind.class), invocation.getArgumentAt(1, WindSourceWithAdditionalID.class))); + synchronized (monitor) { + monitor.notifyAll(); + } + return true; + } + }); + when(mockedTrackedRace.recordWind(any(Wind.class), any(WindSourceWithAdditionalID.class), /* applyFilter */ anyBoolean())).thenAnswer(new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + wind.add(new Pair<>(invocation.getArgumentAt(0, Wind.class), invocation.getArgumentAt(1, WindSourceWithAdditionalID.class))); + return true; + } + }); + final WindTracker tracker; + synchronized (monitor) { + tracker = factory.createWindTracker(mockedTrackedRegatta, mockedRaceDefinition, /* correctByDeclination */ false); + monitor.wait(5000); + } + tracker.stop(); + assertFalse(wind.isEmpty()); // at least one latest measurement + Pair firstFix = wind.iterator().next(); + assertTrue(new HashSet<>(Arrays.asList("de15", "10044N")).contains(((WindSourceWithAdditionalID) firstFix.getB()).getId())); + } +} diff --git a/java/com.sap.sailing.domain.windfinderadapter.test/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderReportParserTest.java b/java/com.sap.sailing.domain.windfinderadapter.test/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderReportParserTest.java new file mode 100755 index 00000000000..ccc205ddc77 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter.test/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderReportParserTest.java @@ -0,0 +1,99 @@ +package com.sap.sailing.domain.windfinderadapter.impl; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.MalformedURLException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.junit.Test; + +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.windfinder.Spot; +import com.sap.sse.common.Util; + +public class WindFinderReportParserTest { + private final String TEST_MESSAGE = "[{\"wg\":16,\"wd\":331,\"dtl\":\"2017-11-13T15:16:00+01:00\",\"dtl_s\":\"2017-11-13T15:15:30+01:00\",\"ws\":12,\"at\":7.0},{\"wg\":14,\"wd\":333,\"dtl\":\"2017-11-13T15:32:00+01:00\",\"dtl_s\":\"2017-11-13T15:31:30+01:00\",\"ws\":12,\"at\":7.0},{\"wg\":16,\"wd\":332,\"dtl\":\"2017-11-13T15:40:00+01:00\",\"dtl_s\":\"2017-11-13T15:39:30+01:00\",\"ws\":12,\"at\":7.0}]"; + + @Test + public void testReadingOneFix() throws ParseException, NumberFormatException, java.text.ParseException { + final JSONArray fullJson = (JSONArray) new JSONParser().parse(TEST_MESSAGE); + assertEquals(3, fullJson.size()); + final Wind wind = new WindFinderReportParser().parse(new DegreePosition(54.47, 10.28), (JSONObject) fullJson.get(0)); + assertEquals(12.0, wind.getKnots(), 0.0000001); + assertEquals(331.0, wind.getFrom().getDegrees(), 0.0000001); + assertPositionEquals(new DegreePosition(54.47, 10.28), wind.getPosition(), 0.000001); + } + + @Test + public void testReadingSeveralFixesFromStream() throws ParseException, NumberFormatException, java.text.ParseException, IOException { + final Reader reader = new InputStreamReader(getClass().getResourceAsStream("/sap_schilksee_10044N.json")); + final JSONArray fullJson = (JSONArray) new JSONParser().parse(reader); + assertEquals(3, fullJson.size()); + final Wind wind = new WindFinderReportParser().parse(new DegreePosition(54.47, 10.28), (JSONObject) fullJson.get(0)); + assertEquals(12.0, wind.getKnots(), 0.0000001); + assertEquals(331.0, wind.getFrom().getDegrees(), 0.0000001); + assertPositionEquals(new DegreePosition(54.47, 10.28), wind.getPosition(), 0.000001); + } + + @Test + public void testOtherSpots() throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException { + assertEquals(4, Util.size(new ReviewedSpotsCollectionImpl("kielerfoerde").getSpots(/* cached */ false))); + assertEquals(3, Util.size(new ReviewedSpotsCollectionImpl("chiemsee").getSpots(/* cached */ false))); + assertEquals(1, Util.size(new ReviewedSpotsCollectionImpl("starnbergersee").getSpots(/* cached */ false))); + assertEquals(1, Util.size(new ReviewedSpotsCollectionImpl("wannsee").getSpots(/* cached */ false))); + assertEquals(4, Util.size(new ReviewedSpotsCollectionImpl("travemuende").getSpots(/* cached */ false))); + } + + @Test + public void testReadingSpotDescriptions() throws IOException, ParseException { + final Reader reader = new InputStreamReader(getClass().getResourceAsStream("/schilksee_nearby.json")); + final JSONArray fullJson = (JSONArray) new JSONParser().parse(reader); + assertEquals(2, fullJson.size()); + final WindFinderReportParser parser = new WindFinderReportParser(); + final Iterable spots = parser.parseSpots(fullJson, new ReviewedSpotsCollectionImpl("schilksee")); + final Spot kielHoltenau = (Spot) Util.get(spots, 0); + final Spot kielLeuchtturm = (Spot) Util.get(spots, 1); + assertEquals("Kiel-Holtenau Airport", kielHoltenau.getName()); + assertEquals("kiel-holtenau", kielHoltenau.getKeyword()); + assertEquals("de15", kielHoltenau.getId()); + assertEquals("https://www.windfinder.com/report/kiel-holtenau", kielHoltenau.getReportUrl().toString()); + assertEquals("https://www.windfinder.com/forecast/kiel-holtenau", kielHoltenau.getForecastUrl().toString()); + assertPositionEquals(new DegreePosition(54.38, 10.15), kielHoltenau.getPosition(), 0.000001); + assertEquals("Kiel/Leuchtturm", kielLeuchtturm.getName()); + assertEquals("kiel_leuchtturm", kielLeuchtturm.getKeyword()); + assertEquals("10044N", kielLeuchtturm.getId()); + assertEquals("https://www.windfinder.com/report/kiel_leuchtturm", kielLeuchtturm.getReportUrl().toString()); + assertEquals("https://www.windfinder.com/forecast/kiel_leuchtturm", kielLeuchtturm.getForecastUrl().toString()); + assertPositionEquals(new DegreePosition(54.47, 10.28), kielLeuchtturm.getPosition(), 0.000001); + } + + @Test + public void testTwoSpotsInSchilksee() throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException { + assertEquals(2, Util.size(new ReviewedSpotsCollectionImpl("schilksee").getSpots(/* cached */ false))); + } + + @Test + public void testSpotsInSchilkseeAreThoseWithIds_10044N_And_de15() throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException { + final Iterable spots = new ReviewedSpotsCollectionImpl("schilksee").getSpots(/* cached */ false); + final Set spotIds = new HashSet<>(); + Util.addAll(Util.map(spots, s->s.getId()), spotIds); + assertEquals(new HashSet<>(Arrays.asList("10044N", "de15")), spotIds); + } + + private static void assertPositionEquals(Position p1, Position p2, double degreeDelta) { + assertEquals(p1.getLatDeg(), p2.getLatDeg(), degreeDelta); + assertEquals(p1.getLngDeg(), p2.getLngDeg(), degreeDelta); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.domain.windfinderadapter/.classpath b/java/com.sap.sailing.domain.windfinderadapter/.classpath new file mode 100755 index 00000000000..eca7bdba8f0 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.sap.sailing.domain.windfinderadapter/.project b/java/com.sap.sailing.domain.windfinderadapter/.project new file mode 100755 index 00000000000..8efe69d0cbe --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter/.project @@ -0,0 +1,28 @@ + + + com.sap.sailing.domain.windfinderadapter + + + + + + 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.windfinderadapter/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.domain.windfinderadapter/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..0c68a61dca8 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter/.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.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sailing.domain.windfinderadapter/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.domain.windfinderadapter/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..f29e940a005 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter/.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.windfinderadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.windfinderadapter/META-INF/MANIFEST.MF new file mode 100755 index 00000000000..16088fed7c6 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter/META-INF/MANIFEST.MF @@ -0,0 +1,16 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Windfinderadapter +Bundle-SymbolicName: com.sap.sailing.domain.windfinderadapter +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: com.sap.sailing.domain.windfinderadapter.impl.Activator +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Import-Package: org.osgi.framework;version="1.3.0", + org.osgi.util.tracker;version="1.5.1" +Bundle-ActivationPolicy: lazy +Require-Bundle: org.json.simple;bundle-version="1.1.0", + com.sap.sailing.domain.common, + com.sap.sse.common, + com.sap.sailing.domain, + com.sap.sse diff --git a/java/com.sap.sailing.domain.windfinderadapter/build.properties b/java/com.sap.sailing.domain.windfinderadapter/build.properties new file mode 100755 index 00000000000..34d2e4d2dad --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sailing.domain.windfinderadapter/pom.xml b/java/com.sap.sailing.domain.windfinderadapter/pom.xml new file mode 100755 index 00000000000..2385819c27e --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sailing.domain.windfinderadapter + eclipse-plugin + diff --git a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/Activator.java b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/Activator.java new file mode 100755 index 00000000000..678b8f342e3 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/Activator.java @@ -0,0 +1,56 @@ +package com.sap.sailing.domain.windfinderadapter.impl; + +import java.util.HashSet; +import java.util.Set; +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.tracking.WindTrackerFactory; +import com.sap.sailing.domain.windfinder.WindFinderTrackerFactory; + +public class Activator implements BundleActivator { + private static final Logger logger = Logger.getLogger(Activator.class.getName()); + + static final String BASE_URL_FOR_JSON_DOCUMENTS = "http://external.windfinder.com/sap"; + + private static BundleContext context; + + /** + * Registrations of OSGi services to be de-registered when the bundle shuts down + */ + private Set> registrations = new HashSet<>(); + + static BundleContext getContext() { + return context; + } + + /* + * (non-Javadoc) + * + * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext bundleContext) throws Exception { + Activator.context = bundleContext; + logger.info("Creating ExpeditionTrackerFactory"); + final WindFinderTrackerFactoryImpl windfinderTrackerFactory = new WindFinderTrackerFactoryImpl(); + registrations.add(context.registerService(WindFinderTrackerFactory.class, windfinderTrackerFactory, /* properties */null)); + registrations.add(context.registerService(WindTrackerFactory.class, windfinderTrackerFactory, /* properties */null)); + } + + /* + * (non-Javadoc) + * + * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext bundleContext) throws Exception { + for (ServiceRegistration reg : registrations) { + reg.unregister(); + } + registrations.clear(); + Activator.context = null; + } + +} diff --git a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java new file mode 100755 index 00000000000..df19a0f3ce1 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/ReviewedSpotsCollectionImpl.java @@ -0,0 +1,109 @@ +package com.sap.sailing.domain.windfinderadapter.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.json.simple.JSONArray; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.sap.sailing.domain.windfinder.ReviewedSpotsCollection; +import com.sap.sailing.domain.windfinder.Spot; +import com.sap.sse.util.ThreadPoolUtil; + +public class ReviewedSpotsCollectionImpl implements ReviewedSpotsCollection { + /** + * What needs to be appended to the spot collection {@link #id} in order + * to obtain the file name of the document that has the JSON array with + * the spot descriptions. + */ + private final static String SPOT_LIST_DOCUMENT_SUFFIX = "_nearby.json"; + + private final String id; + private final WindFinderReportParser parser; + + /** + * Initialized during construction time with a future that is passed to the default foreground executor + * (foreground because shortly a UI request may be depending on it). The task scheduled will create a + * map initialized with the result of calling {@link #loadSpots()}. + */ + private Future> spotsByIdCache; + + public ReviewedSpotsCollectionImpl(String id) { + this.id = id; + this.parser = new WindFinderReportParser(); + this.spotsByIdCache = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor().schedule(()->{ + final ConcurrentMap result = new ConcurrentHashMap<>(); + for (final Spot spot : loadSpots()) { + result.put(spot.getId(), spot); + } + return result; + }, /* delay */ 0, TimeUnit.MILLISECONDS); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.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; + ReviewedSpotsCollectionImpl other = (ReviewedSpotsCollectionImpl) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + return true; + } + + @Override + public String getId() { + return id; + } + + @Override + public Iterable getSpots(boolean cached) throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException { + final Iterable result; + if (cached) { + result = new HashSet<>(spotsByIdCache.get().values()); + } else { + result = loadSpots(); + for (final Spot spot : result) { + spotsByIdCache.get().put(spot.getId(), spot); + } + } + return result; + } + + private Iterable loadSpots() throws IOException, ParseException, MalformedURLException { + final Iterable result; + JSONArray spotsAsJson = (JSONArray) new JSONParser().parse(new InputStreamReader( + (InputStream) new URL(Activator.BASE_URL_FOR_JSON_DOCUMENTS+"/"+getId()+SPOT_LIST_DOCUMENT_SUFFIX).getContent())); + result = parser.parseSpots(spotsAsJson, this); + return result; + } + + @Override + public String toString() { + return "WindFinder Reviewed Spots Collection [id=" + id + "]"; + } +} diff --git a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/SpotImpl.java b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/SpotImpl.java new file mode 100755 index 00000000000..5cbf86a8081 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/SpotImpl.java @@ -0,0 +1,73 @@ +package com.sap.sailing.domain.windfinderadapter.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.json.simple.JSONArray; +import org.json.simple.parser.JSONParser; + +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.windfinder.SpotDTO; +import com.sap.sailing.domain.windfinder.ReviewedSpotsCollection; +import com.sap.sailing.domain.windfinder.Spot; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; + +public class SpotImpl extends SpotDTO implements Spot { + private static final long serialVersionUID = -7793334775486020313L; + + private final WindFinderReportParser parser; + private final ReviewedSpotsCollection collection; + + public SpotImpl(String name, String id, String keyword, String englishCountryName, Position position, WindFinderReportParser parser, ReviewedSpotsCollection collection) { + super(name, id, keyword, englishCountryName, position); + this.parser = parser; + this.collection = collection; + } + + @Override + public Wind getLatestMeasurement() throws NumberFormatException, ParseException, org.json.simple.parser.ParseException, MalformedURLException, IOException { + final Iterable measurements = getAllMeasurements(); + final Wind result; + if (measurements != null && !Util.isEmpty(measurements)) { + final List measurementsSortedByTimepoint = new ArrayList<>(); + Util.addAll(measurements, measurementsSortedByTimepoint); + Collections.sort(measurementsSortedByTimepoint, (w1, w2)->w1.getTimePoint().compareTo(w2.getTimePoint())); + result = Util.last(measurementsSortedByTimepoint); + } else { + result = null; + } + return result; + } + + @Override + public Iterable getAllMeasurementsAfter(TimePoint timePoint) throws MalformedURLException, IOException, ParseException, org.json.simple.parser.ParseException { + final List result = new ArrayList<>(); + for (final Wind measurement : getAllMeasurements()) { + if (timePoint == null || measurement.getTimePoint().after(timePoint)) { + result.add(measurement); + } + } + return result; + } + + @Override + public Iterable getAllMeasurements() + throws IOException, MalformedURLException, ParseException, org.json.simple.parser.ParseException { + final InputStream response = (InputStream) getMeasurementsUrl().getContent(); + final Iterable measurements = parser.parse(getPosition(), (JSONArray) new JSONParser().parse(new InputStreamReader(response))); + return measurements; + } + + private URL getMeasurementsUrl() throws MalformedURLException { + return new URL(Activator.BASE_URL_FOR_JSON_DOCUMENTS+"/"+collection.getId()+"_"+getId()+".json"); + } +} diff --git a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderReportParser.java b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderReportParser.java new file mode 100755 index 00000000000..c9a15cf3041 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderReportParser.java @@ -0,0 +1,76 @@ +package com.sap.sailing.domain.windfinderadapter.impl; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; + +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.WindImpl; +import com.sap.sailing.domain.windfinder.ReviewedSpotsCollection; +import com.sap.sailing.domain.windfinder.Spot; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class WindFinderReportParser { + /** + * Example: 2017-11-13T15:32:00+01:00 + */ + private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX"); + + public WindFinderReportParser() { + super(); + } + + /** + * @param position The position of the station from which messages are to be parsed + */ + Wind parse(Position position, JSONObject jsonOfSingleMeasurement) throws NumberFormatException, ParseException { + final Wind result; + if (jsonOfSingleMeasurement.get("dtl") != null && + jsonOfSingleMeasurement.get("ws") != null && + jsonOfSingleMeasurement.get("wd") != null) { + result = new WindImpl(position, new MillisecondsTimePoint(dateFormat.parse(jsonOfSingleMeasurement.get("dtl").toString())), + new KnotSpeedWithBearingImpl(Double.parseDouble(jsonOfSingleMeasurement.get("ws").toString()), + new DegreeBearingImpl(Double.parseDouble(jsonOfSingleMeasurement.get("wd").toString())).reverse())); + } else { + result = null; + } + return result; + } + + /** + * @param position The position of the station from which messages are to be parsed + */ + Iterable parse(Position position, JSONArray jsonOfSeveralMeasurements) throws NumberFormatException, ParseException { + final List result = new ArrayList<>(); + for (final Object jsonOfSingleMeasurement : jsonOfSeveralMeasurements) { + final Wind wind = parse(position, (JSONObject) jsonOfSingleMeasurement); + if (wind != null) { + result.add(wind); + } + } + return result; + } + + Spot parseSpot(JSONObject jsonOfSingleSpot, ReviewedSpotsCollection reviewedSpotsCollection) { + return new SpotImpl(jsonOfSingleSpot.get("n").toString(), + jsonOfSingleSpot.get("id").toString(), + jsonOfSingleSpot.get("kw").toString(), + jsonOfSingleSpot.get("c") == null ? null : jsonOfSingleSpot.get("c").toString(), + new DegreePosition(((Number) jsonOfSingleSpot.get("lat")).doubleValue(), + ((Number) jsonOfSingleSpot.get("lon")).doubleValue()), this, reviewedSpotsCollection); + } + + Iterable parseSpots(JSONArray jsonOfMultipleSpots, ReviewedSpotsCollection reviewedSpotsCollection) { + return Util.map(jsonOfMultipleSpots, jsonOfSingleSpot->parseSpot((JSONObject) jsonOfSingleSpot, reviewedSpotsCollection)); + } +} diff --git a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderTrackerFactoryImpl.java b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderTrackerFactoryImpl.java new file mode 100755 index 00000000000..a0e8db8f37f --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderTrackerFactoryImpl.java @@ -0,0 +1,170 @@ +package com.sap.sailing.domain.windfinderadapter.impl; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.json.simple.parser.ParseException; +import org.osgi.util.tracker.ServiceTracker; + +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.common.WindFinderReviewedSpotsCollectionIdProvider; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; +import com.sap.sailing.domain.tracking.WindTracker; +import com.sap.sailing.domain.windfinder.ReviewedSpotsCollection; +import com.sap.sailing.domain.windfinder.Spot; +import com.sap.sailing.domain.windfinder.WindFinderTrackerFactory; +import com.sap.sse.common.Util; +import com.sap.sse.util.ServiceTrackerFactory; +import com.sap.sse.util.ThreadPoolUtil; + +public class WindFinderTrackerFactoryImpl implements WindFinderTrackerFactory { + private final Map windTrackerPerRace; + + /** + * The initial filling of this cache is either accomplished immediately if no + * {@link #reviewedSpotsCollectionIdProvider} is available, or the provider is waited for in a background thread and + * then requested to deliver its reviewed spot collection IDs, and once those are received, the future is fulfilled. + */ + private final Future> reviewedSpotsCollectionsByIdCache; + + /** + * Can hold reviewed spots collections. Starts out valid but empty. Useful if we're running outside of an OSGi + * environment such as a test set-up. The collections provided here will be added to the set returned by + * {@link #getReviewedSpotsCollections(boolean)}. + */ + private Set reviewedSpotsCollections; + private final ServiceTracker reviewedSpotsCollectionIdProvider; + + private final ScheduledExecutorService executor; + + public WindFinderTrackerFactoryImpl() { + this.windTrackerPerRace = new HashMap<>(); + this.executor = ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor(); + this.reviewedSpotsCollections = Collections.synchronizedSet(new HashSet<>()); + if (Activator.getContext() != null) { + reviewedSpotsCollectionIdProvider = ServiceTrackerFactory.createAndOpen(Activator.getContext(), WindFinderReviewedSpotsCollectionIdProvider.class); + reviewedSpotsCollectionsByIdCache = executor.schedule( + ()->{ + final ConcurrentMap result = new ConcurrentHashMap<>(); + reviewedSpotsCollectionIdProvider.getService(); + for (final ReviewedSpotsCollection c : loadSpotCollectionsFromProvider(/* lookupInCache */ false)) { + result.put(c.getId(), c); + } + return result; + }, /*delay*/ 0, TimeUnit.MILLISECONDS); + } else { + reviewedSpotsCollectionIdProvider = null; + final ConcurrentMap cache = new ConcurrentHashMap<>(); + this.reviewedSpotsCollectionsByIdCache = new Future>() { + @Override public boolean cancel(boolean mayInterruptIfRunning) { return true; } + @Override public boolean isCancelled() { return false; } + @Override public boolean isDone() { return true; } + @Override public ConcurrentMap get() { return cache; } + @Override public ConcurrentMap get(long timeout, TimeUnit unit) { return cache; } + }; + } + } + + @Override + public WindTracker createWindTracker(DynamicTrackedRegatta trackedRegatta, RaceDefinition race, + boolean correctByDeclination) throws Exception { + final WindTracker result; + synchronized (windTrackerPerRace) { + final WindTracker existingWindTrackerForRace = getExistingWindTracker(race); + if (existingWindTrackerForRace == null) { + final DynamicTrackedRace trackedRace = trackedRegatta.getTrackedRace(race); + result = new WindFinderWindTracker(trackedRace, this); + windTrackerPerRace.put(race, result); + } else { + result = existingWindTrackerForRace; + } + } + return result; + } + + @Override + public WindTracker getExistingWindTracker(RaceDefinition race) { + synchronized (windTrackerPerRace) { + return windTrackerPerRace.get(race); + } + } + + void trackerStopped(RaceDefinition race) { + synchronized (windTrackerPerRace) { + windTrackerPerRace.remove(race); + } + } + + @Override + public Iterable getReviewedSpotsCollections(boolean cached) throws InterruptedException, ExecutionException { + final Set result = new HashSet<>(); + if (cached) { + result.addAll(reviewedSpotsCollectionsByIdCache.get().values()); + } else { + result.addAll(reviewedSpotsCollections); + Util.addAll(loadSpotCollectionsFromProvider(/* lookupInCache */ true), result); + for (final ReviewedSpotsCollection c : result) { + reviewedSpotsCollectionsByIdCache.get().putIfAbsent(c.getId(), c); + } + } + return result; + } + + private Iterable loadSpotCollectionsFromProvider(boolean lookupInCache) throws InterruptedException, ExecutionException { + final Set result = new HashSet<>(); + final WindFinderReviewedSpotsCollectionIdProvider provider; + if (reviewedSpotsCollectionIdProvider != null && (provider = reviewedSpotsCollectionIdProvider.getService()) != null) { + for (String id : provider.getWindFinderReviewedSpotsCollectionIds()) { + result.add(getReviewedSpotsCollectionById(id, lookupInCache)); + } + } + return result; + } + + @Override + public ReviewedSpotsCollection getReviewedSpotsCollectionById(String spotsCollectionId, boolean lookupInCache) throws InterruptedException, ExecutionException { + ReviewedSpotsCollection result; + if (!lookupInCache || (result = reviewedSpotsCollectionsByIdCache.get().get(spotsCollectionId)) == null) { + final ReviewedSpotsCollectionImpl finalResult = new ReviewedSpotsCollectionImpl(spotsCollectionId); + result = finalResult; + executor.schedule(()->reviewedSpotsCollectionsByIdCache.get().put(spotsCollectionId, finalResult), /* delay */ 0, TimeUnit.MILLISECONDS); + } + return result; + } + + @Override + public Spot getSpotById(String spotId, boolean cached) throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException { + Spot result = null; + for (final ReviewedSpotsCollection coll : getReviewedSpotsCollections(/* cached */ cached)) { + for (final Spot spot : coll.getSpots(/* cached */ cached)) { + if (Util.equalsWithNull(spot.getId(), spotId)) { + result = spot; + break; + } + } + } + return result; + } + + public void addReviewedSpotCollection(ReviewedSpotsCollection collection) throws InterruptedException, ExecutionException { + reviewedSpotsCollections.add(collection); + reviewedSpotsCollectionsByIdCache.get().put(collection.getId(), collection); + } + + public void removeReviewedSpotCollection(ReviewedSpotsCollection collection) throws InterruptedException, ExecutionException { + reviewedSpotsCollections.remove(collection); + reviewedSpotsCollectionsByIdCache.get().remove(collection.getId()); + } +} diff --git a/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderWindTracker.java b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderWindTracker.java new file mode 100755 index 00000000000..07cad18abb5 --- /dev/null +++ b/java/com.sap.sailing.domain.windfinderadapter/src/com/sap/sailing/domain/windfinderadapter/impl/WindFinderWindTracker.java @@ -0,0 +1,116 @@ +package com.sap.sailing.domain.windfinderadapter.impl; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.HashSet; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.json.simple.parser.ParseException; + +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.WindSourceType; +import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sailing.domain.tracking.WindTracker; +import com.sap.sailing.domain.windfinder.ReviewedSpotsCollection; +import com.sap.sailing.domain.windfinder.Spot; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.util.ThreadPoolUtil; + +/** + * When an object of this tracker type is created and there are WindFinder stations available, it schedules regular + * requests to those WindFinder spots that are deemed useful for the race. If no location information for the race and + * fleet is available yet, all sources available are considered useful. Otherwise, a rough distance check is performed. + * If a measurement spot is ridiculously far away from the race course it doesn't make sense to even poll its + * measurements, even if the general weighted average would ultimately rank down its contribution. + *

+ * + * {@link #stop() Stopping} this tracker will cancel the task that polls for regular updates and will tell the + * {@link WindFinderTrackerFactoryImpl} that this tracker has been stopped. The tracker will therefore be released by the + * factory and will no longer be returned as the tracker responsible for retrieving WindFinder data for the particular + * race that this tracker was bound to. + * + * @author Axel Uhl (D043530) + * + */ +public class WindFinderWindTracker implements WindTracker, Runnable { + private static final Logger logger = Logger.getLogger(WindFinderWindTracker.class.getName()); + private static final Duration POLL_EVERY = Duration.ONE_MINUTE; + + private final DynamicTrackedRace trackedRace; + private final WindFinderTrackerFactoryImpl factory; + + private final ScheduledFuture poller; + + private WeakHashMap timePointOfLastMeasurement; + + /** + * The set of all {@link ReviewedSpotsCollection}s delivered by the {@link #factory} when this tracker + * was created. This is the basis for {@link #getUsefulSpots()} when evaluating, e.g., based on the + * {@link #trackedRace race's} location, which of the spots is actually useful for this tracker. + */ + private final Iterable allSpotCollections; + + public WindFinderWindTracker(DynamicTrackedRace trackedRace, WindFinderTrackerFactoryImpl factory) throws InterruptedException, ExecutionException { + this.trackedRace = trackedRace; + this.factory = factory; + this.allSpotCollections = factory.getReviewedSpotsCollections(/* cached */ false); // obtain fresh copy of all spots, updating cache + this.timePointOfLastMeasurement = new WeakHashMap<>(); + this.poller = ThreadPoolUtil.INSTANCE.getDefaultBackgroundTaskThreadPoolExecutor().scheduleAtFixedRate(this, + /* initialDelay */ 0, /* period */ POLL_EVERY.asMillis(), TimeUnit.MILLISECONDS); + } + + /** + * This method is executed in order to poll the WindFinder spots for new measurements + */ + @Override + public void run() { + try { + final Iterable usefulSpots = getUsefulSpots(); + for (final Spot usefulSpot : usefulSpots) { + final Iterable windFixes = usefulSpot.getAllMeasurementsAfter(timePointOfLastMeasurement.get(usefulSpot)); + if (!Util.isEmpty(windFixes)) { + final WindSourceWithAdditionalID windSource = new WindSourceWithAdditionalID(WindSourceType.WINDFINDER, usefulSpot.getId()); + final WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource); + for (final Wind wind : windFixes) { + final Wind existingFix = windTrack==null?null/*test scenario*/:windTrack.getFirstRawFixAtOrAfter(wind.getTimePoint()); + if (existingFix == null || !existingFix.getTimePoint().equals(wind.getTimePoint())) { + // avoid duplicates by adding the same fix again + trackedRace.recordWind(wind, windSource); + if (!timePointOfLastMeasurement.containsKey(usefulSpot) || wind.getTimePoint().after(timePointOfLastMeasurement.get(usefulSpot))) { + timePointOfLastMeasurement.put(usefulSpot, wind.getTimePoint()); + } + } + } + } + } + } catch (Exception e) { + logger.log(Level.WARNING, "Exception trying to obtain WindFinder data for race "+trackedRace.getRace().getName(), e); + } + } + + private Iterable getUsefulSpots() throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException { + final Set spots = new HashSet<>(); + for (final ReviewedSpotsCollection collection : allSpotCollections) { + // TODO bug1301 judge each spot's usefulness given the location of trackedRace + Util.addAll(collection.getSpots(/* cached */ false), spots); + } + return spots; + } + + @Override + public void stop() { + poller.cancel(/* mayInterruptIfRunning */ false); + factory.trackerStopped(trackedRace.getRace()); + } + +} diff --git a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF index 90d5779217e..82132c157a7 100755 --- a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF @@ -31,6 +31,7 @@ Export-Package: com.sap.sailing.domain.base, com.sap.sailing.domain.trackimport, com.sap.sailing.domain.tracking, com.sap.sailing.domain.tracking.impl, + com.sap.sailing.domain.windfinder, com.sap.sailing.util, com.sap.sailing.util.impl;x-friends:="com.sap.sailing.server.replication,com.sap.sailing.domain.test" Bundle-ActivationPolicy: lazy 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 071b0bce01a..74aad516501 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,6 +2,8 @@ package com.sap.sailing.domain.base; import java.util.UUID; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; /** @@ -41,4 +43,31 @@ public interface Event extends EventBase { * leaderboard groups in leaderboardGroups. */ void setLeaderboardGroups(Iterable leaderboardGroups); + + /** + * An event may happen in the vicinity of one or more WindFinder (https://www.windfinder.com) weather stations. + * Which ones those are can be defined using {@link #setWindFinderReviewedSpotsCollection(Iterable)}, and this + * getter returns the IDs last set. + * + * @return an always valid (non-{@code null}) but possibly empty and unmodifiable set of strings; the set is "live," + * meaning it will change as the underlying data changes + */ + Iterable getWindFinderReviewedSpotsCollectionIds(); + + /** + * This method may return WindFinder spot IDs based on the tracked races reachable from this event's associated + * leaderboard groups and their wind sources. The {@link WindSource#getId() wind source IDs} of all wind sources of + * type {@link WindSourceType#WINDFINDER} will be collected and returned. + * + * @return an always valid (non-{@code null}) but possibly empty set of strings that is a non-live copy computed + * just for this call + */ + Iterable getAllFinderSpotIdsUsedByTrackedRacesInEvent(); + + /** + * Set the IDs of the reviewed WindFinder spot collections to consider during this event. + * Setting this to a non-empty value shall lead to a corresponding display of a WindFinder + * logo / link on the event's UI representation. + */ + void setWindFinderReviewedSpotsCollection(Iterable reviewedSpotsCollectionIds); } 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 cf7c0044f97..ceb732d4e0b 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 @@ -3,12 +3,20 @@ 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.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.Venue; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.WindSourceType; +import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; @@ -17,6 +25,8 @@ public class EventImpl extends EventBaseImpl implements Event { private ConcurrentLinkedQueue leaderboardGroups; + private ConcurrentMap windFinderReviewedSpotsCollectionIds; + public EventImpl(String name, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id) { this(name, startDate, endDate, new VenueImpl(venueName), isPublic, id); } @@ -27,6 +37,7 @@ 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.leaderboardGroups = new ConcurrentLinkedQueue<>(); + this.windFinderReviewedSpotsCollectionIds = new ConcurrentHashMap<>(); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { @@ -34,6 +45,9 @@ public class EventImpl extends EventBaseImpl implements Event { if (leaderboardGroups == null) { leaderboardGroups = new ConcurrentLinkedQueue<>(); } + if (windFinderReviewedSpotsCollectionIds == null) { + windFinderReviewedSpotsCollectionIds = new ConcurrentHashMap<>(); + } } public String toString() { @@ -60,4 +74,32 @@ public class EventImpl extends EventBaseImpl implements Event { public boolean removeLeaderboardGroup(LeaderboardGroup leaderboardGroup) { return leaderboardGroups.remove(leaderboardGroup); } + + @Override + public Iterable getWindFinderReviewedSpotsCollectionIds() { + return Collections.unmodifiableSet(windFinderReviewedSpotsCollectionIds.keySet()); + } + + @Override + public Iterable getAllFinderSpotIdsUsedByTrackedRacesInEvent() { + final Set result = new HashSet<>(); + for (final LeaderboardGroup leaderboardGroup : getLeaderboardGroups()) { + for (final Leaderboard leaderboard : leaderboardGroup.getLeaderboards()) { + for (final TrackedRace trackedRace : leaderboard.getTrackedRaces()) { + for (final WindSource windTrackerWindSource : trackedRace.getWindSources(WindSourceType.WINDFINDER)) { + result.add(windTrackerWindSource.getId().toString()); + } + } + } + } + return result; + } + + @Override + public void setWindFinderReviewedSpotsCollection(Iterable reviewedSpotsCollectionIds) { + windFinderReviewedSpotsCollectionIds.clear(); + for (final String reviewedSpotsCollectionId : reviewedSpotsCollectionIds) { + windFinderReviewedSpotsCollectionIds.putIfAbsent(reviewedSpotsCollectionId, true); + } + } } 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 505a5cc97b7..26c2a045b4c 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 @@ -2,7 +2,6 @@ package com.sap.sailing.domain.base.impl; import java.io.Serializable; -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent; @@ -49,11 +48,6 @@ public class RaceColumnRaceLogReplicator implements RaceLogEventVisitor, Seriali event); } - @Override - public void visit(RaceLogCourseAreaChangedEvent event) { - notifyOnAdd(event); - } - @Override public void visit(RaceLogStartTimeEvent event) { notifyOnAdd(event); 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 dad0788d027..ce01ff6f962 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 @@ -10,7 +10,6 @@ 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.Wind; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; import com.sap.sse.common.Duration; @@ -297,19 +296,6 @@ public interface TrackedLegOfCompetitor extends Serializable { TrackedLeg getTrackedLeg(); - /** - * Computes the angle between the competitors direction and the wind's "from" direction. The angle's direction is chosen such that - * it can be added to the boat's course over ground to arrive at the wind's {@link Wind#getFrom() "from"} direction. Example: wind - * from the north (0deg), boat's course over ground 90deg (moving east), then the bearing returned is -90deg. - */ - Bearing getBeatAngle(TimePoint at) throws NoWindException; - - /** - * Same as {@link #getBeatAngle}, only that additionally a cache is provided that can allow the method to use - * cached wind and leg type values. - */ - Bearing getBeatAngle(TimePoint at, WindLegTypeAndLegBearingCache cache) throws NoWindException; - /** * Like {@link #getAverageVelocityMadeGood(TimePoint)}, only with an additional cache argument that allows the method to * use already computed values for wind and leg type, potentially also updating the cache as it goes. 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 1aca3e3f7ed..68272f8ce4a 100644 --- 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 @@ -1075,6 +1075,34 @@ public interface TrackedRace extends Serializable, IsManagedByCache sogTrack = this.getTrack(competitor); + if (sogTrack != null) { + SpeedWithBearing speedOverGround = sogTrack.getEstimatedSpeed(timePoint); + Wind wind = cache.getWind(this, competitor, timePoint); + if (wind != null && speedOverGround != null) { + final Bearing projectToDirection = wind.getFrom(); + twa = speedOverGround.getBearing().getDifferenceTo(projectToDirection); + } + } + return twa; + } + + /** + * Same as {@link #getTWA}, only that additionally a cache is provided that can allow the method to use + * cached wind and leg type values. + */ + default Bearing getTWA(Competitor competitor, TimePoint at){ + return getTWA(competitor, at, new LeaderboardDTOCalculationReuseCache(at)); + } + /** * Like {@link #getVelocityMadeGood(Competitor, TimePoint)}, but allowing callers to specify a cache that can * accelerate requests for wind directions, the leg type and the competitor's current leg's bearing. @@ -1092,4 +1120,5 @@ public interface TrackedRace extends Serializable, IsManagedByCache + * + * Equality and hash code are based only on the {@link #getId() ID}. + * + * @author Axel Uhl (D043530) + * + */ +public interface ReviewedSpotsCollection extends WithID { + @Override + String getId(); + + /** + * @param cached + * if {@code true}, only those spots collections are returned that this factory has previously obtained; + * this is useful for a very fast lookup but does not guarantee that all spots collections added recently + * will be considered + */ + Iterable getSpots(boolean cached) throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException; +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/Spot.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/Spot.java new file mode 100755 index 00000000000..5cdf0d3ce53 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/Spot.java @@ -0,0 +1,29 @@ +package com.sap.sailing.domain.windfinder; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.text.ParseException; + +import com.sap.sailing.domain.common.Wind; +import com.sap.sailing.domain.common.windfinder.SpotBase; +import com.sap.sse.common.TimePoint; + +/** + * A measurement spot for which a report and / or a forecast may exist. Equality and + * hash code are based on the {@link #getId() ID} only. + * + * @author Axel Uhl (D043530) + * + */ +public interface Spot extends SpotBase { + Wind getLatestMeasurement() throws NumberFormatException, ParseException, org.json.simple.parser.ParseException, MalformedURLException, IOException; + + Iterable getAllMeasurements() throws IOException, MalformedURLException, ParseException, org.json.simple.parser.ParseException; + + /** + * @param timePoint + * if {@code null} then this call delivers the result of {@link #getAllMeasurements()} + * @return an always valid but possibly empty collection + */ + Iterable getAllMeasurementsAfter(TimePoint timePoint) throws MalformedURLException, IOException, ParseException, org.json.simple.parser.ParseException; +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/WindFinderTrackerFactory.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/WindFinderTrackerFactory.java new file mode 100755 index 00000000000..e48885bb85c --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/WindFinderTrackerFactory.java @@ -0,0 +1,38 @@ +package com.sap.sailing.domain.windfinder; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.concurrent.ExecutionException; + +import org.json.simple.parser.ParseException; + +import com.sap.sailing.domain.tracking.WindTrackerFactory; + +public interface WindFinderTrackerFactory extends WindTrackerFactory { + + /** + * Obtains the reviewed spot collections that this factory knows about. This set is constructed from the collections + * provided explicitly using {@link #addReviewedSpotCollection(ReviewedSpotCollection)} and + * {@link #removeReviewedSpotCollection(ReviewedSpotsCollection)}, and is extended by the collections obtained + * through {@link #reviewedSpotsCollectionIdProvider} if a corresponding service can be resolved. + * + * @param cached + * if {@code true}, only those spots collections are returned that this factory has previously obtained; + * this is useful for a very fast lookup but does not guarantee that all spots collections added recently + * will be considered + * + * @return a non-live set of spots collections known by this factory at this point in time + */ + Iterable getReviewedSpotsCollections(boolean cached) throws InterruptedException, ExecutionException; + + ReviewedSpotsCollection getReviewedSpotsCollectionById(String spotsCollectionId, boolean lookupInCache) throws InterruptedException, ExecutionException; + + /** + * @param cached + * if {@code true}, only those spots collections are returned that this factory has previously obtained; + * this is useful for a very fast lookup but does not guarantee that all spots collections added recently + * will be considered + */ + Spot getSpotById(String spotId, boolean cached) throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException; + +} diff --git a/java/com.sap.sailing.feature.p2build/raceanalysis.product b/java/com.sap.sailing.feature.p2build/raceanalysis.product index a6e570f6fef..5b6634340c0 100755 --- a/java/com.sap.sailing.feature.p2build/raceanalysis.product +++ b/java/com.sap.sailing.feature.p2build/raceanalysis.product @@ -47,6 +47,7 @@ + diff --git a/java/com.sap.sailing.feature/feature.xml b/java/com.sap.sailing.feature/feature.xml index 4ec77eb88b3..1a126624cb7 100755 --- a/java/com.sap.sailing.feature/feature.xml +++ b/java/com.sap.sailing.feature/feature.xml @@ -67,6 +67,13 @@ Computes leaderboard information for sailing races and offers RESTful APIs to al version="0.0.0" unpack="false"/> + + seriesCreationParameters = new LinkedHashMap<>(); + final List fleets = new ArrayList<>(); + fleets.add(new FleetDTO(fleetName, 0, null)); + seriesCreationParameters.put(seriesName, + new SeriesCreationParametersDTO(fleets, false, false, true, false, new int[0], false, 0)); + final RegattaCreationParametersDTO regattaCreationParameters = new RegattaCreationParametersDTO( + seriesCreationParameters); + final RegattaDTO regatta = sailingService.createRegatta(regattaName, boatClassName, null, null, + regattaCreationParameters, false, ScoringSchemeType.HIGH_POINT, null, 3.0, false, false, + RankingMetrics.ONE_DESIGN); + final List> columnNames = new ArrayList<>(); + columnNames.add(new Pair<>(columnNameOriginalRace, 0)); + sailingService.addRaceColumnsToSeries(regatta.getRegattaIdentifier(), seriesName, columnNames); + sailingService.createRegattaLeaderboard(regatta.getRegattaIdentifier(), regattaName, new int[0]); + final List competitors = sailingService.addCompetitors(Arrays.asList(competitor), null); + final CompetitorDTO competitorDTO = competitors.iterator().next(); + sailingService.setCompetitorRegistrationsInRegattaLog(regattaName, new HashSet<>(competitors)); + DeviceIdentifierDTO deviceId = new DeviceIdentifierDTO("FILE", UUID.randomUUID().toString()); + final DeviceMappingDTO deviceMapping = new DeviceMappingDTO(deviceId, startOriginalRace.asDate(), endOriginalRace.asDate(), competitorDTO, null); + sailingService.addDeviceMappingToRegattaLog(regattaName, deviceMapping); + final List> leaderboardRaceColumnFleetNames = new ArrayList<>(); + leaderboardRaceColumnFleetNames.add(new Triple<>(regattaName, columnNameOriginalRace, fleetName)); + RaceLogSetTrackingTimesDTO raceLogSetTrackingTimes = new RaceLogSetTrackingTimesDTO(); + raceLogSetTrackingTimes.leaderboardName = regattaName; + raceLogSetTrackingTimes.authorName = "Test"; + raceLogSetTrackingTimes.raceColumnName = columnNameOriginalRace; + raceLogSetTrackingTimes.fleetName = fleetName; + raceLogSetTrackingTimes.newStartOfTracking = new TimePointSpecificationFoundInLogImpl(startOriginalRace); + raceLogSetTrackingTimes.newEndOfTracking = new TimePointSpecificationFoundInLogImpl(endOriginalRace); + sailingService.setTrackingTimes(raceLogSetTrackingTimes); + sailingService.startRaceLogTracking(leaderboardRaceColumnFleetNames , true, false); + // TODO add wind fixes + + final StrippedLeaderboardDTO leaderboard = sailingService.getLeaderboard(regattaName); + final RaceColumnDTO columnOfOriginalRace = leaderboard.getRaceList().iterator().next(); + final RaceDTO originalRace = columnOfOriginalRace.getRace(columnOfOriginalRace.getFleets().iterator().next()); + final RegattaAndRaceIdentifier raceIdentifierOfOriginalRace = originalRace.getRaceIdentifier(); + final RegattaAndRaceIdentifier sliceRaceIdentifier = sailingService.sliceRace(raceIdentifierOfOriginalRace, columnNameSlicedRace, startSlicedRace, endSlicedRace); + final StrippedLeaderboardDTO leaderboard2 = sailingService.getLeaderboard(regattaName); + final RaceColumnDTO columnOfSlicedRace = leaderboard2.getRaceColumnByName(columnNameSlicedRace); + final RaceDTO slicedRace = columnOfSlicedRace.getRace(columnOfOriginalRace.getFleets().iterator().next()); + Assert.assertEquals(slicedRace.getRaceIdentifier(), sliceRaceIdentifier); + final TrackedRaceDTO trackedRace = slicedRace.trackedRace; + Assert.assertEquals(startSlicedRace.asDate(), trackedRace.startOfTracking); + Assert.assertEquals(endSlicedRace.asDate(), trackedRace.endOfTracking); + } + +} 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 176dead2344..2618262490a 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,4 +1,5 @@ eclipse.preferences.version=1 +encoding//src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html=UTF-8 encoding//src/main/java/com/sap/sailing/gwt/home/mobile/partials/sectionHeader/SectionHeaderContent.java=utf-8 encoding//src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaRaceStatesComponent.java=utf-8 encoding//src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java=utf-8 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 b3046564b6c..e737fe54d3b 100644 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -56,7 +56,8 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.competitorimport, com.sap.sailing.expeditionconnector, com.sap.sailing.expeditionconnector.common, - org.mp4parser.isoparser;bundle-version="1.9.31" + org.mp4parser.isoparser;bundle-version="1.9.31", + com.sap.sailing.domain.windfinderadapter Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator Bundle-ActivationPolicy: lazy Import-Package: javax.servlet;version="3.1.0", diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/windfinder/SpotDTO_CustomFieldSerializer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/windfinder/SpotDTO_CustomFieldSerializer.java new file mode 100755 index 00000000000..e3609057e7d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/windfinder/SpotDTO_CustomFieldSerializer.java @@ -0,0 +1,54 @@ +package com.google.gwt.user.client.rpc.core.com.sap.sailing.domain.common.windfinder; + +import com.google.gwt.user.client.rpc.CustomFieldSerializer; +import com.google.gwt.user.client.rpc.SerializationException; +import com.google.gwt.user.client.rpc.SerializationStreamReader; +import com.google.gwt.user.client.rpc.SerializationStreamWriter; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.windfinder.SpotDTO; + +public class SpotDTO_CustomFieldSerializer extends CustomFieldSerializer { + @Override + public boolean hasCustomInstantiateInstance() { + return true; + } + + @Override + public SpotDTO instantiateInstance(SerializationStreamReader streamReader) throws SerializationException { + return instantiate(streamReader); + } + + public static SpotDTO instantiate(SerializationStreamReader streamReader) throws SerializationException { + final String name = streamReader.readString(); + final String id = streamReader.readString(); + final String keyword = streamReader.readString(); + final String englishCountryName = streamReader.readString(); + final Position position = (Position) streamReader.readObject(); + return new SpotDTO(name, id, keyword, englishCountryName, position); + } + + @Override + public void deserializeInstance(SerializationStreamReader streamReader, SpotDTO instance) + throws SerializationException { + deserialize(streamReader, instance); + } + + public static void deserialize(SerializationStreamReader streamReader, SpotDTO instance) { + // handled by instantiate + } + + @Override + public void serializeInstance(SerializationStreamWriter streamWriter, SpotDTO instance) + throws SerializationException { + serialize(streamWriter, instance); + } + + public static void serialize(SerializationStreamWriter streamWriter, SpotDTO instance) + throws SerializationException { + streamWriter.writeString(instance.getName()); + streamWriter.writeString(instance.getId()); + streamWriter.writeString(instance.getKeyword()); + streamWriter.writeString(instance.getEnglishCountryName()); + streamWriter.writeObject(instance.getPosition()); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/EventWindFinderUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/EventWindFinderUtil.java new file mode 100755 index 00000000000..659150d961c --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/EventWindFinderUtil.java @@ -0,0 +1,46 @@ +package com.sap.sailing.gwt.common.client; + +import java.io.IOException; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.logging.Logger; + +import org.json.simple.parser.ParseException; + +import com.sap.sailing.domain.base.Event; +import com.sap.sailing.domain.common.windfinder.SpotDTO; +import com.sap.sailing.domain.windfinder.Spot; +import com.sap.sailing.domain.windfinder.WindFinderTrackerFactory; + +public class EventWindFinderUtil { + private static final Logger logger = Logger.getLogger(EventWindFinderUtil.class.getName()); + + /** + * Obtains wind finder spots for a given Event + * + * @param useCachedSpotsForTrackedRaces + * whether or not to use the cached set of wind finder spots for tracked races + */ + public Iterable getWindFinderSpotsToConsider(Event event, WindFinderTrackerFactory windFinderTrackerFactory, boolean useCachedSpotsForTrackedRaces) { + final Set windFinderSpots = new LinkedHashSet<>(); + for (final String spotsCollectionId : event.getWindFinderReviewedSpotsCollectionIds()) { + try { + for (final Spot spot : windFinderTrackerFactory.getReviewedSpotsCollectionById(spotsCollectionId, /* lookupInCache */ true). + getSpots(/* cached */ true)) { + windFinderSpots.add(new SpotDTO(spot)); + } + } catch (IOException | ParseException | InterruptedException | ExecutionException e) { + logger.warning("Unable to determine WindFinder spots for reviewed spot collection with ID "+spotsCollectionId); + } + } + for (final String spotIdFromTrackedRace : event.getAllFinderSpotIdsUsedByTrackedRacesInEvent()) { + try { + windFinderSpots.add(new SpotDTO(windFinderTrackerFactory.getSpotById(spotIdFromTrackedRace, /* cached */ true))); + } catch (IOException | ParseException | InterruptedException | ExecutionException e) { + logger.warning("Unable to determine WindFinder spot with ID "+spotIdFromTrackedRace); + } + } + return windFinderSpots; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/controls/tabbar/TabPanel.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/controls/tabbar/TabPanel.ui.xml index 858071eb1ed..15379e2d27b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/controls/tabbar/TabPanel.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/controls/tabbar/TabPanel.ui.xml @@ -22,8 +22,12 @@ /* font-size: 0; */ margin-bottom: -1px; } + .tabExtension { float: right; + } + + .tabExtension > * { margin-left: 0.5em; } @@ -33,12 +37,10 @@

- +
- + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/SailingDispatchContext.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/SailingDispatchContext.java index c87b41977e4..1c55791e0bb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/SailingDispatchContext.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/SailingDispatchContext.java @@ -4,6 +4,7 @@ import java.util.Date; import java.util.Locale; import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.domain.windfinder.WindFinderTrackerFactory; import com.sap.sailing.news.EventNewsService; import com.sap.sailing.server.RacingEventService; import com.sap.sailing.server.statistics.TrackedRaceStatisticsCache; @@ -16,6 +17,9 @@ import com.sap.sse.gwt.dispatch.client.system.DispatchContext; public interface SailingDispatchContext extends DispatchContext { @GwtIncompatible RacingEventService getRacingEventService(); + + @GwtIncompatible + WindFinderTrackerFactory getWindFinderTrackerFactory(); @GwtIncompatible EventNewsService getEventNewsService(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/SailingSerializationDummyResult.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/SailingSerializationDummyResult.java new file mode 100644 index 00000000000..08374455175 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/SailingSerializationDummyResult.java @@ -0,0 +1,17 @@ +package com.sap.sailing.gwt.home.communication; + +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sse.gwt.dispatch.shared.commands.Result; + +public class SailingSerializationDummyResult implements Result { + + private DegreePosition degreePosition; + + private SailingSerializationDummyResult() { + } + + public DegreePosition getDegreePosition() { + return degreePosition; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetEventViewAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetEventViewAction.java index 921ce88f966..264c9861ec1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetEventViewAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetEventViewAction.java @@ -9,6 +9,7 @@ import com.google.gwt.core.shared.GwtIncompatible; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.common.dto.EventType; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; +import com.sap.sailing.gwt.common.client.EventWindFinderUtil; import com.sap.sailing.gwt.home.communication.SailingAction; import com.sap.sailing.gwt.home.communication.SailingDispatchContext; import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO; @@ -62,7 +63,10 @@ public class GetEventViewAction implements SailingAction, IsClient dto.setOfficialWebsiteURL(event.getOfficialWebsiteURL() == null ? null : event.getOfficialWebsiteURL().toString()); URL sailorsInfoWebsiteURL = event.getSailorsInfoWebsiteURLOrFallback(context.getClientLocale()); dto.setSailorsInfoWebsiteURL(sailorsInfoWebsiteURL == null ? null : sailorsInfoWebsiteURL.toString()); - + if (context.getWindFinderTrackerFactory() != null) { + dto.setAllWindFinderSpotsUsedByEvent(new EventWindFinderUtil().getWindFinderSpotsToConsider(event, + context.getWindFinderTrackerFactory(), /* useCachedSpotsForTrackedRaces */ true)); + } dto.setHasMedia(HomeServiceUtil.hasMedia(event)); dto.setState(HomeServiceUtil.calculateEventState(event)); // bug2982: always show leaderboard and competitor analytics diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventview/EventViewDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventview/EventViewDTO.java index 20ee063f1a4..69b1e60286c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventview/EventViewDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventview/EventViewDTO.java @@ -2,13 +2,18 @@ package com.sap.sailing.gwt.home.communication.eventview; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.TreeSet; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.dto.EventType; +import com.sap.sailing.domain.common.windfinder.SpotDTO; import com.sap.sailing.gwt.home.communication.event.EventMetadataDTO; import com.sap.sailing.gwt.home.communication.event.EventReferenceWithStateDTO; import com.sap.sailing.gwt.home.communication.event.HasLogo; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.media.ImageDTO; import com.sap.sse.gwt.dispatch.shared.commands.Result; @@ -24,6 +29,7 @@ public class EventViewDTO extends EventMetadataDTO implements Result, HasLogo { private String officialWebsiteURL; private String sailorsInfoWebsiteURL; private String description; + private List allWindFinderSpotIdsUsedByEvent; public EventType getType() { return type; @@ -110,6 +116,30 @@ public class EventViewDTO extends EventMetadataDTO implements Result, HasLogo { this.sailorsInfoWebsiteURL = sailorsInfoWebsiteURL; } + /** + * In addition to the spots from the wind finder spot collections specified by this event explicitly (see + * {@link #getWindFinderReviewedSpotsCollectionIds()}), this method may return additional spots based + * on the tracked races reachable from this event's associated leaderboard groups and their wind sources. The + * {@link WindSource#getId() wind source IDs} of all wind sources of type {@link WindSourceType#WINDFINDER} will be + * collected and the corresponding {@link SpotDTO} objects are then returned. + */ + public Iterable getAllWindFinderSpotIdsUsedByEvent() { + final Iterable result; + if (allWindFinderSpotIdsUsedByEvent == null) { + result = Collections.emptySet(); + } else { + result = allWindFinderSpotIdsUsedByEvent; + } + return result; + } + + public void setAllWindFinderSpotsUsedByEvent(Iterable windFinderSpots) { + this.allWindFinderSpotIdsUsedByEvent = new ArrayList<>(); + if (windFinderSpots != null) { + Util.addAll(windFinderSpots, this.allWindFinderSpotIdsUsedByEvent); + } + } + public String getLocationAndVenueAndCountry() { String venue = getLocationAndVenue(); if(getVenueCountry() != null && !getVenueCountry().isEmpty()) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/raceviewerlaunchpad/RaceviewerLaunchPad.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/raceviewerlaunchpad/RaceviewerLaunchPad.java index cafa6ccb351..f6a4160658f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/raceviewerlaunchpad/RaceviewerLaunchPad.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/raceviewerlaunchpad/RaceviewerLaunchPad.java @@ -1,5 +1,7 @@ package com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad; +import java.util.function.BiFunction; + import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.DivElement; import com.google.gwt.uibinder.client.UiBinder; @@ -12,7 +14,7 @@ import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.raceboard.RaceBoardModes; -public abstract class RaceviewerLaunchPad extends Composite { +class RaceviewerLaunchPad extends Composite { private static RaceviewerLaunchPadUiBinder uiBinder = GWT.create(RaceviewerLaunchPadUiBinder.class); @@ -21,9 +23,12 @@ public abstract class RaceviewerLaunchPad extends Composite { @UiField RaceviewerLaunchPadResources local_res; @UiField DivElement itemContainerUi; + private final BiFunction raceboardUrlFactory; private final PopupPanel parent; - public RaceviewerLaunchPad(SimpleRaceMetadataDTO data, final PopupPanel parent) { + RaceviewerLaunchPad(SimpleRaceMetadataDTO data, + BiFunction raceboardUrlFactory, PopupPanel parent) { + this.raceboardUrlFactory = raceboardUrlFactory; this.parent = parent; initWidget(uiBinder.createAndBindUi(this)); local_res.css().ensureInjected(); @@ -50,7 +55,7 @@ public abstract class RaceviewerLaunchPad extends Composite { } private void addItem(SimpleRaceMetadataDTO data, RaceviewerLaunchPadMenuItem item) { - String raceViewerUrl = getRaceViewerURL(data, item.raceBoardMode); + String raceViewerUrl = raceboardUrlFactory.apply(data, item.raceBoardMode); itemContainerUi.appendChild(new RaceviewerLaunchPadItem(item.label, item.icon, raceViewerUrl).getElement()); } @@ -70,6 +75,4 @@ public abstract class RaceviewerLaunchPad extends Composite { } } - protected abstract String getRaceViewerURL(SimpleRaceMetadataDTO data, String raceBoardMode); - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/raceviewerlaunchpad/RaceviewerLaunchPadCell.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/raceviewerlaunchpad/RaceviewerLaunchPadCell.java index 3dcc9d69ca6..5fc73b513e6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/raceviewerlaunchpad/RaceviewerLaunchPadCell.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/raceviewerlaunchpad/RaceviewerLaunchPadCell.java @@ -55,7 +55,7 @@ public class RaceviewerLaunchPadCell> extends Abstr if (!launchPadController.renderAsDirectLink(data)) { if (data.hasValidTrackingData() && BrowserEvents.CLICK.equals(event.getType()) && parent.getFirstChildElement().isOrHasChild(Element.as(event.getEventTarget()))) { - launchPadController.showRaceviewerLaunchPad(data, parent); + launchPadController.showLaunchPad(data, parent); } return; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/raceviewerlaunchpad/RaceviewerLaunchPadController.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/raceviewerlaunchpad/RaceviewerLaunchPadController.java index 436f0199468..cd2e11927ea 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/raceviewerlaunchpad/RaceviewerLaunchPadController.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/raceviewerlaunchpad/RaceviewerLaunchPadController.java @@ -2,21 +2,16 @@ package com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad; import java.util.function.BiFunction; -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.dom.client.Element; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO; +import com.sap.sailing.gwt.home.shared.partials.launchpad.AbstractLaunchPadController; import com.sap.sailing.gwt.ui.raceboard.RaceBoardModes; -public class RaceviewerLaunchPadController { +public class RaceviewerLaunchPadController extends AbstractLaunchPadController { public static enum RenderingStyle { WATCH_LIVE_ONLY, WATCH_LIVE_OR_ANALYZE, ANALYZE, NOT_TRACKED } - private final PopupPanel panel = new PopupPanel(true, false); private final BiFunction raceboardUrlFactory; /** @@ -27,78 +22,17 @@ public class RaceviewerLaunchPadController { * race} and {@link RaceBoardModes raceboard mode} information */ public RaceviewerLaunchPadController(BiFunction raceboardUrlFactory) { + super((data, panel) -> new RaceviewerLaunchPad(data, raceboardUrlFactory, panel)); this.raceboardUrlFactory = raceboardUrlFactory; } - /** - * Provides the direct link URL for the given {@link SimpleRaceMetadataDTO race}. - * - * @param data - * the {@link SimpleRaceMetadataDTO race} to get the link URL for - * @return the direct link URL (as {@link String}) - */ - public String getDirectLinkUrl(T data) { + @Override + public String getDirectLinkUrl(SimpleRaceMetadataDTO data) { return raceboardUrlFactory.apply(data, RaceBoardModes.PLAYER.name()); } - /** - * Creates a {@link RaceviewerLaunchPad} for the given {@link SimpleRaceMetadataDTO race} and {@link PopupPanel} - * instance. - * - * @param data - * the {@link SimpleRaceMetadataDTO race} to create a launch pad for - * @param parent - * the {@link PopupPanel} which will contain the launch pad - * @return the newly created {@link RaceviewerLaunchPad} instance - */ - public RaceviewerLaunchPad getRaceviewerLaunchPad(T data, PopupPanel parent) { - return new RaceviewerLaunchPad(data, parent) { - @Override - protected String getRaceViewerURL(SimpleRaceMetadataDTO data, String raceBoardMode) { - return raceboardUrlFactory.apply(data, raceBoardMode); - } - }; - } - - /** - * Shows a {@link RaceviewerLaunchPad} for the given {@link SimpleRaceMetadataDTO race} relative to the also - * provided element. - * - * @param data - * the {@link SimpleRaceMetadataDTO race} to create a launch pad for - * @param relTo - * the {@link Element element} to show the launch pad relative to - */ - public void showRaceviewerLaunchPad(T data, Element relTo) { - relTo.scrollIntoView(); - panel.setWidget(new RaceviewerLaunchPad(data, panel) { - @Override - protected String getRaceViewerURL(SimpleRaceMetadataDTO data, String raceBoardMode) { - return raceboardUrlFactory.apply(data, raceBoardMode); - } - }); - panel.setVisible(false); - panel.show(); - Scheduler.get().scheduleDeferred(() -> { - Widget panelContent = panel.getWidget(); - int alignRight = relTo.getAbsoluteLeft() + relTo.getOffsetWidth() - panelContent.getOffsetWidth(); - int left = (alignRight - Window.getScrollLeft() < 0 ? relTo.getAbsoluteLeft() - 1 : alignRight + 1); - int alignBottom = relTo.getAbsoluteTop() + relTo.getOffsetHeight() - panelContent.getOffsetHeight(); - int top = (alignBottom - Window.getScrollTop() < 0 ? relTo.getAbsoluteTop() - 1 : alignBottom + 1); - panel.setPopupPosition(left, top); - panel.setVisible(true); - }); - } - - /** - * Determine whether a direct link should be/is rendered for the given {@link SimpleRaceMetadataDTO race} instead of - * a menu popup. - * - * @param data - * the {@link SimpleRaceMetadataDTO race} which should be/is rendered - * @return true if a direct link should be/is rendered, false otherwise - */ - public boolean renderAsDirectLink(T data) { + @Override + public boolean renderAsDirectLink(SimpleRaceMetadataDTO data) { return !data.isRunning() && !data.isFinished(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/multiregatta/TabletAndDesktopMultiRegattaEventView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/multiregatta/TabletAndDesktopMultiRegattaEventView.java index ba2e614c7fd..8068c526841 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/multiregatta/TabletAndDesktopMultiRegattaEventView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/multiregatta/TabletAndDesktopMultiRegattaEventView.java @@ -8,13 +8,16 @@ import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.IsWidget; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.windfinder.SpotDTO; import com.sap.sailing.gwt.common.client.controls.tabbar.TabPanel; import com.sap.sailing.gwt.common.client.controls.tabbar.TabPanelPlaceSelectionEvent; import com.sap.sailing.gwt.common.client.controls.tabbar.TabView; import com.sap.sailing.gwt.home.desktop.partials.eventheader.EventHeader; import com.sap.sailing.gwt.home.desktop.partials.sailorinfo.SailorInfo; import com.sap.sailing.gwt.home.shared.app.ApplicationHistoryMapper; +import com.sap.sailing.gwt.home.shared.partials.windfinder.WindfinderControl; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.common.Util; public class TabletAndDesktopMultiRegattaEventView extends Composite implements EventMultiregattaView { @@ -46,6 +49,10 @@ public class TabletAndDesktopMultiRegattaEventView extends Composite implements if(sailorsInfoURL != null && ! sailorsInfoURL.isEmpty()) { tabPanelUi.addTabExtension(new SailorInfo(sailorsInfoURL)); } + final Iterable spots = currentPresenter.getEventDTO().getAllWindFinderSpotIdsUsedByEvent(); + if (spots != null && !Util.isEmpty(spots)) { + tabPanelUi.addTabExtension(new WindfinderControl(spots, SpotDTO::getCurrentlyMostAppropriateUrl)); + } } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/TabletAndDesktopRegattaEventView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/TabletAndDesktopRegattaEventView.java index 65bdfbdd1ba..2e738b85c8d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/TabletAndDesktopRegattaEventView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/TabletAndDesktopRegattaEventView.java @@ -15,6 +15,7 @@ import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.IsWidget; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.dto.EventType; +import com.sap.sailing.domain.common.windfinder.SpotDTO; import com.sap.sailing.gwt.common.client.SharedResources; import com.sap.sailing.gwt.common.client.controls.tabbar.TabPanel; import com.sap.sailing.gwt.common.client.controls.tabbar.TabPanelPlaceSelectionEvent; @@ -25,9 +26,11 @@ import com.sap.sailing.gwt.home.desktop.places.event.regatta.overviewtab.Regatta import com.sap.sailing.gwt.home.desktop.places.event.regatta.racestab.RegattaRacesTabView; import com.sap.sailing.gwt.home.shared.app.ApplicationHistoryMapper; import com.sap.sailing.gwt.home.shared.app.PlaceNavigation; +import com.sap.sailing.gwt.home.shared.partials.windfinder.WindfinderControl; import com.sap.sailing.gwt.home.shared.places.fakeseries.SeriesDefaultPlace; import com.sap.sailing.gwt.ui.client.FlagImageResolver; import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.common.Util; import com.sap.sse.gwt.client.LinkUtil; public class TabletAndDesktopRegattaEventView extends Composite implements EventRegattaView { @@ -88,6 +91,11 @@ public class TabletAndDesktopRegattaEventView extends Composite implements Event tabPanelUi.addTabExtension(new SailorInfo(sailorsInfoURL)); } } + + final Iterable spots = currentPresenter.getEventDTO().getAllWindFinderSpotIdsUsedByEvent(); + if (spots != null && !Util.isEmpty(spots)) { + tabPanelUi.addTabExtension(new WindfinderControl(spots, SpotDTO::getCurrentlyMostAppropriateUrl)); + } } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html index a931629d8fb..ffa018b3d15 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html @@ -4,6 +4,13 @@

What's New - SAP Sailing Analytics

+
January 2018
+
    +
  • Coordinate display (lat/lng), e.g., in pop-ups for marks and wind sensors + now show the more common DD°MM.MMM' format. +
  • +
+
November 2017
  • The language preference set in the user profile (if any), now also affects the website's language. Formerly, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/simpleinfoblock/SimpleInfoBlock.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/simpleinfoblock/SimpleInfoBlock.java index e231411c309..7f70e6fbfe4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/simpleinfoblock/SimpleInfoBlock.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/simpleinfoblock/SimpleInfoBlock.java @@ -25,21 +25,25 @@ public class SimpleInfoBlock extends Composite { public SimpleInfoBlock() { initWidget(uiBinder.createAndBindUi(this)); + getElement().getStyle().setDisplay(Display.NONE); } public void setDescription(SafeHtml description) { descriptionUi.getStyle().setDisplay(Display.BLOCK); + getElement().getStyle().clearDisplay(); descriptionUi.setInnerSafeHtml(description); } public void setAction(String label, String url) { actionUi.getStyle().setDisplay(Display.BLOCK); + getElement().getStyle().clearDisplay(); actionUi.setInnerText(label); actionUi.setHref(UriUtils.fromString(url)); } public void setAction(String label, PlaceNavigation placeNavigation) { actionUi.getStyle().setDisplay(Display.BLOCK); + getElement().getStyle().clearDisplay(); actionUi.setInnerText(label); placeNavigation.configureAnchorElement(actionUi); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/simpleinfoblock/SimpleInfoBlock.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/simpleinfoblock/SimpleInfoBlock.ui.xml index eb8d58a8434..8d56f614227 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/simpleinfoblock/SimpleInfoBlock.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/simpleinfoblock/SimpleInfoBlock.ui.xml @@ -1,27 +1,25 @@ - - - - .description { - display: none; - font-size: .866666666666667em; - margin-bottom: .769230769230769em; - } - .action { - display: none; - border-radius: .384615384615385em; - width: 100%; - text-align: center; - } - .holder { - box-sizing: border-box; - padding: 0px 10px 0px 10px; - } - - -
    - -
    + + + + .description { + display: none; + font-size: .866666666666667em; + margin-bottom: .384615384615385em; + } + .action { + display: none; + border-radius: .384615384615385em; + width: 100%; + text-align: center; + } + .holder { + box-sizing: border-box; + padding: 0px 10px 10px; + } + + +
    + +
    \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java index cbf4b79f802..0ac77756ff0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java @@ -14,6 +14,7 @@ import com.google.gwt.event.shared.EventBus; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.EventType; +import com.sap.sailing.domain.common.windfinder.SpotDTO; import com.sap.sailing.gwt.home.communication.SailingDispatchSystem; import com.sap.sailing.gwt.home.communication.event.EventReferenceWithStateDTO; import com.sap.sailing.gwt.home.communication.event.EventState; @@ -91,6 +92,13 @@ public abstract class AbstractEventActivity ex } } + protected final void initWindfinderNavigations(EventViewBase view) { + final Iterable spots = eventDTO.getAllWindFinderSpotIdsUsedByEvent(); + if (spots != null && !Util.isEmpty(spots)) { + view.setWindfinderNavigations(spots); + } + } + protected final void initQuickfinder(EventViewBase view, boolean showQuickfinder) { EventViewDTO event = eventDTO; if(showQuickfinder && event.getType() == EventType.MULTI_REGATTA) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.java index b511df1d3bc..9f0d24cda4a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.java @@ -13,6 +13,7 @@ import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.windfinder.SpotDTO; import com.sap.sailing.gwt.home.communication.event.EventReferenceWithStateDTO; import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO; import com.sap.sailing.gwt.home.communication.eventview.RegattaMetadataDTO; @@ -23,6 +24,7 @@ import com.sap.sailing.gwt.home.mobile.partials.sectionHeader.SectionHeaderConte import com.sap.sailing.gwt.home.mobile.partials.simpleinfoblock.SimpleInfoBlock; import com.sap.sailing.gwt.home.mobile.places.QuickfinderPresenter; import com.sap.sailing.gwt.home.shared.app.PlaceNavigation; +import com.sap.sailing.gwt.home.shared.partials.windfinder.WindfinderControl; import com.sap.sailing.gwt.home.shared.refresh.RefreshManager; import com.sap.sailing.gwt.home.shared.refresh.RefreshManagerWithErrorAndBusy; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -38,10 +40,12 @@ public abstract class AbstractEventView

    exten @UiField(provided = true) EventHeader eventHeaderUi; @UiField Quickfinder quickFinderUi; @UiField SimpleInfoBlock simpleInfoUi; - @UiField SimplePanel viewContentUi; + @UiField(provided = true) WindfinderControl windfinderUi; + @UiField SimplePanel viewContentUi;; private AbstractEventViewLayout(EventViewDTO event, String regattaName, PlaceNavigation logoNavigation) { this.eventHeaderUi = new EventHeader(event, regattaName, logoNavigation); + this.windfinderUi = new WindfinderControl(SpotDTO::getCurrentlyMostAppropriateUrl); } } @@ -125,6 +129,11 @@ public abstract class AbstractEventView

    exten layout.simpleInfoUi.setAction(buttonLabel, url); } + @Override + public void setWindfinderNavigations(Iterable spotData) { + layout.windfinderUi.setSpotData(spotData); + } + @Override public void setSeriesNavigation(String buttonLabel, PlaceNavigation placeNavigation) { layout.simpleInfoUi.setAction(buttonLabel, placeNavigation); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.ui.xml index 04b1b458666..2258c94f492 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventView.ui.xml @@ -1,19 +1,26 @@ - - - .headerWrapper { - background-color: #f2f2f2; - padding-bottom: 10px; - } - - -

    - - - -
    - - -
    + xmlns:sp="urn:import:com.sap.sailing.gwt.home.shared.partials" + xmlns:mp="urn:import:com.sap.sailing.gwt.home.mobile.partials"> + + + .headerWrapper { + background-color: #f2f2f2; + } + .windfinder { + display: block; + margin: 0 10px 10px; + border-radius: .384615384615385em; + text-align: center; + } + + +
    + + + + +
    + +
    + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/EventViewBase.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/EventViewBase.java index bbfdfa56783..4d156277716 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/EventViewBase.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/EventViewBase.java @@ -5,6 +5,7 @@ import java.util.Map; import java.util.Set; import com.google.gwt.user.client.ui.IsWidget; +import com.sap.sailing.domain.common.windfinder.SpotDTO; import com.sap.sailing.gwt.home.communication.SailingDispatchSystem; import com.sap.sailing.gwt.home.communication.event.EventReferenceWithStateDTO; import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO; @@ -31,6 +32,8 @@ public interface EventViewBase extends IsWidget { void setSailorInfos(String description, String buttonLabel, String url); + void setWindfinderNavigations(Iterable spotData); + void setSeriesNavigation(String buttonLabel, PlaceNavigation placeNavigation); public interface Presenter extends RegattaLeaderboardNavigationProvider, SeriesLeaderboardNavigationProvider, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/overview/multiregatta/MultiRegattaActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/overview/multiregatta/MultiRegattaActivity.java index 0242247e4c9..e091431a82b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/overview/multiregatta/MultiRegattaActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/overview/multiregatta/MultiRegattaActivity.java @@ -24,6 +24,7 @@ public class MultiRegattaActivity extends AbstractEventActivity final AbstractEventOverview view = new RegattaOverviewImpl(this, flagImageResolver); initSeriesNavigation(view); initQuickfinder(view, true); + initWindfinderNavigations(view); if (!isMultiRegattaEvent()) { + // initSailorInfo(view); initMedia(view); } return view; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/SailingDispatchContextImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/SailingDispatchContextImpl.java index df8be881896..f4babfb52bc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/SailingDispatchContextImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/SailingDispatchContextImpl.java @@ -9,6 +9,7 @@ import javax.servlet.http.HttpServletRequest; import org.apache.shiro.authz.AuthorizationException; import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.domain.windfinder.WindFinderTrackerFactory; import com.sap.sailing.gwt.home.communication.SailingDispatchContext; import com.sap.sailing.gwt.server.HomeServiceUtil; import com.sap.sailing.news.EventNewsService; @@ -31,12 +32,15 @@ public class SailingDispatchContextImpl implements SailingDispatchContext { private final SecurityService securityService; private final UserStore userStore; private final TrackedRaceStatisticsCache trackedRaceStatisticsCache; + private final WindFinderTrackerFactory windFinderTrackerFactory; public SailingDispatchContextImpl(Date currentClientTime, RacingEventService racingEventService, - EventNewsService eventNewsService, SecurityService securityService, UserStore userStore, - TrackedRaceStatisticsCache trackedRaceStatisticsCache, String clientLocaleName, HttpServletRequest request) { + WindFinderTrackerFactory windFinderTrackerFactory, EventNewsService eventNewsService, + SecurityService securityService, UserStore userStore, TrackedRaceStatisticsCache trackedRaceStatisticsCache, + String clientLocaleName, HttpServletRequest request) { this.currentClientTime = currentClientTime; this.racingEventService = racingEventService; + this.windFinderTrackerFactory = windFinderTrackerFactory; this.eventNewsService = eventNewsService; this.securityService = securityService; this.userStore = userStore; @@ -50,6 +54,11 @@ public class SailingDispatchContextImpl implements SailingDispatchContext { return racingEventService; } + @Override + public WindFinderTrackerFactory getWindFinderTrackerFactory() { + return windFinderTrackerFactory; + } + public EventNewsService getEventNewsService() { return eventNewsService; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/servlets/SailingDispatchServlet.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/servlets/SailingDispatchServlet.java index 1d4b7468753..323139e954c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/servlets/SailingDispatchServlet.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/servlets/SailingDispatchServlet.java @@ -3,6 +3,7 @@ package com.sap.sailing.gwt.home.server.servlets; import org.osgi.framework.BundleContext; import org.osgi.util.tracker.ServiceTracker; +import com.sap.sailing.domain.windfinder.WindFinderTrackerFactory; import com.sap.sailing.gwt.home.communication.SailingDispatchContext; import com.sap.sailing.gwt.home.server.SailingDispatchContextImpl; import com.sap.sailing.gwt.ui.server.Activator; @@ -21,6 +22,7 @@ public class SailingDispatchServlet extends AbstractDispatchServlet racingEventServiceTracker; + private final ServiceTracker windFinderTrackerFactory; private final ServiceTracker eventNewsServiceTracker; private final ServiceTracker securityServiceTracker; private final ServiceTracker userStoreTracker; @@ -29,6 +31,7 @@ public class SailingDispatchServlet extends AbstractDispatchServlet> SailingDispatchContext createDispatchContextFor( RequestWrapper request) { return new SailingDispatchContextImpl(request.getCurrentClientTime(), racingEventServiceTracker.getService(), + windFinderTrackerFactory.getService(), eventNewsServiceTracker.getService(), securityServiceTracker.getService(), userStoreTracker.getService(), trackedRaceStatisticsCacheTracker.getService(), request.getClientLocaleName(), getThreadLocalRequest()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/launchpad/AbstractLaunchPadController.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/launchpad/AbstractLaunchPadController.java new file mode 100644 index 00000000000..9496a380162 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/launchpad/AbstractLaunchPadController.java @@ -0,0 +1,51 @@ +package com.sap.sailing.gwt.home.shared.partials.launchpad; + +import java.util.function.BiFunction; +import java.util.function.Function; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.Widget; + +/** + * Abstract {@link LaunchPadController} implementation providing common functionality for a specific type of data. + * + * @param + * the actual data type + */ +public abstract class AbstractLaunchPadController implements LaunchPadController { + + private final PopupPanel panel = new PopupPanel(true, false); + private final BiFunction launchPadFactory; + + /** + * Creates a new {@link AbstractLaunchPadController} using the provided {@link Function factory} to create the + * {@link Widget launch pad}. + * + * @param launchPadFactory + * the {@link Function factory} to create the launch pad + */ + protected AbstractLaunchPadController(BiFunction launchPadFactory) { + this.launchPadFactory = launchPadFactory; + } + + @Override + public final void showLaunchPad(D data, Element relTo) { + relTo.scrollIntoView(); + panel.setWidget(launchPadFactory.apply(data, panel)); + panel.setVisible(false); + panel.show(); + Scheduler.get().scheduleDeferred(() -> { + Widget panelContent = panel.getWidget(); + int alignRight = relTo.getAbsoluteLeft() + relTo.getOffsetWidth() - panelContent.getOffsetWidth(); + int left = (alignRight - Window.getScrollLeft() < 0 ? relTo.getAbsoluteLeft() - 1 : alignRight + 1); + int alignBottom = relTo.getAbsoluteTop() + relTo.getOffsetHeight() - panelContent.getOffsetHeight(); + int top = (alignBottom - Window.getScrollTop() < 0 ? relTo.getAbsoluteTop() - 1 : alignBottom + 1); + panel.setPopupPosition(left, top); + panel.setVisible(true); + }); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/launchpad/LaunchPadController.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/launchpad/LaunchPadController.java new file mode 100644 index 00000000000..5d57d5b1037 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/launchpad/LaunchPadController.java @@ -0,0 +1,41 @@ +package com.sap.sailing.gwt.home.shared.partials.launchpad; + +import com.google.gwt.dom.client.Element; + +/** + * Interface for a launch pad controller class for a specific type of data. + * + * @param + * the actual data type + */ +public interface LaunchPadController { + + /** + * Provides the direct link URL for the given data. + * + * @param data + * the data to get the link URL for + * @return the direct link URL (as {@link String}) + */ + String getDirectLinkUrl(D data); + + /** + * Shows a launch pad for the given data relative to the provided element. + * + * @param data + * the data to create a launch pad for + * @param relTo + * the {@link Element element} to show the launch pad relative to + */ + void showLaunchPad(D data, Element relTo); + + /** + * Determine whether a direct link should be/is rendered for the given data instead of a menu popup. + * + * @param data + * the data which should be/is rendered + * @return true if a direct link should be/is rendered, false otherwise + */ + boolean renderAsDirectLink(D data); + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/regattacompetition/AbstractRegattaCompetitionFleetRace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/regattacompetition/AbstractRegattaCompetitionFleetRace.java index c567a4b3429..c75b9104c1f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/regattacompetition/AbstractRegattaCompetitionFleetRace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/regattacompetition/AbstractRegattaCompetitionFleetRace.java @@ -51,7 +51,7 @@ public abstract class AbstractRegattaCompetitionFleetRace extends Widget impleme return; } - launchPadController.showRaceviewerLaunchPad(race, this.getElement()); + launchPadController.showLaunchPad(race, this.getElement()); } private void setupRaceState(SimpleRaceMetadataDTO race) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/Windfinder.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/Windfinder.gss new file mode 100644 index 00000000000..842e9380598 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/Windfinder.gss @@ -0,0 +1,69 @@ +@def WINFINDER_COLOR_PRIMARY #ffffff; +@def WINFINDER_COLOR_SECONDARY #d0021b; + +.windfindercontrol { + background-color: WINFINDER_COLOR_SECONDARY; + text-transform: uppercase; + text-decoration: none; + vertical-align: bottom; + padding-left: 1em; + padding-right: 0.5em +} +.windfindercontrol_text, .windfindercontrol_icon { + display: inline-block; +} +.windfindercontrol_icon { + margin: -0.5em 0.25em; +} +.windfindercontrol_icon svg { + display: block; + fill: WINFINDER_COLOR_PRIMARY; +} +.windfindercontrol:hover { + cursor: pointer; + background-color: #ba011a; +} +.windfinderlaunchpad_content { + display: block; + bottom: 0; + right: 0; + box-shadow: 0px 0px 0.666666666666667em 0px rgba(0,0,0,0.2); +} +.windfinderlaunchpad_content_item { + display: -webkit-box; + display: -webkit-flex; + display: -moz-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + -moz-box-align: center; + -ms-flex-align: center; + align-items: center; + height: 3em; + padding: 0 1.333333333333333em 0 0.666666666666667em; + background: WINFINDER_COLOR_PRIMARY; + color: WINFINDER_COLOR_SECONDARY; + font-weight: normal; +} +.windfinderlaunchpad_content_item:hover { + background-color: WINFINDER_COLOR_SECONDARY; + color: WINFINDER_COLOR_PRIMARY; + text-decoration: underline; +} +.windfinderlaunchpad_content_item .windfinderlaunchpad_content_item_icon { + margin-right: 1em; +} +.windfinderlaunchpad_content_item .windfinderlaunchpad_content_item_icon svg { + fill: WINFINDER_COLOR_SECONDARY; +} +.windfinderlaunchpad_content_item:hover .windfinderlaunchpad_content_item_icon svg { + fill: WINFINDER_COLOR_PRIMARY; +} +.windfinderlaunchpad_content_item .windfinderlaunchpad_content_item_title { + white-space: nowrap; +} +.windfindercontrol_icon, .windfinderlaunchpad_content_item .windfinderlaunchpad_content_item_icon { + width: 1.666666666666667em; + height: 1.666666666666667em; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderControl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderControl.java new file mode 100644 index 00000000000..c6fa8c531bd --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderControl.java @@ -0,0 +1,64 @@ +package com.sap.sailing.gwt.home.shared.partials.windfinder; + +import java.util.function.BiFunction; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Element; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.windfinder.SpotDTO; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.common.TimePoint; + +public class WindfinderControl extends Widget { + + interface WindfinderLaunchPadItemUiBinder extends UiBinder { + } + + private static WindfinderLaunchPadItemUiBinder uiBinder = GWT.create(WindfinderLaunchPadItemUiBinder.class); + + @UiField WindfinderResources local_res; + @UiField DivElement labelUi, iconUi; + private final WindfinderLaunchPadController launchPadController; + private Iterable spotData; + + public WindfinderControl(Iterable spotData, BiFunction windfinderUrlFactory) { + this(windfinderUrlFactory); + this.setSpotData(spotData); + } + + public WindfinderControl(BiFunction windfinderUrlFactory) { + WindfinderResources.INSTANCE.css().ensureInjected(); + this.launchPadController = new WindfinderLaunchPadController(windfinderUrlFactory); + setElement(uiBinder.createAndBindUi(this)); + this.labelUi.setInnerHTML(StringMessages.INSTANCE.windFinderWeatherData()); + this.iconUi.setInnerHTML(local_res.windfinderLogo().getText()); + sinkEvents(Event.ONCLICK); + this.setVisible(false); + } + + public void setSpotData(Iterable spotData) { + this.spotData = spotData; + this.setVisible(true); + } + + @Override + public void onBrowserEvent(Event event) { + // Only handle click events + if (event.getTypeInt() != Event.ONCLICK) { + return; + } + + // If rendered as direct link button, open link in new tab directly instead of showing the menu popup + if (launchPadController.renderAsDirectLink(spotData)) { + Window.open(launchPadController.getDirectLinkUrl(spotData), "_blank", ""); + return; + } + + launchPadController.showLaunchPad(spotData, this.getElement()); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderControl.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderControl.ui.xml new file mode 100644 index 00000000000..01fa7093dd4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderControl.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/shared/partials/windfinder/WindfinderLaunchPad.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderLaunchPad.java new file mode 100644 index 00000000000..dc0a6dfadca --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderLaunchPad.java @@ -0,0 +1,43 @@ +package com.sap.sailing.gwt.home.shared.partials.windfinder; + +import java.util.function.BiFunction; + +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.windfinder.SpotDTO; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +class WindfinderLaunchPad extends Widget { + + private final DivElement container = Document.get().createDivElement(); + private final BiFunction windfinderUrlFactory; + private final PopupPanel parent; + + WindfinderLaunchPad(Iterable windfinderSpots, + BiFunction windfinderUrlFactory, PopupPanel parent) { + this.container.addClassName(WindfinderResources.INSTANCE.css().windfinderlaunchpad_content()); + this.windfinderUrlFactory = windfinderUrlFactory; + this.parent = parent; + windfinderSpots.forEach(this::addItem); + setElement(container); + sinkEvents(Event.ONCLICK); + } + + @Override + public void onBrowserEvent(final Event event) { + if (event.getTypeInt() == Event.ONCLICK) { + parent.hide(); + } + super.onBrowserEvent(event); + } + + private void addItem(SpotDTO windfinderSpot) { + final String url = windfinderUrlFactory.apply(windfinderSpot, MillisecondsTimePoint.now()); + this.container.appendChild(new WindfinderLaunchPadItem(windfinderSpot.getName(), url).getElement()); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderLaunchPadController.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderLaunchPadController.java new file mode 100644 index 00000000000..8ea12343f6e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderLaunchPadController.java @@ -0,0 +1,38 @@ +package com.sap.sailing.gwt.home.shared.partials.windfinder; + +import java.util.function.BiFunction; + +import com.sap.sailing.domain.common.windfinder.SpotDTO; +import com.sap.sailing.gwt.home.shared.partials.launchpad.AbstractLaunchPadController; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +class WindfinderLaunchPadController extends AbstractLaunchPadController> { + + private final BiFunction windfinderUrlFactory; + + /** + * Creates a new {@link WindfinderLaunchPadController} instance with the given URL factory. + * + * @param raceviewerUrlFactory + * {@link BiFunction Factory} to get the windfinder URL for given {@link SpotDTO spot} and + * {@link TimePoint time point} information + */ + WindfinderLaunchPadController(final BiFunction windfinderUrlFactory) { + super((data, panel) -> new WindfinderLaunchPad(data, windfinderUrlFactory, panel)); + this.windfinderUrlFactory = windfinderUrlFactory; + } + + @Override + public String getDirectLinkUrl(Iterable data) { + final SpotDTO spot = data.iterator().next(); + return windfinderUrlFactory.apply(spot, MillisecondsTimePoint.now()); + } + + @Override + public boolean renderAsDirectLink(Iterable data) { + return Util.size(data) == 1; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderLaunchPadItem.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderLaunchPadItem.java new file mode 100644 index 00000000000..4ca39c9601e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderLaunchPadItem.java @@ -0,0 +1,28 @@ +package com.sap.sailing.gwt.home.shared.partials.windfinder; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.AnchorElement; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.UIObject; + +class WindfinderLaunchPadItem extends UIObject { + + private static WindfinderLaunchPadItemUiBinder uiBinder = GWT.create(WindfinderLaunchPadItemUiBinder.class); + + interface WindfinderLaunchPadItemUiBinder extends UiBinder { + } + + @UiField WindfinderResources local_res; + @UiField DivElement itemLabelUi, itemIconUi; + private final AnchorElement anchorUi; + + WindfinderLaunchPadItem(String label, String raceViewerUrl) { + setElement(anchorUi = uiBinder.createAndBindUi(this)); + anchorUi.setHref(raceViewerUrl); + itemLabelUi.setInnerText(label); + itemIconUi.setInnerHTML(local_res.windfinderLogo().getText()); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderLaunchPadItem.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderLaunchPadItem.ui.xml new file mode 100644 index 00000000000..6e8554eafda --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderLaunchPadItem.ui.xml @@ -0,0 +1,8 @@ + + + + +
    +
    +
    +
    \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderResources.java new file mode 100644 index 00000000000..7c705ff1340 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/WindfinderResources.java @@ -0,0 +1,33 @@ +package com.sap.sailing.gwt.home.shared.partials.windfinder; + +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.TextResource; + +public interface WindfinderResources extends ClientBundle { + public static final WindfinderResources INSTANCE = GWT.create(WindfinderResources.class); + + @Source("Windfinder.gss") + LocalCss css(); + + @Source("windfinder-logo.svg") + TextResource windfinderLogo(); + + public interface LocalCss extends CssResource { + + String windfindercontrol(); + + String windfindercontrol_icon(); + + String windfindercontrol_text(); + + String windfinderlaunchpad_content(); + + String windfinderlaunchpad_content_item(); + + String windfinderlaunchpad_content_item_icon(); + + String windfinderlaunchpad_content_item_title(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/windfinder-logo.svg b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/windfinder-logo.svg new file mode 100644 index 00000000000..7234e132c97 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/windfinder/windfinder-logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java index 6bc6940d2ea..c35803d2137 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorTableWrapper.java @@ -8,7 +8,9 @@ import java.util.List; import com.google.gwt.cell.client.FieldUpdater; import com.google.gwt.cell.client.SafeHtmlCell; import com.google.gwt.core.client.Callback; +import com.google.gwt.core.client.GWT; import com.google.gwt.resources.client.ImageResource; +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.Column; @@ -16,7 +18,6 @@ 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.ImageResourceRenderer; import com.google.gwt.user.client.ui.Label; import com.google.gwt.view.client.CellPreviewEvent; import com.sap.sailing.domain.common.dto.CompetitorDTO; @@ -50,6 +51,15 @@ import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; public class CompetitorTableWrapper> extends TableWrapper { private final LabeledAbstractFilterablePanel filterField; + private static final Template TEMPLATE = GWT.create(Template.class); + + interface Template extends SafeHtmlTemplates { + @SafeHtmlTemplates.Template("
    ") + SafeHtml image(String imageUri,int width, int height); + @SafeHtmlTemplates.Template("
    ") + SafeHtml imageWithTitle(String imageUri,int width, int height,String title); + } + public CompetitorTableWrapper(SailingServiceAsync sailingService, StringMessages stringMessages, ErrorReporter errorReporter, boolean multiSelection, boolean enablePager) { super(sailingService, stringMessages, errorReporter, multiSelection, enablePager, @@ -99,11 +109,10 @@ public class CompetitorTableWrapper"); + sb.append(TEMPLATE.imageWithTitle(flagImageURL, 18 ,12,competitor.getName())); sb.appendHtmlConstant(" "); } else { final ImageResource flagImageResource; @@ -113,7 +122,7 @@ public class CompetitorTableWrapper availableLeaderboardGroupsByName; protected ImagesListComposite imagesListComposite; protected VideosListComposite videosListComposite; @@ -160,6 +162,9 @@ public abstract class EventDialog extends DataEntryDialogWithDateTimeBox emptyList(), + new GenericStringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ + Collections.emptyList(), stringMessages.enterIdOfWindfinderReviewedSpotCollection(), 80)); } @Override @@ -189,6 +194,7 @@ public abstract class EventDialog extends DataEntryDialogWithDateTimeBox() { + updatedEvent.getVideos(), updatedEvent.getWindFinderReviewedSpotsCollectionIds(), 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(); + final String[] namesOfCourseAreasToAdd = new String[courseAreasToAdd.size()]; + int i=0; + for (CourseAreaDTO courseAreaToAdd : courseAreasToAdd) { + namesOfCourseAreasToAdd[i++] = courseAreaToAdd.getName(); + } + sailingService.createCourseAreas(oldEvent.id, namesOfCourseAreasToAdd, new AsyncCallback() { @Override public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to update sailing event" + oldEvent.getName() + ": " + t.getMessage()); + errorReporter.reportError("Error trying to add course area to sailing event " + oldEvent.getName() + + ": " + t.getMessage()); } @Override - public void onSuccess(EventDTO result) { - fillEvents(); - final String[] namesOfCourseAreasToAdd = new String[courseAreasToAdd.size()]; - int i=0; - for (CourseAreaDTO courseAreaToAdd : courseAreasToAdd) { - namesOfCourseAreasToAdd[i++] = courseAreaToAdd.getName(); + public void onSuccess(Void result) { + final UUID[] idsOfCourseAreasToRemove = new UUID[courseAreasToRemove.size()]; + int j=0; + for (CourseAreaDTO courseAreaToRemove : courseAreasToRemove) { + idsOfCourseAreasToRemove[j++] = courseAreaToRemove.id; } - sailingService.createCourseAreas(oldEvent.id, namesOfCourseAreasToAdd, new AsyncCallback() { + sailingService.removeCourseAreas(oldEvent.id, idsOfCourseAreasToRemove, new AsyncCallback() { @Override public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to add course area to sailing event " + oldEvent.getName() + errorReporter.reportError("Error trying to remove course area from sailing event " + oldEvent.getName() + ": " + t.getMessage()); } @Override public void onSuccess(Void result) { - final UUID[] idsOfCourseAreasToRemove = new UUID[courseAreasToRemove.size()]; - int j=0; - for (CourseAreaDTO courseAreaToRemove : courseAreasToRemove) { - idsOfCourseAreasToRemove[j++] = courseAreaToRemove.id; - } - sailingService.removeCourseAreas(oldEvent.id, idsOfCourseAreasToRemove, new AsyncCallback() { - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to remove course area from 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) { - } - - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error trying to rename sailing event " + oldEvent.getName() + ": " + t.getMessage()); - } - }); + fillEvents(); + if (!oldEvent.getName().equals(updatedEvent.getName())) { + sailingService.renameEvent(oldEvent.id, updatedEvent.getName(), new AsyncCallback() { + @Override + public void onSuccess(Void result) { } - } - }); + + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error trying to rename sailing event " + oldEvent.getName() + ": " + t.getMessage()); + } + }); + } } }); } }); + } + }); } private Pair, List> getCourseAreasToAdd(final EventDTO oldEvent, final EventDTO updatedEvent) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportResultDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportResultDialog.java index 06ca9877c71..62ff2ffe953 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportResultDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionAllInOneImportResultDialog.java @@ -1,55 +1,15 @@ package com.sap.sailing.gwt.ui.adminconsole; -import java.util.Collections; import java.util.UUID; -import com.google.gwt.user.client.ui.Anchor; -import com.google.gwt.user.client.ui.VerticalPanel; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.settings.client.EntryPointWithSettingsLinkFactory; -import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory; import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sse.gwt.client.dialog.DataEntryDialog; +import com.sap.sailing.gwt.ui.client.shared.race.TrackedRaceCreationResultDialog; -public class ExpeditionAllInOneImportResultDialog extends DataEntryDialog { +public class ExpeditionAllInOneImportResultDialog extends TrackedRaceCreationResultDialog { - private final VerticalPanel verticalPanel; - - public ExpeditionAllInOneImportResultDialog(UUID eventId, String regattaName, String raceName, - String leaderboardName, String leaderboardGroupName) { - super(StringMessages.INSTANCE.importFinished(), StringMessages.INSTANCE.importFinishedMessage(), - StringMessages.INSTANCE.ok(), StringMessages.INSTANCE.cancel(), /* validator */ null, - new DialogCallback() { - @Override - public void ok(Void editedObject) { - } - - @Override - public void cancel() { - } - }); - - verticalPanel = new VerticalPanel(); - verticalPanel.setSpacing(20); - Anchor raceboardAnchor = new Anchor(StringMessages.INSTANCE.importFinishedGotoRaceboard(), - EntryPointWithSettingsLinkFactory.createRaceBoardLinkWithDefaultSettings(eventId, leaderboardName, - leaderboardGroupName, regattaName, raceName)); - raceboardAnchor.setTarget("_blank"); - verticalPanel.add(raceboardAnchor); - Anchor eventAnchor = new Anchor(StringMessages.INSTANCE.importFinishedGotoEvent(), - EntryPointLinkFactory.createEventPlaceLink(eventId.toString(), Collections.emptyMap())); - eventAnchor.setTarget("_blank"); - verticalPanel.add(eventAnchor); - - } - - @Override - protected Widget getAdditionalWidget() { - return verticalPanel; - } - - @Override - protected Void getResult() { - return null; - } + public ExpeditionAllInOneImportResultDialog(UUID eventId, String regattaName, String raceName, + String leaderboardName, String leaderboardGroupName) { + super(StringMessages.INSTANCE.importFinished(), StringMessages.INSTANCE.importFinishedMessage(), eventId, + regattaName, raceName, leaderboardName, leaderboardGroupName); + } } \ 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 58dfb2f4025..73db3adfd40 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 @@ -177,20 +177,27 @@ public class IgtimiAccountsPanel extends FlowPanel { @Override public void onSuccess(String result) { - final Button addIgtimiUserButton = new Button(stringMessages.addIgtimiUser()+" (OAuth)"); - addIgtimiUserButton.addClickHandler(clickEvent->{ + final Button addIgtimiUserButton = new Button(stringMessages.addIgtimiUser() + " (OAuth)"); + addIgtimiUserButton.addClickHandler(clickEvent -> { Frame frame = new Frame(UriUtils.fromString(result).asString()); - frame.addLoadHandler(loadEvent->refresh(sailingService, errorReporter, stringMessages)); - frame.setPixelSize(520, 770); + frame.addLoadHandler(loadEvent -> refresh(sailingService, errorReporter, stringMessages)); + frame.setPixelSize(520, 770); final CaptionImpl caption = new CaptionImpl(); caption.setText(stringMessages.addIgtimiUser()); - final DialogBox dialogBox = new DialogBox(/* autoHide */ true, /* modal */ true); + final DialogBox dialogBox = new DialogBox(/* autoHide */ false, /* modal */ true); + Button closeButton = new Button(stringMessages.close()); + closeButton.addClickHandler(new ClickHandler() { + public void onClick(ClickEvent event) { + dialogBox.hide(); + } + }); dialogBox.setText(stringMessages.addIgtimiUser()); dialogBox.setGlassEnabled(true); final VerticalPanel panel = new VerticalPanel(); dialogBox.setWidget(panel); panel.add(frame); - dialogBox.addCloseHandler(event->{ + panel.add(closeButton); + dialogBox.addCloseHandler(event -> { refresh(sailingService, errorReporter, stringMessages); }); dialogBox.center(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageDialog.java index f1faf70e17a..0bd7c3f1d6b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ImageDialog.java @@ -141,7 +141,7 @@ public abstract class ImageDialog extends DataEntryDialog { tagsListEditor = new StringListInlineEditorComposite(Collections. emptyList(), new GenericStringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ - MediaConstants.imageTagSuggestions, "Enter tags for the image", 30)); + MediaConstants.imageTagSuggestions, stringMessages.enterTagsForTheImage(), 30)); tagsListEditor.addValueChangeHandler(new ValueChangeHandler>() { @Override public void onValueChange(ValueChangeEvent> event) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java index 7ac369d07fd..dc42407db41 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TrackedRacesManagementPanel.java @@ -66,12 +66,12 @@ public class TrackedRacesManagementPanel extends AbstractRaceManagementPanel { raceDataGrid = new Grid(6,2); this.selectedRaceContentPanel.add(raceDataGrid); - raceDataGrid.setText(0, 0, "StartTime:"); - raceDataGrid.setText(1, 0, "EndTime:"); - raceDataGrid.setText(2, 0, "Duration:"); - raceDataGrid.setText(3, 0, "Start of tracking:"); - raceDataGrid.setText(4, 0, "End of tracking:"); - raceDataGrid.setText(5, 0, "Delay to live (ms):"); + raceDataGrid.setText(0, 0, stringMessages.startTime()+":"); + raceDataGrid.setText(1, 0, stringMessages.endTime()+":"); + raceDataGrid.setText(2, 0, stringMessages.duration()+":"); + raceDataGrid.setText(3, 0, stringMessages.startOfTracking()+":"); + raceDataGrid.setText(4, 0, stringMessages.endOfTracking()+":"); + raceDataGrid.setText(5, 0, stringMessages.delayForLiveMode()+" (ms):"); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoDialog.java index b89cfc219b6..c74366727cf 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/VideoDialog.java @@ -92,7 +92,7 @@ public abstract class VideoDialog extends DataEntryDialog { thumbnailURLAndUploadComposite = new URLFieldWithFileUpload(stringMessages); tagsListEditor = new StringListInlineEditorComposite(Collections. emptyList(), new GenericStringListInlineEditorComposite.ExpandedUi(stringMessages, IconResources.INSTANCE.removeIcon(), /* suggestValues */ - MediaConstants.videoTagSuggestions, "Enter tags for the video", 50)); + MediaConstants.videoTagSuggestions, stringMessages.enterTagsForTheVideo(), 50)); } @Override 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 36678e03d9d..82d3c5452a0 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 @@ -197,7 +197,7 @@ public class DetailTypeFormatter { case DISTANCE_TO_START_LINE: return stringMessages.distanceToLine(); case BEAT_ANGLE: - return stringMessages.beatAngle(); + return stringMessages.TWA(); case COURSE_OVER_GROUND_TRUE_DEGREES: return stringMessages.courseOverGroundTrueDegrees(); case TOTAL_SCORED_RACE_COUNT: @@ -535,7 +535,7 @@ public class DetailTypeFormatter { case RACE_TIME_TRAVELED_UPWIND: return stringMessages.raceTimeUpwindTooltip(); case BEAT_ANGLE: - return stringMessages.beatAngleTooltip(); + return stringMessages.TWATooltip(); case DISTANCE_TO_START_LINE: return ""; case COURSE_OVER_GROUND_TRUE_DEGREES: 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 95dc2838d3a..655174b97f8 100755 --- 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 @@ -11,6 +11,7 @@ import java.util.UUID; import com.google.gwt.user.client.rpc.RemoteService; import com.sap.sailing.domain.abstractlog.Revokable; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent; +import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.common.CompetitorDescriptor; import com.sap.sailing.domain.common.DataImportProgress; import com.sap.sailing.domain.common.DetailType; @@ -48,6 +49,8 @@ import com.sap.sailing.domain.common.racelog.tracking.DoesNotHaveRegattaLogExcep import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTrackingException; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.common.tracking.impl.PreciseCompactGPSFixMovingImpl.PreciseCompactPosition; +import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; +import com.sap.sailing.domain.common.windfinder.SpotDTO; import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter; import com.sap.sailing.domain.tracking.TrackedRace; @@ -88,6 +91,7 @@ import com.sap.sailing.gwt.ui.shared.RemoteSailingServerReferenceDTO; import com.sap.sailing.gwt.ui.shared.ScoreCorrectionProviderDTO; import com.sap.sailing.gwt.ui.shared.ServerConfigurationDTO; import com.sap.sailing.gwt.ui.shared.SimulatorResultsDTO; +import com.sap.sailing.gwt.ui.shared.SliceRacePreperationDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationDTO; @@ -317,8 +321,9 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS Pair resolveImageDimensions(String imageUrlAsString) throws Exception; EventDTO updateEvent(UUID eventId, String eventName, String eventDescription, Date startDate, Date endDate, - VenueDTO venue, boolean isPublic, Iterable leaderboardGroupIds, String officialWebsiteURL, String baseURLAsString, - Map sailorsInfoWebsiteURLsByLocaleName, Iterable images, Iterable videos) throws Exception; + VenueDTO venue, boolean isPublic, Iterable leaderboardGroupIds, String officialWebsiteURL, + String baseURL, Map sailorsInfoWebsiteURLsByLocaleName, Iterable images, + Iterable videos, Iterable windFinderReviewedSpotCollectionIds) throws Exception; EventDTO createEvent(String eventName, String eventDescription, Date startDate, Date endDate, String venue, boolean isPublic, List courseAreaNames, String officialWebsiteURL, @@ -710,4 +715,23 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS throws NotFoundException, CompetitorRegistrationOnRaceLogDisabledException; List getRaceDisplayNamesFromLeaderboard(String leaderboardName, List raceColumnNames) throws NotFoundException; + + SpotDTO getWindFinderSpot(String spotId) throws Exception; + + /** + * Returns {@code true} if the given race can be sliced. Only Smarthphone tracked races can be sliced. + * In addition the race must be part of a {@link RegattaLeaderboard}. + */ + boolean canSliceRace(RegattaAndRaceIdentifier raceIdentifier); + + /** + * Slices a new race from the race specified by the given {@link RegattaAndRaceIdentifier} using the given time range. + * A new {@link RaceColumn} with the given name is added to the {@link RegattaLeaderboard}. + */ + RegattaAndRaceIdentifier sliceRace(RegattaAndRaceIdentifier raceIdentifier, String newRaceColumnName, TimePoint sliceFrom, TimePoint sliceTo); + + /** + * Returns specific data needed for the slicing UI. + */ + SliceRacePreperationDTO prepareForSlicingOfRace(RegattaAndRaceIdentifier raceIdentifier); } 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 b3cb66c06b5..4041f6ffddb 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.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -40,6 +41,7 @@ import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.common.tracking.impl.PreciseCompactGPSFixMovingImpl.PreciseCompactPosition; +import com.sap.sailing.domain.common.windfinder.SpotDTO; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.expeditionconnector.ExpeditionDeviceConfiguration; import com.sap.sailing.gwt.ui.adminconsole.RaceLogSetTrackingTimesDTO; @@ -79,6 +81,7 @@ import com.sap.sailing.gwt.ui.shared.SailingServiceConstants; import com.sap.sailing.gwt.ui.shared.ScoreCorrectionProviderDTO; import com.sap.sailing.gwt.ui.shared.ServerConfigurationDTO; import com.sap.sailing.gwt.ui.shared.SimulatorResultsDTO; +import com.sap.sailing.gwt.ui.shared.SliceRacePreperationDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingConfigurationDTO; @@ -429,7 +432,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan void updateEvent(UUID eventId, String eventName, String eventDescription, Date startDate, Date endDate, VenueDTO venue, boolean isPublic, Iterable leaderboardGroupIds, String officialWebsiteURL, String baseURL, Map sailorsInfoWebsiteURLsByLocaleName, Iterable images, - Iterable videos, AsyncCallback callback); + Iterable videos, Iterable windFinderReviewedSpotCollectionIds, AsyncCallback callback); void resolveImageDimensions(String imageUrlAsString, AsyncCallback> callback); @@ -927,5 +930,22 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan void fillRaceLogsFromPairingListTemplate(final String leaderboardName, final int flightMultiplier, final Iterable selectedFlightNames,PairingListDTO pairingListDTO, AsyncCallback callback); - void getRaceDisplayNamesFromLeaderboard(final String leaderboardName,List raceColumnNames, AsyncCallback> callback); + void getRaceDisplayNamesFromLeaderboard(final String leaderboardName, List raceColumnNames, AsyncCallback> callback); + + void canSliceRace(RegattaAndRaceIdentifier raceIdentifier, AsyncCallback callback); + + void sliceRace(RegattaAndRaceIdentifier raceIdentifier, String newRaceColumnName, TimePoint sliceFrom, TimePoint sliceTo, + AsyncCallback callback); + + void prepareForSlicingOfRace(RegattaAndRaceIdentifier raceIdentifier, + AsyncCallback callback); + + /** + * For a given WindFinder spot ID provides a {@link URL} that a web UI can use to link + * to the WindFinder web site with the content most appropriate given the time point. + * This could be the report page if the time is about now; the forecast page if the time point + * is up to ten days in the future, or the statistics page if the time point is out of any + * of the scopes above. + */ + void getWindFinderSpot(String spotId, 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 596d45b67ee..ee396f3b608 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 @@ -110,6 +110,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String eventName(); String event(); String startTime(); + String endTime(); String startOfTracking(); String endOfTracking(); String regatta(); @@ -1089,8 +1090,8 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String timeToStart(String timeToStart); String timeSinceStart(String timeSince); String distanceToLine(); - String beatAngleTooltip(); - String beatAngle(); + String TWATooltip(); + String TWA(); String showBoatClassChartsLabel(); String showDiagram(); String runAutomaticallyTooltip(); @@ -2051,4 +2052,19 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String unexpectedErrorDuringFileImport(); String couldNotDownload(String url); String roles(); + String sliceRace(); + String slicingARaceWasSuccessful(); + String enterNameForSlicedRace(); + String raceNameIsRequired(); + String raceNameIsAlreadyUsed(); + String errorWhilePreparingToSliceARace(); + String errorWhileSlicingARace(); + String windFinderWindSourceTypeName(); + String windFinderWindSourceTypeTooltip(); + String windFinder(); + String enterTagsForTheVideo(); + String enterIdOfWindfinderReviewedSpotCollection(); + String enterTagsForTheImage(); + String unableToResolveWindFinderSpotId(String id, String message); + String windFinderWeatherData(); } 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 9f9fdf110a2..0c1ca062823 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 @@ -108,6 +108,7 @@ regattaName=Regatta name eventName=Event name event=Event startTime=Start time +endTime=End time startOfTracking=Start of tracking endOfTracking=End of tracking regatta=Regatta @@ -1062,8 +1063,8 @@ resultFromIgtimiWindImport=The following number of fixes were imported for the l timeToStart={0} to start timeSinceStart={0} since start distanceToLine=Distance to start line -beatAngleTooltip=The angle between the competitors direction and the wind -beatAngle=Beat Angle +TWATooltip=The angle between the competitors direction and the wind +TWA=True Wind Angle showBoatClassChartsLabel=You can also view the overall diagram for the available boat classes. showDiagram=Show Diagram runAutomaticallyTooltip=Run the query automatically, after you changed e.g. the statistic or the grouping. @@ -2040,3 +2041,18 @@ chooseAName=Choose a name unexpectedErrorDuringFileImport=An unexpected error occurred during file upload. couldNotDownload=Could not download file: {0} roles=Roles +sliceRace=Slice race +slicingARaceWasSuccessful=Slicing a race was successful. +enterNameForSlicedRace=Please enter a name for the race to be sliced +raceNameIsRequired=A valid name must be given for the sliced race +raceNameIsAlreadyUsed=The given name is already used +errorWhilePreparingToSliceARace=Error while preparing to slice a race +errorWhileSlicingARace=Error while slicing a race +windFinderWindSourceTypeName=WindFinder +windFinderWindSourceTypeTooltip=Measured wind from one or more spots of www.windfinder.com +windFinder=WindFinder +enterTagsForTheVideo=Enter tags for the video +enterIdOfWindfinderReviewedSpotCollection=Enter ID of a reviewed WindFinder spot collection, e.g., "schilksee" +enterTagsForTheImage=Enter tags for the image +unableToResolveWindFinderSpotId=Unable to resolve WindFinder spot with ID {0}: {1} +windFinderWeatherData=Weather Data \ 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_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties old mode 100644 new mode 100755 index eace14c4ad7..194488b022f --- 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 @@ -109,6 +109,7 @@ regattaName=Name der Regatta eventName=Event-Name event=Veranstaltung startTime=Startzeit +endTime=Schlusszeit startOfTracking=Start des Trackings endOfTracking=Ende des Trackings regatta=Regatta @@ -1051,8 +1052,8 @@ resultFromIgtimiWindImport=Die folgenden Anzahlen von Windmessungen wurden für timeToStart={0} bis zum Start timeSinceStart={0} seit dem Start distanceToLine=Abstand zur Startlinie -beatAngleTooltip=Der Winkel zwischen Windrichtung und Fahrtrichtung des Teilnehmers -beatAngle=Winkel zum Wind +TWATooltip=Der Winkel zwischen wahrer Windrichtung und Fahrtrichtung des Teilnehmers +TWA=Winkel zum wahren Wind showBoatClassChartsLabel=Im folgenden können außerdem auch die existierenden Diagramme für die vorhandenen Bootsklassen angezeigt werden. showDiagram=Diagramm anzeigen runAutomaticallyTooltip=Führe die Anfrage automatisch durch, wenn zum Beispiel die Statistik oder die Gruppierung geändert wurde. @@ -2037,3 +2038,18 @@ chooseAName=Namen auswählen unexpectedErrorDuringFileImport=Ein unerwarteter Fehler ist beim Datei-Import aufgetreten. couldNotDownload=Konnte Datei nicht laden: {0} roles=Rollen +sliceRace=Rennen schneiden +slicingARaceWasSuccessful=Das Schneiden eines Rennens war erfolgreich. +enterNameForSlicedRace=Bitte einen Namen für das neue Rennen eingeben +raceNameIsRequired=Es muss ein gültiger Name für das geschnittene Rennen eingegeben werden +raceNameIsAlreadyUsed=Der Name wird bereits verwendet +errorWhilePreparingToSliceARace=Ein Fehler ist bei den Vorbereitungen für das schneiden eines Rennens aufgetreten +errorWhileSlicingARace=Ein Fehler ist beim Schneiden eines Rennens aufgetreten +windFinderWindSourceTypeName=WindFinder +windFinderWindSourceTypeTooltip=Gemessener Wind von einer oder mehreren Stationen von www.windfinder.com +windFinder=WindFinder +enterTagsForTheVideo=Tags zum Video erfassen +enterIdOfWindfinderReviewedSpotCollection=ID einer geprüften WindFinder Messstellen-Sammlung eingeben, z.B. "schilksee" +enterTagsForTheImage=Tags zum Bild erfassen +unableToResolveWindFinderSpotId=WindFinder-Station mit Kennung {0} wurde nicht gefunden: {1} +windFinderWeatherData=Wetter​daten \ 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_es.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties index 2bf2bdf7e05..af31cee058c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_es.properties @@ -1058,8 +1058,8 @@ resultFromIgtimiWindImport=Se ha importado el número siguiente de correcciones timeToStart={0} hasta la salida timeSinceStart={0} desde la salida distanceToLine=Distancia hasta la línea de salida -beatAngleTooltip=Ángulo entre la dirección de los competidores y el viento -beatAngle=Mejor ángulo +TWATooltip=Ángulo entre la dirección de los competidores y el viento +TWA=Mejor ángulo showBoatClassChartsLabel=También puede visualizar el diagrama global para las clases de embarcación disponibles. showDiagram=Mostrar diagrama runAutomaticallyTooltip=Ejecute la consulta automáticamente después de modificar, por ejemplo, la estadística o la agrupación. @@ -2033,3 +2033,4 @@ importFinishedGotoRaceboard=Abrir Raceboard importFinishedGotoEvent=Abrir evento chooseAName=Seleccione un nombre unexpectedErrorDuringFileImport=Se ha producido un error inesperado durante la carga del fichero. +couldNotDownload=No se ha podido descargar el fichero: {0} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties index 4ea3990fe20..b1c58ee9ebc 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_fr.properties @@ -1058,8 +1058,8 @@ resultFromIgtimiWindImport=Le nombre suivant de points vent ont été importés timeToStart={0} jusqu''au départ timeSinceStart={0} depuis le départ distanceToLine=Distance jusqu''à la ligne de départ -beatAngleTooltip=L''angle entre la direction du concurrent et le vent. -beatAngle=Angle d''attaque +TWATooltip=L''angle entre la direction du concurrent et le vent. +TWA=Angle d''attaque showBoatClassChartsLabel=Vous pouvez également afficher le diagramme global pour les catégories de bateau disponibles. showDiagram=Afficher diagramme runAutomaticallyTooltip=Exécutez la requête automatiquement, par exemple après avoir modifié les statistiques ou le regroupement. @@ -1411,7 +1411,7 @@ calculatedTimeTraveledTooltip=Temps calculé selon le handicap du concurrent \nb raceCalculatedTimeAtEstimatedArrivalAtCompetitorFarthestAheadInSeconds=Temps calculé @ par rapport au plus rapide raceCalculatedTimeAtEstimatedArrivalAtCompetitorFarthestAheadInSecondsTooltip=Temps calculé selon l''arrivée estimée du concurrent qui est actuellement en tête de la course ; \nestimation basée sur la VMG dans la portion de parcours actuelle, équivaut à la performance (calculée) du bateau le plus rapide dans toutes les portions de parcours suivantes. sailorInfo=Informations concurrents -sailorInfoLongText=Résultats officiels, réclamations, météo marine +sailorInfoLongText=Résultats officiels,\nréclamations, météo marine: eventQuickfinder=Quickfinder manifestation regattaQuickfinder=Quickfinder régate resultsQuickfinder=Quickfinder résultats @@ -2033,3 +2033,4 @@ importFinishedGotoRaceboard=Ouvrir tableau des courses importFinishedGotoEvent=Ouvrir manifestation chooseAName=Indiquer un nom unexpectedErrorDuringFileImport=Une erreur inattendue s''est produite lors du chargement du fichier. +couldNotDownload=Impossible de télécharger le fichier : {0} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties index 0ab94d6477d..6a72bcfa1bd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties @@ -1058,8 +1058,8 @@ resultFromIgtimiWindImport=一覧されたレースに対して次の数のフ timeToStart=スタートまで {0} timeSinceStart=スタートから {0} distanceToLine=スタートラインまでの距離 -beatAngleTooltip=競技者の方向と風の間のアングル -beatAngle=ビートアングル +TWATooltip=競技者の方向と風の間のアングル +TWA=ビートアングル showBoatClassChartsLabel=利用可能な艇種の全体図も表示することができます。 showDiagram=ダイアグラム表示 runAutomaticallyTooltip=統計やグルーピングなどを変更した後にクエリを自動的に実行します。 @@ -2033,3 +2033,4 @@ importFinishedGotoRaceboard=レースボードを開く importFinishedGotoEvent=イベントを開く chooseAName=名称を選択 unexpectedErrorDuringFileImport=ファイルのアップロード時に予期しないエラーが発生しました。 +couldNotDownload={0} ファイルをダウンロードできませんでした。 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties index 0f76cd0e86b..c3bbddfefeb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_pt.properties @@ -1058,8 +1058,8 @@ resultFromIgtimiWindImport=O número seguinte de pontos fixos foi importado para timeToStart={0} até partida timeSinceStart={0} desde partida distanceToLine=Distância para linha de partida -beatAngleTooltip=O ângulo entre a direção dos competidores e o vento -beatAngle=Ângulo de cambada +TWATooltip=O ângulo entre a direção dos competidores e o vento +TWA=Ângulo de cambada showBoatClassChartsLabel=Você também pode ver o diagrama geral para as classes de barcos disponíveis. showDiagram=Visualizar diagrama runAutomaticallyTooltip=Execute a consulta automaticamente, após modificar, por exemplo, a estatística ou o agrupamento. @@ -1687,7 +1687,7 @@ raceAnalysis=Análise da corrida startAnalysis=Análise da partida winningLanes=Rotas vencedoras maximumNumberOfDiscards=Número máximo de descartes -externalLinks=LInks externos +externalLinks=Links externos urlForAllLanguages=URL para todos os idiomas urlForLanguage=URL para idioma ''{0}'' notificationRegattaConfigurationUpdatedUsingByMarks=A configuração de regata ativa foi definida, utilizando o designer do percurso "Por marcas". @@ -2033,3 +2033,4 @@ importFinishedGotoRaceboard=Abrir painel de corridas importFinishedGotoEvent=Abrir evento chooseAName=Selecionar um nome unexpectedErrorDuringFileImport=Ocorreu um erro inesperado durante o carregamento do arquivo. +couldNotDownload=Não foi possível baixar o arquivo: {0} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 497b3fec68d..92eee196c3d 100755 --- 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 @@ -1058,8 +1058,8 @@ resultFromIgtimiWindImport=Для перечисленных гонок импо timeToStart={0} до старта timeSinceStart={0} от старта distanceToLine=Дистанция до линии старта -beatAngleTooltip=Угол между направлением участников и ветром -beatAngle=Угол удара +TWATooltip=Угол между направлением участников и ветром +TWA=Угол удара showBoatClassChartsLabel=Также можно просмотреть общую диаграмму для доступных классов лодок. showDiagram=Показать диаграмму runAutomaticallyTooltip=Автоматически выполнять запрос после изменения, например, статистики или группирования. @@ -2033,3 +2033,4 @@ importFinishedGotoRaceboard=Открыть таблицу гонки importFinishedGotoEvent=Открыть событие chooseAName=Выберите имя unexpectedErrorDuringFileImport=При отправке файла возникла непредвиденная ошибка. +couldNotDownload=Не удалось загрузить файл: {0} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties index 4bad8be6a87..6351277833b 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties @@ -1058,8 +1058,8 @@ resultFromIgtimiWindImport=已为所列的比赛轮次导入以下修复: {0} timeToStart={0} - 开始 timeSinceStart={0} - 开始后 distanceToLine=与起航线的距离 -beatAngleTooltip=赛队航行的方向和风的夹角 -beatAngle=迎风航行角度 +TWATooltip=赛队航行的方向和风的夹角 +TWA=迎风航行角度 showBoatClassChartsLabel=还可以查看可用船只级别的总直直方图。 showDiagram=显示直方图 runAutomaticallyTooltip=在更改统计或分组后,自动运行查询。 @@ -2033,3 +2033,4 @@ importFinishedGotoRaceboard=打开比赛板 importFinishedGotoEvent=打开活动 chooseAName=选择名称 unexpectedErrorDuringFileImport=文件上传期间发生意外错误。 +couldNotDownload=无法下载文件:{0} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/WindSourceTypeFormatter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/WindSourceTypeFormatter.java index dc8364c51b8..4a19e806a3d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/WindSourceTypeFormatter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/WindSourceTypeFormatter.java @@ -20,6 +20,8 @@ public class WindSourceTypeFormatter { return stringMessages.webWindSourceTypeName(); case RACECOMMITTEE: return stringMessages.raceCommitteeWindSourceTypeName(); + case WINDFINDER: + return stringMessages.windFinderWindSourceTypeName(); } return null; } @@ -40,6 +42,8 @@ public class WindSourceTypeFormatter { return stringMessages.webWindSourceTypeTooltip(); case RACECOMMITTEE: return stringMessages.raceCommitteeWindSourceTypeTooltip(); + case WINDFINDER: + return stringMessages.windFinderWindSourceTypeTooltip(); } return null; } 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 2cd05276c5e..4cfb01c7faf 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 @@ -111,6 +111,7 @@ public abstract class AbstractCompetitorRaceChart extends A private final SimpleBusyIndicator busyIndicator; private final Button settingsButton; + private final FlowPanel toolbar = new FlowPanel(); protected AbstractRaceChart(Component parent, ComponentContext context, SailingServiceAsync sailingService, RegattaAndRaceIdentifier selectedRaceIdentifier, Timer timer, @@ -110,10 +113,11 @@ public abstract class AbstractRaceChart extends A busyIndicator = new SimpleBusyIndicator(/* busy */ true, 2.0f); busyIndicator.setPanelStyleClass(chartsCss.busyIndicatorStyle()); busyIndicator.setImageStyleClass(chartsCss.busyIndicatorImageStyle()); + toolbar.addStyleName(chartsCss.toolbar()); + add(toolbar); settingsButton = createSettingsButton(); - settingsButton.setStyleName(chartsCss.settingsButtonStyle()); - settingsButton.addStyleName(chartsCss.settingsButtonBackgroundImage()); - add(settingsButton); + settingsButton.setStyleName(chartsCss.settingsButtonBackgroundImage()); + addToolbarButton(settingsButton); initWidget(rootPanel); getElement().getStyle().setMarginRight(12, Unit.PX); getElement().getStyle().setMarginLeft(12, Unit.PX); @@ -125,6 +129,10 @@ public abstract class AbstractRaceChart extends A */ protected abstract Button createSettingsButton(); + public void addToolbarButton(Button button) { + toolbar.insert(button, 0); + } + /** * Subclasses need to provide a settings button which will be displayed at a useful position in the layout of this * complex panel, e.g., in the top right corner. @@ -145,7 +153,7 @@ public abstract class AbstractRaceChart extends A Widget child = i.next(); if (child == widget) { foundWidget = true; - } else if (child != settingsButton) { + } else if (child != settingsButton && child != toolbar) { i.remove(); } } @@ -178,8 +186,13 @@ public abstract class AbstractRaceChart extends A } //Set a minute as max time zoom just as for chart if (xAxisMax - xAxisMin > MINUTE_IN_MILLIS) { - timeRangeWithZoomProvider.setTimeZoom(new Date(xAxisMin), new Date(xAxisMax), this); + Date rangeStart = new Date(xAxisMin); + Date rangeEnd = new Date(xAxisMax); + timeRangeWithZoomProvider.setTimeZoom(rangeStart, rangeEnd, this); + fireEvent(new ChartZoomChangedEvent(rangeStart, rangeEnd)); + } else { + fireEvent(new ChartZoomResetEvent()); return false; } } catch (Exception e) { @@ -192,12 +205,13 @@ public abstract class AbstractRaceChart extends A // redraw is triggered by the call to onTimeZoomReset() and therefore not necessary again here // after the selection change event, another click event is sent with the mouse position on the "Reset Zoom" button; ignore that ignoreNextClickEvent = true; + fireEvent(new ChartZoomResetEvent()); } }); } return true; } - + protected boolean onClick(ChartClickEvent chartClickEvent) { if (ignoreNextClickEvent) { ignoreNextClickEvent = false; @@ -245,12 +259,15 @@ public abstract class AbstractRaceChart extends A protected void resetMinMaxAndExtremesInterval(boolean redraw) { changeMinMaxAndExtremesInterval(timeRangeWithZoomProvider.getFromTime(), timeRangeWithZoomProvider.getToTime(), redraw); + fireEvent(new ChartZoomResetEvent()); + chart.hideResetZoom(); } @Override public void onTimeZoomChanged(Date zoomStartTimepoint, Date zoomEndTimepoint) { changeMinMaxAndExtremesInterval(zoomStartTimepoint, zoomEndTimepoint, true); chart.showResetZoom(); // Patched method + fireEvent(new ChartZoomChangedEvent(zoomStartTimepoint, zoomEndTimepoint)); } @Override @@ -293,4 +310,12 @@ public abstract class AbstractRaceChart extends A return timestamp == null || (timer.getPlayState() != PlayStates.Playing && timer.getPlayMode() != PlayModes.Live); } + + public HandlerRegistration addChartZoomChangedHandler(ChartZoomChangedEvent.Handler handler) { + return addHandler(handler, ChartZoomChangedEvent.TYPE); + } + + public HandlerRegistration addChartZoomResetHandler(ChartZoomResetEvent.Handler handler) { + return addHandler(handler, ChartZoomResetEvent.TYPE); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartCssResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartCssResources.java index 347d997be35..6b1c9c4b8d8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartCssResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartCssResources.java @@ -19,10 +19,10 @@ public interface ChartCssResources extends ClientBundle { String busyIndicatorImageStyle(); - String settingsButtonStyle(); - String settingsButtonBackgroundImage(); String chartStyle(); + + String toolbar(); } } \ 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/charts/ChartZoomChangedEvent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartZoomChangedEvent.java new file mode 100644 index 00000000000..e3a8cbe9885 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartZoomChangedEvent.java @@ -0,0 +1,41 @@ +package com.sap.sailing.gwt.ui.client.shared.charts; + +import java.util.Date; + +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.event.shared.GwtEvent; + +/** + * Event to be fired by {@link AbstractRaceChart} when a user zooms into the chart. + */ +public class ChartZoomChangedEvent extends GwtEvent { + public static final Type TYPE = new Type<>(); + private final Date rangeStart; + private final Date rangeEnd; + + public interface Handler extends EventHandler { + void handleZoomChanged(ChartZoomChangedEvent cze); + } + + public ChartZoomChangedEvent(Date rangeStart, Date rangeEnd) { + this.rangeStart = rangeStart; + this.rangeEnd = rangeEnd; + } + + public Date getRangeStart() { + return rangeStart; + } + + public Date getRangeEnd() { + return rangeEnd; + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + protected void dispatch(Handler handler) { + handler.handleZoomChanged(this); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartZoomResetEvent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartZoomResetEvent.java new file mode 100644 index 00000000000..2d610be33bd --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/ChartZoomResetEvent.java @@ -0,0 +1,24 @@ +package com.sap.sailing.gwt.ui.client.shared.charts; + +import com.google.gwt.event.shared.EventHandler; +import com.google.gwt.event.shared.GwtEvent; + +/** + * Event to be fired by {@link AbstractRaceChart} when a user resets the zoom to show the whole time range again. + */ +public class ChartZoomResetEvent extends GwtEvent { + public static final Type TYPE = new Type<>(); + + public interface Handler extends EventHandler { + void handleZoomReset(ChartZoomResetEvent czre); + } + + @Override + public Type getAssociatedType() { + return TYPE; + } + + protected void dispatch(Handler handler) { + handler.handleZoomReset(this); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/Charts.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/Charts.css index 94ae74e7154..44a086389e8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/Charts.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/Charts.css @@ -1,40 +1,45 @@ .busyIndicatorStyle { - display: table-cell; - vertical-align: middle; - text-align: center; + display: table-cell; + vertical-align: middle; + text-align: center; } .busyIndicatorImageStyle { - -} - -.settingsButtonStyle { - position: absolute; - right: 0px; - bottom: 10px; - z-index: 1; - margin-top: 14px; - margin-right: 4px; - border: none; -} - -.settingsButtonStyle:focus { - outline: 0 !important; -} - -.settingsButtonStyle:hover { - border: 1px solid #ececec; + } .chartStyle { - position: absolute; - left: 0px; - top: 0px; + position: absolute; + left: 0px; + top: 0px; +} + +.toolbar { + position: absolute; + right: 4px; + bottom: 10px; + + text-align: right; + z-index: 1; +} +.toolbar>button { + margin-top: 8px; + width: 2em; + height: 2em; + background-color: transparent; + background-size: contain; + border: none; + display: block; +} + +.toolbar>button:focus { + outline: 0 !important; +} + +.toolbar>button:hover { + border: 1px solid #ececec; } @sprite .settingsButtonBackgroundImage { - gwt-image: "settingsButton"; - width: 2em; - height: 2em; - background-size: contain; -} \ No newline at end of file + gwt-image: "settingsButton"; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChart.java old mode 100755 new mode 100644 index 96bda990cf7..c925fb708d2 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/MultiCompetitorRaceChart.java @@ -55,7 +55,7 @@ public class MultiCompetitorRaceChart extends AbstractCompetitorRaceChart implements R private final ColorMapImpl colorMap; + private WindSource preselectFilter; + /** * @param selectedRaceIdentifier * if null, this chart won't update its contents automatically upon race selection change; @@ -225,53 +227,89 @@ public class WindChart extends AbstractRaceChart implements R private void updateVisibleSeries() { List currentSeries = Arrays.asList(chart.getSeries()); Set visibleSeries = new HashSet(currentSeries); - - if (settings.isShowWindDirectionsSeries()) { - for (Map.Entry e : windSourceDirectionSeries.entrySet()) { - Series series = e.getValue(); - if (settings.getWindDirectionSourcesToDisplay().contains(e.getKey().getType())) { - if (!visibleSeries.contains(series)) { - chart.addSeries(series, false, false); + + if (preselectFilter != null) { + forceSeriesSelection(visibleSeries, windSourceDirectionSeries); + forceSeriesSelection(visibleSeries, windSourceSpeedSeries); + } else { + if (settings.isShowWindDirectionsSeries()) { + for (Map.Entry e : windSourceDirectionSeries.entrySet()) { + Series series = e.getValue(); + if (settings.getWindDirectionSourcesToDisplay().contains(e.getKey().getType())) { + if (!visibleSeries.contains(series)) { + chart.addSeries(series, false, false); + } series.select(true); // ensures that the checkbox will be ticked + series.setVisible(true); //and that it is actually being rendered + } else { + if (visibleSeries.contains(series)) { + chart.removeSeries(series, false); + } } - } else { + } + } else { + for (Map.Entry e : windSourceDirectionSeries.entrySet()) { + Series series = e.getValue(); if (visibleSeries.contains(series)) { chart.removeSeries(series, false); } } } - } else { - for (Map.Entry e : windSourceDirectionSeries.entrySet()) { - Series series = e.getValue(); - if (visibleSeries.contains(series)) { - chart.removeSeries(series, false); + + if (settings.isShowWindSpeedSeries()) { + for (Map.Entry e : windSourceSpeedSeries.entrySet()) { + Series series = e.getValue(); + if (settings.getWindSpeedSourcesToDisplay().contains(e.getKey().getType())) { + if (!visibleSeries.contains(series)) { + chart.addSeries(series, false, false); + } + series.select(true); // ensures that the checkbox will be ticked + series.setVisible(true); //and that it is actually being rendered + } else { + if (visibleSeries.contains(series)) { + chart.removeSeries(series, false); + } + } + } + } else { + for (Map.Entry e : windSourceSpeedSeries.entrySet()) { + Series series = e.getValue(); + if (visibleSeries.contains(series)) { + chart.removeSeries(series, false); + } } } } - if (settings.isShowWindSpeedSeries()) { - for (Map.Entry e : windSourceSpeedSeries.entrySet()) { - Series series = e.getValue(); - if (settings.getWindSpeedSourcesToDisplay().contains(e.getKey().getType())) { - if (!visibleSeries.contains(series)) { - chart.addSeries(series, false, false); - series.select(true); // ensures that the checkbox will be ticked - } - } else { - if (visibleSeries.contains(series)) { - chart.removeSeries(series, false); - } + onResize(); + } + + private boolean forceSeriesSelection(Set visibleSeries, Map toProcess) { + boolean wasInResult = false; + for (Map.Entry e : toProcess.entrySet()) { + Series series = e.getValue(); + WindSource seriesSource = e.getKey(); + // add all of type, remove non matching ones + if (seriesSource.getType().equals(preselectFilter.getType())) { + if (!visibleSeries.contains(series)) { + chart.addSeries(series, true, false); } - } - } else { - for (Map.Entry e : windSourceSpeedSeries.entrySet()) { - Series series = e.getValue(); + // preselet the matching one + if (preselectFilter.equals(e.getKey())) { + wasInResult = true; + series.select(true); // ensures that the checkbox will be ticked + series.setVisible(true, true); + } else { + series.select(false); + series.setVisible(false, true); + } + } else { if (visibleSeries.contains(series)) { chart.removeSeries(series, false); } } } - onResize(); + return wasInResult; } /** @@ -441,6 +479,7 @@ public class WindChart extends AbstractRaceChart implements R */ @Override public void updateSettings(WindChartSettings newSettings) { + preselectFilter = null; boolean clearCacheAndReload = false; final Set oldWindSourceTypesToRequest = getNamesOfWindSourceTypesOfWhichToDisplaySpeedOrDirection(); if (newSettings.getResolutionInMilliseconds() != settings.getResolutionInMilliseconds()) { @@ -660,4 +699,19 @@ public class WindChart extends AbstractRaceChart implements R public String getId() { return windChartLifecycle.getComponentId(); } + + /** + * Forces the display of a specific windProvider preselected, and all of same type unselected. Will be disabled by either updateSettings or once the provider was properly found and shown + */ + public void showProvider(WindSource windprovider) { + WindSourceType type = windprovider.getType(); + Set windSpeedSourcesToDisplay = new HashSet<>(); + Set windDirectionSourcesToDisplay = new HashSet<>(); + windSpeedSourcesToDisplay.add(type); + windDirectionSourcesToDisplay.add(type); + WindChartSettings patched = new WindChartSettings(true,windSpeedSourcesToDisplay,true,windDirectionSourcesToDisplay,settings.getResolutionInMilliseconds()); + updateSettings(patched); + preselectFilter = windprovider; + updateVisibleSeries(); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/race/TrackedRaceCreationResultDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/race/TrackedRaceCreationResultDialog.java new file mode 100644 index 00000000000..e9bebd55f04 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/race/TrackedRaceCreationResultDialog.java @@ -0,0 +1,63 @@ +package com.sap.sailing.gwt.ui.client.shared.race; + +import java.util.Collections; +import java.util.UUID; + +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.settings.client.EntryPointWithSettingsLinkFactory; +import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; + +/** + * Summary dialog to be shown after actions that generate a new TrackedRace. + * the dialog shows a link to the RaceBoard for the generated TrackedRace. + * In addition, if available a link to the associated event is shown. + */ +public class TrackedRaceCreationResultDialog extends DataEntryDialog { + + private final VerticalPanel verticalPanel; + + public TrackedRaceCreationResultDialog(String title, String message, UUID eventId, String regattaName, String raceName, + String leaderboardName, String leaderboardGroupName) { + super(title, message, + StringMessages.INSTANCE.ok(), StringMessages.INSTANCE.cancel(), /* validator */ null, + new DialogCallback() { + @Override + public void ok(Void editedObject) { + } + + @Override + public void cancel() { + } + }); + + verticalPanel = new VerticalPanel(); + verticalPanel.setSpacing(20); + Anchor raceboardAnchor = new Anchor(StringMessages.INSTANCE.importFinishedGotoRaceboard(), + EntryPointWithSettingsLinkFactory.createRaceBoardLinkWithDefaultSettings(eventId, leaderboardName, + leaderboardGroupName, regattaName, raceName)); + raceboardAnchor.setTarget("_blank"); + verticalPanel.add(raceboardAnchor); + + if (eventId != null) { + Anchor eventAnchor = new Anchor(StringMessages.INSTANCE.importFinishedGotoEvent(), + EntryPointLinkFactory.createEventPlaceLink(eventId.toString(), Collections.emptyMap())); + eventAnchor.setTarget("_blank"); + verticalPanel.add(eventAnchor); + } + + } + + @Override + protected Widget getAdditionalWidget() { + return verticalPanel; + } + + @Override + protected Void getResult() { + return null; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ManagedInfoWindow.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ManagedInfoWindow.java new file mode 100644 index 00000000000..56a74bb491d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ManagedInfoWindow.java @@ -0,0 +1,64 @@ +package com.sap.sailing.gwt.ui.client.shared.racemap; + +import com.google.gwt.maps.client.MapWidget; +import com.google.gwt.maps.client.base.LatLng; +import com.google.gwt.maps.client.overlays.InfoWindow; +import com.google.gwt.maps.client.overlays.InfoWindowOptions; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; + +public class ManagedInfoWindow { + + private final InfoWindowOptions infoWindowOptions = InfoWindowOptions.newInstance(); + private final InfoWindow infoWindow = InfoWindow.newInstance(infoWindowOptions); + private final MapWidget map; + + private final InfoWindowContent container = new InfoWindowContent(); + + private boolean shown = false; + + public ManagedInfoWindow(final MapWidget map) { + this.map = map; + this.infoWindow.setContent(container); + this.infoWindow.addDomReadyHandler(event -> container.maybeAttach()); + this.infoWindow.addCloseClickHandler(event -> { + container.maybeDetach(); + shown = false; + }); + } + + public void openAtPosition(final Widget content, final LatLng position) { + this.container.setWidget(content); + this.infoWindow.setPosition(position); + this.infoWindow.open(map); + this.shown = true; + } + + public void close() { + if (shown) { + this.container.maybeDetach(); + this.infoWindow.close(); + this.shown = false; + } + } + + private class InfoWindowContent extends SimplePanel { + + private boolean attached = false; + + private void maybeAttach() { + if (!attached) { + this.attached = true; + onAttach(); + } + } + + private void maybeDetach() { + if (attached) { + this.attached = false; + onDetach(); + } + } + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ManeuverMarkerClickedListener.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ManeuverMarkerClickedListener.java deleted file mode 100644 index df78d130fff..00000000000 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/ManeuverMarkerClickedListener.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.sap.sailing.gwt.ui.client.shared.racemap; - -import com.google.gwt.maps.client.base.LatLng; -import com.google.gwt.maps.client.events.click.ClickMapEvent; -import com.google.gwt.maps.client.events.click.ClickMapHandler; -import com.google.gwt.maps.client.overlays.InfoWindow; -import com.google.gwt.maps.client.overlays.InfoWindowOptions; -import com.sap.sailing.gwt.ui.shared.ManeuverDTO; - -public class ManeuverMarkerClickedListener implements ClickMapHandler { - - private RaceMap racemap; - private ManeuverDTO maneuver; - - public ManeuverMarkerClickedListener(RaceMap racemap, ManeuverDTO maneuver) { - this.racemap = racemap; - this.maneuver = maneuver; - } - - @Override - public void onEvent(ClickMapEvent event) { - final InfoWindow lastInfoWindow = racemap.getLastInfoWindow(); - if (lastInfoWindow != null) { - lastInfoWindow.close(); - } - LatLng where = racemap.getCoordinateSystem().toLatLng(maneuver.position); - InfoWindowOptions options = InfoWindowOptions.newInstance(); - InfoWindow infoWindow = InfoWindow.newInstance(options); - infoWindow.setContent(racemap.getInfoWindowContent(maneuver)); - infoWindow.setPosition(where); - racemap.setLastInfoWindow(infoWindow); - infoWindow.open(racemap.getMap()); - } -} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java index a8b2ae85178..99af6bc397a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java @@ -11,7 +11,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; +import java.util.function.Consumer; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; @@ -51,8 +53,6 @@ import com.google.gwt.maps.client.events.zoom.ZoomChangeMapEvent; import com.google.gwt.maps.client.events.zoom.ZoomChangeMapHandler; import com.google.gwt.maps.client.maptypes.MapTypeStyleFeatureType; import com.google.gwt.maps.client.mvc.MVCArray; -import com.google.gwt.maps.client.overlays.InfoWindow; -import com.google.gwt.maps.client.overlays.InfoWindowOptions; import com.google.gwt.maps.client.overlays.Marker; import com.google.gwt.maps.client.overlays.MarkerOptions; import com.google.gwt.maps.client.overlays.Polygon; @@ -63,8 +63,10 @@ import com.google.gwt.resources.client.ImageResource; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AbsolutePanel; +import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RequiresResize; @@ -85,6 +87,7 @@ import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing; import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition; +import com.sap.sailing.domain.common.windfinder.SpotDTO; import com.sap.sailing.gwt.ui.actions.GetBoatPositionsAction; import com.sap.sailing.gwt.ui.actions.GetPolarAction; import com.sap.sailing.gwt.ui.actions.GetRaceMapDataAction; @@ -135,6 +138,7 @@ import com.sap.sse.common.Util.Pair; import com.sap.sse.common.Util.Triple; import com.sap.sse.common.filter.Filter; import com.sap.sse.common.filter.FilterSet; +import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.common.impl.RGBColor; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; @@ -324,8 +328,6 @@ public class RaceMap extends AbstractCompositeComponent impleme private RaceTimesInfoDTO lastRaceTimesInfo; - private InfoWindow lastInfoWindow = null; - /** * RPC calls may receive responses out of order if there are multiple calls in-flight at the same time. If the time * slider is moved quickly it generates many requests for boat positions quickly after each other. Sometimes, @@ -415,6 +417,7 @@ public class RaceMap extends AbstractCompositeComponent impleme private boolean orientationChangeInProgress; private final NumberFormat numberFormatOneDecimal = NumberFormat.getFormat("0.0"); + private final NumberFormat numberFormatNoDecimal = NumberFormat.getFormat("0"); /** * The competitor for which the advantage line is currently showing. Should this competitor's quick rank change, or @@ -425,6 +428,10 @@ public class RaceMap extends AbstractCompositeComponent impleme protected Label estimatedDurationOverlay; private RaceMapStyle raceMapStyle; private final boolean showHeaderPanel; + + /** Callback to set the visibility of the wind chart. */ + private final Consumer showWindChartForProvider; + private ManagedInfoWindow managedInfoWindow; private class AdvantageLineUpdater implements QuickRanksListener { @Override @@ -443,6 +450,17 @@ public class RaceMap extends AbstractCompositeComponent impleme ErrorReporter errorReporter, Timer timer, CompetitorSelectionProvider competitorSelection, RaceCompetitorSet raceCompetitorSet, StringMessages stringMessages, RegattaAndRaceIdentifier raceIdentifier, RaceMapResources raceMapResources, boolean showHeaderPanel, QuickRanksDTOProvider quickRanksDTOProvider) { + this(parent, context, raceMapLifecycle, raceMapSettings, sailingService, asyncActionsExecutor, errorReporter, + timer, competitorSelection, raceCompetitorSet, stringMessages, raceIdentifier, raceMapResources, + showHeaderPanel, quickRanksDTOProvider, visible -> {}); + } + + public RaceMap(Component parent, ComponentContext context, RaceMapLifecycle raceMapLifecycle, + RaceMapSettings raceMapSettings, + SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, + ErrorReporter errorReporter, Timer timer, CompetitorSelectionProvider competitorSelection, RaceCompetitorSet raceCompetitorSet, + StringMessages stringMessages, RegattaAndRaceIdentifier raceIdentifier, + RaceMapResources raceMapResources, boolean showHeaderPanel, QuickRanksDTOProvider quickRanksDTOProvider, Consumer showWindChartForProvider) { super(parent, context); this.showHeaderPanel = showHeaderPanel; this.quickRanksDTOProvider = quickRanksDTOProvider; @@ -454,6 +472,7 @@ public class RaceMap extends AbstractCompositeComponent impleme this.errorReporter = errorReporter; this.timer = timer; this.isSimulationEnabled = true; + this.showWindChartForProvider = showWindChartForProvider; timer.addTimeListener(this); raceMapImageManager = new RaceMapImageManager(raceMapResources); markDTOs = new HashMap(); @@ -697,6 +716,7 @@ public class RaceMap extends AbstractCompositeComponent impleme RaceMap.this.redraw(); trueNorthIndicatorPanel.redraw(); showAdditionalControls(map); + RaceMap.this.managedInfoWindow = new ManagedInfoWindow(map); } }; LoadApi.go(onLoad, loadLibraries, sensor, "key="+GoogleMapAPIKey.V3_APIKey); @@ -859,6 +879,7 @@ public class RaceMap extends AbstractCompositeComponent impleme // draw the wind into the map, get the combined wind List windSourceTypeNames = new ArrayList(); windSourceTypeNames.add(WindSourceType.EXPEDITION.name()); + windSourceTypeNames.add(WindSourceType.WINDFINDER.name()); windSourceTypeNames.add(WindSourceType.COMBINED.name()); 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); @@ -878,7 +899,8 @@ public class RaceMap extends AbstractCompositeComponent impleme WindTrackInfoDTO windTrackInfoDTO = windInfo.windTrackInfoByWindSource.get(windSource); switch (windSource.getType()) { case EXPEDITION: - // we filter out measured wind sources with vary low confidence + case WINDFINDER: + // we filter out measured wind sources with very low confidence if (windTrackInfoDTO.minWindConfidence > 0.0001) { windSourcesToShow.add(new com.sap.sse.common.Util.Pair(windSource, windTrackInfoDTO)); } @@ -1630,10 +1652,10 @@ public class RaceMap extends AbstractCompositeComponent impleme final Position startLineRightPosition = numberOfStartWaypointMarks < 2 ? null : courseDTO.getStartMarkPositions().get(1); if (courseDTO.startLineAngleFromPortToStarboardWhenApproachingLineToCombinedWind != null) { startLineAdvantageText.replace(0, startLineAdvantageText.length(), " "+stringMessages.lineAngleToWindAndAdvantage( - NumberFormat.getFormat("0.0").format(courseDTO.startLineLengthInMeters), - NumberFormat.getFormat("0.0").format(Math.abs(courseDTO.startLineAngleFromPortToStarboardWhenApproachingLineToCombinedWind)), + numberFormatOneDecimal.format(courseDTO.startLineLengthInMeters), + numberFormatOneDecimal.format(Math.abs(courseDTO.startLineAngleFromPortToStarboardWhenApproachingLineToCombinedWind)), courseDTO.startLineAdvantageousSide.name().charAt(0)+courseDTO.startLineAdvantageousSide.name().substring(1).toLowerCase(), - NumberFormat.getFormat("0.0").format(courseDTO.startLineAdvantageInMeters))); + numberFormatOneDecimal.format(courseDTO.startLineAdvantageInMeters))); } else { startLineAdvantageText.delete(0, startLineAdvantageText.length()); } @@ -1663,10 +1685,10 @@ public class RaceMap extends AbstractCompositeComponent impleme final Position finishLineRightPosition = numberOfFinishWaypointMarks < 2 ? null : courseDTO.getFinishMarkPositions().get(1); if (courseDTO.finishLineAngleFromPortToStarboardWhenApproachingLineToCombinedWind != null) { finishLineAdvantageText.replace(0, finishLineAdvantageText.length(), " "+stringMessages.lineAngleToWindAndAdvantage( - NumberFormat.getFormat("0.0").format(courseDTO.finishLineLengthInMeters), - NumberFormat.getFormat("0.0").format(Math.abs(courseDTO.finishLineAngleFromPortToStarboardWhenApproachingLineToCombinedWind)), + numberFormatOneDecimal.format(courseDTO.finishLineLengthInMeters), + numberFormatOneDecimal.format(Math.abs(courseDTO.finishLineAngleFromPortToStarboardWhenApproachingLineToCombinedWind)), courseDTO.finishLineAdvantageousSide.name().charAt(0)+courseDTO.finishLineAdvantageousSide.name().substring(1).toLowerCase(), - NumberFormat.getFormat("0.0").format(courseDTO.finishLineAdvantageInMeters))); + numberFormatOneDecimal.format(courseDTO.finishLineAdvantageInMeters))); } else { finishLineAdvantageText.delete(0, finishLineAdvantageText.length()); } @@ -1739,14 +1761,14 @@ public class RaceMap extends AbstractCompositeComponent impleme final StringBuilder sb = new StringBuilder(); sb.append(stringMessages.courseMiddleLine()); sb.append('\n'); - sb.append(NumberFormat.getFormat("0").format( + sb.append(numberFormatNoDecimal.format( Math.abs(position1DTO.getDistance(position2DTO).getMeters()))+stringMessages.metersUnit()); if (lastCombinedWindTrackInfoDTO != null) { final WindTrackInfoDTO windTrackAtLegMiddle = lastCombinedWindTrackInfoDTO.getCombinedWindOnLegMiddle(zeroBasedIndexOfStartWaypoint); if (windTrackAtLegMiddle != null && windTrackAtLegMiddle.windFixes != null && !windTrackAtLegMiddle.windFixes.isEmpty()) { WindDTO windAtLegMiddle = windTrackAtLegMiddle.windFixes.get(0); final double legBearingDeg = position1DTO.getBearingGreatCircle(position2DTO).getDegrees(); - final String diff = NumberFormat.getFormat("0.0").format( + final String diff = numberFormatOneDecimal.format( Math.min(Math.abs(windAtLegMiddle.dampenedTrueWindBearingDeg-legBearingDeg), Math.abs(windAtLegMiddle.dampenedTrueWindFromDeg-legBearingDeg))); sb.append(", "); @@ -2059,21 +2081,11 @@ public class RaceMap extends AbstractCompositeComponent impleme private BoatOverlay createBoatOverlay(int zIndex, final CompetitorDTO competitorDTO, DisplayMode displayMode) { final BoatOverlay boatCanvas = new BoatOverlay(map, zIndex, competitorDTO, competitorSelection.getColor(competitorDTO, raceIdentifier), coordinateSystem); boatCanvas.setDisplayMode(displayMode); - boatCanvas.addClickHandler(new ClickMapHandler() { - @Override - public void onEvent(ClickMapEvent event) { - if (lastInfoWindow != null) { - lastInfoWindow.close(); - } - GPSFixDTOWithSpeedWindTackAndLegType latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime()); - LatLng where = coordinateSystem.toLatLng(latestFixForCompetitor.position); - InfoWindowOptions options = InfoWindowOptions.newInstance(); - InfoWindow infoWindow = InfoWindow.newInstance(options); - infoWindow.setContent(getInfoWindowContent(competitorDTO, latestFixForCompetitor)); - infoWindow.setPosition(where); - lastInfoWindow = infoWindow; - infoWindow.open(map); - } + boatCanvas.addClickHandler(event -> { + GPSFixDTOWithSpeedWindTackAndLegType latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime()); + Widget content = getInfoWindowContent(competitorDTO, latestFixForCompetitor); + LatLng where = coordinateSystem.toLatLng(latestFixForCompetitor.position); + managedInfoWindow.openAtPosition(content, where); }); boatCanvas.addMouseOverHandler(new MouseOverMapHandler() { @@ -2105,81 +2117,55 @@ public class RaceMap extends AbstractCompositeComponent impleme } private void showMarkInfoWindow(MarkDTO markDTO, LatLng position) { - if(lastInfoWindow != null) { - lastInfoWindow.close(); - } - InfoWindowOptions options = InfoWindowOptions.newInstance(); - InfoWindow infoWindow = InfoWindow.newInstance(options); - infoWindow.setContent(getInfoWindowContent(markDTO)); - infoWindow.setPosition(position); - lastInfoWindow = infoWindow; - infoWindow.open(map); + managedInfoWindow.openAtPosition(getInfoWindowContent(markDTO), position); } private void showCompetitorInfoWindow(final CompetitorDTO competitorDTO, LatLng where) { - if(lastInfoWindow != null) { - lastInfoWindow.close(); - } - GPSFixDTOWithSpeedWindTackAndLegType latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime()); - // TODO find close fix where the mouse was; see BUG 470 - InfoWindowOptions options = InfoWindowOptions.newInstance(); - InfoWindow infoWindow = InfoWindow.newInstance(options); - infoWindow.setContent(getInfoWindowContent(competitorDTO, latestFixForCompetitor)); - infoWindow.setPosition(where); - lastInfoWindow = infoWindow; - infoWindow.open(map); + final GPSFixDTOWithSpeedWindTackAndLegType latestFixForCompetitor = getBoatFix(competitorDTO, timer.getTime()); + final Widget content = getInfoWindowContent(competitorDTO, latestFixForCompetitor); + managedInfoWindow.openAtPosition(content, where); } - private String formatPosition(double lat, double lng) { - NumberFormat numberFormat = NumberFormat.getFormat("0.00000"); - String result = numberFormat.format(lat) + " lat, " + numberFormat.format(lng) + " lng"; - return result; - } - private void showWindSensorInfoWindow(final WindSensorOverlay windSensorOverlay) { WindSource windSource = windSensorOverlay.getWindSource(); WindTrackInfoDTO windTrackInfoDTO = windSensorOverlay.getWindTrackInfoDTO(); WindDTO windDTO = windTrackInfoDTO.windFixes.get(0); - if(windDTO != null && windDTO.position != null) { - if(lastInfoWindow != null) { - lastInfoWindow.close(); - } - LatLng where = coordinateSystem.toLatLng(windDTO.position); - InfoWindowOptions options = InfoWindowOptions.newInstance(); - InfoWindow infoWindow = InfoWindow.newInstance(options); - infoWindow.setContent(getInfoWindowContent(windSource, windTrackInfoDTO)); - infoWindow.setPosition(where); - lastInfoWindow = infoWindow; - infoWindow.open(map); + if (windDTO != null && windDTO.position != null) { + final LatLng where = coordinateSystem.toLatLng(windDTO.position); + final Widget content = getInfoWindowContent(windSource, windTrackInfoDTO); + managedInfoWindow.openAtPosition(content, where); } } private Widget createInfoWindowLabelAndValue(String labelName, String value) { + Label valueLabel = new Label(value); + valueLabel.setWordWrap(false); + return createInfoWindowLabelWithWidget(labelName, valueLabel); + } + + public Widget createInfoWindowLabelWithWidget(String labelName, Widget value) { FlowPanel flowPanel = new FlowPanel(); Label label = new Label(labelName + ":"); label.setWordWrap(false); label.getElement().getStyle().setFloat(Style.Float.LEFT); label.getElement().getStyle().setPadding(3, Style.Unit.PX); label.getElement().getStyle().setFontWeight(Style.FontWeight.BOLD); + value.getElement().getStyle().setFloat(Style.Float.LEFT); + value.getElement().getStyle().setPadding(3, Style.Unit.PX); flowPanel.add(label); - - Label valueLabel = new Label(value); - valueLabel.setWordWrap(false); - valueLabel.getElement().getStyle().setFloat(Style.Float.LEFT); - valueLabel.getElement().getStyle().setPadding(3, Style.Unit.PX); - flowPanel.add(valueLabel); - + flowPanel.add(value); return flowPanel; } private Widget getInfoWindowContent(MarkDTO markDTO) { VerticalPanel vPanel = new VerticalPanel(); vPanel.add(createInfoWindowLabelAndValue(stringMessages.mark(), markDTO.getName())); - vPanel.add(createInfoWindowLabelAndValue(stringMessages.position(), formatPosition(markDTO.position.getLatDeg(), markDTO.position.getLngDeg()))); + vPanel.add(createInfoWindowLabelAndValue(stringMessages.position(), markDTO.position.getAsDegreesAndDecimalMinutesWithCardinalPoints())); + vPanel.add(createInfoWindowLabelAndValue(stringMessages.position(), markDTO.position.getAsSignedDecimalDegrees())); return vPanel; } - public Widget getInfoWindowContent(ManeuverDTO maneuver) { + private Widget getInfoWindowContent(ManeuverDTO maneuver) { SpeedWithBearingDTO before = maneuver.speedWithBearingBefore; SpeedWithBearingDTO after = maneuver.speedWithBearingAfter; VerticalPanel vPanel = new VerticalPanel(); @@ -2198,19 +2184,54 @@ public class RaceMap extends AbstractCompositeComponent impleme + " -> " + NumberFormat.getDecimalFormat().format(after.speedInKnots) + " " + stringMessages.knotsUnit() + ")")); if (maneuver.maneuverLossInMeters != null) { - vPanel.add(createInfoWindowLabelAndValue(stringMessages.maneuverLoss(), NumberFormat.getFormat("0.0").format(maneuver.maneuverLossInMeters)+"m")); + vPanel.add(createInfoWindowLabelAndValue(stringMessages.maneuverLoss(), numberFormatOneDecimal.format(maneuver.maneuverLossInMeters)+"m")); } return vPanel; } - private Widget getInfoWindowContent(WindSource windSource, WindTrackInfoDTO windTrackInfoDTO) { + private Widget getInfoWindowContent(final WindSource windSource, WindTrackInfoDTO windTrackInfoDTO) { WindDTO windDTO = windTrackInfoDTO.windFixes.get(0); - NumberFormat numberFormat = NumberFormat.getFormat("0.0"); - VerticalPanel vPanel = new VerticalPanel(); - vPanel.add(createInfoWindowLabelAndValue(stringMessages.windSource(), WindSourceTypeFormatter.format(windSource, stringMessages))); + final VerticalPanel vPanel = new VerticalPanel(); + final Anchor windSourceNameAnchor = new Anchor(WindSourceTypeFormatter.format(windSource, stringMessages)); + vPanel.add(createInfoWindowLabelWithWidget(stringMessages.windSource(), windSourceNameAnchor)); vPanel.add(createInfoWindowLabelAndValue(stringMessages.wind(), Math.round(windDTO.dampenedTrueWindFromDeg) + " " + stringMessages.degreesShort())); - vPanel.add(createInfoWindowLabelAndValue(stringMessages.windSpeed(), numberFormat.format(windDTO.dampenedTrueWindSpeedInKnots))); - vPanel.add(createInfoWindowLabelAndValue(stringMessages.position(), formatPosition(windDTO.position.getLatDeg(), windDTO.position.getLngDeg()))); + vPanel.add(createInfoWindowLabelAndValue(stringMessages.windSpeed(), numberFormatOneDecimal.format(windDTO.dampenedTrueWindSpeedInKnots))); + final MillisecondsTimePoint timePoint = new MillisecondsTimePoint(windDTO.measureTimepoint); + vPanel.add(createInfoWindowLabelAndValue(stringMessages.time(), timePoint.asDate().toString())); + vPanel.add(createInfoWindowLabelAndValue(stringMessages.position(), windDTO.position.getAsDegreesAndDecimalMinutesWithCardinalPoints())); + final Label positionInDecimalDegreesLabel = new Label(windDTO.position.getAsSignedDecimalDegrees()); + positionInDecimalDegreesLabel.setWordWrap(false); + positionInDecimalDegreesLabel.getElement().getStyle().setFloat(Style.Float.LEFT); + positionInDecimalDegreesLabel.getElement().getStyle().setPadding(3, Style.Unit.PX); + positionInDecimalDegreesLabel.getElement().getStyle().setFontWeight(Style.FontWeight.LIGHTER); + positionInDecimalDegreesLabel.getElement().getStyle().setFontSize(0.7, Unit.EM); + vPanel.add(positionInDecimalDegreesLabel); + if (windSource.getType() == WindSourceType.WINDFINDER) { + final HorizontalPanel container = new HorizontalPanel(); + container.setSpacing(1); + final WindfinderIcon windfinderIcon = new WindfinderIcon(raceMapImageManager, stringMessages); + container.add(windfinderIcon); + container.add(vPanel); + sailingService.getWindFinderSpot(windSource.getId().toString(), new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(stringMessages.unableToResolveWindFinderSpotId( + windSource.getId().toString(), caught.getMessage()), /* silentMode */ true); + } + + @Override + public void onSuccess(SpotDTO result) { + final String url = result.getCurrentlyMostAppropriateUrl(timePoint); + windSourceNameAnchor.setTarget("_blank"); + windSourceNameAnchor.setText(result.getName()); + windSourceNameAnchor.setHref(url); + windfinderIcon.setHref(url); + } + }); + return container; + } else { + windSourceNameAnchor.addClickHandler(event -> showWindChartForProvider.accept(windSource)); + } return vPanel; } @@ -2370,18 +2391,22 @@ public class RaceMap extends AbstractCompositeComponent impleme } private void createAndAddMarkerOfManeuver(ManeuverDTO maneuver) { - LatLng latLng = coordinateSystem.toLatLng(maneuver.position); + LatLng latLng = coordinateSystem.toLatLng(maneuver.position); Marker maneuverMarker = raceMapImageManager.maneuverIconsForTypeAndDirectionIndicatingColor .get(new Pair(maneuver.type, ManeuverColor.getManeuverColor(maneuver))); - MarkerOptions options = MarkerOptions.newInstance(); - options.setIcon(maneuverMarker.getIcon_MarkerImage()); - Marker marker = Marker.newInstance(options); - marker.setPosition(latLng); + MarkerOptions options = MarkerOptions.newInstance(); + options.setIcon(maneuverMarker.getIcon_MarkerImage()); + Marker marker = Marker.newInstance(options); + marker.setPosition(latLng); marker.setTitle(ManeuverTypeFormatter.format(maneuver.type, stringMessages)); - marker.addClickHandler(new ManeuverMarkerClickedListener(this, maneuver)); - maneuverMarkers.add(marker); - marker.setMap(map); - } + marker.addClickHandler(event -> { + LatLng where = getCoordinateSystem().toLatLng(maneuver.position); + Widget content = getInfoWindowContent(maneuver); + managedInfoWindow.openAtPosition(content, where); + }); + maneuverMarkers.add(marker); + marker.setMap(map); + } /** * @param date @@ -2739,12 +2764,13 @@ public class RaceMap extends AbstractCompositeComponent impleme Collection marksToZoom = forMap.windSensorOverlays.values(); if (marksToZoom != null) { for (WindSensorOverlay windSensorOverlay : marksToZoom) { - if (BoundsUtil.getAsPosition(windSensorOverlay.getLatLngPosition()) != null) { - LatLngBounds bounds = BoundsUtil.getAsBounds(windSensorOverlay.getLatLngPosition()); + final LatLng latLngPosition = windSensorOverlay.getLatLngPosition(); + if (Objects.nonNull(latLngPosition) && BoundsUtil.getAsPosition(latLngPosition) != null) { + LatLngBounds bounds = BoundsUtil.getAsBounds(latLngPosition); if (newBounds == null) { newBounds = bounds; } else { - newBounds = newBounds.extend(windSensorOverlay.getLatLngPosition()); + newBounds = newBounds.extend(latLngPosition); } } } @@ -3006,14 +3032,6 @@ public class RaceMap extends AbstractCompositeComponent impleme } } - void setLastInfoWindow(InfoWindow infoWindow) { - lastInfoWindow = infoWindow; - } - - InfoWindow getLastInfoWindow() { - return lastInfoWindow; - } - public void addCompetitorsForRaceDefinedListener(CompetitorsForRaceDefinedListener listener) { raceCompetitorSet.addCompetitorsForRaceDefinedListener(listener); } 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 274d05663a7..8404c590b7a 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 @@ -12,6 +12,7 @@ import com.google.gwt.maps.client.overlays.Marker; import com.google.gwt.maps.client.overlays.MarkerImage; import com.google.gwt.maps.client.overlays.MarkerOptions; import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.resources.client.TextResource; import com.sap.sailing.domain.common.ManeuverType; import com.sap.sse.common.Util; @@ -91,4 +92,8 @@ public class RaceMapImageManager { public ImageTransformer getWindSensorIconTransformer() { return windSensorIconTransformer; } + + public TextResource getWindFinderLogo() { + return resources.getWindFinderLogo(); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapResources.java index 7c7d2fdae04..c0efb051e1f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapResources.java @@ -2,6 +2,7 @@ package com.sap.sailing.gwt.ui.client.shared.racemap; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.resources.client.TextResource; public interface RaceMapResources extends ClientBundle { @Source("com/sap/sailing/gwt/ui/client/images/cameraBoat.png") @@ -27,6 +28,9 @@ public interface RaceMapResources extends ClientBundle { @Source("com/sap/sailing/gwt/ui/client/images/maneuver_green.png") ImageResource maneuverMarkerGreen(); + + @Source("com/sap/sailing/gwt/ui/client/images/windfinder-logo.svg") + TextResource getWindFinderLogo(); @Source("RaceMap.css") RaceMapStyle raceMapStyle(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/WindfinderIcon.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/WindfinderIcon.java new file mode 100644 index 00000000000..923c678b7a0 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/WindfinderIcon.java @@ -0,0 +1,32 @@ +package com.sap.sailing.gwt.ui.client.shared.racemap; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.AnchorElement; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Element; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.ui.client.StringMessages; + +public class WindfinderIcon extends Widget { + + private static WindfinderIconUiBinder uiBinder = GWT.create(WindfinderIconUiBinder.class); + + interface WindfinderIconUiBinder extends UiBinder { + } + + @UiField AnchorElement anchorUi; + @UiField DivElement iconContainerUi, textContainerUi; + + public WindfinderIcon(final RaceMapImageManager raceMapImageManager, final StringMessages stringMessages) { + setElement(uiBinder.createAndBindUi(this)); + iconContainerUi.setInnerHTML(raceMapImageManager.getWindFinderLogo().getText()); + textContainerUi.setInnerHTML(stringMessages.windFinderWeatherData()); + } + + public void setHref(String href) { + this.anchorUi.setHref(href); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/WindfinderIcon.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/WindfinderIcon.ui.xml new file mode 100644 index 00000000000..1c6634b10a4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/WindfinderIcon.ui.xml @@ -0,0 +1,34 @@ + + + + .container { + display: inline-block; + width: 8em; + height: 8em; + margin: .15em .5em 0 0; + text-align: center; + text-decoration: none; + color: #fff; + background-color:#d0021b; + border-radius:.5em; + } + .icon { + width: 4em; + height: auto; + margin: .75em auto 0; + } + .icon svg { + fill: #fff; + } + .text { + font-size: 1.25em; + font-family: Arial; + padding: .33333333em .5em; + letter-spacing: 1px; + } + + +
    +
    +
    +
    \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/ChartFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/ChartFactory.java index 79ec317a5a9..e0e7074937c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/ChartFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/ChartFactory.java @@ -87,7 +87,7 @@ public class ChartFactory extends Chart { Chart angleChart = new Chart().setType(Type.LINE).setHeight100().setWidth100(); angleChart.setTitle(new ChartTitle().setText(""), new ChartSubtitle().setText("")); angleChart.setExporting(new Exporting().setEnabled(false)); - angleChart.getYAxis().setAxisTitleText(stringMessages.beatAngle() + " (" + stringMessages.degreesShort() + ")"); + angleChart.getYAxis().setAxisTitleText(stringMessages.TWA() + " (" + stringMessages.degreesShort() + ")"); angleChart.getXAxis().setAxisTitleText(stringMessages.windSpeed()); return angleChart; } @@ -107,7 +107,7 @@ public class ChartFactory extends Chart { lineChart.setTitle(new ChartTitle().setText(""), new ChartSubtitle().setText("")); lineChart.getXAxis().setMin(-179).setMax(180).setTickInterval(45); lineChart.getXAxis().setLabels(new XAxisLabels().setRotation(-90f).setY(30).setEnabled(true)).setAxisTitle( - new AxisTitle().setText(stringMessages.beatAngle() + " (" + stringMessages.degreesShort() + ")")); + new AxisTitle().setText(stringMessages.TWA() + " (" + stringMessages.degreesShort() + ")")); lineChart.setLegend(new Legend().setEnabled(false)); lineChart.setExporting(new Exporting().setEnabled(false)); return lineChart; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/ManeuverSpeedDetailsResultsPresenter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/ManeuverSpeedDetailsResultsPresenter.java index 030c8700952..6925b4189eb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/ManeuverSpeedDetailsResultsPresenter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/presentation/ManeuverSpeedDetailsResultsPresenter.java @@ -101,7 +101,7 @@ public class ManeuverSpeedDetailsResultsPresenter extends AbstractResultsPresent }; dataCountHistogramChart = ChartFactory.createDataCountHistogramChart( - stringMessages.beatAngle() + " (" + stringMessages.degreesShort() + ")", stringMessages); + stringMessages.TWA() + " (" + stringMessages.degreesShort() + ")", stringMessages); dataCountHistogramChart.getXAxis().setMin(-179); dataCountHistogramChart.getXAxis().setMax(180); rightSideChartsWrapperPanel = new DockLayoutPanel(Unit.PCT) { @@ -138,7 +138,7 @@ public class ManeuverSpeedDetailsResultsPresenter extends AbstractResultsPresent polarChart = ChartFactory.createPolarChart(); lineChart = ChartFactory.createLineChartForPolarData(stringMessages); dataCountHistogramChart = ChartFactory.createDataCountHistogramChart( - stringMessages.beatAngle() + " (" + stringMessages.degreesShort() + ")", stringMessages); + stringMessages.TWA() + " (" + stringMessages.degreesShort() + ")", stringMessages); polarChart.getXAxis().setMin(xAxisMin).setMax(xAxisMax); lineChart.getXAxis().setMin(xAxisMin).setMax(xAxisMax); dataCountHistogramChart.getXAxis().setMin(xAxisMin).setMax(xAxisMax); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarResultsPresenter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarResultsPresenter.java index 31f5b5aaced..3f947da4d18 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarResultsPresenter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/polarmining/PolarResultsPresenter.java @@ -88,7 +88,7 @@ public class PolarResultsPresenter extends AbstractResultsPresenter { polarChartWrapperPanel.add(polarChart); dataCountHistogramChart = ChartFactory.createDataCountHistogramChart( - stringMessages.beatAngle() + " (" + stringMessages.degreesShort() + ")", stringMessages); + stringMessages.TWA() + " (" + stringMessages.degreesShort() + ")", stringMessages); dataCountPerAngleHistogramChart = ChartFactory.createDataCountHistogramChart(stringMessages.windSpeed(), stringMessages); histogramChartsWrapperPanel = new DockLayoutPanel(Unit.PCT) { 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 9199ee1ea03..d92d6f877ce 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 @@ -32,6 +32,7 @@ import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.dto.BoatDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.FleetDTO; @@ -223,7 +224,7 @@ public class RaceBoardPanel raceMap = new RaceMap(this, componentContext, raceMapLifecycle, defaultRaceMapSettings, sailingService, asyncActionsExecutor, errorReporter, timer, competitorSelectionProvider, raceCompetitorSet, stringMessages, selectedRaceIdentifier, - raceMapResources, /* showHeaderPanel */ true, quickRanksDTOProvider) { + raceMapResources, /* showHeaderPanel */ true, quickRanksDTOProvider, this::showInWindChart) { private static final String INDENT_SMALL_CONTROL_STYLE = "indentsmall"; private static final String INDENT_BIG_CONTROL_STYLE = "indentbig"; @Override @@ -365,18 +366,18 @@ public class RaceBoardPanel competitorChart.getEntryWidget().setTitle(stringMessages.competitorCharts()); competitorChart.setVisible(false); competitorChart.updateSettings(multiCompetitorRaceChartSettings); + new SliceRaceHandler(sailingService, userService, errorReporter, competitorChart, selectedRaceIdentifier, + leaderboardGroupName, leaderboardName, event); componentsForSideBySideViewer.add(competitorChart); windChart = new WindChart(this, getComponentContext(), windChartLifecycle, sailingService, selectedRaceIdentifier, timer, timeRangeWithZoomModel, windChartSettings, stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true); - windChart.setVisible(false); windChart.getEntryWidget().setTitle(stringMessages.windChart()); componentsForSideBySideViewer.add(windChart); } - editMarkPassingPanel = new EditMarkPassingsPanel(this, getComponentContext(), sailingService, selectedRaceIdentifier, stringMessages, @@ -515,6 +516,11 @@ public class RaceBoardPanel public void setWindChartVisible(boolean visible) { setComponentVisible(leaderboardAndMapViewer, windChart, visible); } + + public void showInWindChart(WindSource windprovider) { + setComponentVisible(leaderboardAndMapViewer, windChart, true); + windChart.showProvider(windprovider); + } /** * Sets the collapsable panel for the competitor chart open or close, if in CASCADE view mode.
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SliceRaceHandler.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SliceRaceHandler.gss new file mode 100644 index 00000000000..8c7613d52e4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SliceRaceHandler.gss @@ -0,0 +1,3 @@ +.sliceButtonBackgroundImage { + background-image: resourceUrl('sliceButton'); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SliceRaceHandler.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SliceRaceHandler.java new file mode 100644 index 00000000000..de5fdc848b4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SliceRaceHandler.java @@ -0,0 +1,220 @@ +package com.sap.sailing.gwt.ui.raceboard; + +import java.util.UUID; +import java.util.function.Consumer; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.security.Permission; +import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider; +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.ChartZoomChangedEvent; +import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart; +import com.sap.sailing.gwt.ui.client.shared.race.TrackedRaceCreationResultDialog; +import com.sap.sailing.gwt.ui.shared.SliceRacePreperationDTO; +import com.sap.sse.common.TimeRange; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.impl.TimeRangeImpl; +import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.dialog.DataEntryDialog; +import com.sap.sse.security.ui.client.UserService; +import com.sap.sse.security.ui.client.UserStatusEventHandler; +import com.sap.sse.security.ui.shared.UserDTO; + +/** + * Encapsulates the UI to slice a race from an existing race. + * The slice functionality is added as an extra action to a given {@link MultiCompetitorRaceChart}. + */ +public class SliceRaceHandler { + interface Resources extends ClientBundle { + @Source("com/sap/sailing/gwt/ui/client/images/close_time_range.png") + ImageResource sliceButton(); + + @Source("SliceRaceHandler.gss") + Styles style(); + } + + interface Styles extends CssResource { + String sliceButtonBackgroundImage(); + } + + private final Styles styles = GWT.create(Resources.class).style(); + + private final Button sliceButtonUi; + + private TimeRange visibleRange; + + private final String leaderboardGroupName; + + private final String leaderboardName; + + private final UUID eventId; + + private final SailingServiceAsync sailingService; + + private final UserService userService; + + private final ErrorReporter errorReporter; + + private final RegattaAndRaceIdentifier selectedRaceIdentifier; + + private boolean canSlice = false; + + /** + * Registers this handler as a zoom event handler on the {@code competitorRaceChart}. + */ + public SliceRaceHandler(SailingServiceAsync sailingService, UserService userService, final ErrorReporter errorReporter, + MultiCompetitorRaceChart competitorRaceChart, RegattaAndRaceIdentifier selectedRaceIdentifier, + final String leaderboardGroupName, String leaderboardName, UUID eventId) { + this.sailingService = sailingService; + this.userService = userService; + this.errorReporter = errorReporter; + this.selectedRaceIdentifier = selectedRaceIdentifier; + this.leaderboardGroupName = leaderboardGroupName; + this.leaderboardName = leaderboardName; + this.eventId = eventId; + styles.ensureInjected(); + sliceButtonUi = new Button(); + sliceButtonUi.setStyleName(styles.sliceButtonBackgroundImage()); + sliceButtonUi.setTitle(StringMessages.INSTANCE.sliceRace()); + competitorRaceChart.addToolbarButton(sliceButtonUi); + sliceButtonUi.setVisible(false); + competitorRaceChart.addChartZoomChangedHandler(this::checkIfMaySliceSelectedRegattaAndRace); + competitorRaceChart.addChartZoomResetHandler(e -> { + visibleRange = null; + updateVisibility(); + }); + sliceButtonUi.addClickHandler((e) -> doSlice()); + sailingService.canSliceRace(selectedRaceIdentifier, new AsyncCallback() { + @Override + public void onSuccess(Boolean result) { + canSlice = Boolean.TRUE.equals(result); + updateVisibility(); + } + + @Override + public void onFailure(Throwable caught) { + canSlice = false; + updateVisibility(); + } + }); + final UserStatusEventHandler userStatusEventHandler = (user, preAuthenticated) -> { + updateVisibility(); + }; + sliceButtonUi.addAttachHandler(e -> { + if (e.isAttached()) { + userService.addUserStatusEventHandler(userStatusEventHandler); + } else { + userService.removeUserStatusEventHandler(userStatusEventHandler); + } + }); + } + + private void updateVisibility() { + sliceButtonUi.setVisible(canSlice && visibleRange != null && allowsEditing()); + } + + private boolean allowsEditing() { + final UserDTO currentUser = userService.getCurrentUser(); + return currentUser != null + && currentUser.hasPermission(Permission.MANAGE_TRACKED_RACES.getStringPermission(), + SailingPermissionsForRoleProvider.INSTANCE); + } + + private void doSlice() { + if (visibleRange != null) { + sailingService.prepareForSlicingOfRace(selectedRaceIdentifier, + new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(StringMessages.INSTANCE.errorWhilePreparingToSliceARace()); + } + + @Override + public void onSuccess(SliceRacePreperationDTO sliceRacePreperatioData) { + new SlicedRaceNameDialog(sliceRacePreperatioData, slicedRaceName -> { + sailingService.sliceRace(selectedRaceIdentifier, slicedRaceName, visibleRange.from(), + visibleRange.to(), new AsyncCallback() { + + @Override + public void onFailure(Throwable caught) { + errorReporter + .reportError(StringMessages.INSTANCE.errorWhileSlicingARace()); + } + + @Override + public void onSuccess(RegattaAndRaceIdentifier result) { + new TrackedRaceCreationResultDialog(StringMessages.INSTANCE.sliceRace(), + StringMessages.INSTANCE.slicingARaceWasSuccessful(), eventId, + result.getRegattaName(), result.getRaceName(), leaderboardName, + leaderboardGroupName).show(); + } + }); + }).show(); + } + }); + } + } + + private void checkIfMaySliceSelectedRegattaAndRace(final ChartZoomChangedEvent e) { + visibleRange = new TimeRangeImpl(new MillisecondsTimePoint(e.getRangeStart()), + new MillisecondsTimePoint(e.getRangeEnd())); + updateVisibility(); + } + + private static class SlicedRaceNameDialog extends DataEntryDialog { + + private final TextBox raceNameInput; + + public SlicedRaceNameDialog(final SliceRacePreperationDTO sliceRacePreperatioData, + final Consumer okCallback) { + super(StringMessages.INSTANCE.sliceRace(), StringMessages.INSTANCE.enterNameForSlicedRace(), + StringMessages.INSTANCE.ok(), StringMessages.INSTANCE.cancel(), new Validator() { + @Override + public String getErrorMessage(String valueToValidate) { + if (valueToValidate == null || valueToValidate.isEmpty()) { + return StringMessages.INSTANCE.raceNameIsRequired(); + } + if (sliceRacePreperatioData.getAlreadyUsedNames().contains(valueToValidate)) { + return StringMessages.INSTANCE.raceNameIsAlreadyUsed(); + } + return null; + } + }, new DialogCallback() { + @Override + public void ok(String editedObject) { + okCallback.accept(editedObject); + } + + @Override + public void cancel() { + } + }); + raceNameInput = createTextBox(sliceRacePreperatioData.getProposedRaceName()); + raceNameInput.addAttachHandler(e -> { + if (e.isAttached()) { + Scheduler.get().scheduleDeferred(() -> raceNameInput.setFocus(true)); + } + }); + } + + @Override + protected Widget getAdditionalWidget() { + return raceNameInput; + } + + @Override + protected String getResult() { + return raceNameInput.getValue().trim(); + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/LogEventTimeRangeWithFallbackFilter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/LogEventTimeRangeWithFallbackFilter.java new file mode 100644 index 00000000000..8ba4f39fb43 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/LogEventTimeRangeWithFallbackFilter.java @@ -0,0 +1,51 @@ +package com.sap.sailing.gwt.ui.server; + +import java.util.NavigableMap; +import java.util.TreeMap; + +import com.sap.sailing.domain.abstractlog.AbstractLogEvent; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.TimeRange; + +/** + * Helper class that filters given {@link AbstractLogEvent AbstractLogEvents} based on a given {@link TimeRange}. All + * {@link AbstractLogEvent AbstractLogEvents} in the given {@link TimeRange} plus the nearest ones outside of the + * {@link TimeRange} are retained. + */ +public class LogEventTimeRangeWithFallbackFilter> { + + private final NavigableMap filteredEvents; + private final TimeRange timeRangeToInclude; + + public LogEventTimeRangeWithFallbackFilter(TimeRange timeRangeToInclude) { + this.timeRangeToInclude = timeRangeToInclude; + this.filteredEvents = new TreeMap<>(); + } + + public void addEvent(E event) { + final TimePoint logicalTimePoint = event.getLogicalTimePoint(); + if (this.timeRangeToInclude.includes(logicalTimePoint)) { + filteredEvents.put(logicalTimePoint, event); + } else if (logicalTimePoint.before(this.timeRangeToInclude.from())) { + final TimePoint currentFallbackBefore = this.filteredEvents.lowerKey(this.timeRangeToInclude.from()); + if (currentFallbackBefore == null || logicalTimePoint.after(currentFallbackBefore)) { + if (currentFallbackBefore != null) { + this.filteredEvents.remove(currentFallbackBefore); + } + filteredEvents.put(logicalTimePoint, event); + } + } else if (logicalTimePoint.after(this.timeRangeToInclude.to())) { + final TimePoint currentFallbackAfter = this.filteredEvents.higherKey(this.timeRangeToInclude.to()); + if (currentFallbackAfter == null || logicalTimePoint.before(currentFallbackAfter)) { + if (currentFallbackAfter != null) { + this.filteredEvents.remove(currentFallbackAfter); + } + filteredEvents.put(logicalTimePoint, event); + } + } + } + + public Iterable getFilteredEvents() { + return this.filteredEvents.values(); + } +} 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 8e786e0bc45..69c6f3c77dd 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 @@ -58,35 +58,72 @@ 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.competitorimport.CompetitorProvider; import com.sap.sailing.domain.abstractlog.AbstractLog; import com.sap.sailing.domain.abstractlog.AbstractLogEvent; +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; import com.sap.sailing.domain.abstractlog.impl.AllEventsOfTypeFinder; import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmedEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningListChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogFixedMarkPassingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogGateLineOpeningTimeEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogPathfinderEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogProtestStartTimeEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartOfTrackingEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogSuppressedMarkPassingsEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.AbortingFlagFinder; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.FinishedTimeFinder; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.FinishingTimeFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.LastPublishedCourseDesignFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.MarkPassingDataFinder; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinderResult; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.TrackingTimesEventFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.TrackingTimesFinder; +import com.sap.sailing.domain.abstractlog.race.impl.BaseRaceLogEventVisitor; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogCourseDesignChangedEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogDependentStartTimeEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogFinishPositioningConfirmedEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogFinishPositioningListChangedEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogFixedMarkPassingEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogFlagEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogGateLineOpeningTimeEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogPathfinderEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogProtestStartTimeEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogRaceStatusEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartProcedureChangedEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogSuppressedMarkPassingsEventImpl; +import com.sap.sailing.domain.abstractlog.race.impl.RaceLogWindFixEventImpl; +import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent; +import com.sap.sailing.domain.abstractlog.race.scoring.impl.RaceLogAdditionalScoringInformationEventImpl; import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.FlagPoleState; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.gate.ReadonlyGateStartRacingProcedure; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.line.ConfigurableStartModeFlagRacingProcedure; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent; +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogRegisterCompetitorEvent; +import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogUseCompetitorsFromRaceLogEvent; import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.RaceLogTrackingStateAnalyzer; +import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogDenoteForTrackingEventImpl; +import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogRegisterCompetitorEventImpl; +import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogStartTrackingEventImpl; +import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogUseCompetitorsFromRaceLogEventImpl; import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogCloseOpenEndedDeviceMappingEvent; @@ -242,6 +279,7 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl; import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.common.tracking.impl.PreciseCompactGPSFixMovingImpl.PreciseCompactPosition; +import com.sap.sailing.domain.common.windfinder.SpotDTO; import com.sap.sailing.domain.igtimiadapter.Account; import com.sap.sailing.domain.igtimiadapter.IgtimiConnection; import com.sap.sailing.domain.igtimiadapter.IgtimiConnectionFactory; @@ -297,6 +335,7 @@ 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.MarkPassingManeuver; +import com.sap.sailing.domain.tracking.RaceHandle; import com.sap.sailing.domain.tracking.RaceTracker; import com.sap.sailing.domain.tracking.Track; import com.sap.sailing.domain.tracking.TrackedLeg; @@ -312,9 +351,12 @@ import com.sap.sailing.domain.tractracadapter.TracTracAdapter; import com.sap.sailing.domain.tractracadapter.TracTracAdapterFactory; import com.sap.sailing.domain.tractracadapter.TracTracConfiguration; import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants; +import com.sap.sailing.domain.windfinder.Spot; +import com.sap.sailing.domain.windfinder.WindFinderTrackerFactory; import com.sap.sailing.expeditionconnector.ExpeditionDeviceConfiguration; import com.sap.sailing.expeditionconnector.ExpeditionSensorDeviceIdentifier; import com.sap.sailing.expeditionconnector.ExpeditionTrackerFactory; +import com.sap.sailing.gwt.common.client.EventWindFinderUtil; import com.sap.sailing.gwt.server.HomeServiceUtil; import com.sap.sailing.gwt.ui.adminconsole.RaceLogSetTrackingTimesDTO; import com.sap.sailing.gwt.ui.client.SailingService; @@ -380,6 +422,7 @@ import com.sap.sailing.gwt.ui.shared.ServerConfigurationDTO; import com.sap.sailing.gwt.ui.shared.SidelineDTO; import com.sap.sailing.gwt.ui.shared.SimulatorResultsDTO; import com.sap.sailing.gwt.ui.shared.SimulatorWindDTO; +import com.sap.sailing.gwt.ui.shared.SliceRacePreperationDTO; import com.sap.sailing.gwt.ui.shared.SpeedWithBearingDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sailing.gwt.ui.shared.SwissTimingArchiveConfigurationDTO; @@ -456,6 +499,7 @@ import com.sap.sailing.server.operationaltransformation.UpdateSeries; import com.sap.sailing.server.operationaltransformation.UpdateServerConfiguration; import com.sap.sailing.server.operationaltransformation.UpdateSpecificRegatta; import com.sap.sailing.server.simulation.SimulationService; +import com.sap.sailing.server.util.WaitForTrackedRaceUtil; import com.sap.sailing.simulator.Path; import com.sap.sailing.simulator.PolarDiagram; import com.sap.sailing.simulator.SimulationResults; @@ -545,6 +589,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private final ServiceTracker scoreCorrectionProviderServiceTracker; private final ServiceTracker competitorProviderServiceTracker; + + private final ServiceTracker windFinderTrackerFactoryServiceTracker; private final MongoObjectFactory mongoObjectFactory; @@ -604,6 +650,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S Activator activator = Activator.getInstance(); quickRanksLiveCache = new QuickRanksLiveCache(this); racingEventServiceTracker = ServiceTrackerFactory.createAndOpen(context, RacingEventService.class); + windFinderTrackerFactoryServiceTracker = ServiceTrackerFactory.createAndOpen(context, WindFinderTrackerFactory.class); replicationServiceTracker = ServiceTrackerFactory.createAndOpen(context, ReplicationService.class); resultUrlRegistryServiceTracker = ServiceTrackerFactory.createAndOpen(context, ResultUrlRegistry.class); swissTimingAdapterTracker = ServiceTrackerFactory.createAndOpen(context, SwissTimingAdapterFactory.class); @@ -2986,10 +3033,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } break; case BEAT_ANGLE: - if (trackedLeg != null) { - Bearing beatAngle = trackedLeg.getBeatAngle(timePoint, cache); - result = beatAngle == null ? null : Math.abs(beatAngle.getDegrees()); - } + Bearing twa = trackedRace.getTWA(competitor, timePoint, cache); + result = twa == null? null:twa.getDegrees(); break; case CURRENT_HEEL_IN_DEGREES: { result = getBravoBearingInDegrees(BravoFixTrack::getHeel, trackedRace, competitor, timePoint); @@ -3664,7 +3709,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public EventDTO updateEvent(UUID eventId, String eventName, String eventDescription, Date startDate, Date endDate, VenueDTO venue, boolean isPublic, Iterable leaderboardGroupIds, String officialWebsiteURLString, String baseURLAsString, - Map sailorsInfoWebsiteURLsByLocaleName, Iterable images, Iterable videos) throws MalformedURLException, UnauthorizedException { + Map sailorsInfoWebsiteURLsByLocaleName, Iterable images, Iterable videos, + Iterable windFinderReviewedSpotCollectionIds) throws MalformedURLException, UnauthorizedException { if (SecurityUtils.getSubject().isPermitted( ShiroPermissionBuilderImpl.getInstance().getPermission(Event.class, DefaultActions.EDIT, eventId.toString()))) { TimePoint startTimePoint = startDate != null ? new MillisecondsTimePoint(startDate) : null; @@ -3676,7 +3722,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S List eventVideos = convertToVideos(videos); getService().apply( new UpdateEvent(eventId, eventName, eventDescription, startTimePoint, endTimePoint, venue.getName(), - isPublic, leaderboardGroupIds, officialWebsiteURL, baseURL, sailorsInfoWebsiteURLs, eventImages, eventVideos)); + isPublic, leaderboardGroupIds, officialWebsiteURL, baseURL, sailorsInfoWebsiteURLs, eventImages, + eventVideos, windFinderReviewedSpotCollectionIds)); return getEventById(eventId, false); } throw new UnauthorizedException("You are not permitted to edit event " + eventId); @@ -3959,7 +4006,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S eventDTO.venue.getCourseAreas().add(courseAreaDTO); } for (LeaderboardGroup lg : event.getLeaderboardGroups()) { - eventDTO.addLeaderboardGroup(convertToLeaderboardGroupDTO(lg, /* withGeoLocationData */false, withStatisticalData)); + eventDTO.addLeaderboardGroup(convertToLeaderboardGroupDTO(lg, /* withGeoLocationData */ false, withStatisticalData)); + } + eventDTO.setWindFinderReviewedSpotsCollection(event.getWindFinderReviewedSpotsCollectionIds()); + final WindFinderTrackerFactory windFinderTrackerFactory = windFinderTrackerFactoryServiceTracker.getService(); + if (windFinderTrackerFactory != null) { + eventDTO.setAllWindFinderSpotsUsedByEvent(new EventWindFinderUtil().getWindFinderSpotsToConsider(event, + windFinderTrackerFactory, /* useCachedSpotsForTrackedRaces */ false)); } final SecurityDTOFactory securityDTOFactory = new SecurityDTOFactory(); final Map fromOriginalToStrippedDownTenant = new HashMap<>(); @@ -4572,7 +4625,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } updateEvent(newEvent.id, newEvent.getName(), description, newEvent.startDate, newEvent.endDate, newEvent.venue, newEvent.isPublic, eventLeaderboardGroupUUIDs, newEvent.getOfficialWebsiteURL(), - newEvent.getBaseURL(), newEvent.getSailorsInfoWebsiteURLs(), newEvent.getImages(), newEvent.getVideos()); + newEvent.getBaseURL(), newEvent.getSailorsInfoWebsiteURLs(), newEvent.getImages(), newEvent.getVideos(), + newEvent.getWindFinderReviewedSpotsCollectionIds()); } @Override @@ -6964,45 +7018,396 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } } - public List getRaceDisplayNamesFromLeaderboard(String leaderboardName,List raceColumnNames) throws NotFoundException{ - Leaderboard leaderboard=this.getLeaderboardByName(leaderboardName); - List result=new ArrayList<>(); + public List getRaceDisplayNamesFromLeaderboard(String leaderboardName,List raceColumnNames) throws NotFoundException { + Leaderboard leaderboard = this.getLeaderboardByName(leaderboardName); + List result = new ArrayList<>(); for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { - if(raceColumn.hasTrackedRaces()){ + if (raceColumn.hasTrackedRaces()) { if (raceColumnNames.contains(raceColumn.getName())) { for (Fleet fleet : raceColumn.getFleets()) { - if(raceColumn.getTrackedRace(fleet) != null && raceColumn.getTrackedRace(fleet).getRaceIdentifier()!=null){ + if(raceColumn.getTrackedRace(fleet) != null && raceColumn.getTrackedRace(fleet).getRaceIdentifier()!=null) { result.add(raceColumn.getTrackedRace(fleet).getRaceIdentifier().getRaceName()); } } } - }else{ + } else { break; } } - if(result.size()==raceColumnNames.size()*Util.size(leaderboard.getRaceColumnByName(raceColumnNames.get(0)).getFleets())){ + if (result.size()==raceColumnNames.size()*Util.size(leaderboard.getRaceColumnByName(raceColumnNames.get(0)).getFleets())) { return result; } result.clear(); - for (RaceColumn raceColumn : leaderboard.getRaceColumns()){ - for(Fleet fleet: raceColumn.getFleets()){ - NavigableSet set=raceColumn.getRaceLog(fleet).getUnrevokedEvents(); - for (RaceLogEvent raceLogEvent : set) { - if(raceLogEvent instanceof RaceLogDenoteForTrackingEvent){ - RaceLogDenoteForTrackingEvent denoteEvent = (RaceLogDenoteForTrackingEvent) raceLogEvent; - result.add(denoteEvent.getRaceName()); - break; - } + for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { + for (Fleet fleet: raceColumn.getFleets()) { + NavigableSet set=raceColumn.getRaceLog(fleet).getUnrevokedEvents(); + for (RaceLogEvent raceLogEvent : set) { + if (raceLogEvent instanceof RaceLogDenoteForTrackingEvent) { + RaceLogDenoteForTrackingEvent denoteEvent = (RaceLogDenoteForTrackingEvent) raceLogEvent; + result.add(denoteEvent.getRaceName()); + break; } } + } } - if(result.size()==raceColumnNames.size()*Util.size(leaderboard.getRaceColumnByName(raceColumnNames.get(0)).getFleets())){ + if (result.size()==raceColumnNames.size()*Util.size(leaderboard.getRaceColumnByName(raceColumnNames.get(0)).getFleets())) { return result; } result.clear(); - for(int count=1;count<=raceColumnNames.size()*Util.size(leaderboard.getRaceColumnByName(raceColumnNames.get(0)).getFleets());count++){ + for (int count=1;count<=raceColumnNames.size()*Util.size(leaderboard.getRaceColumnByName(raceColumnNames.get(0)).getFleets());count++) { result.add("Race "+count); } return result; } + + @Override + public SpotDTO getWindFinderSpot(String spotId) throws MalformedURLException, IOException, org.json.simple.parser.ParseException, InterruptedException, ExecutionException { + final SpotDTO result; + final WindFinderTrackerFactory windFinderTrackerFactory = windFinderTrackerFactoryServiceTracker.getService(); + if (windFinderTrackerFactory != null) { + final Spot spot = windFinderTrackerFactory.getSpotById(spotId, /* cached */ false); + if (spot != null) { + result = new SpotDTO(spot); + } else { + result = null; + } + } else { + result = null; + } + return result; + } + + @Override + public boolean canSliceRace(RegattaAndRaceIdentifier raceIdentifier) { + final Regatta regatta = getService().getRegattaByName(raceIdentifier.getRegattaName()); + final Leaderboard regattaLeaderboard = getService().getLeaderboardByName(raceIdentifier.getRegattaName()); + final DynamicTrackedRace trackedRace = getService().getTrackedRace(raceIdentifier); + final boolean result; + if (regatta == null || !(regattaLeaderboard instanceof RegattaLeaderboard) || trackedRace == null + || trackedRace.getStartOfTracking() == null || !isSmartphoneTrackingEnabled(trackedRace)) { + result = false; + } else { + final Pair raceColumnAndFleetOfRaceToSlice = regattaLeaderboard.getRaceColumnAndFleet(trackedRace); + result = (raceColumnAndFleetOfRaceToSlice != null); // is the TrackedRace associated to the given RegattaLeaderboard? + } + return result; + } + + private boolean isSmartphoneTrackingEnabled(DynamicTrackedRace trackedRace) { + boolean result = false; + for (RaceLog raceLog : trackedRace.getAttachedRaceLogs()) { + RaceLogTrackingState raceLogTrackingState = new RaceLogTrackingStateAnalyzer(raceLog).analyze(); + if (raceLogTrackingState.isTracking()) { + result = true; + break; + } + } + return result; + } + + @Override + public SliceRacePreperationDTO prepareForSlicingOfRace(final RegattaAndRaceIdentifier raceIdentifier) { + final Leaderboard regattaLeaderboard = getService().getLeaderboardByName(raceIdentifier.getRegattaName()); + String prefix = null; + int currentCount = 0; + final Pattern pattern = Pattern.compile("^([a-zA-Z_ -]+)([0-9]+)$"); + final HashSet alreadyUsedRaceNames = new HashSet<>(); + for (RaceColumn column : regattaLeaderboard.getRaceColumns()) { + alreadyUsedRaceNames.add(column.getName()); + final Matcher matcher = pattern.matcher(column.getName()); + if (matcher.matches()) { + prefix = matcher.group(1); + currentCount = Integer.parseInt(matcher.group(2)); + } + } + if (prefix == null) { + prefix = "R"; + } + currentCount++; + return new SliceRacePreperationDTO(prefix + currentCount, alreadyUsedRaceNames); + } + + @Override + public RegattaAndRaceIdentifier sliceRace(RegattaAndRaceIdentifier raceIdentifier, String newRaceColumnName, + TimePoint sliceFrom, TimePoint sliceTo) { + SecurityUtils.getSubject().checkPermission(Permission.MANAGE_TRACKED_RACES.getStringPermissionForObjects(Mode.UPDATE)); + if (!canSliceRace(raceIdentifier)) { + throw new RuntimeException("Can not slice race"); + } + final String trackedRaceName = newRaceColumnName; + final RegattaIdentifier regattaIdentifier = new RegattaName(raceIdentifier.getRegattaName()); + final Regatta regatta = getService().getRegatta(regattaIdentifier); + if (regatta.getRaceColumnByName(newRaceColumnName) != null) { + throw new RuntimeException("The race column name is already used in the given regatta"); + } + final DynamicTrackedRace trackedRaceToSlice = getService().getTrackedRace(raceIdentifier); + final TimePoint startOfTrackingOfRaceToSlice = trackedRaceToSlice.getStartOfTracking(); + final TimePoint endOfTrackingOfRaceToSlice = trackedRaceToSlice.getEndOfTracking(); + if (sliceFrom == null || sliceTo == null || startOfTrackingOfRaceToSlice.after(sliceFrom) + || (endOfTrackingOfRaceToSlice != null && endOfTrackingOfRaceToSlice.before(sliceTo))) { + throw new RuntimeException("The TimeRange to slice is not part of the race"); + } + final RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) getService().getLeaderboardByName(raceIdentifier.getRegattaName()); + final Pair raceColumnAndFleetOfRaceToSlice = regattaLeaderboard.getRaceColumnAndFleet(trackedRaceToSlice); + // RaceColumns in a RegattaLeaderboard are always RaceColumnInSeries instances + final RaceColumnInSeries raceColumnOfRaceToSlice = (RaceColumnInSeries) raceColumnAndFleetOfRaceToSlice.getA(); + final Fleet fleet = raceColumnAndFleetOfRaceToSlice.getB(); + final Series series = raceColumnOfRaceToSlice.getSeries(); + final RaceLog raceLogOfRaceToSlice = raceColumnOfRaceToSlice.getRaceLog(fleet); + getService().apply(new AddColumnToSeries(regattaIdentifier, series.getName(), newRaceColumnName)); + final RaceColumn raceColumn = regattaLeaderboard.getRaceColumnByName(newRaceColumnName); + final RaceLog raceLog = raceColumn.getRaceLog(fleet); + final AbstractLogEventAuthor author = getService().getServerAuthor(); + final TimePoint startOfTracking = sliceFrom; + final TimePoint endOfTracking = sliceTo; + raceLog.add(new RaceLogStartOfTrackingEventImpl(startOfTracking, author, raceLog.getCurrentPassId())); + raceLog.add(new RaceLogEndOfTrackingEventImpl(endOfTracking, author, raceLog.getCurrentPassId())); + final TimeRange timeRange = new TimeRangeImpl(sliceFrom, sliceTo); + final StartTimeFinderResult startTimeFinderResult = new StartTimeFinder(getService(), raceLogOfRaceToSlice).analyze(); + final TimePoint startTime = startTimeFinderResult.getStartTime(); + final boolean hasStartTime = startTime != null && timeRange.includes(startTime); + final boolean dependentStartTime = startTimeFinderResult.isDependentStartTime(); + final boolean hasFinishingTime; + final boolean hasFinishedTime; + if (hasStartTime) { + final TimePoint finishingTime = new FinishingTimeFinder(raceLog).analyze(); + hasFinishingTime = finishingTime != null && timeRange.includes(finishingTime); + if (hasFinishingTime) { + final TimePoint finishedTime = new FinishedTimeFinder(raceLog).analyze(); + hasFinishedTime = finishedTime != null && timeRange.includes(finishedTime); + } else { + hasFinishedTime = false; + } + } else { + hasFinishingTime = false; + hasFinishedTime = false; + } + + // Only wind fixes in the new tracking interval as well as the best fallback fixes are added to the new RaceLog + final LogEventTimeRangeWithFallbackFilter windFixEvents = new LogEventTimeRangeWithFallbackFilter<>( + timeRange); + raceLogOfRaceToSlice.lockForRead(); + try { + for (RaceLogEvent raceLogEvent : raceLogOfRaceToSlice.getUnrevokedEvents()) { + raceLogEvent.accept(new BaseRaceLogEventVisitor() { + @Override + public void visit(RaceLogDependentStartTimeEvent event) { + if (dependentStartTime && isLatestPass(event)) { + raceLog.add(new RaceLogDependentStartTimeEventImpl(event.getCreatedAt(), + event.getLogicalTimePoint(), event.getAuthor(), UUID.randomUUID(), + raceLog.getCurrentPassId(), event.getDependentOnRaceIdentifier(), + event.getStartTimeDifference(), event.getNextStatus())); + } + } + + @Override + public void visit(RaceLogStartTimeEvent event) { + if (!dependentStartTime && isLatestPass(event)) { + raceLog.add(new RaceLogStartTimeEventImpl(event.getCreatedAt(), event.getLogicalTimePoint(), + event.getAuthor(), UUID.randomUUID(), raceLog.getCurrentPassId(), event.getStartTime(), + event.getNextStatus())); + } + } + + @Override + public void visit(RaceLogRegisterCompetitorEvent event) { + raceLog.add(new RaceLogRegisterCompetitorEventImpl(event.getCreatedAt(), + event.getLogicalTimePoint(), event.getAuthor(), UUID.randomUUID(), + raceLog.getCurrentPassId(), event.getCompetitor())); + } + + @Override + public void visit(RaceLogWindFixEvent event) { + windFixEvents.addEvent(event); + } + + @Override + public void visit(RaceLogUseCompetitorsFromRaceLogEvent event) { + raceLog.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(event.getCreatedAt(), event.getAuthor(), + event.getLogicalTimePoint(), UUID.randomUUID(), raceLog.getCurrentPassId())); + } + + @Override + public void visit(RaceLogCourseDesignChangedEvent event) { + // Only course changes up to the end of the sliced race are copied + // to be able to cut several training races where the course is changed + // between races in preparation for the next race. In this case the course + // for a sliced is the course that was valid at the time that race took place. + if (event.getLogicalTimePoint().before(sliceTo)) { + raceLog.add(new RaceLogCourseDesignChangedEventImpl(event.getCreatedAt(), + event.getLogicalTimePoint(), event.getAuthor(), UUID.randomUUID(), + raceLog.getCurrentPassId(), event.getCourseDesign(), event.getCourseDesignerMode())); + } + } + + @Override + public void visit(RaceLogFlagEvent event) { + if (hasStartTime && isLatestPass(event) && !event.getLogicalTimePoint().after(sliceTo)) { + raceLog.add(new RaceLogFlagEventImpl(event.getCreatedAt(), event.getLogicalTimePoint(), + event.getAuthor(), UUID.randomUUID(), raceLog.getCurrentPassId(), event.getUpperFlag(), + event.getLowerFlag(), event.isDisplayed())); + } + } + + @Override + public void visit(RaceLogStartProcedureChangedEvent event) { + if (hasStartTime && isLatestPass(event)) { + raceLog.add(new RaceLogStartProcedureChangedEventImpl(event.getCreatedAt(), + event.getLogicalTimePoint(), event.getAuthor(), UUID.randomUUID(), + raceLog.getCurrentPassId(), event.getStartProcedureType())); + } + } + + @Override + public void visit(RaceLogFinishPositioningConfirmedEvent event) { + if (hasFinishedTime && isLatestPass(event)) { + raceLog.add(new RaceLogFinishPositioningConfirmedEventImpl(event.getCreatedAt(), + event.getLogicalTimePoint(), event.getAuthor(), UUID.randomUUID(), + raceLog.getCurrentPassId(), event.getPositionedCompetitorsIDsNamesMaxPointsReasons())); + } + } + + @Override + public void visit(RaceLogFinishPositioningListChangedEvent event) { + if (hasFinishedTime && isLatestPass(event)) { + raceLog.add(new RaceLogFinishPositioningListChangedEventImpl(event.getCreatedAt(), + event.getLogicalTimePoint(), event.getAuthor(), UUID.randomUUID(), + raceLog.getCurrentPassId(), event.getPositionedCompetitorsIDsNamesMaxPointsReasons())); + } + } + + @Override + public void visit(RaceLogFixedMarkPassingEvent event) { + if (hasStartTime && isLatestPass(event) && timeRange.includes(event.getTimePointOfFixedPassing())) { + raceLog.add(new RaceLogFixedMarkPassingEventImpl(event.getCreatedAt(), + event.getLogicalTimePoint(), event.getAuthor(), UUID.randomUUID(), + event.getInvolvedBoats(), raceLog.getCurrentPassId(), + event.getTimePointOfFixedPassing(), event.getZeroBasedIndexOfPassedWaypoint())); + } + } + + @Override + public void visit(RaceLogSuppressedMarkPassingsEvent event) { + if (hasStartTime && isLatestPass(event) && timeRange.includes(event.getLogicalTimePoint())) { + raceLog.add(new RaceLogSuppressedMarkPassingsEventImpl(event.getCreatedAt(), + event.getLogicalTimePoint(), event.getAuthor(), UUID.randomUUID(), + event.getInvolvedBoats(), raceLog.getCurrentPassId(), + event.getZeroBasedIndexOfFirstSuppressedWaypoint())); + } + } + + @Override + public void visit(RaceLogProtestStartTimeEvent event) { + if (hasFinishedTime && isLatestPass(event)) { + raceLog.add(new RaceLogProtestStartTimeEventImpl(event.getCreatedAt(), + event.getLogicalTimePoint(), event.getAuthor(), UUID.randomUUID(), + raceLog.getCurrentPassId(), event.getProtestTime())); + } + } + + @Override + public void visit(RaceLogAdditionalScoringInformationEvent event) { + if (hasFinishedTime && isLatestPass(event)) { + raceLog.add(new RaceLogAdditionalScoringInformationEventImpl(event.getCreatedAt(), + event.getLogicalTimePoint(), event.getAuthor(), UUID.randomUUID(), + raceLog.getCurrentPassId(), event.getType())); + } + } + + @Override + public void visit(RaceLogPathfinderEvent event) { + if (hasStartTime && isLatestPass(event)) { + raceLog.add(new RaceLogPathfinderEventImpl(event.getCreatedAt(), event.getLogicalTimePoint(), + event.getAuthor(), UUID.randomUUID(), raceLog.getCurrentPassId(), + event.getPathfinderId())); + } + } + + @Override + public void visit(RaceLogGateLineOpeningTimeEvent event) { + if (hasStartTime && isLatestPass(event)) { + raceLog.add(new RaceLogGateLineOpeningTimeEventImpl(event.getCreatedAt(), + event.getLogicalTimePoint(), event.getAuthor(), UUID.randomUUID(), + raceLog.getCurrentPassId(), event.getGateLineOpeningTimes().getGateLaunchStopTime(), + event.getGateLineOpeningTimes().getGolfDownTime())); + } + } + + @Override + public void visit(RaceLogRaceStatusEvent event) { + if (isLatestPass(event) && !(event instanceof RaceLogDependentStartTimeEvent) + && !(event instanceof RaceLogStartTimeEvent)) { + if ((hasStartTime + && event.getNextStatus().getOrderNumber() <= RaceLogRaceStatus.RUNNING.getOrderNumber()) + || (hasFinishingTime && event.getNextStatus() == RaceLogRaceStatus.FINISHING) + || (hasFinishedTime && event.getNextStatus() == RaceLogRaceStatus.FINISHED)) { + raceLog.add(new RaceLogRaceStatusEventImpl(event.getCreatedAt(), event.getLogicalTimePoint(), + event.getAuthor(), UUID.randomUUID(), raceLog.getCurrentPassId(), + event.getNextStatus())); + } + } + } + + private boolean isLatestPass(RaceLogEvent event) { + return event.getPassId() == raceLogOfRaceToSlice.getCurrentPassId(); + } + }); + } + } finally { + raceLogOfRaceToSlice.unlockAfterRead(); + } + windFixEvents.getFilteredEvents() + .forEach(event -> raceLog.add(new RaceLogWindFixEventImpl(event.getCreatedAt(), + event.getLogicalTimePoint(), event.getAuthor(), UUID.randomUUID(), raceLog.getCurrentPassId(), + event.getWindFix(), event.isMagnetic()))); + final TimePoint startTrackingTimePoint = MillisecondsTimePoint.now(); + // this ensures that the events consistently have different timepoints to ensure a consistent result of the state analysis + // that's why we can't just call adapter.denoteRaceForRaceLogTracking + final TimePoint denotationTimePoint = startTrackingTimePoint.minus(1); + raceLog.add(new RaceLogDenoteForTrackingEventImpl(denotationTimePoint, + author, raceLog.getCurrentPassId(), trackedRaceName, regatta.getBoatClass(), UUID.randomUUID())); + raceLog.add(new RaceLogStartTrackingEventImpl(startTrackingTimePoint, author, raceLog.getCurrentPassId())); + try { + final RaceHandle raceHandle = getRaceLogTrackingAdapter().startTracking(getService(), regattaLeaderboard, + raceColumn, fleet, /* trackWind */ true, /* correctWindDirectionByMagneticDeclination */ true); + + // wait for the RaceDefinition to be created + raceHandle.getRace(); + + final DynamicTrackedRace trackedRace = WaitForTrackedRaceUtil.waitForTrackedRace(raceColumn, fleet, 10); + if (trackedRace == null) { + throw new IllegalStateException("Could not obtain sliced race"); + } + for (WindSource windSourceToCopy : trackedRaceToSlice.getWindSources()) { + if (windSourceToCopy.canBeStored()) { + final WindTrack windTrackToCopyFrom = trackedRaceToSlice.getOrCreateWindTrack(windSourceToCopy); + windTrackToCopyFrom.lockForRead(); + try { + for (Wind windToCopy : windTrackToCopyFrom.getFixes(startOfTracking, true, endOfTracking, true)) { + trackedRace.recordWind(windToCopy, windSourceToCopy); + } + } finally { + windTrackToCopyFrom.unlockAfterRead(); + } + } + } + + final Iterable mediaTracksForOriginalRace = getService().getMediaTracksForRace(raceIdentifier); + for (MediaTrack mediaTrack : mediaTracksForOriginalRace) { + if (mediaTrack.overlapsWith(sliceFrom, sliceTo)) { + final Set assignedRaces = new HashSet<>(mediaTrack.assignedRaces); + assignedRaces.add(trackedRace.getRaceIdentifier()); + // we can't just use the original instance and add the Race due to the fact that this leads to + // assignedRaces being empty afterwards. + final MediaTrack mediaTrackToSave = new MediaTrack(mediaTrack.dbId, mediaTrack.title, + mediaTrack.url, mediaTrack.startTime, mediaTrack.duration, mediaTrack.mimeType, + assignedRaces); + getService().mediaTrackAssignedRacesChanged(mediaTrackToSave); + } + } + return trackedRace.getRaceIdentifier(); + } catch (Exception e) { + throw new RuntimeException("Error while slicing race", e); + } + } } 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 070f415a7da..dc3d607824b 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 @@ -1,11 +1,16 @@ package com.sap.sailing.gwt.ui.shared; 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.WindSource; +import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO; +import com.sap.sailing.domain.common.windfinder.SpotDTO; +import com.sap.sse.common.Util; public class EventDTO extends EventBaseDTO { private static final long serialVersionUID = -7100030301376959817L; @@ -13,6 +18,9 @@ public class EventDTO extends EventBaseDTO { private Date currentServerTime; private List leaderboardGroups; // keeps the more specific type accessible in a type-safe way + + private List windFinderReviewedSpotsCollectionIds; + private List allWindFinderSpotIdsUsedByEvent; public EventDTO() { this(new ArrayList()); @@ -80,5 +88,57 @@ public class EventDTO extends EventBaseDTO { } return null; } + + /** + * An event may happen in the vicinity of one or more WindFinder (https://www.windfinder.com) weather + * stations. Which ones those are can be defined using {@link #setWindFinderReviewedSpotsCollection(Iterable)}, + * and this getter returns the IDs last set. + * + * @return always a valid {@link Iterable} which may, though, be empty + */ + public Iterable getWindFinderReviewedSpotsCollectionIds() { + final Iterable result; + if (windFinderReviewedSpotsCollectionIds == null) { + result = Collections.emptySet(); + } else { + result = windFinderReviewedSpotsCollectionIds; + } + return result; + } + /** + * Set the IDs of the reviewed WindFinder spot collections to consider during this event. + * Setting this to a non-empty value shall lead to a corresponding display of a WindFinder + * logo / link on the event's UI representation. + */ + public void setWindFinderReviewedSpotsCollection(Iterable reviewedSpotsCollectionId) { + windFinderReviewedSpotsCollectionIds = new ArrayList<>(); + if (reviewedSpotsCollectionId != null) { + Util.addAll(reviewedSpotsCollectionId, windFinderReviewedSpotsCollectionIds); + } + } + + /** + * In addition to the wind finder spot collections specified by this event explicitly (see + * {@link #getWindFinderReviewedSpotsCollectionIds()}), this method may return additional spot collection IDs based + * on the tracked races reachable from this event's associated leaderboard groups and their wind sources. The + * {@link WindSource#getId() wind source IDs} of all wind sources of type {@link WindSourceType#WINDFINDER} will be + * collected and returned. + */ + public Iterable getAllWindFinderSpotIdsUsedByEvent() { + final Iterable result; + if (allWindFinderSpotIdsUsedByEvent == null) { + result = Collections.emptySet(); + } else { + result = allWindFinderSpotIdsUsedByEvent; + } + return result; + } + + public void setAllWindFinderSpotsUsedByEvent(Iterable windFinderSpots) { + this.allWindFinderSpotIdsUsedByEvent = new ArrayList<>(); + if (windFinderSpots != null) { + Util.addAll(windFinderSpots, this.allWindFinderSpotIdsUsedByEvent); + } + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SliceRacePreperationDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SliceRacePreperationDTO.java new file mode 100644 index 00000000000..cb9f8824ab6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/SliceRacePreperationDTO.java @@ -0,0 +1,34 @@ +package com.sap.sailing.gwt.ui.shared; + +import java.util.HashSet; +import java.util.Set; + +import com.google.gwt.user.client.rpc.IsSerializable; +import com.sap.sse.common.Util; + +/** + * DTO that carries data that is needed to show the race slicing UI. + * It contains a proposed race name and a list of already used race names. + */ +public class SliceRacePreperationDTO implements IsSerializable { + + private String proposedRaceName; + private HashSet alreadyUsedNames; + + public SliceRacePreperationDTO(String proposedRaceName, Iterable alreadyUsedNames) { + this.proposedRaceName = proposedRaceName; + this.alreadyUsedNames = new HashSet<>(); + Util.addAll(alreadyUsedNames, this.alreadyUsedNames); + } + + @Deprecated + SliceRacePreperationDTO() {} + + public String getProposedRaceName() { + return proposedRaceName; + } + + public Set getAlreadyUsedNames() { + return alreadyUsedNames; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/windfinder-logo.svg b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/windfinder-logo.svg new file mode 100644 index 00000000000..7234e132c97 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/windfinder-logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadRaceLogEventsTest.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadRaceLogEventsTest.java index faf5c6d5423..4eb5c543b50 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadRaceLogEventsTest.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/StoreAndLoadRaceLogEventsTest.java @@ -20,14 +20,12 @@ import com.mongodb.DBObject; import com.mongodb.MongoException; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogPassChangeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogRaceStatusEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; -import com.sap.sailing.domain.abstractlog.race.impl.RaceLogCourseAreaChangeEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogFlagEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogPassChangeEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogRaceStatusEventImpl; @@ -120,20 +118,6 @@ public class StoreAndLoadRaceLogEventsTest extends AbstractMongoDBTest { assertEquals(isDisplayed, actualEvent.isDisplayed()); } - @Test - public void testStoreAndLoadCourseAreaChangedEvent() { - Serializable courseAreaId = UUID.randomUUID(); - - RaceLogCourseAreaChangedEvent expectedEvent = new RaceLogCourseAreaChangeEventImpl(MillisecondsTimePoint.now(), - expectedEventTime, author, expectedId, expectedPassId, courseAreaId); - - DBObject dbObject = mongoFactory.storeRaceLogEntry(logIdentifier, expectedEvent); - RaceLogCourseAreaChangedEvent actualEvent = loadEvent(dbObject); - - assertBaseFields(expectedEvent, actualEvent); - assertEquals(courseAreaId, actualEvent.getCourseAreaId()); - } - @Test public void testStoreAndLoadPassChangeEvent() { RaceLogPassChangeEvent expectedEvent = new RaceLogPassChangeEventImpl(MillisecondsTimePoint.now(), diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java index 7a28a46958a..5b7c550d75c 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInLeaderboards.java @@ -24,7 +24,6 @@ import com.sap.sailing.domain.abstractlog.race.CompetitorResult; import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState; import com.sap.sailing.domain.abstractlog.race.CompetitorResults; import com.sap.sailing.domain.abstractlog.race.RaceLog; -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; @@ -42,7 +41,6 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; import com.sap.sailing.domain.abstractlog.race.SimpleRaceLogIdentifier; import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultImpl; import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultsImpl; -import com.sap.sailing.domain.abstractlog.race.impl.RaceLogCourseAreaChangeEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogCourseDesignChangedEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogDependentStartTimeEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEventComparator; @@ -430,27 +428,6 @@ public class TestStoringAndRetrievingRaceLogInLeaderboards extends RaceLogMongoD } } - @Test - public void testStoreAndRetrieveSimpleLeaderboardWithRaceLogCourseAreaChangeEvent() { - final UUID uuid = UUID.randomUUID(); - RaceLogCourseAreaChangedEvent event = new RaceLogCourseAreaChangeEventImpl(now, author, 0, uuid); - addAndStoreRaceLogEvent(leaderboard, raceColumnName, event); - RaceLog loadedRaceLog = retrieveRaceLog(); - loadedRaceLog.lockForRead(); - try { - RaceLogEvent loadedEvent = loadedRaceLog.getFirstRawFix(); - RaceLogCourseAreaChangedEvent courseAreaEvent = (RaceLogCourseAreaChangedEvent) loadedEvent; - assertEquals(event.getCreatedAt(), courseAreaEvent.getCreatedAt()); - assertEquals(event.getLogicalTimePoint(), courseAreaEvent.getLogicalTimePoint()); - assertEquals(event.getPassId(), courseAreaEvent.getPassId()); - assertEquals(event.getId(), courseAreaEvent.getId()); - assertEquals(event.getCourseAreaId(), courseAreaEvent.getCourseAreaId()); - assertEquals(1, Util.size(loadedRaceLog.getFixes())); - } finally { - loadedRaceLog.unlockAfterRead(); - } - } - @Test public void testStoreAndRetrieveSimpleLeaderboardWithRaceLogRaceStatusEvent() { diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInRegatta.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInRegatta.java index f35ff25cace..6670ad26ebb 100644 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInRegatta.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingRaceLogInRegatta.java @@ -17,7 +17,6 @@ import com.mongodb.MongoException; import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState; import com.sap.sailing.domain.abstractlog.race.CompetitorResults; import com.sap.sailing.domain.abstractlog.race.RaceLog; -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmedEvent; @@ -33,7 +32,6 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultImpl; import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultsImpl; -import com.sap.sailing.domain.abstractlog.race.impl.RaceLogCourseAreaChangeEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogCourseDesignChangedEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogFinishPositioningConfirmedEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogFinishPositioningListChangedEventImpl; @@ -298,29 +296,6 @@ public class TestStoringAndRetrievingRaceLogInRegatta extends AbstractTestStorin MongoObjectFactoryImpl factoryImpl = (MongoObjectFactoryImpl) mongoObjectFactory; factoryImpl.getRaceLogCollection().insert(raceLogResult); } - - @Test - public void testStoreAndRetrieveRegattaWithRaceLogCourseAreaChangeEvent() { - final UUID uuid = UUID.randomUUID(); - RaceLogCourseAreaChangedEvent event = new RaceLogCourseAreaChangeEventImpl(now, author, 0, uuid); - - addAndStoreRaceLogEvent(regatta, raceColumnName, event); - - RaceLog loadedRaceLog = retrieveRaceLog(); - - loadedRaceLog.lockForRead(); - try { - RaceLogEvent loadedEvent = loadedRaceLog.getFirstRawFix(); - RaceLogCourseAreaChangedEvent courseAreaEvent = (RaceLogCourseAreaChangedEvent) loadedEvent; - assertEquals(event.getLogicalTimePoint(), courseAreaEvent.getLogicalTimePoint()); - assertEquals(event.getPassId(), courseAreaEvent.getPassId()); - assertEquals(event.getId(), courseAreaEvent.getId()); - assertEquals(event.getCourseAreaId(), courseAreaEvent.getCourseAreaId()); - assertEquals(1, Util.size(loadedRaceLog.getFixes())); - } finally { - loadedRaceLog.unlockAfterRead(); - } - } @Test public void testStoreAndRetrieveRegattaWithRaceLogRaceStatusEvent() { diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java index 08f103dd8c4..2d3fa5db46f 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/AngleAndSpeedRegression.java @@ -26,7 +26,7 @@ import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util.Pair; /** - * This container has two regressions. One for boatSpeed over windSpeed and one for beatAngle over windSpeed + * This container has two regressions. One for boatSpeed over windSpeed and one for TWA (true wind angle) over windSpeed * estimations.

    * * It can return speed and angle for a given windSpeed and should only be used for restricted sets of input data. There diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java index 5e5fd9a5ab9..7820a9ed828 100644 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/mining/GPSFixMovingWithPolarContext.java @@ -77,14 +77,10 @@ public class GPSFixMovingWithPolarContext implements LegTypePolarClusterKey, Ang private BearingWithConfidence computeTrueWindAngleAbsolute() { BearingWithConfidenceImpl result = null; Bearing bearing = null; - try { - TrackedLegOfCompetitor currentLeg = race.getCurrentLeg(competitor, fix.getTimePoint()); - if (currentLeg != null) { - Bearing realBearing = currentLeg.getBeatAngle(fix.getTimePoint()); - bearing = realBearing == null ? null : new DegreeBearingImpl(Math.abs(realBearing.getDegrees())); - } - } catch (NoWindException e) { - bearing = null; + TrackedLegOfCompetitor currentLeg = race.getCurrentLeg(competitor, fix.getTimePoint()); + if (currentLeg != null) { + Bearing realBearing = race.getTWA(competitor, fix.getTimePoint()); + bearing = realBearing == null ? null : new DegreeBearingImpl(Math.abs(realBearing.getDegrees())); } WindWithConfidence> wind = race.getWindWithConfidence(fix.getPosition(), fix.getTimePoint()); diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogCourseAreaChangedEventDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogCourseAreaChangedEventDeserializer.java deleted file mode 100644 index 9820f070d6a..00000000000 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogCourseAreaChangedEventDeserializer.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.sap.sailing.server.gateway.deserialization.racelog.impl; - -import java.io.Serializable; -import java.util.List; - -import org.json.simple.JSONObject; - -import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; -import com.sap.sailing.domain.abstractlog.race.impl.RaceLogCourseAreaChangeEventImpl; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; -import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; -import com.sap.sailing.server.gateway.deserialization.impl.Helpers; -import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogCourseAreaChangedEventSerializer; -import com.sap.sse.common.TimePoint; - -public class RaceLogCourseAreaChangedEventDeserializer extends BaseRaceLogEventDeserializer { - - public RaceLogCourseAreaChangedEventDeserializer(JsonDeserializer competitorDeserializer) { - super(competitorDeserializer); - } - - @Override - protected RaceLogEvent deserialize(JSONObject object, Serializable id, TimePoint createdAt, AbstractLogEventAuthor author, TimePoint timePoint, int passId, List competitors) - throws JsonDeserializationException { - Serializable courseAreaId = (Serializable) object.get(RaceLogCourseAreaChangedEventSerializer.FIELD_COURSE_AREA_ID); - return new RaceLogCourseAreaChangeEventImpl(createdAt, timePoint, author, id, - passId, Helpers.tryUuidConversion(courseAreaId)); - } - -} diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogEventDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogEventDeserializer.java index 0962ae5f77f..048608e5046 100755 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogEventDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogEventDeserializer.java @@ -20,7 +20,6 @@ import com.sap.sailing.server.gateway.deserialization.impl.PositionJsonDeseriali import com.sap.sailing.server.gateway.deserialization.impl.WindJsonDeserializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.BaseRaceLogEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogAdditionalScoringInformationSerializer; -import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogCourseAreaChangedEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogCourseDesignChangedEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogDenoteForTrackingEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogDependentStartTimeEventSerializer; @@ -61,7 +60,6 @@ public class RaceLogEventDeserializer implements JsonDeserializer new RaceLogStartTimeEventDeserializer(competitorDeserializer), new RaceLogDependentStartTimeEventDeserializer(competitorDeserializer), new RaceLogRaceStatusEventDeserializer(competitorDeserializer), - new RaceLogCourseAreaChangedEventDeserializer(competitorDeserializer), new RaceLogCourseDesignChangedEventDeserializer(competitorDeserializer, new CourseBaseDeserializer( new WaypointDeserializer( @@ -93,7 +91,6 @@ public class RaceLogEventDeserializer implements JsonDeserializer protected final JsonDeserializer startTimeEventDeserializer; protected final JsonDeserializer dependentStartTimeEventDeserializer; protected final JsonDeserializer raceStatusEventDeserializer; - protected final JsonDeserializer courseAreaChangedEventDeserializer; protected final JsonDeserializer courseDesignChangedEventDeserializer; protected final JsonDeserializer finishPositioningListChangedEventDeserializer; protected final JsonDeserializer finishPositioningConfirmedEventDeserializer; @@ -118,7 +115,6 @@ public class RaceLogEventDeserializer implements JsonDeserializer JsonDeserializer startTimeEventDeserializer, JsonDeserializer dependentStartTimeEventDeserializer, JsonDeserializer raceStatusEventDeserializer, - JsonDeserializer courseAreaChangedEventDeserializer, JsonDeserializer courseDesignChangedEventDeserializer, JsonDeserializer finishPositioningListChangedEventDeserializer, JsonDeserializer finishPositioningConfirmedEventDeserializer, @@ -142,7 +138,6 @@ public class RaceLogEventDeserializer implements JsonDeserializer this.startTimeEventDeserializer = startTimeEventDeserializer; this.dependentStartTimeEventDeserializer = dependentStartTimeEventDeserializer; this.raceStatusEventDeserializer = raceStatusEventDeserializer; - this.courseAreaChangedEventDeserializer = courseAreaChangedEventDeserializer; this.courseDesignChangedEventDeserializer = courseDesignChangedEventDeserializer; this.finishPositioningListChangedEventDeserializer = finishPositioningListChangedEventDeserializer; this.finishPositioningConfirmedEventDeserializer = finishPositioningConfirmedEventDeserializer; @@ -177,8 +172,6 @@ public class RaceLogEventDeserializer implements JsonDeserializer return raceStatusEventDeserializer; } else if (type.equals(RaceLogPassChangeEventSerializer.VALUE_CLASS)) { return passChangeEventDeserializer; - } else if (type.equals(RaceLogCourseAreaChangedEventSerializer.VALUE_CLASS)) { - return courseAreaChangedEventDeserializer; } else if (type.equals(RaceLogCourseDesignChangedEventSerializer.VALUE_CLASS)) { return courseDesignChangedEventDeserializer; } else if (type.equals(RaceLogFinishPositioningListChangedEventSerializer.VALUE_CLASS)) { diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogCourseAreaChangedEventSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogCourseAreaChangedEventSerializer.java deleted file mode 100644 index 76c041c8f84..00000000000 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogCourseAreaChangedEventSerializer.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.sap.sailing.server.gateway.serialization.racelog.impl; - -import org.json.simple.JSONObject; - -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.server.gateway.serialization.JsonSerializer; - -public class RaceLogCourseAreaChangedEventSerializer extends BaseRaceLogEventSerializer { - - public static final String VALUE_CLASS = RaceLogCourseAreaChangedEvent.class.getSimpleName(); - public static final String FIELD_COURSE_AREA_ID = "courseAreaId"; - - public RaceLogCourseAreaChangedEventSerializer( - JsonSerializer competitorSerializer) { - super(competitorSerializer); - } - - @Override - protected String getClassFieldValue() { - return VALUE_CLASS; - } - - @Override - public JSONObject serialize(RaceLogEvent object) { - RaceLogCourseAreaChangedEvent caChangedEvent = (RaceLogCourseAreaChangedEvent) object; - - JSONObject result = super.serialize(caChangedEvent); - result.put(FIELD_COURSE_AREA_ID, caChangedEvent.getCourseAreaId().toString()); - - return result; - } - -} diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogEventSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogEventSerializer.java index 68c6e58beae..70ac90e7c86 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogEventSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogEventSerializer.java @@ -2,7 +2,6 @@ package com.sap.sailing.server.gateway.serialization.racelog.impl; import org.json.simple.JSONObject; -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogDependentStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEndOfTrackingEvent; @@ -54,7 +53,6 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac new RaceLogFlagEventSerializer(competitorSerializer), new RaceLogStartTimeEventSerializer(competitorSerializer), new RaceLogRaceStatusEventSerializer(competitorSerializer), - new RaceLogCourseAreaChangedEventSerializer(competitorSerializer), new RaceLogPassChangeEventSerializer(competitorSerializer), new RaceLogCourseDesignChangedEventSerializer(competitorSerializer, new CourseBaseJsonSerializer( @@ -87,7 +85,6 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac private final JsonSerializer flagEventSerializer; private final JsonSerializer startTimeSerializer; private final JsonSerializer raceStatusSerializer; - private final JsonSerializer courseAreaChangedEventSerializer; private final JsonSerializer passChangedEventSerializer; private final JsonSerializer courseDesignChangedEventSerializer; private final JsonSerializer finishPositioningListChangedEventSerializer; @@ -116,7 +113,6 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac JsonSerializer flagEventSerializer, JsonSerializer startTimeSerializer, JsonSerializer raceStatusSerializer, - JsonSerializer courseAreaChangedEventSerializer, JsonSerializer passChangedEventSerializer, JsonSerializer courseDesignChangedEventSerializer, JsonSerializer finishPositioningListChangedEventSerializer, @@ -140,7 +136,6 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac this.flagEventSerializer = flagEventSerializer; this.startTimeSerializer = startTimeSerializer; this.raceStatusSerializer = raceStatusSerializer; - this.courseAreaChangedEventSerializer = courseAreaChangedEventSerializer; this.passChangedEventSerializer = passChangedEventSerializer; this.courseDesignChangedEventSerializer = courseDesignChangedEventSerializer; this.finishPositioningListChangedEventSerializer = finishPositioningListChangedEventSerializer; @@ -201,11 +196,6 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac chosenSerializer = startTimeSerializer; } - @Override - public void visit(RaceLogCourseAreaChangedEvent event) { - chosenSerializer = courseAreaChangedEventSerializer; - } - @Override public void visit(RaceLogCourseDesignChangedEvent event) { chosenSerializer = courseDesignChangedEventSerializer; diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/CourseAreaChangedEventSerializerTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/CourseAreaChangedEventSerializerTest.java deleted file mode 100644 index 184d86c0bd1..00000000000 --- a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/CourseAreaChangedEventSerializerTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.sap.sailing.server.gateway.serialization.test.racelog; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import java.io.Serializable; -import java.util.UUID; - -import org.json.simple.JSONObject; -import org.junit.Before; -import org.junit.Test; - -import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.server.gateway.serialization.JsonSerializer; -import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogCourseAreaChangedEventSerializer; - -public class CourseAreaChangedEventSerializerTest extends AbstractEventSerializerTest { - - private Serializable expectedCourseAreaId; - - @Before - public void setUp() { - expectedCourseAreaId = UUID.randomUUID(); - super.setUp(); - } - - @Override - protected RaceLogCourseAreaChangedEvent createMockEvent() { - RaceLogCourseAreaChangedEvent event = mock(RaceLogCourseAreaChangedEvent.class); - when(event.getCourseAreaId()).thenReturn(expectedCourseAreaId); - return event; - } - - @Override - protected JsonSerializer createSerializer(JsonSerializer competitorSerializer) { - return new RaceLogCourseAreaChangedEventSerializer(competitorSerializer); - } - - @Test - public void testCourseAreaId() { - - JSONObject result = serializer.serialize(event); - - assertEquals( - expectedCourseAreaId.toString(), - result.get(RaceLogCourseAreaChangedEventSerializer.FIELD_COURSE_AREA_ID).toString()); - - } - -} diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java index a6a52e35d98..ff8b0983862 100644 --- a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java @@ -11,7 +11,6 @@ import org.junit.Test; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; -import com.sap.sailing.domain.abstractlog.race.impl.RaceLogCourseAreaChangeEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogCourseDesignChangedEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogFinishPositioningConfirmedEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogFinishPositioningListChangedEventImpl; @@ -44,7 +43,6 @@ public class RaceLogEventSerializerTest { private JsonSerializer flagEventSerializer; private JsonSerializer startTimeSerializer; private JsonSerializer raceStatusSerializer; - private JsonSerializer courseAreaChangedEventSerializer; private JsonSerializer passChangedSerializer; private JsonSerializer courseDesignChangedEventSerializer; private JsonSerializer finishPositioningListChangedEventSerializer; @@ -74,7 +72,6 @@ public class RaceLogEventSerializerTest { flagEventSerializer = mock(JsonSerializer.class); startTimeSerializer = mock(JsonSerializer.class); raceStatusSerializer = mock(JsonSerializer.class); - courseAreaChangedEventSerializer = mock(JsonSerializer.class); passChangedSerializer = mock(JsonSerializer.class); courseDesignChangedEventSerializer = mock(JsonSerializer.class); finishPositioningListChangedEventSerializer = mock(JsonSerializer.class); @@ -97,7 +94,7 @@ public class RaceLogEventSerializerTest { endOfTrackingEventSerializer = mock(JsonSerializer.class); serializer = new RaceLogEventSerializer(flagEventSerializer, startTimeSerializer, raceStatusSerializer, - courseAreaChangedEventSerializer, passChangedSerializer, courseDesignChangedEventSerializer, + passChangedSerializer, courseDesignChangedEventSerializer, finishPositioningListChangedEventSerializer, finishPositioningConfirmedEventSerializer, pathfinderEventSerializer, gateLineOpeningTimeEventSerializer, startProcedureTypeChangedEventSerializer, protestStartTimeEventSerializer, windFixEventSerializer, @@ -132,14 +129,6 @@ public class RaceLogEventSerializerTest { verify(raceStatusSerializer).serialize(event); } - @Test - public void testChoosesCourseAreaChangedSerializer() { - // we use the real event type here because we do not want to re-implement the dispatching. - RaceLogEvent event = new RaceLogCourseAreaChangeEventImpl(null, author, 0, null); - serializer.serialize(event); - verify(courseAreaChangedEventSerializer).serialize(event); - } - @Test public void testChoosesPassChangedSerializer() { // we use the real event type here because we do not want to re-implement the dispatching. diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java index e48334b6549..38400375108 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/EventsResource.java @@ -236,7 +236,7 @@ public class EventsResource extends AbstractSailingServerResource { baseURL = baseURLParam != null ? new URL(baseURLParam) : event.getBaseURL(); getService().updateEvent(id, eventName, eventDescription, startDate, endDate, venueName, isPublic, leaderboardGroupIds, officialWebsiteURL, baseURL, event.getSailorsInfoWebsiteURLs(), - event.getImages(), event.getVideos()); + event.getImages(), event.getVideos(), event.getWindFinderReviewedSpotsCollectionIds()); response = Response.ok().build(); } return response; @@ -524,7 +524,7 @@ public class EventsResource extends AbstractSailingServerResource { getService().updateEvent(event.getId(), event.getName(), event.getDescription(), event.getStartDate(), event.getEndDate(), event.getVenue().getName(), event.isPublic(), newLeaderboardGroupIds, event.getOfficialWebsiteURL(), event.getBaseURL(), event.getSailorsInfoWebsiteURLs(), event.getImages(), - event.getVideos()); + event.getVideos(), event.getWindFinderReviewedSpotsCollectionIds()); } private CourseArea addCourseArea(UUID eventId, String courseAreaName) { diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/ExpeditionAllInOneImporter.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/ExpeditionAllInOneImporter.java old mode 100644 new mode 100755 index 0020e45a5a2..c6feaa5d4ca --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/ExpeditionAllInOneImporter.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/trackfiles/impl/ExpeditionAllInOneImporter.java @@ -19,7 +19,7 @@ import java.util.logging.Logger; import org.apache.commons.fileupload.FileItem; import org.osgi.framework.BundleContext; -import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl; @@ -61,6 +61,7 @@ import com.sap.sailing.server.operationaltransformation.AddSpecificRegatta; import com.sap.sailing.server.operationaltransformation.CreateLeaderboardGroup; import com.sap.sailing.server.operationaltransformation.CreateRegattaLeaderboard; import com.sap.sailing.server.operationaltransformation.UpdateEvent; +import com.sap.sailing.server.util.WaitForTrackedRaceUtil; import com.sap.sse.common.TimePoint; import com.sap.sse.common.TypeBasedServiceFinderFactory; import com.sap.sse.common.Util.Pair; @@ -147,15 +148,10 @@ public class ExpeditionAllInOneImporter { final String seriesName = Series.DEFAULT_NAME; // TODO wild guess... - final ScoringSchemeType scoringSchemeType = ScoringSchemeType.HIGH_POINT; + final ScoringSchemeType scoringSchemeType = ScoringSchemeType.LOW_POINT; final RankingMetrics rankingMetric = RankingMetrics.ONE_DESIGN; final int[] discardThresholds = new int[0]; - // TODO These are the defaults also used by the UI - final String raceLogEventAuthorName = "Shore"; - final int raceLogEventPriority = 4; - final boolean correctWindDirectionByMagneticDeclination = true; - final ImportResultDTO jsonHolderForGpsFixImport = new ImportResultDTO(logger); final List> filesForGpsFixImport = Arrays.asList(new Pair<>(filenameWithSuffix, fileItem)); try { @@ -231,13 +227,13 @@ public class ExpeditionAllInOneImporter { service.apply(new UpdateEvent(event.getId(), event.getName(), event.getDescription(), event.getStartDate(), event.getEndDate(), event.getVenue().getName(), event.isPublic(), Collections.singleton(leaderboardGroup.getId()), event.getOfficialWebsiteURL(), event.getBaseURL(), - event.getSailorsInfoWebsiteURLs(), event.getImages(), event.getVideos())); + event.getSailorsInfoWebsiteURLs(), event.getImages(), event.getVideos(), event.getWindFinderReviewedSpotsCollectionIds())); final RaceColumn raceColumn = regattaLeaderboard.getRaceColumns().iterator().next(); final Fleet fleet = raceColumn.getFleets().iterator().next(); final RaceLog raceLog = raceColumn.getRaceLog(fleet); - final LogEventAuthorImpl author = new LogEventAuthorImpl(raceLogEventAuthorName, raceLogEventPriority); + final AbstractLogEventAuthor author = service.getServerAuthor(); final TimePoint startOfTracking = firstFixAt; final TimePoint endOfTracking = lastFixAt; @@ -255,14 +251,17 @@ public class ExpeditionAllInOneImporter { raceLog.add(new RaceLogStartTrackingEventImpl(startTrackingTimePoint, author, raceLog.getCurrentPassId())); - final RaceHandle raceHandle = adapter.startTracking(service, regattaLeaderboard, raceColumn, fleet, true, - correctWindDirectionByMagneticDeclination); - - // TODO do we need to wait or is the TrackedRace guaranteed to be reachable after calling startTracking? + final RaceHandle raceHandle = adapter.startTracking(service, regattaLeaderboard, raceColumn, fleet, + /* trackWind */ true, /* correctWindDirectionByMagneticDeclination */ true); + + // wait for the RaceDefinition to be created raceHandle.getRace(); - - final DynamicTrackedRace trackedRace = (DynamicTrackedRace) raceColumn.getTrackedRace(fleet); - + + final DynamicTrackedRace trackedRace = WaitForTrackedRaceUtil.waitForTrackedRace(raceColumn, fleet, 10); + if (trackedRace == null) { + throw new IllegalStateException("Could not obtain imported race"); + } + final WindImportResult windImportResult = new AbstractWindImporter.WindImportResult(); final WindSourceWithAdditionalID windSource = new WindSourceWithAdditionalID(WindSourceType.EXPEDITION, windSourceId); diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/gpsFixesPostDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/gpsFixesPostDoc.html index 02e7de66c69..57f8a2f1549 100644 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/gpsFixesPostDoc.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/gpsFixesPostDoc.html @@ -66,6 +66,8 @@ These can be mapped to a competitor or a mark via the DeviceMappings in the Race + Note that the "speed" field is provided in meters per second (m/s). The course is the course over ground (COG) in + true degrees.

    Back to Web Service Overview diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html index 9501aa42504..074f64505f8 100644 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/index.html @@ -122,11 +122,11 @@ name to make sure you get to the event you're interested in. Executes the query for the regatta and gets the result - /api/v1/regattas/{regattaname}/addracecolumns + /api/v1/regattas/{regattaname}/addracecolumns Adds one or more race columns to a regatta - /api/v1/regattas/{regattaname}/removeracecolumn + /api/v1/regattas/{regattaname}/removeracecolumn Removes a race column from a regatta @@ -134,7 +134,7 @@ name to make sure you get to the event you're interested in. Obtains the competitors participating in a regatta - /api/v1/regattas/{regattaname}/competitors/createandadd + /api/v1/regattas/{regattaname}/competitors/createandadd Creates a competitor and adds it to the regatta diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy OSX).launch b/java/com.sap.sailing.server/SailingServer (No Proxy OSX).launch index 8a38261cebe..50ddbbe9d46 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy OSX).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy OSX).launch @@ -34,5 +34,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index 4bc105f898b..9c666ad1841 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -36,5 +36,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch index 83de290efeb..bc13b83671c 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Cached MTB).launch @@ -33,5 +33,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch index 382d6018fee..a079c1dafb1 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889).launch @@ -27,6 +27,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch index 061080d2c38..1022e318e18 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, Cached MTB).launch @@ -27,6 +27,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, auto-replicate dev).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, auto-replicate dev).launch index 02ea5715797..e1fdc7a5677 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, auto-replicate dev).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, auto-replicate dev).launch @@ -27,6 +27,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, auto-replicate).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, auto-replicate).launch index 0d1546ddc17..5e2558a962f 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, auto-replicate).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8889, auto-replicate).launch @@ -29,6 +29,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch index 60122b2c7c5..f13515530c0 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Jetty on 8890).launch @@ -27,6 +27,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch index ed7a0147b45..d29bfab24ef 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, Remote Debug SAP VM).launch @@ -27,5 +27,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch index 8b00fc3f534..5979fcc875d 100755 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch @@ -33,5 +33,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index 6d4cdfd7070..5ef2a34f8ae 100644 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -36,6 +36,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch index 85d41a99b44..fe57de72501 100644 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Jetty on 8889).launch @@ -27,6 +27,6 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch index a5c8931e57e..8ebbb7e8ac3 100644 --- a/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, Remote Debug SAP VM).launch @@ -27,5 +27,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch index deae6a62f3f..a4bc3b8754f 100644 --- a/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, SwissTiming Live Simulation).launch @@ -26,5 +26,5 @@ - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch index 2a7ff0580bf..23bddcb66ed 100644 --- a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch @@ -28,6 +28,6 @@ - + diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java index 758fcad562a..37a2756626c 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java @@ -55,6 +55,7 @@ import com.sap.sailing.domain.common.RegattaFetcher; import com.sap.sailing.domain.common.RegattaIdentifier; import com.sap.sailing.domain.common.RegattaName; import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.common.WindFinderReviewedSpotsCollectionIdProvider; import com.sap.sailing.domain.common.dto.AnniversaryType; import com.sap.sailing.domain.common.media.MediaTrack; import com.sap.sailing.domain.common.racelog.RacingProcedureType; @@ -132,7 +133,7 @@ import com.sap.sse.shared.media.VideoDescriptor; public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetcher, RegattaRegistry, RaceFetcher, LeaderboardRegistry, EventResolver, LeaderboardGroupResolver, TrackerManager, Searchable, ReplicableWithObjectInputStream>, RaceLogResolver, - SensorFixStoreSupplier { + SensorFixStoreSupplier, WindFinderReviewedSpotsCollectionIdProvider { @Override Regatta getRegatta(RegattaName regattaName); @@ -373,6 +374,7 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch /** * Updates a sailing event with the name eventName, the venuevenue and the regattas with * the names in regattaNames and updates it in the database. + * * @param eventName * The name of the event to update * @param startDate @@ -383,11 +385,15 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch * The name of the venue of the event * @param isPublic * Indicates whether the event is public accessible via the publication URL or not + * @param windFinderReviewedSpotCollectionIds + * IDs of WindFinder spot collections, reviewed by www.windfinder.com, to use by the WindFinder wind + * tracker and to expose on the event's UI * @return The new event */ void updateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, Iterable leaderboardGroupIds, URL officialWebsiteURL, URL baseURL, - Map sailorsInfoWebsiteURLs, Iterable images, Iterable videos); + Map sailorsInfoWebsiteURLs, Iterable images, Iterable videos, + Iterable windFinderReviewedSpotCollectionIds); /** * Renames a sailing event. If a sailing event by the name oldName does not exist in {@link #getEvents()}, diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java index 3d8bb6275d3..5296fd28d91 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/Activator.java @@ -22,6 +22,7 @@ import org.osgi.util.tracker.ServiceTrackerCustomizer; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.common.WindFinderReviewedSpotsCollectionIdProvider; import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl; import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl; import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; @@ -141,6 +142,7 @@ public class Activator implements BundleActivator { context.registerService(RaceLogResolver.class, racingEventService, null); context.registerService(ClearStateTestSupport.class, racingEventService, null); context.registerService(SensorFixStoreSupplier.class, racingEventService, null); + context.registerService(WindFinderReviewedSpotsCollectionIdProvider.class, racingEventService, null); Dictionary properties = new Hashtable(); final GPSFixMongoHandlerImpl gpsFixMongoHandler = new GPSFixMongoHandlerImpl( racingEventService.getMongoObjectFactory(), racingEventService.getDomainObjectFactory()); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index 0adfb11669b..aea65c2c308 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -3178,7 +3178,8 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes @Override public void updateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, Iterable leaderboardGroupIds, URL officialWebsiteURL, URL baseURL, - Map sailorsInfoWebsiteURLs, Iterable images, Iterable videos) { + Map sailorsInfoWebsiteURLs, Iterable images, Iterable videos, + Iterable windFinderReviewedSpotCollectionIds) { final Event event = eventsById.get(id); if (event == null) { throw new IllegalArgumentException("Sailing event with ID " + id + " does not exist."); @@ -3205,6 +3206,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes event.setSailorsInfoWebsiteURLs(sailorsInfoWebsiteURLs); event.setImages(images); event.setVideos(videos); + event.setWindFinderReviewedSpotsCollection(windFinderReviewedSpotCollectionIds); // TODO consider use diffutils to compute diff between old and new leaderboard groups list and apply the patch // to keep changes minimial mongoObjectFactory.storeEvent(event); @@ -4223,4 +4225,12 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes return pairingList; } + @Override + public Iterable getWindFinderReviewedSpotsCollectionIds() { + final Set result = new HashSet<>(); + for (final Event event : getAllEvents()) { + Util.addAll(event.getWindFinderReviewedSpotsCollectionIds(), result); + } + return result; + } } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateEvent.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateEvent.java index 4cabbe3c4a4..ffd879182a0 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateEvent.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateEvent.java @@ -25,10 +25,11 @@ public class UpdateEvent extends AbstractEventOperation { private final Map sailorsInfoWebsiteURLs; private final Iterable images; private final Iterable videos; + private final Iterable windFinderReviewedSpotCollectionIds; public UpdateEvent(UUID id, String eventName, String eventDescription, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, Iterable leaderboardGroupIds, URL officialWebsiteURL, URL baseURL, - Map sailorsInfoWebsiteURLs, Iterable images, Iterable videos) { + Map sailorsInfoWebsiteURLs, Iterable images, Iterable videos, Iterable windFinderReviewedSpotCollectionIds) { super(id); this.eventName = eventName; this.eventDescription = eventDescription; @@ -42,6 +43,7 @@ public class UpdateEvent extends AbstractEventOperation { this.sailorsInfoWebsiteURLs = sailorsInfoWebsiteURLs; this.images = images; this.videos = videos; + this.windFinderReviewedSpotCollectionIds = windFinderReviewedSpotCollectionIds; } @Override @@ -59,7 +61,8 @@ public class UpdateEvent extends AbstractEventOperation { @Override public Void internalApplyTo(RacingEventService toState) { toState.updateEvent(getId(), eventName, eventDescription, startDate, endDate, venueName, isPublic, - leaderboardGroupIds, officialWebsiteURL, baseURL, sailorsInfoWebsiteURLs, images, videos); + leaderboardGroupIds, officialWebsiteURL, baseURL, sailorsInfoWebsiteURLs, images, videos, + windFinderReviewedSpotCollectionIds); return null; } } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/util/WaitForTrackedRaceUtil.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/util/WaitForTrackedRaceUtil.java new file mode 100644 index 00000000000..92a586fed27 --- /dev/null +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/util/WaitForTrackedRaceUtil.java @@ -0,0 +1,69 @@ +package com.sap.sailing.server.util; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +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.RaceColumnListenerWithDefaultAction; +import com.sap.sailing.domain.tracking.TrackedRace; + +public class WaitForTrackedRaceUtil { + + private WaitForTrackedRaceUtil() { + } + + /** + * Waits for a {@link TrackedRace} to show up for the given {@link RaceColumn} and {@link Fleet}. If no + * {@link TrackedRace} shows up within the given amount of seconds, null is returned. + */ + @SuppressWarnings("unchecked") + public static T waitForTrackedRace(RaceColumn raceColumn, Fleet fleet, int secondsToWait) { + final CompletableFuture future = new CompletableFuture<>(); + + final RaceColumnListener raceColumnListener = new WaitForTrackedRaceListener(fleet, future); + raceColumn.addRaceColumnListener(raceColumnListener); + final TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + if (trackedRace != null) { + raceColumn.removeRaceColumnListener(raceColumnListener); + return (T) trackedRace; + } + + try { + return (T) future.get(secondsToWait, TimeUnit.SECONDS); + } catch (Exception e) { + raceColumn.removeRaceColumnListener(raceColumnListener); + return null; + } + } + + private static class WaitForTrackedRaceListener implements RaceColumnListenerWithDefaultAction { + private static final long serialVersionUID = 1L; + + private final Fleet fleet; + private final CompletableFuture future; + + public WaitForTrackedRaceListener(Fleet fleet, CompletableFuture future) { + this.fleet = fleet; + this.future = future; + } + + @Override + public void trackedRaceLinked(RaceColumn raceColumn, Fleet fleetOfLinkedRace, TrackedRace trackedRace) { + if (fleet == fleetOfLinkedRace) { + raceColumn.removeRaceColumnListener(this); + future.complete(trackedRace); + } + } + + @Override + public void defaultAction() { + } + + @Override + public boolean isTransient() { + return true; + } + } +} diff --git a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target index 709ec5135bd..1312f3565c2 100644 --- a/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target +++ b/java/com.sap.sailing.targetplatform/definitions/race-analysis-p2-remote.target @@ -1,5 +1,5 @@ - + @@ -10,10 +10,6 @@ - - - - @@ -90,6 +86,10 @@ + + + + diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index 04ef859c02f..f13a389177c 100755 --- a/java/com.sap.sailing.www/release_notes_admin.html +++ b/java/com.sap.sailing.www/release_notes_admin.html @@ -24,6 +24,19 @@

    February 2018

      +
    • There is an integration with WindFinder (https://www.windfinder.com) now. + In the Event edit dialog there is a new tab "WindFinder" where so-called "spot collection ID" can be entered. + Such spot collection IDs are negotiated and manually selected by the WindFinder team upon request. Talk to + the SAP Sailing Analytics team or to WindFinder directly to obtain one for your area.

      + When such spot collections are provided for an event, a "Weather Data" button will appear on the event + landing page. Races whose tracking is started with wind tracking included will obtain wind information + from the wind finder spots of the spot collections configured. When the wind arrows for those readings + appear on the map, the bubble displayed when clicking them also contains a link to the respective + WindFinder web page.

      + The links to the WindFinder page are sensitive to the event time. If it's a few days before the event, + a forecast link will be used. During the event the actual report page is shown. Outside of these dates + a general weather statistics page is shown for the venue. +

    • User, role and permission management has been extended. A new "tenant" concept is now available. Users can belong to zero or more tenants and can have a default tenant for a login session. Zero or more users can belong to a tenant. Objects have @@ -67,12 +80,20 @@

      January 2018

        -
      • The auto detection for the media add dialog was improved. It will now attempt to only load the parts of a video, that are required to parse the tags. Additionally if the server cannot reach a video, the client (browser) will make an attempt to load the video. This allows to analyze locally hosted videos without the need to upload them.
      • +
      • The auto detection for the media add dialog was improved. It will now attempt to only load the parts of a video that are required to parse the tags. Additionally if the server cannot reach a video, the client (browser) will make an attempt to load the video. This allows to analyze locally hosted videos without the need to upload them.
      • In the raceboard the floating video box was improved, leading to a more solid resizing and moving behavior.
      • In the raceboard, the adding of videos has now an additional fallback startDate, if no startOfRace could be determined, and the file did not contain a proper creationtime tag, startOfTracking is used.
      • -
      • In the raceboard, the floating video box will now only display an edit button in the header, next to close and minimize. Upon clicking it, the box will expand and show the prior always shown editing buttons. This saves a lot of screenspace for logged in admin enabled users. +
      • In the raceboard, the floating video box will now only display an edit button in the header, next to close and minimize. Upon clicking it, the box will expand and show the prior always shown editing buttons. This saves a lot of screenspace for logged in admin enabled users.
      • +
      • For smartphone tracked races it's now possible to slice a new race from an existing one in the RaceBoard. + As a user having sufficient permissions you need to select a specific time range in the competitor chart of a race. + This will make a new Button appear near to the settings button of the chart. + When you click this button you are being asked for a name of the new race. + A new race column is created in the same series the current race is associated to. + A new tracked race is then associated to the new column for the fleet of the current race. + This new race is being filled with the data from the current race for the selected time range. + After the slicing process is finished, a dialog will open providing a link to the RaceBoard for the newly added race.
      - +

      December 2017

      • The date and time pickers have been replaced by more native browser elements, @@ -82,9 +103,9 @@
      • In the raceboard it is now possible to upload 360° videos in addition to the already supported video types. 360° Videos must use MimeType mp4panorama, 2d mp4 videos must still use mimetype mp4, as in older releases. The dialog will try an auto-detection for the mimetype upon entering the link. - Additionally, for mp4 videos it is now possible to use the start time, written in the file, - as an start time preset for the mediatrack, simplifying the video linking.
      • -
      • In Home it is now also possible to use 360° videos, for example as a highlight video.
      • + Additionally, for mp4 videos it is now possible to use the start time, written in the file, as + a start time preset for the mediatrack, simplifying the video linking. +
      • In Home it is now also possible to use 360° videos, for example as a highlight video
      • Various fix importers can now properly report errors and other states.
      • Pairing list generation is now supported. Add competitors to a regatta, then use the new action icons in the Leaderboards/Leaderboards panel in the administration console to generate diff --git a/java/com.sap.sse.common.test/src/com/sap/sse/common/test/NumberPaddingTest.java b/java/com.sap.sse.common.test/src/com/sap/sse/common/test/NumberPaddingTest.java new file mode 100755 index 00000000000..149dc0021de --- /dev/null +++ b/java/com.sap.sse.common.test/src/com/sap/sse/common/test/NumberPaddingTest.java @@ -0,0 +1,49 @@ +package com.sap.sse.common.test; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import static com.sap.sse.common.Util.padPositiveValue; + +public class NumberPaddingTest { + @Test + public void testValueLessThanOne() { + assertEquals(".1", padPositiveValue(0.1, 0, 1, /* round */ false)); + } + + @Test + public void testValueGreaterThanOne() { + assertEquals("1.1", padPositiveValue(1.1, 0, 1, /* round */ false)); + } + + @Test + public void testValueGreaterThanTen() { + assertEquals("10.1", padPositiveValue(10.1, 0, 1, /* round */ false)); + } + + @Test + public void testTruncation() { + assertEquals("10.1", padPositiveValue(10.179, 0, 1, /* round */ false)); + } + + @Test + public void testRounding() { + assertEquals("10.2", padPositiveValue(10.179, 0, 1, /* round */ true)); + } + + @Test + public void testRoundingCloseToInteger() { + assertEquals("11.0", padPositiveValue(10.9999999999, 0, 1, /* round */ true)); + assertEquals("11.0", padPositiveValue(11.0000000001, 0, 1, /* round */ true)); + } + + @Test + public void testLeftPad() { + assertEquals("0010.2", padPositiveValue(10.179, 4, 1, /* round */ true)); + } + + @Test + public void testRightPad() { + assertEquals("10.1790", padPositiveValue(10.179, 0, 4, /* round */ true)); + } +} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java index eb9bea8cb11..bc8c03f494d 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java @@ -789,4 +789,41 @@ public class Util { } return toStringOrNull.toString(); } + + /** + * Pads a numerical value with '0' characters up to a number of digits left and right of the decimal point. If the + * number of digits right of the decimal point is zero, no decimal point will be rendered in the result. The sum of + * digits requested must be greater than zero. Digits will appear left of the decimal point if required to represent + * the integer part of the {@code value}'s magnitude. + * + * @param digitsLeftOfDecimal + * a non-negative number; if zero, + * @param digitsRightOfDecimal + * a non-negative number; if zero, no decimal point will appear at all + * @param round + * whether or not the value shall be rounded to the number of decimals requested. If {@code false}, the + * value will be displayed in a truncated form + */ + public static String padPositiveValue(double value, int digitsLeftOfDecimal, int digitsRightOfDecimal, boolean round) { + assert value >= 0; + assert digitsLeftOfDecimal >= 0; + assert digitsRightOfDecimal >= 0; + final StringBuilder sb = new StringBuilder(); + final double scalePow = Math.pow(10.0, digitsRightOfDecimal); + final double scaledValue = round?Math.round(value * scalePow):(value*scalePow); + double remainder = scaledValue; + if (digitsLeftOfDecimal==0 && scaledValue>=scalePow) { + sb.append(Math.round(remainder/scalePow)); + remainder = remainder%scalePow; + } + for (int i=digitsLeftOfDecimal+digitsRightOfDecimal; i>0; i--) { + if (i==digitsRightOfDecimal) { + sb.append('.'); + } + final double pow = Math.pow(10.0, i-1); + sb.append((int) (remainder/pow)); + remainder = remainder % pow; + } + return sb.toString(); + } } \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/listedit/GenericStringListEditorComposite.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/listedit/GenericStringListEditorComposite.java index 1f8c346401b..8d1cd6e555f 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/listedit/GenericStringListEditorComposite.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/listedit/GenericStringListEditorComposite.java @@ -22,7 +22,8 @@ import com.sap.sse.gwt.client.StringMessages; /** * A list editor that renders values of type {@code ValueType} as {@link String}s and lets the user edit them - * as strings. For this to work, implementing subclasses need to specify the + * as strings. For this to work, implementing subclasses need to specify how strings are parsed to objects + * of type {@code ValueType} and vice versa. * * @author Lukas Niemeier * @author Axel Uhl (d043530) @@ -85,6 +86,10 @@ public abstract class GenericStringListEditorComposite extends ListEd this(stringMessages, removeImage, suggestValues, /* placeholderTextForAddTextbox */ null); } + /** + * @param suggestValues must not be null but may be empty + * @param placeholderTextForAddTextbox may be null + */ public ExpandedUi(StringMessages stringMessages, ImageResource removeImage, Iterable suggestValues, String placeholderTextForAddTextbox) { super(stringMessages, removeImage, /*canRemoveItems*/true); this.placeholderTextForAddTextbox = placeholderTextForAddTextbox; diff --git a/java/com.sap.sse/src/com/sap/sse/pairinglist/impl/PairingListTemplateImpl.java b/java/com.sap.sse/src/com/sap/sse/pairinglist/impl/PairingListTemplateImpl.java index 4309f19acb6..27c5ce39696 100644 --- a/java/com.sap.sse/src/com/sap/sse/pairinglist/impl/PairingListTemplateImpl.java +++ b/java/com.sap.sse/src/com/sap/sse/pairinglist/impl/PairingListTemplateImpl.java @@ -195,10 +195,14 @@ public class PairingListTemplateImpl implements PairingListTemplate { } /** - * Divides the allSeeds array into parts with seed combinations that starts with the same seeds. If there are to less - * flights this method cuts the off Seeds into 4000 parts to avoid to less Tasks. - * @param equalSeeds number of equal seeds at the beginning of seed combinations which should be grouped. Attention: If this number is to big there will be very many little parts. - * @param allSeeds array with seed combinations, which should be divided + * Divides the allSeeds array into parts with seed combinations that starts with the same seeds. The parts + * are sized according to the thread pool size used so as to achieve reasonable concurrency. + * + * @param equalSeeds + * number of equal seeds at the beginning of seed combinations which should be grouped. Attention: If + * this number is to big there will be very many little parts. + * @param allSeeds + * array with seed combinations, which should be divided * @return an Arraylist, that contains parts of allSeeds. */ private ArrayList divideSeeds(int[][] allSeeds) { @@ -217,8 +221,11 @@ public class PairingListTemplateImpl implements PairingListTemplate { /** * Fast sorting algorithm to sort a huge amount of seeds in max n*log(n) time. Uses counting sort. - * @param allSeeds array of seeds, that should be sorted - * @param competitorCount highest value inside the complete array. Needed for counting sort. + * + * @param allSeeds + * array of seeds, that should be sorted + * @param competitorCount + * highest value inside the complete array. Needed for counting sort. * @return sorted array with seed combinations */ protected int[][] radixSort(int[][] allSeeds, int competitorCount) { @@ -229,7 +236,6 @@ public class PairingListTemplateImpl implements PairingListTemplate { return allSeeds; } - //TODO use comparator private int[][] countSort(int[][] allSeeds, int i, int competitorCount) { int[][] output = new int[allSeeds.length][allSeeds[0].length]; int[] count = new int[competitorCount]; @@ -239,7 +245,6 @@ public class PairingListTemplateImpl implements PairingListTemplate { for (int z = 1; z < count.length; z++) { count[z] += count[z - 1]; } - //TODO change to for each for (int z = allSeeds.length - 1; z >= 0; z--) { output[--count[allSeeds[z][i]]] = allSeeds[z]; } @@ -474,18 +479,21 @@ public class PairingListTemplateImpl implements PairingListTemplate { } /** - * Switches competitors inside a group to improve competitor allocations. - * Method does not change the order of groups or flights. The standard deviation - * of team associations will not be influenced by this method. After executing the - * method the assignment should be well distributed. + * Switches competitors inside a group to improve competitor allocations. Method does not change the order of groups + * or flights. The standard deviation of team associations will not be influenced by this method. After executing + * the method the assignment should be well distributed. * - * @param pairinglist current pairing list - * @param flights count of flights - * @param groups count of groups - * @param competitors count of competitors + * @param pairinglist + * current pairing list + * @param flights + * count of flights + * @param groups + * count of groups + * @param competitors + * count of competitors * @return improved pairing list template */ - protected int[][] improveCompetitorAllocations(int[][] pairinglist, int flights, int groups, int competitors) { + protected int[][] improveCompetitorAllocations(final int[][] pairinglist, int flights, int groups, int competitors) { int[][] assignments = this.getAssignmentAssociations(pairinglist, new int[competitors][competitors / groups]); double neededAssigments = flights / (competitors / groups); double bestDev = Double.POSITIVE_INFINITY; @@ -531,8 +539,9 @@ public class PairingListTemplateImpl implements PairingListTemplate { } } } - if (this.calcStandardDev(pairinglist) < bestDev) { - bestDev = this.calcStandardDev(pairinglist); + final double standardDev = this.calcStandardDev(pairinglist); + if (standardDev < bestDev) { + bestDev = standardDev; bestPLT = pairinglist; } } @@ -541,13 +550,17 @@ public class PairingListTemplateImpl implements PairingListTemplate { } /** - * Improves the Assignment changes between two flights by switching the groups inside a flight. - * Competitors and there assignment will not be changed. - * @param pairingList complete pairingListTemplate which needs to be improved - * @param flights number of flights which was used to generate the pairingListTemplate - * @param competitors number of competitors which was used to generate the pairingListTemplate + * Improves the Assignment changes between two flights by switching the groups inside a flight. Competitors and + * their assignment will not be changed. * - * @return the improved pairingListTemplate + * @param pairingList + * complete pairingListTemplate which needs to be improved; will be modified in place and then returned + * @param flights + * number of flights which was used to generate the pairingListTemplate + * @param competitors + * number of competitors which was used to generate the pairingListTemplate + * + * @return the improved pairingListTemplate; identical to the {@code pairingList} parameter and returned only for convenience */ private int[][] improveAssignmentChanges(int[][] pairingList, int flights, int competitors) { int boatChanges[] = new int[flights - 1]; @@ -564,10 +577,10 @@ public class PairingListTemplateImpl implements PairingListTemplate { bestMatchesIndex = j; } } - if (bestMatchesIndex > 0) { + if (bestMatchesIndex > 0) { // for ==0 nothing needs to be done; the group that matches the last group + // of the previous flight best is already the first in its flight int[] temp = new int[pairingList[0].length]; System.arraycopy(pairingList[i * pairingList.length / flights], 0, temp, 0, temp.length); - System.arraycopy(pairingList[i * pairingList.length / flights + bestMatchesIndex], 0, pairingList[i * pairingList.length / flights], 0, groupNext.length); System.arraycopy(temp, 0, pairingList[i * pairingList.length / flights + bestMatchesIndex], 0, @@ -645,7 +658,9 @@ public class PairingListTemplateImpl implements PairingListTemplate { */ private boolean contains(int[] arr, int value) { for (int i = 0; i < arr.length; i++) { - if (arr[i] == value) return true; + if (arr[i] == value) { + return true; + } } return false; } diff --git a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF index 6da9ddc6be7..318b514342b 100644 --- a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF +++ b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: TracTrac Client module Bundle-SymbolicName: com.tractrac.clientmodule -Bundle-Version: 3.8.0 +Bundle-Version: 3.10.0 Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ClassPath: ., lib/TracAPI.jar @@ -47,6 +47,7 @@ Export-Package: com.tractrac.asio.lib.api, com.tractrac.model.lib.api.event, com.tractrac.model.lib.api.metadata, com.tractrac.model.lib.api.route, + com.tractrac.model.lib.api.sensor, com.tractrac.model.lib.api.spatial, com.tractrac.model.lib.impl, com.tractrac.model.lib.impl.attachment, diff --git a/java/com.tractrac.clientmodule/README.txt b/java/com.tractrac.clientmodule/README.txt index 0e96e2a7ae5..6c8802c4d2d 100755 --- a/java/com.tractrac.clientmodule/README.txt +++ b/java/com.tractrac.clientmodule/README.txt @@ -15,6 +15,33 @@ It contains also some files: - test.sh -> script that compiles the code in the src folder, creates the test.jar library and execute the code of the example. - Manifest.txt -> manifest used to create the test.jar file +******************************************** +************* TracAPI 3.10.0 *************** +******************************************** +This is a final version. It changes the implementation of the ISensorData interface, breaking the +backward compatibility: + +Release date: 02/03/2018 +Build number: a0cbb8116ab0917334b9b323db6664671a05f79b + + 1) Features + + - Updating the interface ISensorData (Requested by Axel Uhl, 01/03/2018) + + +******************************************** +************* TracAPI 3.9.0 **************** +******************************************** +This is a final version. It changes the implementation of the ISensorData interface, breaking the +backward compatibility: + +Release date: 01/03/2018 +Build number: 8fdd19ee81b3ccbfc2694963e8e398346eebde7a + + 1) Features + + - Updating the interface ISensorData (Requested by Axel Uhl and Jakob Odum, 22/11/2017) + - Support for https downloading MTBs (Requested by Axel Uhl, 07/01/2018) ******************************************** ************* TracAPI 3.8.0 **************** diff --git a/java/com.tractrac.clientmodule/javadoc/allclasses-frame.html b/java/com.tractrac.clientmodule/javadoc/allclasses-frame.html index 859c398f090..7034a5bfbbb 100755 --- a/java/com.tractrac.clientmodule/javadoc/allclasses-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/allclasses-frame.html @@ -2,9 +2,9 @@ - + All Classes - + diff --git a/java/com.tractrac.clientmodule/javadoc/allclasses-noframe.html b/java/com.tractrac.clientmodule/javadoc/allclasses-noframe.html index a52a3bedec2..b101d156627 100755 --- a/java/com.tractrac.clientmodule/javadoc/allclasses-noframe.html +++ b/java/com.tractrac.clientmodule/javadoc/allclasses-noframe.html @@ -2,9 +2,9 @@ - + All Classes - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/IIdentifiable.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/IIdentifiable.html index 1bfd1664530..718ed0cbd88 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/IIdentifiable.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/IIdentifiable.html @@ -2,9 +2,9 @@ - + IIdentifiable - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/INamed.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/INamed.html index 2d0d21559cc..9250e6aefa3 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/INamed.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/INamed.html @@ -2,9 +2,9 @@ - + INamed - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/ModelLocator.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/ModelLocator.html index ecdde91d5fd..f64f700e96d 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/ModelLocator.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/ModelLocator.html @@ -2,9 +2,9 @@ - + ModelLocator - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/AbstractAttachable.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/AbstractAttachable.html index 082d35cc400..8c4c9c2ca93 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/AbstractAttachable.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/AbstractAttachable.html @@ -2,9 +2,9 @@ - + AbstractAttachable - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachable.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachable.html index 5be0214ab17..649fd39b3c6 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachable.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachable.html @@ -2,9 +2,9 @@ - + IAttachable - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentKey.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentKey.html index 7a3a54301a0..a3bd5834d59 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentKey.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentKey.html @@ -2,9 +2,9 @@ - + IAttachmentKey - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentManager.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentManager.html index 8568fc5136c..29dd171b1c1 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentManager.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/IAttachmentManager.html @@ -2,9 +2,9 @@ - + IAttachmentManager - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-frame.html index 3ea356a1f99..dde7e1c6e6f 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.attachment - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-summary.html index 967681fb8a8..f20a4b5f56f 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.attachment - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-tree.html index a365d757722..93dad8cd7ad 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/attachment/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.attachment Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassing.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassing.html index 8f446d4ff4d..520dfd014c8 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassing.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassing.html @@ -2,9 +2,9 @@ - + IControlPassing - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassings.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassings.html index 4983120c653..fedc0c5e273 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassings.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IControlPassings.html @@ -2,9 +2,9 @@ - + IControlPassings - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IMessageData.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IMessageData.html index fbdd5398e54..26d8f750b06 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IMessageData.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IMessageData.html @@ -2,9 +2,9 @@ - + IMessageData - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPosition.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPosition.html index 1bb267c2cac..9cc4644e6fe 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPosition.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPosition.html @@ -2,9 +2,9 @@ - + IPosition - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionFactory.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionFactory.html index 580b7a20b43..6fe0cefe7e5 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionFactory.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionFactory.html @@ -2,9 +2,9 @@ - + IPositionFactory - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionOffset.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionOffset.html index 94802caad10..12af620c739 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionOffset.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionOffset.html @@ -2,9 +2,9 @@ - + IPositionOffset - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionSnapped.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionSnapped.html index 1074a0c4cc6..4b714756efc 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionSnapped.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IPositionSnapped.html @@ -2,9 +2,9 @@ - + IPositionSnapped - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IStartStopData.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IStartStopData.html index e01ad8709d9..85082893cfd 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IStartStopData.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/IStartStopData.html @@ -2,9 +2,9 @@ - + IStartStopData - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/ITimeData.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/ITimeData.html index 35dee7d2332..4702febb7c3 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/ITimeData.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/ITimeData.html @@ -2,9 +2,9 @@ - + ITimeData - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-frame.html index e140cc41722..5ded1043747 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.data - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-summary.html index 453e6df06d5..8f4bc2f0ba0 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.data - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-tree.html index 4b2a9dddcb3..15ca104c64e 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/data/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.data Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/CreateModelException.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/CreateModelException.html index 5dd7c3aaea0..be860aa41f2 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/CreateModelException.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/CreateModelException.html @@ -2,9 +2,9 @@ - + CreateModelException - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/DataSource.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/DataSource.html index f54a2d257dd..e14e3541ddd 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/DataSource.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/DataSource.html @@ -2,9 +2,9 @@ - + DataSource - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/EventType.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/EventType.html index c7f4adb6704..273bcab5fbe 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/EventType.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/EventType.html @@ -2,9 +2,9 @@ - + EventType - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitor.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitor.html index 02c4cc8da35..e8119408c38 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitor.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitor.html @@ -2,9 +2,9 @@ - + ICompetitor - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitorClass.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitorClass.html index 6e30f501972..be94e76482d 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitorClass.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ICompetitorClass.html @@ -2,9 +2,9 @@ - + ICompetitorClass - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEvent.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEvent.html index 0eabdc2b51b..de9db275185 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEvent.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEvent.html @@ -2,9 +2,9 @@ - + IEvent - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEventFactory.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEventFactory.html index 7734d56364c..c3183bef8c6 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEventFactory.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IEventFactory.html @@ -2,9 +2,9 @@ - + IEventFactory - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRace.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRace.html index a3b5e48ac69..f60f21b6eee 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRace.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRace.html @@ -2,9 +2,9 @@ - + IRace - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceCompetitor.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceCompetitor.html index f4c99b5036b..c09b045c4ec 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceCompetitor.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceCompetitor.html @@ -2,9 +2,9 @@ - + IRaceCompetitor - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceSerie.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceSerie.html index c30b131bb15..bb62df44ab5 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceSerie.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/IRaceSerie.html @@ -2,9 +2,9 @@ - + IRaceSerie - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ITeam.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ITeam.html index 874cc3bf9a1..685bc1d36d3 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ITeam.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/ITeam.html @@ -2,9 +2,9 @@ - + ITeam - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceCompetitorStatusType.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceCompetitorStatusType.html index c22ce1c8c1a..bd0632bab21 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceCompetitorStatusType.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceCompetitorStatusType.html @@ -2,9 +2,9 @@ - + RaceCompetitorStatusType - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceLoadingException.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceLoadingException.html index 62c1c408d5e..7c1203eedae 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceLoadingException.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceLoadingException.html @@ -2,9 +2,9 @@ - + RaceLoadingException - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceStatusType.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceStatusType.html index b83e8d1c6c1..41dae392e2c 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceStatusType.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceStatusType.html @@ -2,9 +2,9 @@ - + RaceStatusType - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceVisibilityType.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceVisibilityType.html index a106c91d71c..fc31e8e46f6 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceVisibilityType.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceVisibilityType.html @@ -2,9 +2,9 @@ - + RaceVisibilityType - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/StartTimeType.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/StartTimeType.html index 52ffa04b064..32484cd9fb2 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/StartTimeType.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/StartTimeType.html @@ -2,9 +2,9 @@ - + StartTimeType - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-frame.html index 2217386c41f..74e0373a891 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.event - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-summary.html index 7e0836b21a0..4bfb9cbb647 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.event - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-tree.html index fe396165ad9..dd930f8659c 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.event Class Hierarchy - + @@ -150,12 +150,12 @@ diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadata.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadata.html index b0ab7dcee7c..18bf57d6fa5 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadata.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadata.html @@ -2,9 +2,9 @@ - + IMetadata - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataContainer.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataContainer.html index 0e67f95567c..fdddc3f8079 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataContainer.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataContainer.html @@ -2,9 +2,9 @@ - + IMetadataContainer - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataFactory.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataFactory.html index ee036635fea..6b04553976e 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataFactory.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IMetadataFactory.html @@ -2,9 +2,9 @@ - + IMetadataFactory - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IPropertiesContainer.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IPropertiesContainer.html index 4f45229c90d..de0c6a24d88 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IPropertiesContainer.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/IPropertiesContainer.html @@ -2,9 +2,9 @@ - + IPropertiesContainer - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-frame.html index 93b05901bed..4e5326d2d22 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.metadata - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-summary.html index e2037213857..9cd30ef4b4c 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.metadata - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-tree.html index 4d7bc684038..94685541f68 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/metadata/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.metadata Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-frame.html index 0fe045d687a..3e302456cae 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-summary.html index 7e61e5ca263..905f510b36f 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-tree.html index 7fdce4a1148..08833871556 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControl.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControl.html index 11639c54d7d..32a2e6901e3 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControl.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControl.html @@ -2,9 +2,9 @@ - + IControl - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlPoint.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlPoint.html index 948d8cd63c3..2027a203127 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlPoint.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlPoint.html @@ -2,9 +2,9 @@ - + IControlPoint - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlRoute.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlRoute.html index b1b60e038ec..d2aefd44d42 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlRoute.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IControlRoute.html @@ -2,9 +2,9 @@ - + IControlRoute - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRoute.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRoute.html index a2585529129..0826726e79e 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRoute.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRoute.html @@ -2,9 +2,9 @@ - + IPathRoute - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRouteFactory.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRouteFactory.html index 2a3a858979e..4bb0c307805 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRouteFactory.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathRouteFactory.html @@ -2,9 +2,9 @@ - + IPathRouteFactory - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathSegment.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathSegment.html index 5b170df9510..80991928799 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathSegment.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IPathSegment.html @@ -2,9 +2,9 @@ - + IPathSegment - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IRoute.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IRoute.html index 55e922421ef..6839319609b 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IRoute.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/IRoute.html @@ -2,9 +2,9 @@ - + IRoute - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/ISegment.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/ISegment.html index 04805ffedfa..08153f15ce7 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/ISegment.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/ISegment.html @@ -2,9 +2,9 @@ - + ISegment - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-frame.html index 045161dfb4b..2b0aebf19b8 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.route - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-summary.html index e79c94e6205..30ed8c76083 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.route - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-tree.html index 262ea87dcb5..50ff345f5ec 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/route/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.route Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/ISensorData.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/ISensorData.html index 1208ec8dead..cf1eeba60b8 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/ISensorData.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/ISensorData.html @@ -2,9 +2,9 @@ - + ISensorData - + @@ -18,7 +18,7 @@ catch(err) { } //--> -var methods = {"i0":6,"i1":6,"i2":6,"i3":6,"i4":6,"i5":6,"i6":6}; +var methods = {"i0":6,"i1":6,"i2":6,"i3":6,"i4":6,"i5":6}; var tabs = {65535:["t0","All Methods"],2:["t2","Instance Methods"],4:["t3","Abstract Methods"]}; var altColor = "altColor"; var rowColor = "rowColor"; @@ -106,8 +106,18 @@ var activeTableTab = "activeTableTab";
        public interface ISensorData
         extends ITimeData
        -
        This interface implements sensor data. It has some methods to retrieve custom dimensions - and it also has methods to retrieve sensor data in a raw format
        +

        + This interface implements sensor data. It has methods to retrieve all the dimensions + that are supported at this moment (at it will be extended with new methods when the + system supports new types of sensors). All the dimensions have the same time stamp + (returned by the @ITimeData.getTimestamp() method). +

        +

        + If the method for a dimension returns null, means that there is not a value + for this dimension at this time (it can arrive in a future message). The library doesn't + interpolate values for the dimensions and only returns values that have been retrieved + from the physical sensor. +

@@ -129,43 +139,48 @@ extends java.lang.String getDimensionAt(int index) -
Gets a dimension by index
+
+ Gets a dimension by index (it can be used if the consumer app knows the + order of the dimensions.
-float +java.lang.Float getHeel() -
Gets the heel
+
+ Gets the heel, defined as the tilting rotation of a vessel about its + longitudinal/X (front-back or bow-stern) axis.
java.lang.String getRaw() -
Gets the sensor data in a raw format.
+
+ Gets the sensor data in a raw format.
-float -getRideHeight() -
Gets the ride height
+java.lang.Float +getRideHeightPort() +
+ Gets the perpendicular distance from the bottom of the port-hull + to the water surface.
-float -getTrim() -
Gets the trim
+java.lang.Float +getRideHeightStarboard() +
+ Gets the perpendicular distance from the bottom of the starboard-hull + to the water surface.
-float -getVMG() -
Gets the VMG
- - - -float -getWind() -
Gets the wind
+java.lang.Float +getTrim() +
+ Gets the trim, defined as the up/down rotation of a vessel about its + lateral/Y (side-to-side or port-starboard) axis.
@@ -196,8 +211,20 @@ extends
  • getHeel

    -
    float getHeel()
    -
    Gets the heel
    +
    java.lang.Float getHeel()
    +

    + Gets the heel, defined as the tilting rotation of a vessel about its + longitudinal/X (front-back or bow-stern) axis. +

    +

    + It's measured in degrees, and it's positive when the boat is on + starboard tack. 0 means that the boat is not heeled +

    +

    + null means that there is not a measure for this + dimension at this time (but a correct value can arrive in a + future message) +

    Returns:
    the heel
    @@ -210,53 +237,77 @@ extends
  • getTrim

    -
    float getTrim()
    -
    Gets the trim
    +
    java.lang.Float getTrim()
    +

    + Gets the trim, defined as the up/down rotation of a vessel about its + lateral/Y (side-to-side or port-starboard) axis. +

    +

    + It is measured in degrees, and it's positive when the bow is up. + 0 means that the boat is not trimmed +

    +

    + null means that there is not a measure for this + dimension at this time (but a correct value can arrive in a + future message) +

    Returns:
    the trim
  • -
    +
    • -

      getRideHeight

      -
      float getRideHeight()
      -
      Gets the ride height
      +

      getRideHeightPort

      +
      java.lang.Float getRideHeightPort()
      +

      + Gets the perpendicular distance from the bottom of the port-hull + to the water surface. +

      +

      + It is measured in meters. A positive value means that the hull is + over the water. Zero means the hull is just touching the water. + Negative means that is under the water. +

      +

      + null means that there is not a measure for this + dimension at this time (but a correct value can arrive in a + future message) +

      Returns:
      the ride height
    - +
    • -

      getWind

      -
      float getWind()
      -
      Gets the wind
      +

      getRideHeightStarboard

      +
      java.lang.Float getRideHeightStarboard()
      +

      + Gets the perpendicular distance from the bottom of the starboard-hull + to the water surface. +

      +

      + It is measured in meters. A positive value means that the hull is + over the water. Zero means the hull is just touching the water. + Negative means that is under the water. +

      +

      + null means that there is not a measure for this + dimension at this time (but a correct value can arrive in a + future message) +

      Returns:
      -
      the wind
      -
      -
    • -
    - - - -
      -
    • -

      getVMG

      -
      float getVMG()
      -
      Gets the VMG
      -
      -
      Returns:
      -
      the VMG
      +
      the ride height
    @@ -267,8 +318,10 @@ extends

    getRaw

    java.lang.String getRaw()
    -
    Gets the sensor data in a raw format. It is just an String with - all the dimensions separated by ";"
    +

    + Gets the sensor data in a raw format. It is just an String with + all the dimensions separated by ";" +

    Returns:
    all sensor data in a raw format
    @@ -282,7 +335,14 @@ extends

    getDimensionAt

    java.lang.String getDimensionAt(int index)
    -
    Gets a dimension by index
    +

    + Gets a dimension by index (it can be used if the consumer app knows the + order of the dimensions. +

    +

    + It returns null if the dimension doesn't has a valid value + at this timestamp +

    Parameters:
    index - the index of the dimension
    diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-frame.html index 489340764be..1d775dc53b5 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.sensor - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-summary.html index dfdc978d316..9a62c28a0aa 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.sensor - + @@ -70,6 +70,11 @@
      @@ -84,13 +89,21 @@ ISensorData -
      This interface implements sensor data.
      +
      + This interface implements sensor data.
    + + + +

    Package com.tractrac.model.lib.api.sensor Description

    +

    + Sensor data interfaces +

    diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-tree.html index c9f446060ac..570a18409d4 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/sensor/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.sensor Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinate.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinate.html index 311ad53aaa5..27748e19679 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinate.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinate.html @@ -2,9 +2,9 @@ - + ICoordinate - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinateSequence.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinateSequence.html index 3e903b2af86..65ab26af492 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinateSequence.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/ICoordinateSequence.html @@ -2,9 +2,9 @@ - + ICoordinateSequence - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IExtent.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IExtent.html index a93a07c439f..e50673934eb 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IExtent.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IExtent.html @@ -2,9 +2,9 @@ - + IExtent - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IGeoCoordinate.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IGeoCoordinate.html index 72bf71b24dc..8370f16f519 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IGeoCoordinate.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/IGeoCoordinate.html @@ -2,9 +2,9 @@ - + IGeoCoordinate - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-frame.html index e3c14e3b127..293a321eaa7 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.spatial - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-summary.html index a864314221f..a0e41459e7c 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.spatial - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-tree.html index 3c2aa91c6d9..efbe40c727e 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/spatial/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.model.lib.api.spatial Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IEventSubscriber.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IEventSubscriber.html index f50a638016b..c9e058bb0c3 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IEventSubscriber.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IEventSubscriber.html @@ -2,9 +2,9 @@ - + IEventSubscriber - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IRaceSubscriber.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IRaceSubscriber.html index 9c70f977881..77ef6adf411 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IRaceSubscriber.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/IRaceSubscriber.html @@ -2,9 +2,9 @@ - + IRaceSubscriber - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/ISubscriber.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/ISubscriber.html index 72c3ab44d7c..392a8d8ff44 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/ISubscriber.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/ISubscriber.html @@ -2,9 +2,9 @@ - + ISubscriber - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/ISubscriberFactory.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/ISubscriberFactory.html index 6ce6d90cfc4..721597e08ec 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/ISubscriberFactory.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/ISubscriberFactory.html @@ -2,9 +2,9 @@ - + ISubscriberFactory - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/ISubscriberListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/ISubscriberListener.html index 4aceaf07445..44e9e38881b 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/ISubscriberListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/ISubscriberListener.html @@ -2,9 +2,9 @@ - + ISubscriberListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/SubscriberInitializationException.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/SubscriberInitializationException.html index d272f7ba7b3..9966eb53d16 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/SubscriberInitializationException.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/SubscriberInitializationException.html @@ -2,9 +2,9 @@ - + SubscriberInitializationException - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/SubscriptionLocator.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/SubscriptionLocator.html index 68e1852d2e2..9445a1db9c3 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/SubscriptionLocator.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/SubscriptionLocator.html @@ -2,9 +2,9 @@ - + SubscriptionLocator - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorSensorDataListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorSensorDataListener.html index 6d77a09a4d6..65e7a7b2dfd 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorSensorDataListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorSensorDataListener.html @@ -2,9 +2,9 @@ - + ICompetitorSensorDataListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorsListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorsListener.html index f26df2c8168..6615e293771 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorsListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/ICompetitorsListener.html @@ -2,9 +2,9 @@ - + ICompetitorsListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionListener.html index fdd09a61d7e..07701cd6bc7 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionListener.html @@ -2,9 +2,9 @@ - + IPositionListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionOffsetListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionOffsetListener.html index 6633a869b4f..2f2df1429b4 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionOffsetListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionOffsetListener.html @@ -2,9 +2,9 @@ - + IPositionOffsetListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionSnappedListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionSnappedListener.html index 072b6d3ca21..b022fc139df 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionSnappedListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/IPositionSnappedListener.html @@ -2,9 +2,9 @@ - + IPositionSnappedListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-frame.html index 161e06dae6d..2480d0287a1 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.competitor - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-summary.html index 62d73939dea..78dfa7dc551 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.competitor - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-tree.html index d5879133b2a..a4786842090 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/competitor/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.competitor Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPassingsListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPassingsListener.html index e8fe586d727..1e1537d3bda 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPassingsListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPassingsListener.html @@ -2,9 +2,9 @@ - + IControlPassingsListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointPositionListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointPositionListener.html index ec9789f5cef..717dc036a11 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointPositionListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointPositionListener.html @@ -2,9 +2,9 @@ - + IControlPointPositionListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointSensorDataListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointSensorDataListener.html index 602806a4f2a..62029137746 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointSensorDataListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlPointSensorDataListener.html @@ -2,9 +2,9 @@ - + IControlPointSensorDataListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlRouteChangeListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlRouteChangeListener.html index 2d2679e5b66..741e6336cab 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlRouteChangeListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlRouteChangeListener.html @@ -2,9 +2,9 @@ - + IControlRouteChangeListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlsListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlsListener.html index 8c2cdb26ada..5d1a9f59df6 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlsListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/IControlsListener.html @@ -2,9 +2,9 @@ - + IControlsListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-frame.html index abf8a19fb6b..4cf8232f65e 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.control - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-summary.html index 90a214adc10..47edd25e949 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.control - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-tree.html index b14995df5ff..b9406ae962f 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/control/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.control Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IConnectionStatusListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IConnectionStatusListener.html index 5968e5a8d33..2c649839f31 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IConnectionStatusListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IConnectionStatusListener.html @@ -2,9 +2,9 @@ - + IConnectionStatusListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IEventMessageListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IEventMessageListener.html index 1f51db0aac8..26f2b273201 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IEventMessageListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IEventMessageListener.html @@ -2,9 +2,9 @@ - + IEventMessageListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.StatusType.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.StatusType.html index 219cc6aa7ce..96a59f5cfce 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.StatusType.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.StatusType.html @@ -2,9 +2,9 @@ - + ILiveDataEvent.StatusType - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.html index cedae74b981..ce65b729aa7 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/ILiveDataEvent.html @@ -2,9 +2,9 @@ - + ILiveDataEvent - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IMessage.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IMessage.html index 627439a79c4..584075ed6ee 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IMessage.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IMessage.html @@ -2,9 +2,9 @@ - + IMessage - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IServerTimeListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IServerTimeListener.html index bc1c48007a8..b5fbdd0036f 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IServerTimeListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IServerTimeListener.html @@ -2,9 +2,9 @@ - + IServerTimeListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.Type.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.Type.html index dec51601ddb..49d0e3c26e6 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.Type.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.Type.html @@ -2,9 +2,9 @@ - + IStoredDataEvent.Type - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.html index 9106ee9fc74..1f023ef8ab3 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/IStoredDataEvent.html @@ -2,9 +2,9 @@ - + IStoredDataEvent - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-frame.html index 3e17f82957c..97f80c93446 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.event - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-summary.html index 8acf440b543..c51653ee5c6 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.event - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-tree.html index b54dcd81713..5bbe73d0d5d 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/event/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.event Class Hierarchy - + @@ -95,8 +95,8 @@ diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-frame.html index 6c39de14161..f9a3aa54977 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-summary.html index 1000254e8a9..28b84f49458 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-tree.html index 01f681326df..05e92009ba9 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceCompetitorListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceCompetitorListener.html index ac032d3b9bd..b503870a34d 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceCompetitorListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceCompetitorListener.html @@ -2,9 +2,9 @@ - + IRaceCompetitorListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceMessageListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceMessageListener.html index 6548150dfe8..2c7b6f0b011 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceMessageListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceMessageListener.html @@ -2,9 +2,9 @@ - + IRaceMessageListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceStartStopTimesChangeListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceStartStopTimesChangeListener.html index 36fa9beac2a..49405d2ec8e 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceStartStopTimesChangeListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRaceStartStopTimesChangeListener.html @@ -2,9 +2,9 @@ - + IRaceStartStopTimesChangeListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRacesListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRacesListener.html index bd3aefb5ad4..66741c9ad87 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRacesListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IRacesListener.html @@ -2,9 +2,9 @@ - + IRacesListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IStartStopTimesChangeListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IStartStopTimesChangeListener.html index 7080677521c..4a363357a9d 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IStartStopTimesChangeListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/IStartStopTimesChangeListener.html @@ -2,9 +2,9 @@ - + IStartStopTimesChangeListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-frame.html index 6729c3e1674..2b7b1295cf7 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.race - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-summary.html index 2c002c8b660..6c93f7e96bf 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.race - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-tree.html index 6af70423a88..56d1e34897e 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/race/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.race Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/IRoutesListener.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/IRoutesListener.html index 16f74892b4c..3129db7443d 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/IRoutesListener.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/IRoutesListener.html @@ -2,9 +2,9 @@ - + IRoutesListener - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-frame.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-frame.html index 37586170c42..2bbe32618d8 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-frame.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-frame.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.route - + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-summary.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-summary.html index 96bfe893a6f..e77847abc31 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-summary.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.route - + @@ -70,6 +70,11 @@

    Package com.tractrac.subscription.lib.api.route

    +
    +
    + Subscriptions related with the routes
    +
    +

    See: Description

      @@ -92,6 +97,13 @@
    + + + +

    Package com.tractrac.subscription.lib.api.route Description

    +

    + Subscriptions related with the routes +

    diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-tree.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-tree.html index cd9b54867b4..52aac07a444 100755 --- a/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/subscription/lib/api/route/package-tree.html @@ -2,9 +2,9 @@ - + com.tractrac.subscription.lib.api.route Class Hierarchy - + diff --git a/java/com.tractrac.clientmodule/javadoc/constant-values.html b/java/com.tractrac.clientmodule/javadoc/constant-values.html index a15c886d233..1a93b320e07 100755 --- a/java/com.tractrac.clientmodule/javadoc/constant-values.html +++ b/java/com.tractrac.clientmodule/javadoc/constant-values.html @@ -2,9 +2,9 @@ - + Constant Field Values - + diff --git a/java/com.tractrac.clientmodule/javadoc/deprecated-list.html b/java/com.tractrac.clientmodule/javadoc/deprecated-list.html index 7a7ca673520..0cbbc667491 100755 --- a/java/com.tractrac.clientmodule/javadoc/deprecated-list.html +++ b/java/com.tractrac.clientmodule/javadoc/deprecated-list.html @@ -2,9 +2,9 @@ - + Deprecated List - + diff --git a/java/com.tractrac.clientmodule/javadoc/help-doc.html b/java/com.tractrac.clientmodule/javadoc/help-doc.html index 4e7c1d32b41..ae0daa65b65 100755 --- a/java/com.tractrac.clientmodule/javadoc/help-doc.html +++ b/java/com.tractrac.clientmodule/javadoc/help-doc.html @@ -2,9 +2,9 @@ - + API Help - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-1.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-1.html index 0d20c0f1dc3..7c8049d8518 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-1.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-1.html @@ -2,9 +2,9 @@ - + A-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-10.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-10.html index 0d51e37fa8d..3abe9e0ad14 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-10.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-10.html @@ -2,9 +2,9 @@ - + L-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-11.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-11.html index abbfbb9106e..3b95bf121a0 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-11.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-11.html @@ -2,9 +2,9 @@ - + M-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-12.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-12.html index a5812f80029..ff9219c1063 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-12.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-12.html @@ -2,9 +2,9 @@ - + N-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-13.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-13.html index 4dc051721b7..434e8ebacf5 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-13.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-13.html @@ -2,9 +2,9 @@ - + O-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-14.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-14.html index 62e137086ce..222418dc1c9 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-14.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-14.html @@ -2,9 +2,9 @@ - + R-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-15.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-15.html index d45ad461c32..0ff70d2903b 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-15.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-15.html @@ -2,9 +2,9 @@ - + S-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-16.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-16.html index a5b17c63534..dbddc592ab6 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-16.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-16.html @@ -2,9 +2,9 @@ - + T-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-17.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-17.html index c04800eeca4..d1a5f2ac061 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-17.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-17.html @@ -2,9 +2,9 @@ - + U-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-18.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-18.html index de3aa7f61f0..2aa9d25b145 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-18.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-18.html @@ -2,9 +2,9 @@ - + V-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-2.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-2.html index 4d36bcfebd9..7b79ce0b3d8 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-2.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-2.html @@ -2,9 +2,9 @@ - + B-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-3.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-3.html index b64c4d60bf8..acd8fe8e536 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-3.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-3.html @@ -2,9 +2,9 @@ - + C-Index - + @@ -110,7 +110,10 @@ The route configuration: controls, paths and routes
    com.tractrac.model.lib.api.sensor - package com.tractrac.model.lib.api.sensor
    -
     
    +
    +
    + Sensor data interfaces
    +
    com.tractrac.model.lib.api.spatial - package com.tractrac.model.lib.api.spatial
    @@ -142,7 +145,10 @@ Subscriptions related with the races
    com.tractrac.subscription.lib.api.route - package com.tractrac.subscription.lib.api.route
    -
     
    +
    +
    + Subscriptions related with the routes
    +
    createCompressedPosition(double, double, double, double, double) - Method in interface com.tractrac.model.lib.api.data.IPositionFactory
    Create a compressed position that is a position that can be serialized and diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-4.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-4.html index f5fbecbbb04..0f2aab2b2bf 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-4.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-4.html @@ -2,9 +2,9 @@ - + D-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-5.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-5.html index f0ed8454ed7..f58324406df 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-5.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-5.html @@ -2,9 +2,9 @@ - + E-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-6.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-6.html index c34797543b9..8d0d5621170 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-6.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-6.html @@ -2,9 +2,9 @@ - + F-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-7.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-7.html index dce0bb7561f..08fe7bfe401 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-7.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-7.html @@ -2,9 +2,9 @@ - + G-Index - + @@ -197,7 +197,9 @@
    getDimensionAt(int) - Method in interface com.tractrac.model.lib.api.sensor.ISensorData
    -
    Gets a dimension by index
    +
    + Gets a dimension by index (it can be used if the consumer app knows the + order of the dimensions.
    getDirection() - Method in interface com.tractrac.model.lib.api.data.IPosition
    @@ -272,7 +274,9 @@
    getHeel() - Method in interface com.tractrac.model.lib.api.sensor.ISensorData
    -
    Gets the heel
    +
    + Gets the heel, defined as the tilting rotation of a vessel about its + longitudinal/X (front-back or bow-stern) axis.
    getHeight() - Method in interface com.tractrac.model.lib.api.spatial.IGeoCoordinate
    @@ -487,11 +491,20 @@
    getRaw() - Method in interface com.tractrac.model.lib.api.sensor.ISensorData
    -
    Gets the sensor data in a raw format.
    +
    + Gets the sensor data in a raw format.
    -
    getRideHeight() - Method in interface com.tractrac.model.lib.api.sensor.ISensorData
    +
    getRideHeightPort() - Method in interface com.tractrac.model.lib.api.sensor.ISensorData
    -
    Gets the ride height
    +
    + Gets the perpendicular distance from the bottom of the port-hull + to the water surface.
    +
    +
    getRideHeightStarboard() - Method in interface com.tractrac.model.lib.api.sensor.ISensorData
    +
    +
    + Gets the perpendicular distance from the bottom of the starboard-hull + to the water surface.
    getRoute(UUID) - Method in interface com.tractrac.model.lib.api.event.IEvent
    @@ -639,7 +652,9 @@
    getTrim() - Method in interface com.tractrac.model.lib.api.sensor.ISensorData
    -
    Gets the trim
    +
    + Gets the trim, defined as the up/down rotation of a vessel about its + lateral/Y (side-to-side or port-starboard) axis.
    getType() - Method in interface com.tractrac.subscription.lib.api.event.IStoredDataEvent
    @@ -688,14 +703,6 @@ Coming up: if the race has the ONLINE value in the event manager and it is coming up
    -
    getVMG() - Method in interface com.tractrac.model.lib.api.sensor.ISensorData
    -
    -
    Gets the VMG
    -
    -
    getWind() - Method in interface com.tractrac.model.lib.api.sensor.ISensorData
    -
    -
    Gets the wind
    -
    getX() - Method in interface com.tractrac.model.lib.api.spatial.ICoordinate
    The value of the x coordinate
    diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-8.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-8.html index 9cc5f8fdeab..8e505408b3e 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-8.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-8.html @@ -2,9 +2,9 @@ - + H-Index - + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-9.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-9.html index 1839be39b50..630338b3df8 100755 --- a/java/com.tractrac.clientmodule/javadoc/index-files/index-9.html +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-9.html @@ -2,9 +2,9 @@ - + I-Index - + @@ -358,7 +358,8 @@
    ISensorData - Interface in com.tractrac.model.lib.api.sensor
    -
    This interface implements sensor data.
    +
    + This interface implements sensor data.
    IServerTimeListener - Interface in com.tractrac.subscription.lib.api.event
    diff --git a/java/com.tractrac.clientmodule/javadoc/index.html b/java/com.tractrac.clientmodule/javadoc/index.html index 510f94ea79b..6e3820b553e 100755 --- a/java/com.tractrac.clientmodule/javadoc/index.html +++ b/java/com.tractrac.clientmodule/javadoc/index.html @@ -2,7 +2,7 @@ - + Generated Documentation (Untitled) diff --git a/java/com.tractrac.clientmodule/javadoc/overview-summary.html b/java/com.tractrac.clientmodule/javadoc/overview-summary.html index 1aa2941dee8..3bc03c7a133 100755 --- a/java/com.tractrac.clientmodule/javadoc/overview-summary.html +++ b/java/com.tractrac.clientmodule/javadoc/overview-summary.html @@ -2,9 +2,9 @@ - + Overview - + @@ -120,7 +120,10 @@ com.tractrac.model.lib.api.sensor -  + +
    + Sensor data interfaces
    + com.tractrac.model.lib.api.spatial @@ -166,7 +169,10 @@ com.tractrac.subscription.lib.api.route -  + +
    + Subscriptions related with the routes
    + diff --git a/java/com.tractrac.clientmodule/javadoc/overview-tree.html b/java/com.tractrac.clientmodule/javadoc/overview-tree.html index f06474e318a..15b46c0d56d 100755 --- a/java/com.tractrac.clientmodule/javadoc/overview-tree.html +++ b/java/com.tractrac.clientmodule/javadoc/overview-tree.html @@ -2,9 +2,9 @@ - + Class Hierarchy - + @@ -337,14 +337,14 @@ diff --git a/java/com.tractrac.clientmodule/javadoc/serialized-form.html b/java/com.tractrac.clientmodule/javadoc/serialized-form.html index 99e7b4fd22f..41b76082098 100755 --- a/java/com.tractrac.clientmodule/javadoc/serialized-form.html +++ b/java/com.tractrac.clientmodule/javadoc/serialized-form.html @@ -2,9 +2,9 @@ - + Serialized Form - + diff --git a/java/com.tractrac.clientmodule/lib/TracAPI-src.jar b/java/com.tractrac.clientmodule/lib/TracAPI-src.jar index efb9660b8d5..31f01cdeaf2 100755 Binary files a/java/com.tractrac.clientmodule/lib/TracAPI-src.jar and b/java/com.tractrac.clientmodule/lib/TracAPI-src.jar differ diff --git a/java/com.tractrac.clientmodule/lib/TracAPI.jar b/java/com.tractrac.clientmodule/lib/TracAPI.jar index 20f224c3c1b..290f947d14e 100755 Binary files a/java/com.tractrac.clientmodule/lib/TracAPI.jar and b/java/com.tractrac.clientmodule/lib/TracAPI.jar differ diff --git a/java/com.tractrac.clientmodule/pom.xml b/java/com.tractrac.clientmodule/pom.xml index 129a99459ff..83574fa3088 100755 --- a/java/com.tractrac.clientmodule/pom.xml +++ b/java/com.tractrac.clientmodule/pom.xml @@ -8,6 +8,6 @@ 1.0.0-SNAPSHOT com.tractrac.clientmodule - 3.8.0 + 3.10.0 eclipse-plugin diff --git a/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/BaseChart.java b/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/BaseChart.java index 5ad43857455..0f0dbdeb71a 100755 --- a/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/BaseChart.java +++ b/java/org.moxieapps.gwt.highcharts/src/org/moxieapps/gwt/highcharts/client/BaseChart.java @@ -3046,4 +3046,17 @@ public abstract class BaseChart extends Widget { if (chart) chart.showResetZoom(); }-*/; + + /** + * Patch, to provide highcharts functionality in Java API. + */ + public native void hideResetZoom() /*-{ + var chart = this.@org.moxieapps.gwt.highcharts.client.BaseChart::chart; + if (chart) { + if (chart.resetZoomButton) { + chart.zoomOut(); + } + } + }-*/; + } diff --git a/java/pom.xml b/java/pom.xml index 7a5cdf6c57d..b6a3145cd62 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -108,6 +108,8 @@ com.sap.sailing.domain.expeditionadapter com.sap.sailing.domain.tractracadapter com.sap.sailing.domain.tractracadapter.persistence + com.sap.sailing.domain.windfinderadapter + com.sap.sailing.domain.windfinderadapter.test com.sap.sailing.domain.igtimiadapter com.sap.sailing.domain.igtimiadapter.persistence com.sap.sailing.domain.racelogtrackingadapter diff --git a/java/target/refreshInstance.sh b/java/target/refreshInstance.sh index fc71af629cf..72c91465afc 100755 --- a/java/target/refreshInstance.sh +++ b/java/target/refreshInstance.sh @@ -143,15 +143,17 @@ install_environment () load_from_release_file () { if [[ $INSTALL_FROM_RELEASE != "" ]]; then - echo "Build/Deployment process has been started - it can take 5 to 20 minutes until your instance is ready. " | mail -r simon.marcel.pamies@sap.com -s "Build or Deployment of $INSTANCE_ID starting" $BUILD_COMPLETE_NOTIFY + echo "Build/Deployment process has been started - it can take 5 to 20 minutes until your instance is ready. " | mail -r simon.marcel.pamies@sap.com -s "Build or Deployment of $INSTANCE_ID to $SERVER_HOME for server $SERVER_NAME starting" $BUILD_COMPLETE_NOTIFY cd $SERVER_HOME rm -f $SERVER_HOME/$INSTALL_FROM_RELEASE.tar.gz* rm -rf plugins start stop status native-libraries org.eclipse.osgi *.tar.gz echo "Loading from release file http://releases.sapsailing.com/$INSTALL_FROM_RELEASE/$INSTALL_FROM_RELEASE.tar.gz" wget http://releases.sapsailing.com/$INSTALL_FROM_RELEASE/$INSTALL_FROM_RELEASE.tar.gz mv env.sh env.sh.preserved + mv configuration/mail.properties configuration/mail.properties.preserved tar xvzf $INSTALL_FROM_RELEASE.tar.gz mv env.sh.preserved env.sh + mv configuration/mail.properties.preserved configuration/mail.properties echo "Configuration for this server is unchanged - just binaries have been changed." else echo "The variable INSTALL_FROM_RELEASE has not been set therefore no release file will be installed!" @@ -165,8 +167,10 @@ load_from_local_release_file () rm -rf plugins start stop status native-libraries org.eclipse.osgi echo "Loading from release file $INSTALL_FROM_RELEASE" mv env.sh env.sh.preserved + mv configuration/mail.properties configuration/mail.properties.preserved tar xvzf $INSTALL_FROM_RELEASE mv env.sh.preserved env.sh + mv configuration/mail.properties.preserved configuration/mail.properties echo "Configuration for this server is unchanged - just binaries have been changed." else echo "The variable INSTALL_FROM_RELEASE has not been set therefore no release file will be installed!" diff --git a/java/target/start b/java/target/start index abc40dc289b..6d2ca902c50 100755 --- a/java/target/start +++ b/java/target/start @@ -62,7 +62,7 @@ sed $SED_ARGS "/replication.exchangeName/d" ./configuration/config.ini sed $SED_ARGS "/replication.exchangeHost/d" ./configuration/config.ini # Ensure that Jetty Port is set correctly -sed $SED_ARGS "s/^.*jetty.port.*$/<\/Set>/g" ./configuration/jetty/etc/jetty-selector.xml +sed $SED_ARGS "s/^.*jetty.http.port.*$/<\/Set>/g" ./configuration/jetty/etc/jetty-http.xml # Update monitoring with the right ports sed $SED_ARGS "s/127.0.0.1:[0-9][0-9][0-9][0-9]\//127.0.0.1:$SERVER_PORT\//g" ./configuration/monitoring.properties diff --git a/wiki/bug4232-review.md b/wiki/bug4232-review.md index f89ff10d1ce..95d9ed32576 100644 --- a/wiki/bug4232-review.md +++ b/wiki/bug4232-review.md @@ -20,15 +20,18 @@ One device will be called **A** and the other **B**, so they have unique names w * devices open the penalty fragment * device A give Omar Air OCS, device B set DNS for Omar Air * device A closes the penalty view (without publishing) -* device B should see a red warning sign (without active publish button) after the automatic refresh +* device B should see a red warning sign after the automatic refresh +* publish should be disabled and can be activated by changing every red item in the list ### Test 2 * same as [test 1](#test-scenarios_while-starting_test-1), but this time the result should be published * same result as [test 1](#test-scenarios_while-starting_test-1). +* publish should be disabled and can be activated by opening every red item in the list ### Test 3 * same as [test 1](#test-scenarios_while-starting_test-1), but after refresh device B should close the penalty view * after refresh device A should also see the red warning sign, without the possibility to publish +* publish should be disabled and can be activated by opening every red item in the list ### Test 4 * both devices open the penalty fragment @@ -64,6 +67,7 @@ One device will be called **A** and the other **B**, so they have unique names w * both devices set the penalty to different values * device A closes the result list * device B should see a red warning sign and the data should show the values from device A +* publish should be disabled and can be activated by opening every red item in the list ### Test 10 * both devices open result list @@ -76,12 +80,20 @@ One device will be called **A** and the other **B**, so they have unique names w * device A add Omar Air and Alinghi * device B add Alinghi and Omar Air * device A closes the result list -* device B should see yellow warning sign and the order Omar Air and Alinghi +* device B should see red warning sign and the order Omar Air and Alinghi +* publish should be disabled and can be activated by opening every red item in the list ### Test 12 * both devices open result list * device A add Omar Air and Alinghi * device B add Omar Air and LR Bar * device A closes the result list -* device B should see 1. Omar Air, 2. Alinghi and 2. LR Bar - because of two second places the publish button should be disabled +* device B should see 1. Omar Air, 2. Alinghi and 2. LR Bar +* publish should be disabled and can be activated by opening every item in the list and clean the duplicate rankings +### Test 13 +* devices open the penalty fragment +* device A give Omar Air OCS, device B set DNS for Omar Air +* device A pressed the home button +* device B should see a red warning sign after the automatic refresh +* publish should be disabled and can be activated by opening every red item in the list \ No newline at end of file diff --git a/wiki/howto/eventmanagers/linking-race-videos.md b/wiki/howto/eventmanagers/linking-race-videos.md index 54ca4f9a313..bab7c8a7785 100644 --- a/wiki/howto/eventmanagers/linking-race-videos.md +++ b/wiki/howto/eventmanagers/linking-race-videos.md @@ -1,43 +1,62 @@ -1. Open the video on Youtube +This articel provides a short guidance of how to synchronize races with their YouTube Videos. + +# 1. Open the video on YouTube 1. Check first race covered by the video. - 1. Seek to race start. Ideally, the video shows a start countdown for this race. - 1. Note the video time for the start - 1. Take the Youtube URL to the clip board - 1. Check last race covered -1. Open Admin Console + 2. Seek to race start. Ideally, the video shows a start countdown for this race (see figure 1). + 3. Note the video time for the start + 4. Take the Youtube URL to the clip board + 5. Check last race covered + +![Figure 1: Most races provide a race start indicator](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/linking%20race%20videos/start_race_indicator.png) +**Figure 1: Most races provide a countdown which indicates when the race is going to start** + +# 2. Open Admin Console 1. This step is necessary to elevate you as admin user - 1. Open sapsailing.com - 1. Go to event home page - 1. In the URL, replace "http:///gwt/Home.html/...." by "http:///gwt/AdminConsole.html" - 1. Enter login credentials -1. In a new bowser tab - 1. Open the first race covered by the video - 1. Click "Manage Media" (only visible when logged into Admin Console before) - 1. Click Add - 1. Paste the video’s Youtube URL - 1. Wait for the dialog field to fill - 1. Adjust the “Name” field --> pattern, e.g.: Day - (Race ..). E.g.: + 2. Open sapsailing.com + 3. Go to event home page + 4. In the URL, replace "http://`server name>`/gwt/Home.html/...." by "http://`server name`/gwt/AdminConsole.html". _Remember to access the -master url!_ + 5. Enter login credentials + +# 3. In a new bowser tab + 1. Open the first race covered by the video. It is located under the _races_ tab and will redirect you to the raceboard. + 2. Click "Manage Media" (see figure 2, only visible when logged into Admin Console before) + 3. Click Add + 4. Paste the video’s Youtube URL + 5. Wait for the dialog field to fill + 6. Adjust the “Name” field --> pattern, e.g.: Day - (Race ..). E.g.: 1. “Live Replay – Day One – ESS Nice 2014 (Race 8..13)” 1. “3D Graphics Only – Day One – ESS Nice 2014 (Race 8..13)” - 1. Click OK - 1. A video window will pop up with the video a time 0:00. -1. In the video window + 7. Click OK + 8. A video window will pop up with the video a time 0:00. + +![Figure 2: Manage media button is only visible when logged in as admin](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/linking%20race%20videos/manage-media-btn.png) +**Figure 2: Manage media button is only visible when logged in as admin** + +# 4. In the video window 1. Click “Edit” (--> decouples video from race) - 1. Seek video to start of first race (if available, a count down overlay on the video is very helpful) - 1. Click “Preview” - 1. Start the race --> the video should start running - 1. Jump race to first mark rounding --> the video should show leader round first mark - 1. Check a few other distinct race points (mark rounding, finish, …). Be cautious with sections of 3D graphics embedded into live video: they have a delay and don't suit for alignment. - 1. For fine tuning press “Edit” again, use +/- buttons and confirm with “Preview” - 1. Click “Save”. If you forget to save, changes will not be stored on our server! - 1. Done. The video is linked to first race and synchronized. -1. While subsequent races covered by the same video should already be synchronized, they still need to be linked explicitly -1. Go to admin console - 1. Open tab “Races/Manage Media” - 1. Search the recently added video ( use search box) - 1. For this video, click column “Linked Race” (showing first race only) - 1. In the “Linked Races” dialog select all races covered by the video -1. Open each race and check a few distinct points (start, finish) -1. If a race doesn’t match the video - 1. decouple this race and all subsequent races (most likely, the video has been cut) - 1. start again from step 3 \ No newline at end of file + 2. Seek video to start of first race (see figure 3, if available, a count down overlay on the video is very helpful) + 3. Click “Preview” + 4. Start the race --> the video should start running + 5. Jump race to first mark rounding --> the video should show leader round first mark + 6. Check a few other distinct race points (mark rounding, finish, …). Be cautious with sections of 3D graphics embedded into live video: they have a delay and don't suit for alignment. + 7. For fine tuning press “Edit” again, use +/- buttons and confirm with “Preview” + 8. Click “Save”. If you forget to save, changes will not be stored on our server! + 9. Done. The video is linked to first race and synchronized. + 10. While subsequent races covered by the same video should already be synchronized, they still need to be linked explicitly + +![Figure 3: Synchronize the tracked race with the Live-Stream](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/linking%20race%20videos/sync_races.png) + +**Figure 3: Synchronize the tracked race with the Live-Stream** + +# 5. Go to Admin Console + 1. Open tab “Races/Manage Media” (see figure 4) + 2. Search the recently added video ( use search box) + 3. For this video, click column “Linked Race” (showing first race only) + 4. In the “Linked Races” dialog select all races covered by the video + 5. Open each race and check a few distinct points (start, finish) + 6. If a race doesn’t match the video + 7. decouple this race and all subsequent races (most likely, the video has been cut) + 8. start again from step 3. of chaper 1 + +![Figure 4: Link all races covered by the video](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/linking%20race%20videos/multi_sync.png) +**Figure 4: Link all races covered by the video** \ No newline at end of file