diff --git a/configuration/buildAndUpdateProduct.sh b/configuration/buildAndUpdateProduct.sh index 36aed38c70d..608991787f5 100755 --- a/configuration/buildAndUpdateProduct.sh +++ b/configuration/buildAndUpdateProduct.sh @@ -272,7 +272,7 @@ if [[ "$@" == "release" ]]; then cp -v $p2PluginRepository/configuration/config.ini configuration/ cp -v $PROJECT_HOME/java/target/configuration/jetty/etc/jetty.xml configuration/jetty/etc - cp -v $PROJECT_HOME/java/target/configuration/jetty/etc/jetty-selector.xml configuration/jetty/etc + cp -v $PROJECT_HOME/java/target/configuration/jetty/etc/jetty-http.xml configuration/jetty/etc cp -v $PROJECT_HOME/java/target/configuration/jetty/etc/jetty-deployer.xml configuration/jetty/etc cp -v $PROJECT_HOME/java/target/configuration/jetty/etc/realm.properties configuration/jetty/etc cp -v $PROJECT_HOME/java/target/configuration/monitoring.properties configuration/ @@ -821,7 +821,7 @@ if [[ "$@" == "install" ]] || [[ "$@" == "all" ]]; then # overwrite configurations that should never be customized and belong to the build cp -v $p2PluginRepository/configuration/config.ini configuration/ cp -v $PROJECT_HOME/java/target/configuration/jetty/etc/jetty.xml configuration/jetty/etc - cp -v $PROJECT_HOME/java/target/configuration/jetty/etc/jetty-selector.xml configuration/jetty/etc + cp -v $PROJECT_HOME/java/target/configuration/jetty/etc/jetty-http.xml configuration/jetty/etc cp -v $PROJECT_HOME/java/target/configuration/jetty/etc/jetty-deployer.xml configuration/jetty/etc cp -v $PROJECT_HOME/java/target/configuration/jetty/etc/realm.properties configuration/jetty/etc @@ -857,7 +857,7 @@ if [[ "$@" == "install" ]] || [[ "$@" == "all" ]]; then sed -i "/expedition.udp.port/d" "$ACDIR/configuration/config.ini" sed -i "/replication.exchangeName/d" "$ACDIR/configuration/config.ini" sed -i "/replication.exchangeHost/d" "$ACDIR/configuration/config.ini" - sed -i "s/^.*jetty.port.*$/<\/Set>/g" "$ACDIR/configuration/jetty/etc/jetty-selector.xml" + sed -i "s/^.*jetty.port.*$/<\/Set>/g" "$ACDIR/configuration/jetty/etc/jetty-http.xml" echo "I have read the following configuration from $ACDIR/env.sh:" echo "SERVER_NAME: $SERVER_NAME" @@ -905,7 +905,7 @@ if [[ "$@" == "remote-deploy" ]]; then $SCP_CMD $p2PluginRepository/configuration/config.ini $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/ $SCP_CMD $PROJECT_HOME/java/target/configuration/jetty/etc/jetty.xml $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/jetty/etc - $SCP_CMD $PROJECT_HOME/java/target/configuration/jetty/etc/jetty-selector.xml $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/jetty/etc + $SCP_CMD $PROJECT_HOME/java/target/configuration/jetty/etc/jetty-http.xml $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/jetty/etc $SCP_CMD $PROJECT_HOME/java/target/configuration/jetty/etc/jetty-deployer.xml $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/jetty/etc $SCP_CMD $PROJECT_HOME/java/target/configuration/jetty/etc/realm.properties $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/jetty/etc $SCP_CMD $PROJECT_HOME/java/target/configuration/monitoring.properties $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/configuration/ 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 c5559225fb1..6dc5626ac91 100644 --- a/home.md +++ b/home.md @@ -117,7 +117,7 @@ SAP is at the center of today’s technology revolution, developing innovations ### Setup * [[Configure Races on Server|wiki/howto/setup/configure-races-on-server]] -* [[Setup local webserver to serve 360° videos|wiki/howto/setup/webserver/chrome-webserver]] +* [[Setup local webserver to serve 360° videos|wiki/howto/setup/webserver/nginx-webserver]] * [[Setting up internal Jenkins on SAP Monsoon|wiki/howto/setup/setting-up-jenkins-on-sap-monsoon]] ### Miscellaneous diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_es.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_es.properties index 95ff91055b5..4800033761f 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_es.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_es.properties @@ -81,6 +81,7 @@ Tack=Amura SpeedBefore=Velocidad antes de maniobra SpeedAfter=Velocidad después de maniobra DirectionChange=Cambio de dirección en grados +DirectionChangeWithinMainCurve=Cambio de dirección con curva principal en grados AbsoluteDirectionChange=Cambio absoluto de dirección en grados (positivo) ToSide=Al lado (babor / estribor) ManeuverLoss=Pérdida en maniobra @@ -126,3 +127,42 @@ AverageAbsoluteTrueWindAngle=Promedio del ángulo absoluto de viento real AbsoluteTrueWindAngle=Ángulo absoluto del viento real (TWA) Wind=Viento VMG=Velocity Made Good +MaxAngularVelocityInDegreesPerSecond=Velocidad máxima angular en grados +ManeuverEnteringAbsTWA=TWA absoluto antes de maniobra +ManeuverExitingAbsTWA=TWA absoluto tras maniobra +ManeuverSpeedDetailsRetrieverChain=Detalles de velocidad de maniobra +ManeuverSpeedDetails=Detalles de velocidad +SpeedSlope=Inclinación velocidad +ManeuverDirection=Dirección de maniobra +RatioToEnteringSpeed=Ratio por velocidad de entrada en maniobra +RatioToPreviousTWASpeed=Ratio por velocidad de TWA previo en maniobra +LowestRatioToEnteringSpeed=Ratio más baja por velocidad de entrada en maniobra +HighestRatioToEnteringSpeed=Ratio más alta por velocidad de entrada en maniobra +HighestRatioToEnteringSpeedMinusLowest=Ratio más alta por velocidad de entrada menos más baja en maniobra +EnteringManeuverSpeedMinusExitingSpeed=Velocidad de maniobra de entrada menos velocidad de salida +RatioBetweenManeuverEnteringAndExitingSpeed=Ratio entre velocidad de entrada y salida de maniobra +AvgTrendForTWAs=Promedio por TWA +MedianTrendForTWAs=Media por TWA +ManeuverDurationInSeconds=Duración de maniobra en segundos +AbsTWAAtManeuverClimax=TWA absoluto en clímax de maniobra +ManeuverEnteringSpeedInKnots=Velocidad de entrada de maniobra (kts) +ManeuverExitingSpeedInKnots=Velocidad de salida de maniobra (kts) +TackBeforeManeuver=Bordo antes de maniobra +ManeuverBoundariesRetrieverChain=Límites de maniobra +ManeuverBoundaries=Límites de maniobra +ManeuverStartSpeedDeviationRatioFromAvg=Ratio de desviación de velocidad de inicio de maniobra con velocidad media antes de maniobra +ManeuverStartCogDeviationFromAvgInDegrees=Desviación COG de inicio de maniobra con COG medio antes de maniobra en grados +ManeuverEndSpeedDeviationRatioFromAvg=Ratio de desviación de velocidad de fin de maniobra con velocidad media tras maniobra +ManeuverEndCogDeviationFromAvgInDegrees=Desviación COG de fin de maniobra con COG medio tras maniobra en grados +DurationToNextManeuverInSeconds=Duración de fin de maniobra actual hasta siguiente inicio de maniobra en segundos +DurationFromPreviousManeuverInSeconds=Duración de fin de maniobra anterior hasta siguiente inicio de maniobra actual en segundos +NextManeuverAtLeastOneSecondInFront=Siguiente inicio de maniobra es al menos un segundo después de fin de maniobra actual +PreviousManeuverAtLeastOneSecondBehind=Anterior fin de maniobra es al menos un segundo antes de inicio de maniobra actual +DurationBetweenStableSpeedWithCourseAndMainCurveBeginningInSeconds=Duración entre velocidad estable con rumbo e inicio de curva principal en segundos +SpeedRatioBetweenStableSpeedWithCourseAndMainCurveBeginning=Ratio de velocidad entre velocidad estable con rumbo e inicio de curva principal +AbsCourseDifferenceBetweenStableSpeedWithCourseAndMainCurveBeginningInDegrees=Diferencia de rumbo absoluto entre velocidad estable con rumbo e inicio de curva principal en grados +DurationBetweenStableSpeedWithCourseAndMainCurveEndInSeconds=Duración entre velocidad estable con rumbo y fin de curva principal en segundos +SpeedRatioBetweenStableSpeedWithCourseAndMainCurveEnd=Ratio de velocidad entre velocidad estable con rumbo y fin de curva principal +AbsCourseDifferenceBetweenStableSpeedWithCourseAndMainCurveEndInDegrees=Diferencia de rumbo absoluto entre velocidad estable con rumbo y fin de curva principal en grados +AbsTwaAtLowestRatioToEnteringSpeed=TWA absoluto en ratio más baja para velocidad de entrada +AbsTwaAtHighestRatioToEnteringSpeed=TWA absoluto en ratio más alta para velocidad de entrada diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_fr.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_fr.properties index 3ce43dc3a30..42a5e6f2c5a 100755 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_fr.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_fr.properties @@ -81,6 +81,7 @@ Tack=Virement SpeedBefore=Vitesse avant la manœuvre SpeedAfter=Vitesse après la manœuvre DirectionChange=Changement de cap en degrés +DirectionChangeWithinMainCurve=Changement de cap sur la courbe principale en degrés AbsoluteDirectionChange=Changement de cap absolu en degrés (positif) ToSide=À bâbord/À tribord ManeuverLoss=Perte suite à manœuvre @@ -126,3 +127,42 @@ AverageAbsoluteTrueWindAngle=Angle du vent réel moyen AbsoluteTrueWindAngle=Angle du vent réel (TWA) Wind=Vent VMG=Compromis Cap/Vitesse (VMG) +MaxAngularVelocityInDegreesPerSecond=Vitesse angulaire maximale en degrés/seconde +ManeuverEnteringAbsTWA=TWA absolu avant manœuvre +ManeuverExitingAbsTWA=TWA absolu après manœuvre +ManeuverSpeedDetailsRetrieverChain=Détails sur la vitesse lors de la manœuvre +ManeuverSpeedDetails=Détails sur la vitesse +SpeedSlope=Inclinaison en fonction de la vitesse +ManeuverDirection=Cap pendant la manœuvre +RatioToEnteringSpeed=Ratio de vitesse d''entrée lors de la manœuvre +RatioToPreviousTWASpeed=Ratio par rapport à la vitesse du précédent TWA lors de la manœuvre +LowestRatioToEnteringSpeed=Ratio le plus bas par rapport à la vitesse d''entrée lors de la manœuvre +HighestRatioToEnteringSpeed=Ratio le plus élevé par rapport à la vitesse d''entrée lors de la manœuvre +HighestRatioToEnteringSpeedMinusLowest=Ratio le plus élevé par rapport à la vitesse d''entrée moins ratio le plus bas +EnteringManeuverSpeedMinusExitingSpeed=Vitesse d''entrée de manœuvre moins vitesse de sortie +RatioBetweenManeuverEnteringAndExitingSpeed=Ratio de variation entre la vitesse d''entrée et de sortie de manœuvre +AvgTrendForTWAs=Moyenne par TWA +MedianTrendForTWAs=Médiane par TWA +ManeuverDurationInSeconds=Durée de la manœuvre en secondes +AbsTWAAtManeuverClimax=TWA absolu lors du point culminant de la manœuvre +ManeuverEnteringSpeedInKnots=Vitesse d''entrée de manœuvre (nds) +ManeuverExitingSpeedInKnots=Vitesse de sortie de manœuvre (nds) +TackBeforeManeuver=Virement avant manœuvre +ManeuverBoundariesRetrieverChain=Limites de manœuvre +ManeuverBoundaries=Limites de manœuvre +ManeuverStartSpeedDeviationRatioFromAvg=Ratio de variation de vitesse entre le début de la manœuvre et la vitesse moyenne avant manœuvre +ManeuverStartCogDeviationFromAvgInDegrees=Écart de route fond au début de la manœuvre avec route fond moyenne avant manœuvre en degrés +ManeuverEndSpeedDeviationRatioFromAvg=Ratio d''écart de vitesse à la fin de la manœuvre avec vitesse moyenne après manœuvre +ManeuverEndCogDeviationFromAvgInDegrees=Écart de route fond à la fin de la manœuvre avec route fond moyenne après manœuvre en degrés +DurationToNextManeuverInSeconds=Durée entre la fin de la manœuvre actuelle et le début de la prochaine manœuvre (en secondes) +DurationFromPreviousManeuverInSeconds=Durée entre la fin de la manœuvre précédente et le début de la manœuvre actuelle (en secondes) +NextManeuverAtLeastOneSecondInFront=La prochaine manœuvre débute au moins une seconde après la fin de la manœuvre actuelle +PreviousManeuverAtLeastOneSecondBehind=La manœuvre précédente se termine au moins une seconde avant le début de la manœuvre actuelle +DurationBetweenStableSpeedWithCourseAndMainCurveBeginningInSeconds=Durée entre vitesse stable de la course et début de la courbe principale en secondes +SpeedRatioBetweenStableSpeedWithCourseAndMainCurveBeginning=Ratio de variation de vitesse entre vitesse stable de la course et début de la courbe principale +AbsCourseDifferenceBetweenStableSpeedWithCourseAndMainCurveBeginningInDegrees=Écart de course absolu entre vitesse stable de la course et début de la courbe principale en degrés +DurationBetweenStableSpeedWithCourseAndMainCurveEndInSeconds=Durée entre vitesse stable de la course et fin de la courbe principale en secondes +SpeedRatioBetweenStableSpeedWithCourseAndMainCurveEnd=Ratio de variation de vitesse entre vitesse stable de la course et fin de la courbe principale +AbsCourseDifferenceBetweenStableSpeedWithCourseAndMainCurveEndInDegrees=Écart de course absolu entre vitesse stable de la course et fin de la courbe principale en degrés +AbsTwaAtLowestRatioToEnteringSpeed=TWA absolu au ratio le plus bas par rapport à la vitesse d''entrée +AbsTwaAtHighestRatioToEnteringSpeed=TWA absolu au ratio le plus élevé par rapport à la vitesse d''entrée diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ja.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ja.properties index f717f279487..d845c9fa791 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ja.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ja.properties @@ -81,6 +81,7 @@ Tack=タック SpeedBefore=マニューバー前の艇速 SpeedAfter=マニューバー後の艇速 DirectionChange=方向転換 (度) +DirectionChangeWithinMainCurve=主曲線内の方向転換 (度) AbsoluteDirectionChange=方向転換 (度) の絶対値 ToSide=方向転換する側 (ポート/スターボード) ManeuverLoss=マニューバーロス @@ -126,3 +127,42 @@ AverageAbsoluteTrueWindAngle=平均絶対真風角度 AbsoluteTrueWindAngle=絶対真風角度 (TWA) Wind=風 VMG=VMG (有効速度) +MaxAngularVelocityInDegreesPerSecond=最大角速度 (deg/s) +ManeuverEnteringAbsTWA=マニューバー前の絶対 TWA +ManeuverExitingAbsTWA=マニューバー後の絶対 TWA +ManeuverSpeedDetailsRetrieverChain=マニューバー速度詳細 +ManeuverSpeedDetails=速度詳細 +SpeedSlope=速度勾配 +ManeuverDirection=マニューバー方向 +RatioToEnteringSpeed=マニューバー内の進入速度との比率 +RatioToPreviousTWASpeed=マニューバー内の前 TWA の速度との比率 +LowestRatioToEnteringSpeed=マニューバー内の進入速度との最低比率 +HighestRatioToEnteringSpeed=マニューバー内の進入速度との最高比率 +HighestRatioToEnteringSpeedMinusLowest=マニューバー内の進入速度から最低速度を引いたものとの最高比率 +EnteringManeuverSpeedMinusExitingSpeed=マニューバーの進入速度から脱出速度を引いたもの +RatioBetweenManeuverEnteringAndExitingSpeed=マニューバーの進入速度と脱出速度の比率 +AvgTrendForTWAs=TWA 別の平均値 +MedianTrendForTWAs=TWA 別の中央値 +ManeuverDurationInSeconds=マニューバー持続時間 (秒) +AbsTWAAtManeuverClimax=マニューバー頂点の絶対 TWA +ManeuverEnteringSpeedInKnots=マニューバー進入速度 (kts) +ManeuverExitingSpeedInKnots=マニューバー脱出速度 (kts) +TackBeforeManeuver=マニューバー前のタック +ManeuverBoundariesRetrieverChain=マニューバー境界 +ManeuverBoundaries=マニューバー境界 +ManeuverStartSpeedDeviationRatioFromAvg=マニューバー前平均速度と比較したマニューバー開始時の速度差異比 +ManeuverStartCogDeviationFromAvgInDegrees=マニューバー前平均 COG と比較したマニューバー開始時の COG 差異 (度) +ManeuverEndSpeedDeviationRatioFromAvg=マニューバー後平均速度と比較したマニューバー終了時の速度差異比 +ManeuverEndCogDeviationFromAvgInDegrees=マニューバー後平均 COG と比較したマニューバー終了時の COG 差異 (度) +DurationToNextManeuverInSeconds=現マニューバー終了から次マニューバー開始までの時間 (秒) +DurationFromPreviousManeuverInSeconds=前マニューバー終了から現マニューバー開始までの時間 (秒) +NextManeuverAtLeastOneSecondInFront=次マニューバー開始は現マニューバー終了の少なくとも 1 秒後 +PreviousManeuverAtLeastOneSecondBehind=前マニューバー終了は現マニューバー開始の少なくとも 1 秒前 +DurationBetweenStableSpeedWithCourseAndMainCurveBeginningInSeconds=コース安定速度と主曲線始点の間の時間 (秒) +SpeedRatioBetweenStableSpeedWithCourseAndMainCurveBeginning=コース安定速度と主曲線始点の速度比 +AbsCourseDifferenceBetweenStableSpeedWithCourseAndMainCurveBeginningInDegrees=コース安定速度と主曲線始点の絶対コース差異 (度) +DurationBetweenStableSpeedWithCourseAndMainCurveEndInSeconds=コース安定速度と主曲線終点の間の時間 (秒) +SpeedRatioBetweenStableSpeedWithCourseAndMainCurveEnd=コース安定速度と主曲線終点の速度比 +AbsCourseDifferenceBetweenStableSpeedWithCourseAndMainCurveEndInDegrees=コース安定速度と主曲線終点の絶対コース差異 (度) +AbsTwaAtLowestRatioToEnteringSpeed=進入速度との最低比率での絶対 TWA +AbsTwaAtHighestRatioToEnteringSpeed=進入速度との最高比率での絶対 TWA diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_pt.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_pt.properties index 85f42265561..ae5146cf7cb 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_pt.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_pt.properties @@ -81,6 +81,7 @@ Tack=Cambar SpeedBefore=Velocidade antes de manobra SpeedAfter=Velocidade depois de manobra DirectionChange=Mudança de direção em graus +DirectionChangeWithinMainCurve=Mudança de direção na curva principal em graus AbsoluteDirectionChange=Mudança absoluta de direção em graus (positiva) ToSide=Até lado (bombordo/boreste) ManeuverLoss=Perda de manobra @@ -126,3 +127,42 @@ AverageAbsoluteTrueWindAngle=Ângulo de vento verdadeiro absoluto médio AbsoluteTrueWindAngle=Ângulo de vento verdadeiro absoluto (TWA) Wind=Vento VMG=Velocidade diretamente na linha do vento real (VMG) +MaxAngularVelocityInDegreesPerSecond=Velocidade angular máxima em graus/s +ManeuverEnteringAbsTWA=TWA absoluto antes da manobra +ManeuverExitingAbsTWA=TWA absoluto após a manobra +ManeuverSpeedDetailsRetrieverChain=Detalhes da velocidade da manobra +ManeuverSpeedDetails=Detalhes da velocidade +SpeedSlope=Inclinação de velocidade +ManeuverDirection=Direção da manobra +RatioToEnteringSpeed=Proporção em relação à velocidade de entrada na manobra +RatioToPreviousTWASpeed=Proporção em relação à velocidade do TWA anterior na manobra +LowestRatioToEnteringSpeed=Proporção mínima em relação à velocidade de entrada na manobra +HighestRatioToEnteringSpeed=Proporção máxima em relação à velocidade de entrada na manobra +HighestRatioToEnteringSpeedMinusLowest=Proporção máxima em relação à velocidade de entrada menos a mínima na manobra +EnteringManeuverSpeedMinusExitingSpeed=Velocidade de entrada da manobra menos velocidade de saída +RatioBetweenManeuverEnteringAndExitingSpeed=Proporção entre velocidade de entrada e saída da manobra +AvgTrendForTWAs=Média por TWA +MedianTrendForTWAs=Mediana por TWA +ManeuverDurationInSeconds=Duração da manobra em segundos +AbsTWAAtManeuverClimax=TWA absoluto no ponto culminante da manobra +ManeuverEnteringSpeedInKnots=Velocidade de entrada da manobra (nós) +ManeuverExitingSpeedInKnots=Velocidade de saída da manobra (nós) +TackBeforeManeuver=Cambada antes da manobra +ManeuverBoundariesRetrieverChain=Limites da manobra +ManeuverBoundaries=Limites da manobra +ManeuverStartSpeedDeviationRatioFromAvg=Proporção de desvio de velocidade de início da manobra com velocidade média antes da manobra +ManeuverStartCogDeviationFromAvgInDegrees=Desvio de percurso no fundo de início da manobra com percurso médio no fundo antes da manobra em graus +ManeuverEndSpeedDeviationRatioFromAvg=Proporção de desvio de velocidade de fim da manobra com velocidade média após a manobra +ManeuverEndCogDeviationFromAvgInDegrees=Desvio de percurso no fundo de fim da manobra com percurso médio no fundo após a manobra em graus +DurationToNextManeuverInSeconds=Duração desde o fim da manobra atual até o início da próxima manobra em segundos +DurationFromPreviousManeuverInSeconds=Duração desde o fim da manobra anterior até o início da manobra atual em segundos +NextManeuverAtLeastOneSecondInFront=O início da manobra seguinte é pelo menos um segundo após o fim da manobra atual +PreviousManeuverAtLeastOneSecondBehind=O fim da manobra anterior é pelo menos um segundo antes do início da manobra atual +DurationBetweenStableSpeedWithCourseAndMainCurveBeginningInSeconds=Duração entre velocidade estável com percurso e início da curva principal em segundos +SpeedRatioBetweenStableSpeedWithCourseAndMainCurveBeginning=Proporção de velocidade entre velocidade estável com percurso e início da curva principal +AbsCourseDifferenceBetweenStableSpeedWithCourseAndMainCurveBeginningInDegrees=Diferença de percurso absoluto entre velocidade estável com percurso e início da curva principal em graus +DurationBetweenStableSpeedWithCourseAndMainCurveEndInSeconds=Duração entre velocidade estável com percurso e fim da curva principal em segundos +SpeedRatioBetweenStableSpeedWithCourseAndMainCurveEnd=Proporção de velocidade entre velocidade estável com percurso e fim da curva principal +AbsCourseDifferenceBetweenStableSpeedWithCourseAndMainCurveEndInDegrees=Diferença de percurso absoluto entre velocidade estável com percurso e fim da curva principal em graus +AbsTwaAtLowestRatioToEnteringSpeed=TWA absoluto na proporção mínima em relação a velocidade de entrada +AbsTwaAtHighestRatioToEnteringSpeed=TWA absoluto na proporção máxima em relação a velocidade de entrada diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ru.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ru.properties index 85f6623a1fa..bf787d80fbd 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ru.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ru.properties @@ -81,6 +81,7 @@ Tack=Оверштаг SpeedBefore=Скорость до маневра SpeedAfter=Скорость после маневра DirectionChange=Изменение направления в градусах +DirectionChangeWithinMainCurve=Изменение направления в пределах основной кривой в градусах AbsoluteDirectionChange=Абсолютное изменение направления в градусах (положительное) ToSide=Борт (левый/правый) ManeuverLoss=Потеря маневра @@ -126,3 +127,42 @@ AverageAbsoluteTrueWindAngle=Средний абсолютный угол ист AbsoluteTrueWindAngle=Абсолютный угол истинного ветра (TWA) Wind=Ветер VMG=Эффективная скорость лавирования (VMG) +MaxAngularVelocityInDegreesPerSecond=Максимальная угловая скорость (град./с) +ManeuverEnteringAbsTWA=Абсолютный TWA до маневра +ManeuverExitingAbsTWA=Абсолютный TWA после маневра +ManeuverSpeedDetailsRetrieverChain=Скорость маневра подробно +ManeuverSpeedDetails=Скорость подробно +SpeedSlope=Градиент скорости +ManeuverDirection=Направление маневра +RatioToEnteringSpeed=Отношение к входной скорости маневра +RatioToPreviousTWASpeed=Отношение к скорости предыдущего TWA в маневре +LowestRatioToEnteringSpeed=Наименьшее отношение к входной скорости маневра +HighestRatioToEnteringSpeed=Наибольшее отношение к входной скорости маневра +HighestRatioToEnteringSpeedMinusLowest=Разность наибольшего и наименьшего отношений к входной скорости маневра в маневре +EnteringManeuverSpeedMinusExitingSpeed=Разность входной и выходной скоростей маневра +RatioBetweenManeuverEnteringAndExitingSpeed=Отношение входной и выходной скоростей маневра +AvgTrendForTWAs=Среднее по TWA +MedianTrendForTWAs=Медиана по TWA +ManeuverDurationInSeconds=Длительность маневра в секундах +AbsTWAAtManeuverClimax=Абсолютный TWA в крайней точке маневра +ManeuverEnteringSpeedInKnots=Входная скорость маневра (уз.) +ManeuverExitingSpeedInKnots=Выходная скорость маневра (уз.) +TackBeforeManeuver=Галс до маневра +ManeuverBoundariesRetrieverChain=Границы маневра +ManeuverBoundaries=Границы маневра +ManeuverStartSpeedDeviationRatioFromAvg=Коэффициент отклонения скорости начала маневра со ср. скоростью до маневра +ManeuverStartCogDeviationFromAvgInDegrees=Отклонение COG начала маневра со ср. COG до маневра в градусах +ManeuverEndSpeedDeviationRatioFromAvg=Коэффициент отклонения скорости завершения маневра со ср. скоростью после маневра +ManeuverEndCogDeviationFromAvgInDegrees=Отклонение COG завершения маневра со ср. COG после маневра в градусах +DurationToNextManeuverInSeconds=Длительность от завершения текущего маневра до начала следующего маневра в секундах +DurationFromPreviousManeuverInSeconds=Длительность от завершения предыдущего маневра до начала текущего маневра в секундах +NextManeuverAtLeastOneSecondInFront=Начало следующего маневра не ранее одной секунды после завершения текущего маневра +PreviousManeuverAtLeastOneSecondBehind=Завершение предыдущего маневра не ранее одной секунды после начала текущего маневра +DurationBetweenStableSpeedWithCourseAndMainCurveBeginningInSeconds=Длительность между стабильной скоростью с курсом и началом основной кривой в секундах +SpeedRatioBetweenStableSpeedWithCourseAndMainCurveBeginning=Соотношение скоростей между стабильной скоростью с курсом и основной кривой +AbsCourseDifferenceBetweenStableSpeedWithCourseAndMainCurveBeginningInDegrees=Абс. разница курса между стабильной скоростью с курсом и началом основной кривой в градусах +DurationBetweenStableSpeedWithCourseAndMainCurveEndInSeconds=Длительность между стабильной скоростью с курсом и завершением основной кривой в секундах +SpeedRatioBetweenStableSpeedWithCourseAndMainCurveEnd=Соотношение скоростей между стабильной скоростью с курсом и завершением основной кривой +AbsCourseDifferenceBetweenStableSpeedWithCourseAndMainCurveEndInDegrees=Абс. разница курса между стабильной скоростью с курсом и завершением основной кривой в градусах +AbsTwaAtLowestRatioToEnteringSpeed=Абс. TWA при наименьшем отношении к входной скорости +AbsTwaAtHighestRatioToEnteringSpeed=Абс. TWA при наибольшем отношении к входной скорости diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_zh.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_zh.properties index 3ef6c3ac327..76c24aabd74 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_zh.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_zh.properties @@ -81,6 +81,7 @@ Tack=迎风转向 SpeedBefore=操作前的速度 SpeedAfter=操作后的速度 DirectionChange=风向变化角度 +DirectionChangeWithinMainCurve=主曲线内的风向变化角度 AbsoluteDirectionChange=绝对风向变化角度(正向) ToSide=至侧(左舷/右舷) ManeuverLoss=操作损失 @@ -126,3 +127,42 @@ AverageAbsoluteTrueWindAngle=平均绝对实际风向角 AbsoluteTrueWindAngle=绝对实际风向角 (TWA) Wind=风力 VMG=沿计划航线上的航速 +MaxAngularVelocityInDegreesPerSecond=最大角速度 (deg/s) +ManeuverEnteringAbsTWA=操作前的绝对实际风向角 +ManeuverExitingAbsTWA=操作后的绝对实际风向角 +ManeuverSpeedDetailsRetrieverChain=操作速度详情 +ManeuverSpeedDetails=速度详情 +SpeedSlope=速度坡度 +ManeuverDirection=操作方向 +RatioToEnteringSpeed=操作内驶入速度的比率 +RatioToPreviousTWASpeed=操作内先前实际风向角速度的比率 +LowestRatioToEnteringSpeed=操作内驶入速度的最低比率 +HighestRatioToEnteringSpeed=操作内驶入速度的最高比率 +HighestRatioToEnteringSpeedMinusLowest=操作内驶入速度减去最低速度的最高比率 +EnteringManeuverSpeedMinusExitingSpeed=驶入操作速度减去驶出速度 +RatioBetweenManeuverEnteringAndExitingSpeed=操作驶入速度与驶出速度之间的比率 +AvgTrendForTWAs=各实际风向角的平均值 +MedianTrendForTWAs=各实际风向角的中值 +ManeuverDurationInSeconds=操作持续时间(秒) +AbsTWAAtManeuverClimax=操作顶点的绝对实际风向角 +ManeuverEnteringSpeedInKnots=操作驶入速度 (kts) +ManeuverExitingSpeedInKnots=操作驶出速度 (kts) +TackBeforeManeuver=操作前迎风转向 +ManeuverBoundariesRetrieverChain=操作边界 +ManeuverBoundaries=操作边界 +ManeuverStartSpeedDeviationRatioFromAvg=操作前以平均速度开始操作的速度偏差率 +ManeuverStartCogDeviationFromAvgInDegrees=操作前以平均对地航向开始操作的对地航向偏差角度 +ManeuverEndSpeedDeviationRatioFromAvg=操作后以平均速度结束操作的速度偏差率 +ManeuverEndCogDeviationFromAvgInDegrees=操作后以平均对地航向结束操作的对地航向偏差角度 +DurationToNextManeuverInSeconds=自当前操作结束到下次操作开始的持续时间(秒) +DurationFromPreviousManeuverInSeconds=自先前操作结束到当前操作开始的持续时间(秒) +NextManeuverAtLeastOneSecondInFront=下次操作开始至少在当前操作结束后一秒 +PreviousManeuverAtLeastOneSecondBehind=先前操作结束至少在当前操作开始前一秒 +DurationBetweenStableSpeedWithCourseAndMainCurveBeginningInSeconds=场地内稳定速度与主曲线开始之间的持续时间(秒) +SpeedRatioBetweenStableSpeedWithCourseAndMainCurveBeginning=场地内稳定速度与主曲线开始之间的速度比率 +AbsCourseDifferenceBetweenStableSpeedWithCourseAndMainCurveBeginningInDegrees=场地内稳定速度与主曲线开始之间的绝对场地差异角度 +DurationBetweenStableSpeedWithCourseAndMainCurveEndInSeconds=场地内稳定速度与主曲线结束之间的持续时间(秒) +SpeedRatioBetweenStableSpeedWithCourseAndMainCurveEnd=场地内稳定速度与主曲线结束之间的速度比率 +AbsCourseDifferenceBetweenStableSpeedWithCourseAndMainCurveEndInDegrees=场地内稳定速度与主曲线结束之间的绝对场地差异角度 +AbsTwaAtLowestRatioToEnteringSpeed=最低比率下驶入速度的绝对实际风向角 +AbsTwaAtHighestRatioToEnteringSpeed=最高比率下驶入速度的绝对实际风向角 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/security/Permission.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Permission.java index befba93a52f..e8cc4b42e5b 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Permission.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Permission.java @@ -32,6 +32,7 @@ public enum Permission implements com.sap.sse.security.shared.Permission { MANAGE_MARK_PASSINGS, MANAGE_MARK_POSITIONS, CAN_REPLAY_DURING_LIVE_RACES, + DETAIL_TIMER, // back-end permissions EVENT, 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/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.igtimiadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.igtimiadapter/META-INF/MANIFEST.MF index df6b9e37db7..ce51ab8437d 100644 --- a/java/com.sap.sailing.domain.igtimiadapter/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.igtimiadapter/META-INF/MANIFEST.MF @@ -23,15 +23,16 @@ Import-Package: javax.ws.rs;version="1.1.1", javax.ws.rs.ext;version="1.1.1", org.apache.commons.lang, org.codehaus.jackson.jaxrs;version="1.9.13", - org.eclipse.jetty.io;version="9.0.0", - org.eclipse.jetty.util.component;version="9.0.4", - org.eclipse.jetty.util.ssl, - org.eclipse.jetty.util.thread, - org.eclipse.jetty.websocket.api;version="9.0.0", - org.eclipse.jetty.websocket.api.annotations;version="9.0.0", - org.eclipse.jetty.websocket.api.extensions;version="9.0.0", - org.eclipse.jetty.websocket.client;version="9.0.0", - org.eclipse.jetty.websocket.common;version="9.0.0", + org.eclipse.jetty.io;version="9.4.8", + org.eclipse.jetty.util.component;version="9.4.8", + org.eclipse.jetty.util.ssl;version="9.4.8", + org.eclipse.jetty.util.thread;version="9.4.8", + org.eclipse.jetty.websocket.api;version="9.4.8", + org.eclipse.jetty.websocket.api.annotations;version="9.4.8", + org.eclipse.jetty.websocket.api.extensions;version="9.4.8", + org.eclipse.jetty.websocket.client;version="9.4.8", + org.eclipse.jetty.websocket.common;version="9.4.8", + org.eclipse.jetty.client;version="9.4.8", org.osgi.framework;version="1.7.0" Web-ContextPath: /igtimi Bundle-Activator: com.sap.sailing.domain.igtimiadapter.impl.Activator 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/CreateAndTrackWithRaceLogTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java index 4c9f475b7f3..0e20c9b4525 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java @@ -54,6 +54,8 @@ import com.sap.sailing.domain.racelogtracking.impl.fixtracker.RaceLogFixTrackerM import com.sap.sailing.domain.racelogtracking.test.RaceLogTrackingTestHelper; import com.sap.sailing.domain.ranking.OneDesignRankingMetric; import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.RaceHandle; +import com.sap.sailing.domain.tracking.RaceListener; import com.sap.sailing.domain.tracking.Track; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.server.RacingEventService; @@ -119,7 +121,7 @@ public class CreateAndTrackWithRaceLogTest extends RaceLogTrackingTestHelper { Exception { RaceColumn column = leaderboard.getRaceColumnByName(columnName); exception.expect(NotDenotedForRaceLogTrackingException.class); - adapter.startTracking(service, leaderboard, column, fleet, /* trackWind */ false, /* correctWindDirectionByMagneticDeclination */ false); + trackRace(column); } private void testSize(Track track, int expected) { @@ -190,7 +192,7 @@ public class CreateAndTrackWithRaceLogTest extends RaceLogTrackingTestHelper { raceLog.add(new RaceLogRegisterCompetitorEventImpl(t(), author, 0, comp1)); raceLog.add(new RaceLogStartOfTrackingEventImpl(t(0), author, /* passId */ 0)); // start tracking - adapter.startTracking(service, leaderboard, column, fleet, /* trackWind */ false, /* correctWindDirectionByMagneticDeclination */ false); + trackRace(column); // now there is a tracked race @@ -212,6 +214,23 @@ public class CreateAndTrackWithRaceLogTest extends RaceLogTrackingTestHelper { raceLogFixTrackerManager.stop(/* preemptive */ false, /* willBeRemoved */ false); addFixes3(race, comp1, dev1); } + + private void trackRace(RaceColumn column) throws NotDenotedForRaceLogTrackingException, Exception { + final RaceHandle raceHandle = adapter.startTracking(service, leaderboard, column, fleet, /* trackWind */ false, /* correctWindDirectionByMagneticDeclination */ false); + + // The following lines ensure that we received all necessary events from the TrackedRegatta + // When removing a listener we get a Future whose get method will block until all previously triggered events are processed. + final RaceListener raceListener = new RaceListener() { + @Override + public void raceRemoved(TrackedRace trackedRace) { + } + @Override + public void raceAdded(TrackedRace trackedRace) { + } + }; + raceHandle.getTrackedRegatta().addRaceListener(raceListener); + raceHandle.getTrackedRegatta().removeRaceListener(raceListener).get(); + } /** * See bug 4114: when the regatta is set to control the tracking times from start / finish times, @@ -227,6 +246,7 @@ public class CreateAndTrackWithRaceLogTest extends RaceLogTrackingTestHelper { adapter.denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, "race"); // start tracking adapter.startTracking(service, leaderboard, column, fleet, /* trackWind */ false, /* correctWindDirectionByMagneticDeclination */ false); + // now there is a trackedrace TrackedRace race = column.getTrackedRace(fleet); assertNotNull(race); @@ -246,7 +266,7 @@ public class CreateAndTrackWithRaceLogTest extends RaceLogTrackingTestHelper { // now remove the tracked race and track again; again, the explicit tracking times in the race log must prevail service.removeRace(regatta, race.getRace()); - adapter.startTracking(service, leaderboard, column, fleet, /* trackWind */ false, /* correctWindDirectionByMagneticDeclination */ false); + trackRace(column); TrackedRace race2 = column.getTrackedRace(fleet); assertEquals(explicitStartOfTracking, race2.getStartOfTracking()); assertEquals(explicitEndOfTracking, race2.getEndOfTracking()); @@ -269,8 +289,7 @@ public class CreateAndTrackWithRaceLogTest extends RaceLogTrackingTestHelper { regattaLog.add(new RegattaLogRegisterCompetitorEventImpl(t(), t(), author, UUID.randomUUID(), comp1)); raceLog.add(new RaceLogStartOfTrackingEventImpl(t(0), author, /* passId */ 0)); - // start tracking - adapter.startTracking(service, leaderboard, column, fleet, /* trackWind */ false, /* correctWindDirectionByMagneticDeclination */ false); + trackRace(column); // now there is a trackedrace TrackedRace race = column.getTrackedRace(fleet); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/CompetitorResults.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/CompetitorResults.java index b248d2bf176..094837aeb06 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/CompetitorResults.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/CompetitorResults.java @@ -7,6 +7,8 @@ package com.sap.sailing.domain.abstractlog.race; */ public interface CompetitorResults extends Iterable { boolean add(CompetitorResult result); + void clear(); + int size(); /** * @return {@code true} if there are more than one {@link CompetitorResult} contained with equal 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.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceState.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceState.java index fec6c05ab7e..6df0c3f9ca0 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceState.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/RaceState.java @@ -170,7 +170,7 @@ public interface RaceState extends ReadonlyRaceState { /** * Sets a new confirmed finishing list. */ - void setFinishPositioningConfirmed(TimePoint now); + void setFinishPositioningConfirmed(TimePoint now, CompetitorResults positionedCompetitors); /** * Sets a new active course design. diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java index 2fbd998177e..a20fc0ad4ea 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java @@ -202,9 +202,9 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState { } @Override - public void setFinishPositioningConfirmed(TimePoint timePoint) { + public void setFinishPositioningConfirmed(TimePoint timePoint, CompetitorResults positionedCompetitors) { raceLog.add(new RaceLogFinishPositioningConfirmedEventImpl( - timePoint, author, raceLog.getCurrentPassId(), getFinishPositioningList())); + timePoint, author, raceLog.getCurrentPassId(), positionedCompetitors)); } @Override 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.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index d5750979088..e4a45f5d83b 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.NavigableSet; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.Future; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver; @@ -590,10 +591,6 @@ public class MockedTrackedRace implements DynamicTrackedRace { return null; } - @Override - public void removeTrackedRace(RaceDefinition raceDefinition) { - } - @Override public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable sidelines, WindStore windStore, long delayToLiveInMillis, @@ -620,7 +617,8 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public void removeRaceListener(RaceListener listener) { + public Future removeRaceListener(RaceListener listener) { + return null; } }; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaTest.java new file mode 100644 index 00000000000..0c18176671a --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaTest.java @@ -0,0 +1,122 @@ +package com.sap.sailing.domain.tracking.impl; + +import static org.mockito.Mockito.mock; + +import java.util.Arrays; +import java.util.Collections; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.Phaser; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver; +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Sideline; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.CourseImpl; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; +import com.sap.sailing.domain.ranking.OneDesignRankingMetric; +import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; +import com.sap.sailing.domain.tracking.RaceListener; +import com.sap.sailing.domain.tracking.TrackedRace; + +public class TrackedRegattaTest { + + private final Mark mark = DomainFactory.INSTANCE.getOrCreateMark("mark"); + private final Mark mark2 = DomainFactory.INSTANCE.getOrCreateMark("mark2"); + private final BoatClass boatClass = DomainFactory.INSTANCE.getOrCreateBoatClass("49er"); + + private Course course = new CourseImpl("course", + Arrays.asList(new Waypoint[] { new WaypointImpl(mark), new WaypointImpl(mark2) })); + private DynamicTrackedRegatta regatta; + + @Before + public void setUp() { + regatta = new DynamicTrackedRegattaImpl(new RegattaImpl(EmptyRaceLogStore.INSTANCE, + EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, + /* startDate */ null, /* endDate */null, null, null, "a", null)); + } + + @Test(expected = TimeoutException.class) + public void testBug4429() throws Exception { + final Phaser addPhaser = new Phaser(2); + final CyclicBarrier removeBarrier = new CyclicBarrier(2); + + regatta.addRaceListener(new RaceListener() { + @Override + public void raceRemoved(TrackedRace trackedRace) { + try { + removeBarrier.await(1, TimeUnit.SECONDS); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void raceAdded(TrackedRace trackedRace) { + try { + addPhaser.arriveAndAwaitAdvance(); + addPhaser.arriveAndAwaitAdvance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + + DynamicTrackedRace race1 = createRace("R1"); + Thread thread1 = new Thread(() -> { + regatta.addTrackedRace(race1); + }); + thread1.start(); + // This ensures, that the add event is being processed but is not finished because + // this unblocks the first arriveAndAwaitAdvance() in raceAdded, but not the second. + // This way, the raceRemoved(...) call is expected to not be started because it has + // to wait for the raceAdded(...) call to have finished. + addPhaser.arriveAndAwaitAdvance(); + + Thread thread2 = new Thread(() -> { + regatta.removeTrackedRace(race1); + }); + thread2.start(); + // If the implementation ensures that the events are fired in order, + // the removeBarrier will run into a TimeoutException because the addBarrier + // is not solved and while the first event is processed the second one + // should not be started to be processed. + try { + removeBarrier.await(10, TimeUnit.MILLISECONDS); + // if this line is reached, the order of events is not correctly ensured + Assert.fail(); + } finally { + addPhaser.forceTermination(); + try { + // solved the barrier if one hangs at this point + // a TimeoutException can occur but we do not care about it + removeBarrier.await(10, TimeUnit.MILLISECONDS); + } catch (Exception e) { + } + thread1.join(1000); + thread2.join(1000); + } + + } + + private DynamicTrackedRace createRace(String name) { + RaceDefinition race1 = new RaceDefinitionImpl(name, course, boatClass, Collections.emptyList()); + return new DynamicTrackedRaceImpl(regatta, race1, Collections. emptyList(), + EmptyWindStore.INSTANCE, 0, 0, 0, /* useMarkPassingCalculator */ false, OneDesignRankingMetric::new, + mock(RaceLogResolver.class)); + } +} 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/AbstractTrackedRegattaAndRaceObserver.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/AbstractTrackedRegattaAndRaceObserver.java index 3915af62602..bdfacf6644e 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/AbstractTrackedRegattaAndRaceObserver.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/AbstractTrackedRegattaAndRaceObserver.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.tracking; import java.io.Serializable; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; @@ -134,10 +135,24 @@ public abstract class AbstractTrackedRegattaAndRaceObserver implements TrackedRe * Called by {@link AbstractTrackedRegattaAndRaceObserver} when the {@link TrackedRegatta} was removed or the * {@link TrackedRegatta} shouldn't be tracked anymore (this is e.g. the case in replication state). */ - public synchronized void stop() { - trackedRegatta.removeRaceListener(raceListener); - knownTrackedRaces.keySet().forEach(this::remove); - knownTrackedRaces.clear(); + public void stop() { + try { + // It's necessary that we wait for the returned future to be completed + // to ensure that all regular removals are correctly processed before + // manually removing the left over ones (see below). + // This is now necessary due to the fact that TrackedRegattaImpl fires events concurrently, + // which makes it possible to receive the removal of the TrackedRegatta before the removals + // for the contained TrackedRaces are received. + trackedRegatta.removeRaceListener(raceListener).get(); + } catch (InterruptedException | ExecutionException e) { + log.log(Level.SEVERE, "Error while waiting for the raceListener to be removed", e); + } + // Depending on the code path (e.g. replication) it's possible that we still track some TrackedRace at this point. + // We need to ensure that all listeners are correctly stopped to not leak any instances. + synchronized (this) { + knownTrackedRaces.keySet().forEach(this::remove); + knownTrackedRaces.clear(); + } } @Override 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 removeRaceListener(RaceListener listener); int getTotalPoints(Competitor competitor, TimePoint timePoint) throws NoWindException; - void removeTrackedRace(RaceDefinition raceDefinition); - } \ No newline at end of file diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java index 20195086089..d29be1410fc 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java @@ -815,28 +815,6 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { return result; } - @Override - public Bearing getBeatAngle(TimePoint at) throws NoWindException { - return getBeatAngle(at, new LeaderboardDTOCalculationReuseCache(at)); - } - - @Override - public Bearing getBeatAngle(TimePoint at, WindLegTypeAndLegBearingCache cache) throws NoWindException { - Bearing beatAngle = null; - Bearing projectToBearing; - Wind wind = cache.getWind(getTrackedRace(), getCompetitor(), at); - if (wind == null) { - throw new NoWindException("Need at least wind direction to determine windward speed"); - } - projectToBearing = wind.getFrom(); - SpeedWithBearing speed = getSpeedOverGround(at); - if (speed != null) { - beatAngle = speed.getBearing().getDifferenceTo(projectToBearing); - } - return beatAngle; - } - - @Override public String toString() { return "TrackedLegOfCompetitor for "+getCompetitor()+" in leg "+getLeg(); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java index c2a16855e18..e1185b9adfc 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java @@ -6,10 +6,15 @@ import java.io.ObjectOutputStream; import java.io.ObjectStreamException; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Future; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; @@ -49,6 +54,24 @@ public class TrackedRegattaImpl implements TrackedRegatta { private final Map trackedRaces; private transient ConcurrentMap raceListeners; + + /** + * This {@link WorkQueue} is used to do all event related work. This ensures that e.g. events are fired in order. + * The following cases need to be handles through this queue: + *

    + *
  • Firing events when adding/removing {@link TrackedRace} instances (see {@link #enqueEvent}). The list of + * listeners to fire the event to need to be the list of listeners existing when enqueuing the event. This ensures + * that newly added listeners only receive events after the initial {@link TrackedRace} instances are delivered to + * this listener.
  • + *
  • Firing events for the already existing {@link TrackedRace} instances when adding a new listener (see + * {@link #addRaceListener(RaceListener)}). This ensures that all events are correctly fired to this listener that + * are triggered after the listener was added while suppressing inconsistent events before/while the initial + * {@link TrackedRace} instances are delivered to this listener.
  • + *
  • Completing the future returned by {@link #removeRaceListener(RaceListener)} to ensure that the receiver gets + * to know when it is guaranteed that no more event will be fired to the listener. + *
+ */ + private transient WorkQueue eventQueue; public TrackedRegattaImpl(Regatta regatta) { super(); @@ -56,11 +79,13 @@ public class TrackedRegattaImpl implements TrackedRegatta { this.regatta = regatta; this.trackedRaces = new HashMap(); raceListeners = new ConcurrentHashMap(); + eventQueue = new WorkQueue(); } private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); this.raceListeners = new ConcurrentHashMap(); + eventQueue = new WorkQueue(); } @Override @@ -110,28 +135,25 @@ public class TrackedRegattaImpl implements TrackedRegatta { logger.info("adding tracked race for "+trackedRace.getRace()+" to tracked regatta "+getRegatta().getName()+ " with regatta hash code "+getRegatta().hashCode()); oldTrackedRace = trackedRaces.put(trackedRace.getRace(), trackedRace); + if (oldTrackedRace != trackedRace) { + notifyListenersAboutTrackedRaceAdded(trackedRace); + } } finally { unlockTrackedRacesAfterWrite(); } - if (oldTrackedRace != trackedRace) { - notifyListenersAboutTrackedRaceAdded(trackedRace); - } } protected void notifyListenersAboutTrackedRaceAdded(TrackedRace trackedRace) { - for (RaceListener listener : raceListeners.keySet()) { - listener.raceAdded(trackedRace); - } + enqueEvent(listener -> listener.raceAdded(trackedRace)); } - @Override - public void removeTrackedRace(RaceDefinition raceDefinition) { - lockTrackedRacesForWrite(); - try { - trackedRaces.remove(raceDefinition); - } finally { - unlockTrackedRacesAfterWrite(); - } + protected void enqueEvent(Consumer fireEventCallback) { + final Set listenersToInform = new HashSet<>(raceListeners.keySet()); + eventQueue.addWork(() -> { + for (RaceListener listener : listenersToInform) { + fireEventCallback.accept(listener); + } + }); } @Override @@ -139,19 +161,14 @@ public class TrackedRegattaImpl implements TrackedRegatta { lockTrackedRacesForWrite(); try { trackedRaces.remove(trackedRace.getRace()); + notifyListenersAboutTrackedRaceRemoved(trackedRace); } finally { unlockTrackedRacesAfterWrite(); } - // Fix for bug4414: put this into a separate thread to avoid deadlock caused by - // a synchronized RegattaListener.raceRemoved while holding the TrackedRegattaImpl.trackedRacesLock's write lock - // acquired in RacingEventServiceImpl.removeRace - new Thread(()->notifyListenersAboutTrackedRaceRemoved(trackedRace)).start(); } protected void notifyListenersAboutTrackedRaceRemoved(TrackedRace trackedRace) { - for (RaceListener listener : raceListeners.keySet()) { - listener.raceRemoved(trackedRace); - } + enqueEvent(listener -> listener.raceRemoved(trackedRace)); } @Override @@ -216,27 +233,37 @@ public class TrackedRegattaImpl implements TrackedRegatta { @Override public void addRaceListener(RaceListener listener) { - final List trackedRacesCopy = new ArrayList<>(); lockTrackedRacesForRead(); try { raceListeners.put(listener, listener); + final List trackedRacesCopy = new ArrayList<>(); Util.addAll(getTrackedRaces(), trackedRacesCopy); + eventQueue.addWork(() -> { + for (TrackedRace trackedRace : trackedRacesCopy) { + listener.raceAdded(trackedRace); + } + }); } finally { unlockTrackedRacesAfterRead(); } - for (TrackedRace trackedRace : trackedRacesCopy) { - listener.raceAdded(trackedRace); - } } @Override - public void removeRaceListener(RaceListener listener) { + public Future removeRaceListener(RaceListener listener) { + final CompletableFuture result = new CompletableFuture(); lockTrackedRacesForRead(); try { - raceListeners.remove(listener); + if (raceListeners.remove(listener) != null) { + eventQueue.addWork(() -> { + result.complete(Boolean.TRUE); + }); + } else { + result.complete(Boolean.TRUE); + } } finally { unlockTrackedRacesAfterRead(); } + return result; } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WorkQueue.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WorkQueue.java new file mode 100644 index 00000000000..3620c2ad5c2 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/WorkQueue.java @@ -0,0 +1,54 @@ +package com.sap.sailing.domain.tracking.impl; + +import java.util.Queue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Queue that executes {@link Runnable} instances in order on a distinct {@link Thread}. The {@link WorkQueue} ensures + * that a Thread is started when the first {@link Runnable} is added via {@link #addWork(Runnable)} and that the + * {@link Thread} is terminated after executing the last available {@link Runnable}. + */ +public class WorkQueue { + private static final Logger LOG = Logger.getLogger(WorkQueue.class.getName()); + + private final LinkedBlockingQueue queue; + private Thread workThread; + + public WorkQueue() { + queue = new LinkedBlockingQueue<>(); + } + + /** + * Adds the given {@link Runnable} to the {@link Queue} of work to be executed. + * Ensures that a Thread is started if there isn't one running yet. + */ + public synchronized void addWork(Runnable workToAdd) { + queue.add(workToAdd); + if (workThread == null) { + workThread = new Thread(this::doWork); + workThread.start(); + } + } + + private void doWork() { + while (true) { + final Runnable workToDo; + synchronized (this) { + workToDo = queue.poll(); + if (workToDo == null) { + workThread = null; + break; + } + } + if (workToDo != null) { + try { + workToDo.run(); + } catch (Exception e) { + LOG.log(Level.SEVERE, "Error while executing work in queue", e); + } + } + } + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/ReviewedSpotsCollection.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/ReviewedSpotsCollection.java new file mode 100755 index 00000000000..8a822d55754 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/windfinder/ReviewedSpotsCollection.java @@ -0,0 +1,33 @@ +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.sse.common.WithID; + +/** + * A set of {@link Spot}s that WindFinder has reviewed and selected for a dedicated request for + * a location, such as a sailing club or an event venue. The collection has a {@link String} as + * a dedicated ID which is used in URL construction to obtain the {@link Spot}s in the collection + * as well as the reports from those spots.

+ * + * 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 a66a3570932..5b6634340c0 100755 --- a/java/com.sap.sailing.feature.p2build/raceanalysis.product +++ b/java/com.sap.sailing.feature.p2build/raceanalysis.product @@ -47,6 +47,7 @@ + @@ -75,12 +76,10 @@ - - @@ -88,7 +87,6 @@ - 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"/> + + sidelines, WindStore windStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, @@ -349,7 +346,8 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public void removeRaceListener(RaceListener listener) { + public Future removeRaceListener(RaceListener listener) { + return null; } }; } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SlicingTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SlicingTest.java new file mode 100644 index 00000000000..8a4b954e15b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SlicingTest.java @@ -0,0 +1,137 @@ +package com.sap.sailing.gwt.ui.test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.UUID; + +import org.apache.shiro.subject.Subject; +import org.apache.shiro.subject.support.SubjectThreadState; +import org.apache.shiro.util.ThreadState; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; + +import com.sap.sailing.domain.common.BoatClassMasterdata; +import com.sap.sailing.domain.common.CompetitorDescriptor; +import com.sap.sailing.domain.common.LeaderboardNameConstants; +import com.sap.sailing.domain.common.RankingMetrics; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.common.abstractlog.TimePointSpecificationFoundInLogImpl; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.domain.common.dto.RegattaCreationParametersDTO; +import com.sap.sailing.domain.common.dto.SeriesCreationParametersDTO; +import com.sap.sailing.domain.common.dto.TrackedRaceDTO; +import com.sap.sailing.gwt.ui.adminconsole.RaceLogSetTrackingTimesDTO; +import com.sap.sailing.gwt.ui.shared.DeviceIdentifierDTO; +import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.Util.Triple; +import com.sap.sse.common.impl.CountryCodeFactoryImpl; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.util.ClearStateTestSupport; + +import junit.framework.Assert; + +public class SlicingTest { + private final String regattaName = "test"; + private final String boatClassName = BoatClassMasterdata.TP52.name(); + private final TimePoint startOriginalRace = new MillisecondsTimePoint(0); + private final TimePoint endOriginalRace = new MillisecondsTimePoint(300); + private final TimePoint startSlicedRace = new MillisecondsTimePoint(100); + private final TimePoint endSlicedRace = new MillisecondsTimePoint(200); + private final String seriesName = LeaderboardNameConstants.DEFAULT_SERIES_NAME; + private final String fleetName = "default"; + private final String columnNameOriginalRace = "original"; + private final String columnNameSlicedRace = "sliced"; + private final CompetitorDescriptor competitor = new CompetitorDescriptor(null, regattaName, boatClassName, + columnNameOriginalRace, fleetName, "test1", "test", "test", "test", + CountryCodeFactoryImpl.INSTANCE.getFromTwoLetterISOName("de"), null, 1.0, null); + + private SailingServiceImplMock sailingService; + + private ThreadState threadState; + protected Subject subject; + + @Before + public void setUp() throws Exception { + sailingService = new SailingServiceImplMock(); + ((ClearStateTestSupport)(sailingService.getRacingEventService())).clearState(); + + subject = Mockito.mock(Subject.class); + threadState = new SubjectThreadState(subject); + threadState.bind(); + } + + @After + public void tearDown() throws Exception { + threadState.clear(); + ((ClearStateTestSupport)(sailingService.getRacingEventService())).clearState(); + } + + @Test + @Ignore + /** + * Currently non functional test case for race slicing. Should be finished when SailingServiceImplMock is fixed to + * work properly for test cases. See bug 4418 for details. + */ + public void testSliceRace() throws Exception { + final LinkedHashMap 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/SailingGWT sdm.launch b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch index 553394cf99f..91d87dbb354 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch @@ -6,7 +6,7 @@ - + @@ -61,7 +61,7 @@ - + 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/autoplay/client/app/AutoplayPerspectiveLifecycle.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoplayPerspectiveLifecycle.java index 2e7f725bd2b..ebae065a208 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoplayPerspectiveLifecycle.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoplayPerspectiveLifecycle.java @@ -7,6 +7,7 @@ import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.raceboard.RaceBoardPerspectiveLifecycle; import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; import com.sap.sse.gwt.client.shared.perspective.AbstractPerspectiveLifecycle; +import com.sap.sse.security.ui.client.UserService; /** * This lifecycle contains the necessary child lifecycles to allow AutoPlay to create and use both, a Leaderboard for @@ -17,10 +18,10 @@ public class AutoplayPerspectiveLifecycle extends AbstractPerspectiveLifecycle settings) { - + this.cf = cf; final UUID eventUUID = context.getEventId(); AsyncCallback getEventByIdAsyncCallback = new AsyncCallback() { @SuppressWarnings("unchecked") @@ -33,7 +34,7 @@ public class AutoPlayClassicConfiguration extends AutoPlayConfiguration { public void onSuccess(final EventDTO event) { StrippedLeaderboardDTO leaderBoardDTO = AutoplayHelper.getSelectedLeaderboard(event, context.getLeaderboardName()); - AutoplayPerspectiveLifecycle autoplayLifecycle = new AutoplayPerspectiveLifecycle(leaderBoardDTO); + AutoplayPerspectiveLifecycle autoplayLifecycle = new AutoplayPerspectiveLifecycle(leaderBoardDTO, cf.getUserService()); cf.setAutoPlayContext(new AutoPlayContextImpl(autoplayLifecycle, (PerspectiveCompositeSettings) settings, AutoPlayClassicConfiguration.this, context)); @@ -63,7 +64,7 @@ public class AutoPlayClassicConfiguration extends AutoPlayConfiguration { public void cancel() { } }; - AutoplayPerspectiveLifecycle autoplayLifecycle = new AutoplayPerspectiveLifecycle(leaderboard); + AutoplayPerspectiveLifecycle autoplayLifecycle = new AutoplayPerspectiveLifecycle(leaderboard, cf.getUserService()); PerspectiveCompositeSettings autoplayPerspectiveSettings = autoplayLifecycle .createDefaultSettings(); LinkWithSettingsGenerator> settingsGenerator = new LinkWithSettingsGenerator<>(Window.Location.getPath(), autoplayLifecycle::createDefaultSettings, apcd); @@ -74,7 +75,7 @@ public class AutoPlayClassicConfiguration extends AutoPlayConfiguration { @Override public void loadSettingsDefault(EventDTO selectedEvent, StrippedLeaderboardDTO leaderboard, OnSettingsCallback settingsCallback) { - AutoplayPerspectiveLifecycle autoplayLifecycle = new AutoplayPerspectiveLifecycle(leaderboard); + AutoplayPerspectiveLifecycle autoplayLifecycle = new AutoplayPerspectiveLifecycle(leaderboard, cf.getUserService()); settingsCallback.newSettings(autoplayLifecycle.createDefaultSettings()); } 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/EventActionUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/EventActionUtil.java index 51a063e80e3..1461e4d71ef 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/EventActionUtil.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/EventActionUtil.java @@ -78,7 +78,7 @@ public final class EventActionUtil { } } if (leaderboardGroups.isEmpty()) { - throw new DispatchException("The leaderboard is not part of the given event."); + throw new DispatchException("The leaderboard '" + leaderboardId + "' is not part of the given event '" + eventId + "'."); } return new LeaderboardContext(context, event, leaderboardGroups, leaderboard); } 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 2a91249fba1..956205892d8 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 d99d30f2e24..b531117dd50 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; @@ -49,6 +50,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, @@ -98,11 +108,10 @@ public class CompetitorTableWrapper"); + sb.append(TEMPLATE.imageWithTitle(flagImageURL, 18 ,12,competitor.getName())); sb.appendHtmlConstant(" "); } else { final ImageResource flagImageResource; @@ -112,7 +121,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 2fe7c0ddb15..57448b3ee20 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 @@ -175,20 +175,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/RaceTimePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java index b731cad1f19..b8add480333 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java @@ -80,7 +80,7 @@ public class RaceTimePanel extends TimePanel implements R RaceTimesInfoProvider raceTimesInfoProvider, boolean canReplayWhileLiveIsPossible, boolean forcePaddingRightToAlignToCharts, RegattaAndRaceIdentifier selectedRaceIdentifier, Duration initialTimeAfterRaceStartInReplayMode) { super(parent, context, timer, timeRangeProvider, stringMessages, canReplayWhileLiveIsPossible, - forcePaddingRightToAlignToCharts); + forcePaddingRightToAlignToCharts, userService); this.componentLifecycle = componentLifecycle; this.userService = userService; this.raceTimesInfoProvider = raceTimesInfoProvider; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanelLifecycle.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanelLifecycle.java index 2de1a01900c..743f5b81fd5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanelLifecycle.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanelLifecycle.java @@ -1,20 +1,23 @@ package com.sap.sailing.gwt.ui.client; import com.sap.sse.gwt.client.shared.components.ComponentLifecycle; +import com.sap.sse.security.ui.client.UserService; public class RaceTimePanelLifecycle implements ComponentLifecycle { private final StringMessages stringMessages; + private UserService userService; public static final String ID = "rt"; - public RaceTimePanelLifecycle(StringMessages stringMessages) { + public RaceTimePanelLifecycle(StringMessages stringMessages, UserService userService) { this.stringMessages = stringMessages; + this.userService = userService; } @Override public RaceTimePanelSettingsDialogComponent getSettingsDialogComponent(RaceTimePanelSettings settings) { - return new RaceTimePanelSettingsDialogComponent(settings, stringMessages); + return new RaceTimePanelSettingsDialogComponent(settings, stringMessages, userService); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanelSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanelSettingsDialogComponent.java index 706140beaa1..0c79d887938 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanelSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanelSettingsDialogComponent.java @@ -1,10 +1,11 @@ package com.sap.sailing.gwt.ui.client; +import com.sap.sse.security.ui.client.UserService; public class RaceTimePanelSettingsDialogComponent extends TimePanelSettingsDialogComponent { - public RaceTimePanelSettingsDialogComponent(RaceTimePanelSettings settings, StringMessages stringMessages) { - super(settings, stringMessages); + public RaceTimePanelSettingsDialogComponent(RaceTimePanelSettings settings, StringMessages stringMessages, UserService userService) { + super(settings, stringMessages, userService); } @Override 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 5ee4920d4b3..56947010596 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, @@ -709,4 +714,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 c179402ea0e..f25c44d2e64 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; @@ -428,7 +431,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); @@ -926,5 +929,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 097d301b161..af83952173b 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(); @@ -2050,4 +2051,19 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String chooseAName(); String unexpectedErrorDuringFileImport(); String couldNotDownload(String url); + 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 c009fff343d..7eb4297c922 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. @@ -2038,3 +2039,18 @@ importFinishedGotoEvent=Open event chooseAName=Choose a name unexpectedErrorDuringFileImport=An unexpected error occurred during file upload. couldNotDownload=Could not download file: {0} +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 99d8c6518b4..eca6f67acfd --- 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. @@ -2035,3 +2036,18 @@ importFinishedGotoEvent=Event öffnen chooseAName=Namen auswählen unexpectedErrorDuringFileImport=Ein unerwarteter Fehler ist beim Datei-Import aufgetreten. couldNotDownload=Konnte Datei nicht laden: {0} +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 53ce0c014f3..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 @@ -29,7 +29,7 @@ playSpeedHelp=Utilice "1" para velocidad de reproducción normal, "2" para doble playModeLive=En directo playModeReplay=Modo de reproducción timeDelay=Retraso -timeDelayHelp=Deje el campo vacío para reproducir arbitrariamente; indique una cantidad de segundos de retraso (p.ej., «60» para un minuto, o bien «0») previa a la transmisión de datos en directo. +timeDelayHelp=Deje el campo vacío para reproducir arbitrariamente; indique una cantidad de segundos de retraso (p.ej., "60" para un minuto, o bien "0") previa a la transmisión de datos en directo. speedInKnots=Velocidad (nudos) fromDeg=Desde (grados) noRacesYet=Todavía no hay ninguna carrera. @@ -141,7 +141,7 @@ delayMustBeNonNegative=El retraso debe ser mayor que cero. chooseUpdateIntervalOfAtLeastOneSecond=Seleccione un intervalo de actualización de al menos un segundo errorSettingWindForRace=Error al configurar viento para la carrera errorFetchingWindInformationForRace=Error al obtener información de viento para la carrera -errorWhileTryingToSetWindSourceForRace=Error al intentar fijar el indicador «viento empieza a barlovento» +errorWhileTryingToSetWindSourceForRace=Error al intentar fijar el indicador "viento empieza a barlovento" inEvent=en evento to=a toSide=a @@ -233,6 +233,32 @@ hoverOverAPoint=Pase el cursor por encima de un punto para obtener detalles noSelection=Ninguna selección raceIsKnownToStartUpwind=La carrera empieza con ceñida events=Eventos +pairingList=Lista de emparejamientos +pairingLists=Listas de emparejamientos +pairingListCreationInfo=Inicialmente se calculará un plantilla que contenga solo los números de competidores y que pueda aplicarse a otras clasificaciones. Si el recuento de competidores no es divisible por recuento de embarcaciones sin remanente, se introducirán marcadores de posición en blanco en la lista de emparejamientos. \ Además, si el recuento de competidores difiere del recuento de competidores registrado en la clasificación real, la lista de emparejamientos no podrá aplicarse a los registros de carrera. \ El recuento de flights, que se utiliza para generar una lista de emparejamientos, se fija con todos los flights de la clasificación que no son carreras de medalla. +setCompetitors=Fije el recuento de competidores: +seriesHint=Seleccione una o más series que deberían estar en una lista de emparejamientos: +amountOfFlightRepeats=Cantidad de repeticiones de flight +insertIntoRegatta=Insertar en regata +recalculate=Recalcular +csvExport=Exportar como CSV +printView=Imprimir vista preliminar +print=Imprimir +invalidCompetitorCount=Recuento de competidores no válido +invalidFlightMultiplier=Repeticiones de flight no válidas +invalidSeriesSelection=Selección de series no válida +flightsMustBeAMultipleOfMultiplier=Los flights deben ser múltiplos de multiplicador de flight +parameters=Parámetros +numberOfFlights=Cantidad de flights +numberOfFleets=Cantidad de carreras por flight +numberOfCompetitors=Cantidad de competidores +numberOfRaces=Número de carreras +quality=Calidad +pairingListTemplate=Plantilla de lista de emparejamientos +printHint=Imprime la versión aplicada +blockedApplyButton=Los competidores registrados difieren de los competidores de la lista de emparejamientos +multiplierInfo=Multiplica los flights y los crea uno junto a otro de modo que sea posible una competición con menos modificaciones de embarcación. +noPairingListAvailable=La función de impresión solo está disponible si ya se han aplicado las listas de emparejamientos a los registros de carrera de las clasificaciones seleccionadas. settingsForComponent=Opciones para {0} noEventsFound=No se han encontrado eventos noEventSelected=Ningún evento seleccionado @@ -351,6 +377,7 @@ replicatingFromMaster=Replicando desde el maestro {0}:{2}, puerto de mensajería showWindSpeedSeries=Mostrar datos de velocidad del viento showWindDirectionSeries=Mostrar datos de dirección del viento fleet=Flota +boat=Bote boatClass=Clase de bote setDelayToLive=Fijar retraso en en directo. pleaseEnterNonEmptyVenue=Indique un lugar de celebración. @@ -524,7 +551,6 @@ additionalUrlSettings=Opciones de URL adicionales restrictToSingleSelectedRace=Restringir a la carrera individual seleccionada expandSelectedRace=Expandir carrera seleccionada showRaceDetails=Permitir detalles de carrera -numberOfRaces=Número de carreras numberOfRacesMustBeNonNegativeNumber=Indique el número de últimas carreras a visualizar chooseTheWayYouSelectRaces=Elija la manera de seleccionar carreras selectFromAllRaces=Seleccionar desde todas las carreras @@ -1032,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. @@ -1393,7 +1419,7 @@ racesQuickfinder=Carreras Quickfinder profileQuickfinder=Perfil Quickfinder statistics=Estadísticas statisticsFor=Estadísticas para {0} -impressions=Impresiones +impressions=Instantáneas notAvailableOnMobileMessage=Este contenido no está disponible para la versión móvil. ¿Desea pasar a la versión de escritorio? gotoDesktop=Ir a la versión de escritorio goBack=Volver @@ -1454,6 +1480,7 @@ plainResultsPresenter=Presentador de resultados simples resultsChart=Gráfico de resultados tabbedResultsPresenter=Presentador de resultados por pestañas polarResultsPresenter=Presentador de resultados de coordenadas polares +maneuverSpeedDetailsResultsPresenter=Presentador de resultados detallados de velocidad de maniobra dataMiningRetrieval=Recuperación de datos actionWatch=Observar actionAnalyze=Analizar @@ -1463,8 +1490,12 @@ doYouWantToCreateADefaultRegattaLeaderboard=¿Desea crear una tabla de clasifica copyCourse=Copiar regata copyCompetitors=Copiar competidores smartphoneTracking=Rastreo de smartphone -flightsCount={0,number} vuelos -flightsCount[one]={0,number} vuelo +titelOfChooseNameDialog=Seleccione un nombre +ownPrefix=Prefijo propio +defaultName=Por defecto +exampleTextForName=Su nombre aparece como: +flightsCount={0,number} flights +flightsCount[one]={0,number} flight viewQueryDefinition=Ver definición de consulta queryDefinitionViewer=Visor de definición de consulta groupAverageAscending=Promedio de grupo (ascendente) @@ -1702,7 +1733,7 @@ headerLogo=Sailing Analytics headerSearchPlaceholder=Buscar mainEventShowAll=Mostrar todos los eventos mainEventTitle=Últimos eventos de Sailing World -mainMediaSubtitle=Recomendamos perderse en las mejores y más recientes impresiones de su evento de navegación favorito. +mainMediaSubtitle=Recomendamos perderse en las mejores y más recientes instantáneas de su evento de navegación favorito. mainMediaTitle=Fotógrafos amantes de la navegación mainMediaVideosSubtitle=Sumérjase en el mundo de la navegación y revise los eventos más recientes para más vídeos. mainMediaVideosTitle=Entradas de vídeo recientes @@ -1873,6 +1904,26 @@ showEstimatedDuration=Mostrar duración estimada estimatedDuration=Duración estimada: countDown=Evento comienza en {0} {1} countDownEnd=El evento comenzará en pocos segundos +maneuverSpeedDetails=Detalles de velocidad de maniobra +maneuverDirectionNormalization=Normalización de dirección de maniobra +maneuver=Maniobra +minManeuverDuration=Duración mínima de maniobra (s) +maxManeuverDuration=Duración máxima de maniobra (s) +minManeuverEnteringSpeedInKnots=Velocidad mínima de entrada de maniobra (kts) +maxManeuverEnteringSpeedInKnots=Velocidad máxima de entrada de maniobra (kts) +minManeuverExitingSpeedInKnots=Velocidad mínima de salida de maniobra (kts) +maxManeuverExitingSpeedInKnots=Velocidad máxima de salida de maniobra (kts) +minManeuverEnteringAbsTWA=TWA absoluto de velocidad mínima de entrada de maniobra +maxManeuverEnteringAbsTWA=TWA absoluto de velocidad máxima de entrada de maniobra +minManeuverExitingAbsTWA=TWA absoluto de velocidad mínima de salida de maniobra +maxManeuverExitingAbsTWA=TWA absoluto de velocidad máxima de salida de maniobra +maneuverDirectionEqualWeightingEnabled=Activar ponderación 50%-50% para la dirección de maniobra en agregaciones de "valor por TWA" +minValue=Valor mínimo +maxValue=Valor máximo +zeroTo360AxisLabeling=Etiqueta de eje 0-360° +exportStatisticsCurveToCsv=Curva estadística de exportación a CSV +csvCopiedToClipboard=Se ha copiado el contenido CSV al portapapeles. Péguelo donde quiera. +minDataCount=Registro de datos mínimo moreLoginInformationHeadline=Ventajas de registrarse en SAP Sailing Analytics moreLoginInformationIntroduction=Como usuario registrado de SAP Sailing Analytics dispone de acceso a la funcionalidad que no está disponible para usuarios anónimos. Simplemente regístrese para acceder a las funciones que se muestras más abajo. moreLoginInformationSectionUserSettingsHeading=Opciones de usuario @@ -1918,6 +1969,7 @@ currentStbdRudderRake=Inclinación del timón a estribor currentStbdRudderRakeTooltip=La inclinación actual del timón a estribor currentMastRotationInDegree=Rotación del mástil currentMastRotationInDegreeTooltip=La rotación actual del mástil en grados +mainCurveAnalysis=Analizar solo curva principal finishingTime=Hora de llegada finishTimeString=Hora de fin setFinishingAndFinishTime=Fijar hora de llegada y de fin @@ -1969,3 +2021,16 @@ totalDurationFoiledInSeconds=Tiempo total con foils totalDistanceFoiledInMeters=Distancia total con foils totalDurationFoiledInSecondsTooltip=Tiempo total con foils, a lo largo de todas la clasificación totalDistanceFoiledInMetersTooltip=Distancia total con foils, a lo largo de todas la clasificación +threeSixtyVideoHint=Se trata de un vídeo de 360º, la cámara se puede girar +processingMP4=MP4 se analiza (esto durará unos segundos) +resetStartTimeToDefault=Fijar en Por defecto +importFullExpeditionData=Importar datos de expedición completos +importCanceledNoCompetitorAdded=No se ha añadido ningún competidor. Se cancelará la importación. +importCanceledByUser=Importación cancelada +importFinished=Datos de importación finalizados +importFinishedMessage=Datos de expedición importados correctamente. +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 f3db1e03d4f..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 @@ -233,6 +233,32 @@ hoverOverAPoint=Placez le curseur sur un point pour obtenir des détails. noSelection=Aucune sélection raceIsKnownToStartUpwind=La course a commencé avec une portion de parcours au vent. events=Manifestations +pairingList=Liste d''appariement +pairingLists=Listes d''appariement +pairingListCreationInfo=Tout d''abord, un modèle contenant uniquement le nombre de concurrents sera calculé. Il pourra être appliqué à d''autres palmarès. Si la division du nombre de concurrents par le nombre de bateaux ne permet pas d''obtenir un nombre entier, des caractères génériques vides seront ajoutés à la liste d''appariement. \ En outre, si le nombre de concurrents ne correspond pas au nombre de concurrents inscrits au palmarès actuel, la liste d''appariement ne pourra pas être appliquée aux journaux de course. \ Le nombre de flights, utilisé pour générer une liste d''appariement, est défini par tous les flights du palmarès, hors courses médaillées. +setCompetitors=Veuillez définir le nombre de concurrents : +seriesHint=Veuillez sélectionner une ou plusieurs séries à ajouter à une liste de paires : +amountOfFlightRepeats=Nombre de répétitions de flights +insertIntoRegatta=Ajouter à la régate +recalculate=Recalculer +csvExport=Exporter au format CSV +printView=Aperçu avant impression +print=Imprimer +invalidCompetitorCount=Nombre de concurrents non valide +invalidFlightMultiplier=Répétitions de flights non valides +invalidSeriesSelection=Sélection de séries non valide +flightsMustBeAMultipleOfMultiplier=Les flights doivent être un multiple du multiplicateur de flight. +parameters=Paramètres +numberOfFlights=Nombre de flights +numberOfFleets=Nombre de courses par flight +numberOfCompetitors=Nombre de concurrents +numberOfRaces=Nombre de courses +quality=Qualité +pairingListTemplate=Modèle de liste d''appariement +printHint=Imprime la version appliquée +blockedApplyButton=Le nombre de concurrents inscrits ne correspond pas au nombre de concurrents de la liste d''appariement. +multiplierInfo=Multipliez les flights et créez-les les uns après les autres pour obtenir une compétition avec le moins de bateaux possible. +noPairingListAvailable=La fonction d''impression est uniquement disponible lorsqu''une liste d''appariement a déjà été appliquée aux journaux de course des palmarès sélectionnés. settingsForComponent=Options pour {0} noEventsFound=Aucune manifestation trouvée noEventSelected=Aucune manifestation sélectionnée @@ -351,6 +377,7 @@ replicatingFromMaster=Réplication du serveur maître {0}:{2}, port de messageri showWindSpeedSeries=Afficher données de vitesse du vent showWindDirectionSeries=Afficher données de direction du vent fleet=Flotte +boat=Bateau boatClass=Catégorie de bateau setDelayToLive=Définir décalage par rapport au direct pleaseEnterNonEmptyVenue=Saisissez un lieu de manifestation. @@ -524,7 +551,6 @@ additionalUrlSettings=Options d''URL supplémentaires restrictToSingleSelectedRace=Limiter à la seule course sélectionnée expandSelectedRace=Développer course sélectionnée showRaceDetails=Autoriser détails de la course -numberOfRaces=Nombre de courses numberOfRacesMustBeNonNegativeNumber=Saisissez le nombre de dernières courses à afficher. chooseTheWayYouSelectRaces=Indiquez de quelle façon vous souhaitez sélectionner les courses selectFromAllRaces=Sélectionner parmi toutes les courses @@ -1032,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. @@ -1385,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 @@ -1454,6 +1480,7 @@ plainResultsPresenter=Visualiseur de résultats bruts resultsChart=Graphique des résultats tabbedResultsPresenter=Visualiseur de résultats avec onglets polarResultsPresenter=Visualiseur de résultats polaires +maneuverSpeedDetailsResultsPresenter=Visualiseur des résultats : détails sur la vitesse lors de la manœuvre dataMiningRetrieval=Récupération de données actionWatch=Regarder actionAnalyze=Analyser @@ -1463,6 +1490,10 @@ doYouWantToCreateADefaultRegattaLeaderboard=Voulez-vous créer un palmarès de r copyCourse=Copier course copyCompetitors=Copier concurrents smartphoneTracking=Suivi smartphone +titelOfChooseNameDialog=Indiquer un nom +ownPrefix=Préfixe personnalisé +defaultName=Par défaut +exampleTextForName=Votre nom apparaît de la manière suivante : flightsCount={0,number} départs de deux équipages flightsCount[one]={0,number} départ de deux équipages viewQueryDefinition=Afficher la définition de la requête @@ -1873,6 +1904,26 @@ showEstimatedDuration=Afficher la durée estimée estimatedDuration=Durée estimée : countDown=La manifestation commence dans {0} {1} countDownEnd=La manifestation commencera dans quelques secondes. +maneuverSpeedDetails=Détails sur la vitesse lors de la manœuvre +maneuverDirectionNormalization=Normalisation du cap pendant la manœuvre +maneuver=Manœuvre +minManeuverDuration=Durée min. de la manœuvre (s) +maxManeuverDuration=Durée max. de la manœuvre (s) +minManeuverEnteringSpeedInKnots=Vitesse min. d''entrée de manœuvre (nds) +maxManeuverEnteringSpeedInKnots=Vitesse max. d''entrée de manœuvre (nds) +minManeuverExitingSpeedInKnots=Vitesse min. de sortie de manœuvre (nds) +maxManeuverExitingSpeedInKnots=Vitesse max. de sortie de manœuvre (nds) +minManeuverEnteringAbsTWA=TWA absolu min. d''entrée de manœuvre +maxManeuverEnteringAbsTWA=TWA absolu max. d''entrée de manœuvre +minManeuverExitingAbsTWA=TWA absolu min. de sortie de manœuvre +maxManeuverExitingAbsTWA=TWA absolu max. de sortie de manœuvre +maneuverDirectionEqualWeightingEnabled=Activer pondération 50 %-50 % pour le cap pendant la manœuvre dans les agrégations "valeur par TWA" +minValue=Valeur min. +maxValue=Valeur max. +zeroTo360AxisLabeling=Étiquetage axe 0-360° +exportStatisticsCurveToCsv=Exporter la courbe des statistiques au format CSV +csvCopiedToClipboard=Le contenu du CSV a été copié dans le presse-papiers. Copiez-le où vous le souhaitez. +minDataCount=Nombre de données min. moreLoginInformationHeadline=Avantages de SAP Sailing Analytics moreLoginInformationIntroduction=Si vous êtes un utilisateur inscrit sur SAP Sailing Analytics, vous avez accès à des fonctionnalités qui ne sont pas disponibles pour les utilisateurs anonymes. Inscrivez-vous pour accéder à toutes les fonctionnalités indiquées ci-dessous. moreLoginInformationSectionUserSettingsHeading=Options utilisateur @@ -1918,6 +1969,7 @@ currentStbdRudderRake=Inclinaison du gouvernail tribord currentStbdRudderRakeTooltip=Inclinaison actuelle du gouvernail tribord currentMastRotationInDegree=Rotation du mât currentMastRotationInDegreeTooltip=Rotation actuelle du mât en degrés +mainCurveAnalysis=Analyser uniquement la courbe principale finishingTime=Heure d''arrivée du premier finishTimeString=Heure de fermeture de la ligne d''arrivée setFinishingAndFinishTime=Définir heure d''arrivée du premier et de fermeture de la ligne d''arrivée @@ -1969,3 +2021,16 @@ totalDurationFoiledInSeconds=Temps total passé en planing totalDistanceFoiledInMeters=Distance totale parcourue en planing totalDurationFoiledInSecondsTooltip=Temps total passé en planing, sur la totalité du palmarès totalDistanceFoiledInMetersTooltip=Distance totale parcourue en planing, sur la totalité du palmarès +threeSixtyVideoHint=Il s''agit d''une vidéo à 360°. Vous pouvez tourner la caméra. +processingMP4=Analyse du Mp4 en cours (patientez quelques secondes). +resetStartTimeToDefault=Act. valeur par défaut +importFullExpeditionData=Importer toutes les données d''Expedition +importCanceledNoCompetitorAdded=Aucun concurrent n''a été ajouté ! L''importation sera annulée. +importCanceledByUser=Importation annulée ! +importFinished=Importation des données terminée +importFinishedMessage=Données d''Expedition correctement importées +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 14d6f10a0f9..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 @@ -233,6 +233,32 @@ hoverOverAPoint=詳細についてはポイントをホバー noSelection=未選択 raceIsKnownToStartUpwind=アップウィンドレグでレーススタート events=イベント +pairingList=対戦表 +pairingLists=対戦表 +pairingListCreationInfo=まず最初に、競技者番号のみが含まれていて、他のリーダーボードに適用できるテンプレートが計算されます。競技者カウントが艇カウントで割り切れない場合、空のプレースホルダーが対戦表に入力されます。 \ また、競技者カウントが実際のリーダーボードにある登録済の競技者カウントと等しくない場合、対戦表をレースログに適用することはできません。 \ 対戦表を生成するために使用されるフライトのカウントは、リーダーボードの全フライト (非メダルレース) によって設定されます。 +setCompetitors=競技者カウントを設定してください: +seriesHint=対戦表にあることが必要なシリーズを 1 つ以上選択してください: +amountOfFlightRepeats=フライト繰返回数 +insertIntoRegatta=レガッタに挿入 +recalculate=再計算 +csvExport=エクスポート (CSV) +printView=印刷プレビュー +print=印刷 +invalidCompetitorCount=競技者カウントが無効 +invalidFlightMultiplier=フライト繰返回数が無効 +invalidSeriesSelection=シリーズ選択が無効 +flightsMustBeAMultipleOfMultiplier=フライト回数はフライト乗数の倍数であることが必要 +parameters=パラメータ +numberOfFlights=フライト回数 +numberOfFleets=フライト毎のレース回数 +numberOfCompetitors=競技者数 +numberOfRaces=レース数 +quality=質 +pairingListTemplate=対戦表テンプレート +printHint=適用バージョンを印刷 +blockedApplyButton=登録されている競技者数が対戦表にある競技者数と同じでありません +multiplierInfo=複数のフライトを艇変更がより少なく競技が可能となるよう互いに隣り合わせに作成 +noPairingListAvailable=印刷機能は選択したリーダーボードレースログに対戦表がすでに適用されている場合にのみ利用が可能です。 settingsForComponent={0} の設定 noEventsFound=イベントが見つかりませんでした noEventSelected=イベントが選択されていません @@ -351,6 +377,7 @@ replicatingFromMaster=マスタ {0}:{2}、メッセージングポート {3}:{1} showWindSpeedSeries=風速データ表示 showWindDirectionSeries=風向データ表示 fleet=フリート +boat=艇 boatClass=艇種 setDelayToLive=ライブに遅延を設定 pleaseEnterNonEmptyVenue=開催地を入力してください。 @@ -524,7 +551,6 @@ additionalUrlSettings=追加 URL 設定 restrictToSingleSelectedRace=単一の選択レースに制限 expandSelectedRace=選択レースを拡大 showRaceDetails=レース詳細を許容 -numberOfRaces=レース数 numberOfRacesMustBeNonNegativeNumber=表示する最終レース数を入力してください chooseTheWayYouSelectRaces=レース選択方法の選択 selectFromAllRaces=全レースから選択 @@ -1032,8 +1058,8 @@ resultFromIgtimiWindImport=一覧されたレースに対して次の数のフ timeToStart=スタートまで {0} timeSinceStart=スタートから {0} distanceToLine=スタートラインまでの距離 -beatAngleTooltip=競技者の方向と風の間のアングル -beatAngle=ビートアングル +TWATooltip=競技者の方向と風の間のアングル +TWA=ビートアングル showBoatClassChartsLabel=利用可能な艇種の全体図も表示することができます。 showDiagram=ダイアグラム表示 runAutomaticallyTooltip=統計やグルーピングなどを変更した後にクエリを自動的に実行します。 @@ -1454,6 +1480,7 @@ plainResultsPresenter=プレーン結果表示ツール resultsChart=結果チャート tabbedResultsPresenter=タブ結果表示ツール polarResultsPresenter=ポーラー結果表示ツール +maneuverSpeedDetailsResultsPresenter=マニューバー速度詳細結果表示ツール dataMiningRetrieval=データ取得 actionWatch=視聴 actionAnalyze=分析 @@ -1463,6 +1490,10 @@ doYouWantToCreateADefaultRegattaLeaderboard=レガッタリーダーボードを copyCourse=コースのコピー copyCompetitors=競技者のコピー smartphoneTracking=スマートフォン追跡 +titelOfChooseNameDialog=名前を選択 +ownPrefix=自身のプレフィクス +defaultName=デフォルト +exampleTextForName=名前は次のようになります: flightsCount={0,number} フライト flightsCount[one]={0,number} フライト viewQueryDefinition=クエリ定義の表示 @@ -1873,6 +1904,26 @@ showEstimatedDuration=推定持続時間の表示 estimatedDuration=推定持続時間: countDown=イベントは {0} {1} に開始 countDownEnd=このイベントはあと数秒で開始します +maneuverSpeedDetails=マニューバー速度詳細 +maneuverDirectionNormalization=マニューバー方向正規化 +maneuver=マニューバー +minManeuverDuration=マニューバー最小持続時間 (s) +maxManeuverDuration=マニューバー最大持続時間 (s) +minManeuverEnteringSpeedInKnots=マニューバー最小進入速度 (kts) +maxManeuverEnteringSpeedInKnots=マニューバー最大進入速度 (kts) +minManeuverExitingSpeedInKnots=マニューバー最小脱出速度 (kts) +maxManeuverExitingSpeedInKnots=マニューバー最大脱出速度 (kts) +minManeuverEnteringAbsTWA=マニューバー最小進入絶対 TWA +maxManeuverEnteringAbsTWA=マニューバー最大進入絶対 TWA +minManeuverExitingAbsTWA=マニューバー最小脱出絶対 TWA +maxManeuverExitingAbsTWA=マニューバー最大脱出絶対 TWA +maneuverDirectionEqualWeightingEnabled=マニューバー方向の 50%-50% 重み付けを有効化 ("TWA 別の値" の集約) +minValue=最小値 +maxValue=最大値 +zeroTo360AxisLabeling=0-360° 軸のラベリング +exportStatisticsCurveToCsv=統計曲線を CSV にエクスポート +csvCopiedToClipboard=CSV の内容がクリップボードにコピーされました。好きな場所にペーストしてください。 +minDataCount=最小データカウント moreLoginInformationHeadline=SAP Sailing Analytics にサインインする利点 moreLoginInformationIntroduction=SAP Sailing Analytics の登録ユーザーには、匿名ユーザーに許されていない機能へのアクセス権限があります。登録を行って、下記の機能にアクセスしてください。 moreLoginInformationSectionUserSettingsHeading=ユーザー設定 @@ -1885,7 +1936,7 @@ anniversaryUnitText=レース anniversaryUnitText[one]=レース anniversaryMajorCountdownTeaser=カウントダウン情報です。{1,number,#,###} 番目のレースまでもうわずか {0,number,#,###} レースです。 anniversaryMajorCountdownTeaser[one]=カウントダウン情報です。{1,number,#,###} 番目のレースまでもうわずか {0,number,#,###} レースです。 -anniversaryMajorCountdownDescription=SAP では、www.sapsailing.com における {0,number,#,###} 番目のレースを祝うことにしています。どのレースがゴールテープを切るでしょうか。その記念レースの主催者には、義援目的で合計で 10,000 ユーロが贈呈されます。当選者はこの Web サイトで発表されます。引き続きご注目いただき、一緒にカウントダウンしていきましょう。 +anniversaryMajorCountdownDescription=SAP では、www.sapsailing.com における {0,number,#,###} 番目のレースを祝うことにしています。どのレースがゴールテープを切るでしょうか。その記念レースの主催者には、義援目的で合計で 10,000 ユーロが贈呈されます。当選者はこの Web サイトで発表されます。引き続きご注目いただき、一緒にカウントダウンしていきましょう。 anniversaryRepdigitCountdownTeaser=カウントダウン情報です。{1,number,#,###} 番目のレースまでもうわずか {0,number,#,###} レースです。 anniversaryRepdigitCountdownTeaser[one]=カウントダウン情報です。{1,number,#,###} 番目のレースまでもうわずか {0,number,#,###} レースです。 anniversaryRepdigitCountdownDescription=SAP では、縁起のいい番号のレースを祝うことにしています。www.sapsailing.com における {0,number, #,###} 番目のレースは誰が行うことになるでしょうか。そのレースの参加者には、SAP から最上級のサマーフェスティバルが無料で提供されます。当選者はこの Web サイトで発表されます。引き続きご注目いただき、一緒にカウントダウンしていきましょう。 @@ -1918,6 +1969,7 @@ currentStbdRudderRake=右舷ラダー傾斜角 currentStbdRudderRakeTooltip=現在の右舷ラダー傾斜角 currentMastRotationInDegree=マスト回転 currentMastRotationInDegreeTooltip=現在のマスト回転 (度) +mainCurveAnalysis=主曲線のみ分析 finishingTime=フィニッシュ予定時刻 finishTimeString=フィニッシュ時刻 setFinishingAndFinishTime=フィニッシュ予定時刻とフィニッシュ時刻の設定 @@ -1969,3 +2021,16 @@ totalDurationFoiledInSeconds=合計フォイリング時間 totalDistanceFoiledInMeters=合計フォイリング距離 totalDurationFoiledInSecondsTooltip=合計フォイリング時間 (リーダーボード全体) totalDistanceFoiledInMetersTooltip=合計フォイリング距離 (リーダーボード全体) +threeSixtyVideoHint=これは 360 度の動画であり、カメラを回転できます +processingMP4=Mp4 が解析されます (これには数秒かかります) +resetStartTimeToDefault=デフォルトに設定 +importFullExpeditionData=Expedition データ全体をインポート +importCanceledNoCompetitorAdded=競技者が追加されませんでした。インポートは取り消されます。 +importCanceledByUser=インポートが取り消されました +importFinished=データのインポートが終了 +importFinishedMessage=Expedition データがインポートされました。 +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 d07b26923ae..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 @@ -233,6 +233,32 @@ hoverOverAPoint=Passar com o mouse sobre um ponto para detalhes noSelection=Nenhuma seleção raceIsKnownToStartUpwind=A corrida começa com perna de contravento events=Eventos +pairingList=Lista de pares +pairingLists=Listas de pares +pairingListCreationInfo=Primeiro será calculado um modelo que contém somente os números dos competidores e pode ser aplicado a outros painéis de classificação. Se a contagem de competidores não for divisível pela contagem de barcos sem restante, os caracteres de preenchimento em branco serão inseridos na lista de pares. \ Além disso, se a contagem de competidores for diferente da contagem de competidores registrados no painel de classificação real, a lista de pares não pode ser aplicada aos registros das corridas. \ A contagem dos voos, que é utilizada para gerar uma lista de pares, é definida por todos os voos do painel de classificação que não sejam corridas para medalha. +setCompetitors=Defina a contagem dos competidores: +seriesHint=Selecione uma ou mais séries que devam estar em uma lista de pares: +amountOfFlightRepeats=Número de repetições de voos +insertIntoRegatta=Inserir na regata +recalculate=Recalcular +csvExport=Exportar como CSV +printView=Visualizar impressão +print=Imprimir +invalidCompetitorCount=Contagem de competidores inválida +invalidFlightMultiplier=Repetições de voos inválidas +invalidSeriesSelection=Seleção de séries inválida +flightsMustBeAMultipleOfMultiplier=Os voos devem ser um múltiplo do multiplicador de voos +parameters=Parâmetros +numberOfFlights=Número de voos +numberOfFleets=Número de corridas por voo +numberOfCompetitors=Número de competidores +numberOfRaces=Número de corridas +quality=Qualidade +pairingListTemplate=Modelo da lista de pares +printHint=Imprime a versão aplicada +blockedApplyButton=Os competidores registrados são diferentes dos competidores da lista de pares! +multiplierInfo=Multiplica os voos e cria-os uns junto aos outros de modo que seja possível uma competição com menos mudanças de barco. +noPairingListAvailable=A função de impressão só está disponível caso já tenha sido aplicada uma lista de pares aos registros de corridas dos painéis de classificação selecionados. settingsForComponent=Configurações para {0} noEventsFound=Nenhum evento encontrado noEventSelected=Nenhum evento selecionado @@ -351,6 +377,7 @@ replicatingFromMaster=Replicando a partir do mestre {0}:{2}, porta de mensagens showWindSpeedSeries=Visualizar dados de velocidade do vento showWindDirectionSeries=Visualizar dados de direções do vento fleet=Flotilha +boat=Barco boatClass=Classe de barcos setDelayToLive=Definir atraso ao vivo pleaseEnterNonEmptyVenue=Insira um local. @@ -524,7 +551,6 @@ additionalUrlSettings=Configurações de URL adicionais restrictToSingleSelectedRace=Restringir a uma única corrida selecionada expandSelectedRace=Expandir corrida selecionada showRaceDetails=Permitir detalhes da corrida -numberOfRaces=Número de corridas numberOfRacesMustBeNonNegativeNumber=Insira o número das últimas corridas a serem exibidas chooseTheWayYouSelectRaces=Selecionar a forma de seleção das corridas selectFromAllRaces=Selecionar a partir de todas as corridas @@ -1032,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. @@ -1454,6 +1480,7 @@ plainResultsPresenter=Apresentador de resultados simples resultsChart=Diagrama de resultados tabbedResultsPresenter=Apresentador de resultados por fichas polarResultsPresenter=Apresentador de resultados da carta polar +maneuverSpeedDetailsResultsPresenter=Apresentador de resultados detalhados da velocidade da manobra dataMiningRetrieval=Obtenção de dados actionWatch=Ver actionAnalyze=Analisar @@ -1463,6 +1490,10 @@ doYouWantToCreateADefaultRegattaLeaderboard=Você quer criar um painel de classi copyCourse=Copiar percurso copyCompetitors=Copiar competidores smartphoneTracking=Rastreamento do Smartphone +titelOfChooseNameDialog=Selecionar um nome +ownPrefix=Prefixo próprio +defaultName=Padrão +exampleTextForName=Seu nome é parecido com: flightsCount={0,number} voos flightsCount[one]={0,number} voo viewQueryDefinition=Ver definição da consulta @@ -1656,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". @@ -1873,6 +1904,26 @@ showEstimatedDuration=Exibir duração estimada estimatedDuration=duração estimada: countDown=O evento começa em {0} {1} countDownEnd=O evento irá começar dentro de alguns segundos +maneuverSpeedDetails=Detalhes da velocidade da manobra +maneuverDirectionNormalization=Normalização da direção da manobra +maneuver=Manobra +minManeuverDuration=Duração mínima da manobra (s) +maxManeuverDuration=Duração máxima da manobra (s) +minManeuverEnteringSpeedInKnots=Velocidade mínima de entrada da manobra (nós) +maxManeuverEnteringSpeedInKnots=Velocidade máxima de entrada da manobra (nós) +minManeuverExitingSpeedInKnots=Velocidade mínima de saída da manobra (nós) +maxManeuverExitingSpeedInKnots=Velocidade máxima de saída da manobra (nós) +minManeuverEnteringAbsTWA=TWA absoluto mínimo de entrada da manobra +maxManeuverEnteringAbsTWA=TWA absoluto máximo de entrada da manobra +minManeuverExitingAbsTWA=TWA absoluto mínimo de saída da manobra +maxManeuverExitingAbsTWA=TWA absoluto máximo de saída da manobra +maneuverDirectionEqualWeightingEnabled=Permitir ponderação 50%-50% para mudança de direção em agregações "valor por TWA" +minValue=Valor mínimo +maxValue=Valor máximo +zeroTo360AxisLabeling=Etiquetagem de eixo 0-360° +exportStatisticsCurveToCsv=Exportar curva de estatística para CSV +csvCopiedToClipboard=O conteúdo CSV foi copiado para o clipboard. Cole-o onde quiser. +minDataCount=Contagem de dados mínimos moreLoginInformationHeadline=Benefícios da conexão ao SAP Sailing Analytics moreLoginInformationIntroduction=Como usuário registrado do SAP Sailing Analytics, você tem acesso a funções que não estão abertas para usuários anônimos. Registre-se simplesmente para ter acesso às funcionalidades exibidas embaixo. moreLoginInformationSectionUserSettingsHeading=Configurações do usuário @@ -1918,6 +1969,7 @@ currentStbdRudderRake=Inclinação leme boreste currentStbdRudderRakeTooltip=A inclinação atual do leme para boreste currentMastRotationInDegree=Rotação do mastro currentMastRotationInDegreeTooltip=A rotação atual do mastro em graus +mainCurveAnalysis=Analisar somente curva principal finishingTime=Hora de chegada finishTimeString=Hora de conclusão setFinishingAndFinishTime=Definir hora de chegada e conclusão @@ -1969,3 +2021,16 @@ totalDurationFoiledInSeconds=Total de tempo com hidrofólio totalDistanceFoiledInMeters=Total de distância com hidrofólio totalDurationFoiledInSecondsTooltip=Total de tempo com hidrofólio, em todo o painel de classificação totalDistanceFoiledInMetersTooltip=Total de distância com hidrofólio, em todo o painel de classificação +threeSixtyVideoHint=Este é um vídeo de 360°, a câmera pode ser virada +processingMP4=O Mp4 é analisado (isso demorará alguns segundos) +resetStartTimeToDefault=Definir como padrão +importFullExpeditionData=Importar todos os dados do Expedition +importCanceledNoCompetitorAdded=Nenhum competidor adicionado! A importação será cancelada. +importCanceledByUser=Importação cancelada! +importFinished=Importação de dados concluída +importFinishedMessage=Dados do Expedition importados com êxito. +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 c34ddcd0d7f..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 @@ -233,6 +233,32 @@ hoverOverAPoint=Наведите указатель для просмотра п noSelection=Ничего не выбрано raceIsKnownToStartUpwind=Гонка начинается против ветра events=События +pairingList=Список пар +pairingLists=Списки пар +pairingListCreationInfo=Первым будет рассчитан шаблон, который содержит только номера участников и может быть применен к другим таблицам лидеров. Если число участников не делится на число лодок без остатка, в списке пар будут пустые места. \ Если число участников не равно числу зарегистрированных участников в фактической таблице лидеров, список пар не может быть применен к журналам гонки. \ Для генерирования списка пар используется число всех флайтов таблицы лидеров, не являющихся гонками на медали. +setCompetitors=Укажите число участников: +seriesHint=Выберите не менее одной серии для списка пар: +amountOfFlightRepeats=Число повторов флайта +insertIntoRegatta=Вставить в регату +recalculate=Пересчитать +csvExport=Экспорт в CSV +printView=Просмотр перед печатью +print=Печать +invalidCompetitorCount=Недопустимое число участников +invalidFlightMultiplier=Недопустимое число повторов флайта +invalidSeriesSelection=Недопустимый выбор серий +flightsMustBeAMultipleOfMultiplier=Число флайтов должно быть кратно множителю флайтов +parameters=Параметры +numberOfFlights=Число флайтов +numberOfFleets=Число гонок на флайт +numberOfCompetitors=Число участников +numberOfRaces=Число гонок +quality=Качество +pairingListTemplate=Шаблон списка пар +printHint=Печать примененной версии +blockedApplyButton=Число зарегистрированных участников не равно числу участников из списка пар! +multiplierInfo=Планируйте флайты таким образом, чтобы обеспечить соревнования с минимально возможной переменой лодок +noPairingListAvailable=Функция печати доступна только после применения списка пар к журналам гонок выбранных таблиц лидеров. settingsForComponent={0} — параметры noEventsFound=События не найдены noEventSelected=Не выбрано событие @@ -351,6 +377,7 @@ replicatingFromMaster=Репликация с главного экземпля showWindSpeedSeries=Показать данные о скорости ветра showWindDirectionSeries=Показать данные о направлениях ветра fleet=Флот +boat=Лодка boatClass=Класс лодок setDelayToLive=Установить задержку оперативных данных pleaseEnterNonEmptyVenue=Введите место. @@ -524,7 +551,6 @@ additionalUrlSettings=Дополнительные параметры URL-адр restrictToSingleSelectedRace=Ограничить одной выбранной гонкой expandSelectedRace=Развернуть выбранную гонку showRaceDetails=Разрешить подробности гонки -numberOfRaces=Число гонок numberOfRacesMustBeNonNegativeNumber=Введите число последних гонок для просмотра chooseTheWayYouSelectRaces=Выберите способ выбора гонок selectFromAllRaces=Выбрать из всех гонок @@ -1032,8 +1058,8 @@ resultFromIgtimiWindImport=Для перечисленных гонок импо timeToStart={0} до старта timeSinceStart={0} от старта distanceToLine=Дистанция до линии старта -beatAngleTooltip=Угол между направлением участников и ветром -beatAngle=Угол удара +TWATooltip=Угол между направлением участников и ветром +TWA=Угол удара showBoatClassChartsLabel=Также можно просмотреть общую диаграмму для доступных классов лодок. showDiagram=Показать диаграмму runAutomaticallyTooltip=Автоматически выполнять запрос после изменения, например, статистики или группирования. @@ -1454,6 +1480,7 @@ plainResultsPresenter=Презентатор простых результато resultsChart=Диаграмма результатов tabbedResultsPresenter=Презентатор результатов со вкладками polarResultsPresenter=Полярный презентатор результатов +maneuverSpeedDetailsResultsPresenter=Демонстратор результатов детализации скорости маневра dataMiningRetrieval=Извлечение данных actionWatch=Смотреть actionAnalyze=Анализировать @@ -1463,6 +1490,10 @@ doYouWantToCreateADefaultRegattaLeaderboard=Создать таблицу лид copyCourse=Копировать курс copyCompetitors=Копировать участников smartphoneTracking=Отслеживание смартфона +titelOfChooseNameDialog=Выберите имя +ownPrefix=Префикс +defaultName=По умолчанию +exampleTextForName=Выше имя выглядит так: flightsCount={0,number} полетов flightsCount[one]={0,number} полет viewQueryDefinition=Просмотреть определение запроса @@ -1873,6 +1904,26 @@ showEstimatedDuration=Показать оценку длительности estimatedDuration=оценка длительности: countDown=Начало события в {0} {1} countDownEnd=Событие начнется через несколько секунд +maneuverSpeedDetails=Скорость маневра подробно +maneuverDirectionNormalization=Нормализация направления маневра +maneuver=Маневр +minManeuverDuration=Мин. длительность маневра (с) +maxManeuverDuration=Макс. длительность маневра (с) +minManeuverEnteringSpeedInKnots=Мин. входная скорость маневра (уз.) +maxManeuverEnteringSpeedInKnots=Макс. входная скорость маневра (уз.) +minManeuverExitingSpeedInKnots=Мин. выходная скорость маневра (уз.) +maxManeuverExitingSpeedInKnots=Макс. выходная скорость маневра (уз.) +minManeuverEnteringAbsTWA=Мин. входной абс. TWA маневра +maxManeuverEnteringAbsTWA=Макс. входной абс. TWA маневра +minManeuverExitingAbsTWA=Мин. выходной абс. TWA маневра +maxManeuverExitingAbsTWA=Макс. выходной абс. TWA маневра +maneuverDirectionEqualWeightingEnabled=Включить взвешивание 50%-50% для направления маневра в агрегациях значений по TWA +minValue=Мин. значение +maxValue=Макс. значение +zeroTo360AxisLabeling=Маркировка оси 0–360° +exportStatisticsCurveToCsv=Экспортировать кривую статистики в CSV +csvCopiedToClipboard=Содержимое CSV скопировано в буфер обмена. Вставьте его в нужном месте. +minDataCount=Мин. число данных moreLoginInformationHeadline=Преимущества регистрации в SAP Sailing Analytics moreLoginInformationIntroduction=Зарегистрированные пользователи SAP Sailing Analytics имеют доступ к функциям, которые закрыты для анонимных пользователей. Зарегистрируйтесь, чтобы получить доступ к перечисленным ниже функциям. moreLoginInformationSectionUserSettingsHeading=Настройки пользователя @@ -1918,6 +1969,7 @@ currentStbdRudderRake=Скос задней кромки руля на прав currentStbdRudderRakeTooltip=Текущий скос задней кромки руля на правый борт currentMastRotationInDegree=Вращение мачты currentMastRotationInDegreeTooltip=Текущее значение вращения мачты в градусах +mainCurveAnalysis=Анализировать только основную кривую finishingTime=Время финиширования finishTimeString=Время финиша setFinishingAndFinishTime=Указать время финиширования и финиша @@ -1969,3 +2021,16 @@ totalDurationFoiledInSeconds=Общее время в крыльевом реж totalDistanceFoiledInMeters=Общая дистанция в крыльевом режиме totalDurationFoiledInSecondsTooltip=Общее время в крыльевом режиме, по всей таблице лидеров totalDistanceFoiledInMetersTooltip=Общая дистанция в крыльевом режиме, по всей таблице лидеров +threeSixtyVideoHint=Это панорамное видео, камеру можно повернуть +processingMP4=Анализ Mp4 (это займет несколько секунд) +resetStartTimeToDefault=Установить по умолчанию +importFullExpeditionData=Импортировать полные данные Expedition +importCanceledNoCompetitorAdded=Не добавлено ни одного участника! Импорт будет отменен. +importCanceledByUser=Импорт отменен! +importFinished=Импорт данных завершен +importFinishedMessage=Данные Expedition успешно импортированы. +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 f4ef4226ac8..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 @@ -233,6 +233,32 @@ hoverOverAPoint=将鼠标移到得分,查看详情 noSelection=无选择 raceIsKnownToStartUpwind=比赛轮次从迎风航段开始 events=活动 +pairingList=配对清单 +pairingLists=配对清单 +pairingListCreationInfo=首先,将计算仅包含参赛队数并且可以应用到其他积分榜的模板。如果参赛队数量无法被船只数量除尽,则在配对清单中输入空占位符。\ 此外,如果参赛队数量不等于实际积分榜中注册的参赛队数量,配对清单无法应用到比赛轮次日志。\ 用于生成配对清单的航程数由积分榜中不是奖牌轮的所有航程确定。 +setCompetitors=请设置参赛队数量: +seriesHint=请选择一场或多场应位于配对清单的系列赛: +amountOfFlightRepeats=航程重复次数 +insertIntoRegatta=插入比赛 +recalculate=重新计算 +csvExport=导出为 CSV +printView=打印预览 +print=打印 +invalidCompetitorCount=参赛队数量无效 +invalidFlightMultiplier=航程重复无效 +invalidSeriesSelection=系列赛选择无效 +flightsMustBeAMultipleOfMultiplier=航程需要是航程乘数的倍数 +parameters=参数 +numberOfFlights=航程数 +numberOfFleets=各航程的比赛轮次数 +numberOfCompetitors=参赛队数 +numberOfRaces=比赛轮次数 +quality=质量 +pairingListTemplate=配对清单模板 +printHint=打印应用的版本 +blockedApplyButton=注册的参赛队不等于配对清单中的参赛队! +multiplierInfo=多个航程,彼此相邻,这样就可以减少船只更换情况进行比赛 +noPairingListAvailable=打印功能仅在配对清单可应用于选择的积分榜比赛轮次日志时可用。 settingsForComponent={0}设置 noEventsFound=未找到活动 noEventSelected=未选择活动 @@ -351,6 +377,7 @@ replicatingFromMaster=正在从主服务器{0}:{2}、消息传送端口{3}:{ showWindSpeedSeries=显示风速数据 showWindDirectionSeries=显示风向数据 fleet=船队 +boat=船只 boatClass=船只级别 setDelayToLive=设置实况延迟 pleaseEnterNonEmptyVenue=请输入场地。 @@ -524,7 +551,6 @@ additionalUrlSettings=附加 URL 设置 restrictToSingleSelectedRace=限制为单个选择的比赛轮次 expandSelectedRace=展开选择的比赛轮次 showRaceDetails=允许比赛轮次详情 -numberOfRaces=比赛轮次数 numberOfRacesMustBeNonNegativeNumber=请输入要显示的最后比赛轮次的数量 chooseTheWayYouSelectRaces=选择选取比赛轮次的方式 selectFromAllRaces=从所有比赛轮次中选择 @@ -1032,8 +1058,8 @@ resultFromIgtimiWindImport=已为所列的比赛轮次导入以下修复: {0} timeToStart={0} - 开始 timeSinceStart={0} - 开始后 distanceToLine=与起航线的距离 -beatAngleTooltip=赛队航行的方向和风的夹角 -beatAngle=迎风航行角度 +TWATooltip=赛队航行的方向和风的夹角 +TWA=迎风航行角度 showBoatClassChartsLabel=还可以查看可用船只级别的总直直方图。 showDiagram=显示直方图 runAutomaticallyTooltip=在更改统计或分组后,自动运行查询。 @@ -1454,6 +1480,7 @@ plainResultsPresenter=纯结果展示区 resultsChart=结果记录 tabbedResultsPresenter=选项卡式结果展示区 polarResultsPresenter=极坐标结果展示区 +maneuverSpeedDetailsResultsPresenter=操作速度详细结果展示区 dataMiningRetrieval=数据检索 actionWatch=观看 actionAnalyze=分析 @@ -1463,6 +1490,10 @@ doYouWantToCreateADefaultRegattaLeaderboard=是否确认默认的比赛积分榜 copyCourse=复制场地 copyCompetitors=复制参赛队 smartphoneTracking=智能手机跟踪 +titelOfChooseNameDialog=选择名称 +ownPrefix=自己的前缀 +defaultName=默认值 +exampleTextForName=名称如下所示: flightsCount={0,number} 个航程 flightsCount[one]={0,number} 个航程 viewQueryDefinition=查看查询定义 @@ -1873,6 +1904,26 @@ showEstimatedDuration=显示预计持续时间 estimatedDuration=预计持续时间: countDown=活动于{0} {1}开始 countDownEnd=活动将在数秒后开始 +maneuverSpeedDetails=操作速度详情 +maneuverDirectionNormalization=操作方向规范化 +maneuver=操作 +minManeuverDuration=最短操作持续时间 (s) +maxManeuverDuration=最长操作持续时间 (s) +minManeuverEnteringSpeedInKnots=最小操作驶入速度 (kts) +maxManeuverEnteringSpeedInKnots=最大操作驶入速度 (kts) +minManeuverExitingSpeedInKnots=最小操作驶出速度 (kts) +maxManeuverExitingSpeedInKnots=最大操作驶出速度 (kts) +minManeuverEnteringAbsTWA=最小操作驶入绝对实际风向角 +maxManeuverEnteringAbsTWA=最大操作驶入绝对实际风向角 +minManeuverExitingAbsTWA=最小操作驶出绝对实际风向角 +maxManeuverExitingAbsTWA=最大操作驶出绝对实际风向角 +maneuverDirectionEqualWeightingEnabled=以“各实际风向角的值”汇总为操作方向启用 50%-50% 加权 +minValue=最小值 +maxValue=最大值 +zeroTo360AxisLabeling=0-360° 轴标签 +exportStatisticsCurveToCsv=将统计曲线导出到 CSV +csvCopiedToClipboard=CSV 内容已复制到剪贴板。将其粘贴在需要的任何地方。 +minDataCount=最小数据数 moreLoginInformationHeadline=注册 SAP Sailing Analytics 的优势 moreLoginInformationIntroduction=作为 SAP Sailing Analytics 的注册用户,您可以访问尚未对匿名用户开放的功能。注册即可访问以下显示的功能。 moreLoginInformationSectionUserSettingsHeading=用户设置 @@ -1918,6 +1969,7 @@ currentStbdRudderRake=右舵倾斜度 currentStbdRudderRakeTooltip=当前右舵倾斜度 currentMastRotationInDegree=桅杆旋转度 currentMastRotationInDegreeTooltip=当前桅杆旋转度数 +mainCurveAnalysis=仅分析主曲线 finishingTime=结束时间 finishTimeString=终点线时间 setFinishingAndFinishTime=设置结束和终点线时间 @@ -1969,3 +2021,16 @@ totalDurationFoiledInSeconds=水翼腾空总时间 totalDistanceFoiledInMeters=水翼腾空总距离 totalDurationFoiledInSecondsTooltip=水翼腾空总时间(在整个积分榜中) totalDistanceFoiledInMetersTooltip=水翼腾空总距离(在整个积分榜中) +threeSixtyVideoHint=这是 360° 视频,相机可以转动 +processingMP4=分析 Mp4(将需要几秒钟) +resetStartTimeToDefault=设为默认值 +importFullExpeditionData=导入全部导航图数据 +importCanceledNoCompetitorAdded=未添加参赛队!导入将被取消。 +importCanceledByUser=导入已取消! +importFinished=已完成数据导入 +importFinishedMessage=已成功导入导航图数据。 +importFinishedGotoRaceboard=打开比赛板 +importFinishedGotoEvent=打开活动 +chooseAName=选择名称 +unexpectedErrorDuringFileImport=文件上传期间发生意外错误。 +couldNotDownload=无法下载文件:{0} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java index a84c7df5666..b6d781fd6f4 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanel.java @@ -38,6 +38,7 @@ import com.sap.sse.gwt.client.shared.components.Component; import com.sap.sse.gwt.client.shared.components.SettingsDialog; import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; import com.sap.sse.gwt.client.shared.settings.ComponentContext; +import com.sap.sse.security.ui.client.UserService; public class TimePanel extends AbstractCompositeComponent implements TimeListener, TimeZoomChangeListener, TimeRangeChangeListener, PlayStateListener, RequiresResize { @@ -64,7 +65,8 @@ public class TimePanel extends AbstractCompositeCom private final Button backToLivePlayButton; protected final StringMessages stringMessages; protected final DateTimeFormat dateFormatter = DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.DATE_FULL); - protected final DateTimeFormat timeFormatter = DateTimeFormat.getFormat("HH:mm:ss"); + protected final DateTimeFormat timeFormatter = DateTimeFormat.getFormat("HH:mm:ss"); + protected final DateTimeFormat timeFormatterDetailed = DateTimeFormat.getFormat("HH:mm:ss:SS"); private final ImageResource playSpeedImg; private final ImageResource playModeLiveActiveImg; private final ImageResource playModeReplayActiveImg; @@ -92,6 +94,7 @@ public class TimePanel extends AbstractCompositeCom protected static TimePanelCss timePanelCss = TimePanelCssResources.INSTANCE.css(); private final boolean forcePaddingRightToAlignToCharts; + private UserService userService; /** * @param forcePaddingRightToAlignToCharts @@ -102,8 +105,9 @@ public class TimePanel extends AbstractCompositeCom public TimePanel(Component parent, ComponentContext context, Timer timer, TimeRangeWithZoomProvider timeRangeProvider, StringMessages stringMessages, - boolean canReplayWhileLiveIsPossible, boolean forcePaddingRightToAlignToCharts) { + boolean canReplayWhileLiveIsPossible, boolean forcePaddingRightToAlignToCharts, UserService userService) { super(parent, context); + this.userService = userService; this.timer = timer; this.timeRangeProvider = timeRangeProvider; this.stringMessages = stringMessages; @@ -409,10 +413,14 @@ public class TimePanel extends AbstractCompositeCom protected String getTimeLabelText(Date time) { final String timeLabelText; + DateTimeFormat formatter = timeFormatter; + if (timer.getRefreshInterval() < 1000) { + formatter = timeFormatterDetailed; + } if (lastReceivedDataTimepoint == null) { - timeLabelText = timeFormatter.format(time); + timeLabelText = formatter.format(time); } else { - timeLabelText = timeFormatter.format(time) + " (" + timeFormatter.format(lastReceivedDataTimepoint) + ")"; + timeLabelText = formatter.format(time) + " (" + formatter.format(lastReceivedDataTimepoint) + ")"; } return timeLabelText; } @@ -597,7 +605,7 @@ public class TimePanel extends AbstractCompositeCom @Override public SettingsDialogComponent getSettingsDialogComponent(T settings) { - return new TimePanelSettingsDialogComponent(settings, stringMessages); + return new TimePanelSettingsDialogComponent(settings, stringMessages, userService); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanelSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanelSettingsDialogComponent.java index 6441cb91c49..17ec8454e44 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanelSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/TimePanelSettingsDialogComponent.java @@ -5,23 +5,28 @@ import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FocusWidget; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.security.Permission; +import com.sap.sailing.domain.common.security.SailingPermissionsForRoleProvider; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator; import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; +import com.sap.sse.security.ui.client.UserService; public class TimePanelSettingsDialogComponent implements SettingsDialogComponent { protected DoubleBox refreshIntervalBox; protected final StringMessages stringMessages; protected final T initialSettings; protected FlowPanel mainContentPanel; + private UserService userService; private static String STYLE_LABEL = "settingsDialogLabel"; private static String STYLE_INPUT = "settingsDialogValue"; private static String STYLE_BOXPANEL = "boxPanel"; - public TimePanelSettingsDialogComponent(T settings, StringMessages stringMessages) { + public TimePanelSettingsDialogComponent(T settings, StringMessages stringMessages, UserService userService) { this.stringMessages = stringMessages; initialSettings = settings; + this.userService = userService; } protected StringMessages getStringMessages() { @@ -57,8 +62,15 @@ public class TimePanelSettingsDialogComponent imple @Override public String getErrorMessage(TimePanelSettings valueToValidate) { String errorMessage = null; - if (valueToValidate.getRefreshInterval() < 500) { - errorMessage = stringMessages.refreshIntervalMustBeGreaterThanXSeconds("0.5"); + if (userService.getCurrentUser() != null + && userService.getCurrentUser().hasPermission(Permission.DETAIL_TIMER.name(), SailingPermissionsForRoleProvider.INSTANCE)) { + if (valueToValidate.getRefreshInterval() < 50) { + errorMessage = stringMessages.refreshIntervalMustBeGreaterThanXSeconds("0.05"); + } + } else { + if (valueToValidate.getRefreshInterval() < 500) { + errorMessage = stringMessages.refreshIntervalMustBeGreaterThanXSeconds("0.5"); + } } return errorMessage; } 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/EmbeddedMapAndWindChartEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java index 1fdc9cbc083..25af06fa8b1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java @@ -181,7 +181,7 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint // let the time panel always return to "live" mode. final TimePanel timePanel = new TimePanel(null, null, timer, timeRangeWithZoomProvider, getStringMessages(), /* canReplayWhileLive */ false, - /* isScreenLargeEnoughToOfferChartSupport set to true iff wind chart will be displayed */ raceboardPerspectiveSettings.isShowWindChart()) { + /* isScreenLargeEnoughToOfferChartSupport set to true iff wind chart will be displayed */ raceboardPerspectiveSettings.isShowWindChart(), getUserService()) { protected boolean isLiveModeToBeMadePossible() { return true; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java index fbe467e5032..0d2b9edf7b9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardEntryPoint.java @@ -117,7 +117,7 @@ public class RaceBoardEntryPoint extends AbstractSailingEntryPoint { @Override public void onSuccess(List result) { final RaceBoardPerspectiveLifecycle lifeCycle = new RaceBoardPerspectiveLifecycle( - StringMessages.INSTANCE, result); + StringMessages.INSTANCE, result, getUserService()); RaceBoardComponentContext componentContext = new RaceBoardComponentContext(lifeCycle, getUserService(), storageDefinition); 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/RaceBoardPerspectiveLifecycle.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPerspectiveLifecycle.java index 79fb57393bf..70633398574 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPerspectiveLifecycle.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPerspectiveLifecycle.java @@ -13,6 +13,7 @@ import com.sap.sailing.gwt.ui.client.shared.charts.WindChartLifecycle; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapLifecycle; import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; import com.sap.sse.gwt.client.shared.perspective.AbstractPerspectiveLifecycle; +import com.sap.sse.security.ui.client.UserService; public class RaceBoardPerspectiveLifecycle extends AbstractPerspectiveLifecycle { @@ -28,18 +29,18 @@ public class RaceBoardPerspectiveLifecycle extends AbstractPerspectiveLifecycle< public static final String ID = "rb"; //constructor used by Standalone RaceBoard - public RaceBoardPerspectiveLifecycle(StringMessages stringMessages, List competitorChartAllowedDetailTypes) { - this(null, stringMessages, competitorChartAllowedDetailTypes); + public RaceBoardPerspectiveLifecycle(StringMessages stringMessages, List competitorChartAllowedDetailTypes, UserService userService) { + this(null, stringMessages, competitorChartAllowedDetailTypes, userService); } - public RaceBoardPerspectiveLifecycle(AbstractLeaderboardDTO leaderboard, StringMessages stringMessages, List competitorChartAllowedDetailTypes) { + public RaceBoardPerspectiveLifecycle(AbstractLeaderboardDTO leaderboard, StringMessages stringMessages, List competitorChartAllowedDetailTypes, UserService userService) { this.stringMessages = stringMessages; raceMapLifecycle = new RaceMapLifecycle(stringMessages); windChartLifecycle = new WindChartLifecycle(stringMessages); leaderboardPanelLifecycle = new SingleRaceLeaderboardPanelLifecycle(stringMessages); multiCompetitorRaceChartLifecycle = new MultiCompetitorRaceChartLifecycle(stringMessages, competitorChartAllowedDetailTypes); mediaPlayerLifecycle = new MediaPlayerLifecycle(stringMessages); - raceTimePanelLifecycle = new RaceTimePanelLifecycle(stringMessages); + raceTimePanelLifecycle = new RaceTimePanelLifecycle(stringMessages, userService); addLifeCycle(raceMapLifecycle); addLifeCycle(windChartLifecycle); 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/raceboard/raceboard-main.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/raceboard-main.gss index 775d3ee518d..b7fc684a8a4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/raceboard-main.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/raceboard-main.gss @@ -1,5 +1,5 @@ .usermanagement_icon { - width: 3rem; + width: 3rem; height: 3rem; vertical-align: middle; background-repeat: no-repeat; @@ -12,18 +12,18 @@ top: 6px; } .usermanagement_icon.usermanagement_open { - background-image: resourceUrl('loggedoutOpen'); + background-image: resourceUrl('loggedoutOpen'); } .usermanagement_icon.usermanagement_loggedin { - background-image: resourceUrl('loggedin'); + background-image: resourceUrl('loggedin'); } .usermanagement_icon.usermanagement_loggedin.usermanagement_open { - background-image: resourceUrl('loggedinOpen'); + background-image: resourceUrl('loggedinOpen'); } .usermanagement_view { - top: 60px !important; + top: 60px !important; left: inherit !important; - right: -0.25em !important; + right: 0; z-index: 10; line-height: 1.333; } @@ -31,12 +31,18 @@ font-size: 15px; } .usermanagement_view.usermanagement_mobile { - top: 60px !important; + top: 60px !important; left: 0 !important; } +.usermanagement_view_content_wrapper { + padding: 0 0.75rem 0 1rem !important; +} +.usermanagement_view_content:before { + right: 1em !important; +} .usermanagement_mobile .usermanagement_view_content_wrapper { - padding: 0; + padding: 0 !important; } .usermanagement_mobile .usermanagement_view_content { - width: 100%; + width: 100%; } 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/MediaServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java index 607ff21f47f..35002e5fa6e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/MediaServiceImpl.java @@ -19,6 +19,7 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import org.apache.shiro.SecurityUtils; import org.mp4parser.IsoFile; import org.mp4parser.boxes.UserBox; import org.mp4parser.boxes.iso14496.part12.MovieBox; @@ -35,6 +36,8 @@ import com.google.gwt.user.server.rpc.RemoteServiceServlet; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.VideoMetadataDTO; import com.sap.sailing.domain.common.media.MediaTrack; +import com.sap.sailing.domain.common.security.Permission; +import com.sap.sailing.domain.common.security.Permission.Mode; import com.sap.sailing.gwt.ui.client.MediaService; import com.sap.sailing.server.RacingEventService; import com.sap.sse.common.Duration; @@ -47,8 +50,8 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi private static final int METADATA_CONNECTION_TIMEOUT = 10000; DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); - private ServiceTracker racingEventServiceTracker; + private static final int REQUIRED_SIZE = 1000000; private static final long serialVersionUID = -8917349579281305977L; @@ -78,9 +81,14 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi public Iterable getAllMediaTracks() { return racingEventService().getAllMediaTracks(); } + + private void ensureUserCanManageMedia() { + SecurityUtils.getSubject().checkPermission(Permission.MANAGE_MEDIA.getStringPermissionForObjects(Mode.UPDATE)); + } @Override public String addMediaTrack(MediaTrack mediaTrack) { + ensureUserCanManageMedia(); if (mediaTrack.dbId != null) { throw new IllegalStateException("Property dbId must not be null for newly created media track."); } @@ -90,36 +98,43 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi @Override public void deleteMediaTrack(MediaTrack mediaTrack) { + ensureUserCanManageMedia(); racingEventService().mediaTrackDeleted(mediaTrack); } @Override public void updateTitle(MediaTrack mediaTrack) { + ensureUserCanManageMedia(); racingEventService().mediaTrackTitleChanged(mediaTrack); } @Override public void updateUrl(MediaTrack mediaTrack) { + ensureUserCanManageMedia(); racingEventService().mediaTrackUrlChanged(mediaTrack); } @Override public void updateStartTime(MediaTrack mediaTrack) { + ensureUserCanManageMedia(); racingEventService().mediaTrackStartTimeChanged(mediaTrack); } @Override public void updateDuration(MediaTrack mediaTrack) { + ensureUserCanManageMedia(); racingEventService().mediaTrackDurationChanged(mediaTrack); } @Override public void updateRace(MediaTrack mediaTrack) { + ensureUserCanManageMedia(); racingEventService().mediaTrackAssignedRacesChanged(mediaTrack); } @Override public VideoMetadataDTO checkMetadata(String url) { + ensureUserCanManageMedia(); VideoMetadataDTO response = null; try { URL input = new URL(url); @@ -199,6 +214,7 @@ public class MediaServiceImpl extends RemoteServiceServlet implements MediaServi @Override public VideoMetadataDTO checkMetadata(byte[] start, byte[] end, Long skipped) { + ensureUserCanManageMedia(); File tmp = null; boolean spherical = false; Duration duration = null; 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 1f5a127097e..d3f8ae3d8ee 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 @@ -57,35 +57,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; @@ -241,6 +278,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; @@ -296,6 +334,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; @@ -311,9 +350,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; @@ -379,6 +421,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; @@ -455,6 +498,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; @@ -536,6 +580,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S private final ServiceTracker scoreCorrectionProviderServiceTracker; private final ServiceTracker competitorProviderServiceTracker; + + private final ServiceTracker windFinderTrackerFactoryServiceTracker; private final MongoObjectFactory mongoObjectFactory; @@ -596,6 +642,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } 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); @@ -2970,10 +3017,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); @@ -3645,7 +3690,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 { + Map sailorsInfoWebsiteURLsByLocaleName, Iterable images, Iterable videos, + Iterable windFinderReviewedSpotCollectionIds) throws MalformedURLException { TimePoint startTimePoint = startDate != null ? new MillisecondsTimePoint(startDate) : null; TimePoint endTimePoint = endDate != null ? new MillisecondsTimePoint(endDate) : null; URL officialWebsiteURL = officialWebsiteURLString != null ? new URL(officialWebsiteURLString) : null; @@ -3655,7 +3701,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); } @@ -3912,7 +3959,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)); } return eventDTO; } @@ -4513,7 +4566,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 @@ -6905,45 +6959,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/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java index 5f5561271f9..3183e0f3a0f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorEntryPoint.java @@ -50,9 +50,9 @@ public class SimulatorEntryPoint extends AbstractSailingEntryPoint { @Override protected void doOnModuleLoad() { Highcharts.ensureInjectedWithExport(); - super.doOnModuleLoad(); + super.doOnModuleLoad(); EntryPointHelper.registerASyncService((ServiceDefTarget) simulatorService, RemoteServiceMappingConstants.simulatorServiceRemotePath); - checkUrlParameters(); + checkUrlParameters(); createSimulatorPanel(); } @@ -195,7 +195,7 @@ public class SimulatorEntryPoint extends AbstractSailingEntryPoint { @Override public Widget get() { SimulatorMainPanel simulatorPanel = new SimulatorMainPanel(simulatorService, getStringMessages(), SimulatorEntryPoint.this, xRes, yRes, border, streamletPars, - autoUpdate, mode, event, showGrid, showLines, seedLines, showArrows, showLineGuides, showStreamlets, showMapControls); + autoUpdate, mode, event, showGrid, showLines, seedLines, showArrows, showLineGuides, showStreamlets, showMapControls, getUserService()); return simulatorPanel; } }); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMainPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMainPanel.java index 715c861ad2b..36687259f26 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMainPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/simulator/SimulatorMainPanel.java @@ -66,6 +66,7 @@ import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel; import com.sap.sse.gwt.client.player.TimeRangeWithZoomProvider; import com.sap.sse.gwt.client.player.Timer; import com.sap.sse.gwt.client.player.Timer.PlayModes; +import com.sap.sse.security.ui.client.UserService; public class SimulatorMainPanel extends SimplePanel { @@ -237,7 +238,7 @@ public class SimulatorMainPanel extends SimplePanel { SimulatorMainPanel(SimulatorServiceAsync svc, StringMessages stringMessages, ErrorReporter errorReporter, int xRes, int yRes, int border, StreamletParameters streamletPars, boolean autoUpdate, char mode, char event, boolean showGrid, boolean showLines, char seedLines, boolean showArrows, - boolean showLineGuides, boolean showStreamlets, boolean showMapControls) { + boolean showLineGuides, boolean showStreamlets, boolean showMapControls, UserService userService) { super(); this.formatter = new WindPatternFormatter(stringMessages); this.macroWeather = streamletPars.macroWeather; @@ -317,7 +318,7 @@ public class SimulatorMainPanel extends SimplePanel { timer.setPlaySpeedFactor(secondsTimeStep); timePanel = new TimePanel(null, null, timer, timeRangeProvider, stringMessages, false, /* isScreenLargeEnoughToOfferChartSupport: no wind or competitor chart is shown; use full horizontal - * extension of time panel */ false); + * extension of time panel */ false, userService); busyIndicator = new SimpleBusyIndicator(false, 0.8f); simulatorMap = new SimulatorMap(simulatorSvc, stringMessages, errorReporter, xRes, yRes, border, streamletPars, timer, timePanel, windParams, busyIndicator, mode, this, showMapControls, diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_Settings.png b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_Settings.png index 23f7a60ea33..dbaf291466e 100755 Binary files a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_Settings.png and b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/client/images/SAP_RV_Settings.png differ 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/META-INF/MANIFEST.MF b/java/com.sap.sailing.polars/META-INF/MANIFEST.MF index dce9c6c27d8..370ba599688 100644 --- a/java/com.sap.sailing.polars/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.polars/META-INF/MANIFEST.MF @@ -31,12 +31,7 @@ Require-Bundle: javax.servlet;bundle-version="2.6.0", org.apache.shiro.core;bundle-version="1.2.2", org.apache.shiro.web;bundle-version="1.2.2", - org.apache.shiro.ehcache;bundle-version="1.2.3", - org.eclipse.jetty.websocket.api;bundle-version="9.0.4", - org.eclipse.jetty.websocket.server;bundle-version="9.0.4", - org.eclipse.jetty.websocket.servlet;bundle-version="9.0.4", - org.eclipse.jetty.websocket.client;bundle-version="9.0.4", - org.eclipse.jetty.websocket.common;bundle-version="9.0.4" + org.apache.shiro.ehcache;bundle-version="1.2.3" Import-Package: com.sap.sailing.util, javax.ws.rs;version="1.1.1", 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/META-INF/MANIFEST.MF b/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF index 92321848915..23b64076981 100644 --- a/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF @@ -30,11 +30,6 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.server.gateway.serialization, com.sap.sailing.domain.shared.android, com.sap.sailing.server.trackfiles, - org.eclipse.jetty.websocket.api;bundle-version="9.0.4", - org.eclipse.jetty.websocket.server;bundle-version="9.0.4", - org.eclipse.jetty.websocket.servlet;bundle-version="9.0.4", - org.eclipse.jetty.websocket.client;bundle-version="9.0.4", - org.eclipse.jetty.websocket.common;bundle-version="9.0.4", javax.validation;bundle-version="1.0.0", com.sun.jersey;bundle-version="1.17.0", org.objectweb.asm;bundle-version="3.3.1", diff --git a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml index 9d51bd36f21..4d8d4732fba 100644 --- a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml +++ b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml @@ -207,16 +207,6 @@ /fileupload - - websockettest - com.sap.sailing.server.gateway.impl.WebSocketTestServlet - - - - websockettest - /websockettest - - Jersey REST API com.sap.sailing.server.gateway.jaxrs.RestServletContainer diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/WebSocketTestServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/WebSocketTestServlet.java deleted file mode 100755 index 07bfb4559e5..00000000000 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/WebSocketTestServlet.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.sap.sailing.server.gateway.impl; - -import java.io.IOException; - -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.WebSocketAdapter; -import org.eclipse.jetty.websocket.api.annotations.WebSocket; -import org.eclipse.jetty.websocket.servlet.WebSocketServlet; -import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; - -/** - * A test servlet for web socket connectivity. To use from within GWT, use something like this: - * - *

    - *     private void testWebSockets() {
    -        final String baseUrl = GWT.getModuleBaseURL().replace("http", "ws");
    -        int indexOfGwt = baseUrl.indexOf("/gwt/");
    -        String url = baseUrl.substring(0, indexOfGwt)+"/sailingserver/websockettest";
    -        new JavaScriptWebSocketFactory().createWebSocket(url, new WebSocketCallback() {
    -            public void onOpen(WebSocket webSocket) {
    -                webSocket.send("Hello");
    -            }
    -            
    -            public void onMessage(WebSocket webSocket, String message) {
    -                ...
    -            }
    -            
    -            public void onError(WebSocket webSocket) {
    -                ...
    -            }
    -            
    -            public void onClose(WebSocket webSocket) {
    -                ...
    -            }
    -        });
    -    }
    - * 
    - * @author Axel Uhl (D043530) - * - */ -public class WebSocketTestServlet extends WebSocketServlet { - private static final long serialVersionUID = -5160225187387804019L; - - @Override - public void configure(WebSocketServletFactory factory) { - factory.register(MyFirstWebSocket.class); - } - - @WebSocket - public static class MyFirstWebSocket extends WebSocketAdapter { - @Override - public void onWebSocketText(String s) { - System.out.println(s); - try { - getSession().getRemote().sendString("This is my response to "+s); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public void onWebSocketBinary(byte[] payload, int offset, int len) { - super.onWebSocketBinary(payload, offset, len); - } - - @Override - public void onWebSocketClose(int statusCode, String reason) { - super.onWebSocketClose(statusCode, reason); - } - - @Override - public void onWebSocketConnect(Session sess) { - super.onWebSocketConnect(sess); - } - - @Override - public void onWebSocketError(Throwable cause) { - super.onWebSocketError(cause); - } - } -} 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 dbe534e7865..ddf7bc694c8 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/jaxrs/api/LeaderboardsResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java index 80e779360d4..94073fdcfd2 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/LeaderboardsResource.java @@ -6,6 +6,7 @@ import java.io.StringWriter; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.Iterator; @@ -16,6 +17,8 @@ import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import javax.ws.rs.Consumes; import javax.ws.rs.DefaultValue; @@ -113,6 +116,7 @@ import com.sap.sailing.server.gateway.serialization.coursedata.impl.CourseJsonSe import com.sap.sailing.server.gateway.serialization.coursedata.impl.GateJsonSerializer; import com.sap.sailing.server.gateway.serialization.coursedata.impl.MarkJsonSerializer; import com.sap.sailing.server.gateway.serialization.coursedata.impl.WaypointJsonSerializer; +import com.sap.sailing.server.gateway.serialization.impl.CompetitorJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.FlatGPSFixJsonSerializer; import com.sap.sailing.server.gateway.serialization.impl.MarkJsonSerializerWithPosition; import com.sap.sse.InvalidDateException; @@ -1086,6 +1090,116 @@ public class LeaderboardsResource extends AbstractSailingServerResource { return Response.ok(result.toJSONString()).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build(); } + /** + * @return the actual or anticipated start order for the race identified by {@code raceColumnName}, {@code fleetName}. + * Those competitors for which a start mark passing is already known are sorted by those start mark passings. All other + * boats are ordered by their geometric distance from the start line or the windward distance from the start mark + * if the start for some reason is defined by a single mark. See {@link #compareDistanceFromStartLine(Competitor, Competitor)}. + */ + @GET + @Produces("application/json;charset=UTF-8") + @Path("{leaderboardName}/startorder") + public Response getStartOrder(@PathParam("leaderboardName") String leaderboardName, + @QueryParam(RaceLogServletConstants.PARAMS_RACE_COLUMN_NAME) String raceColumnName, + @QueryParam(RaceLogServletConstants.PARAMS_RACE_FLEET_NAME) String fleetName) { + Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); + if (leaderboard == null) { + return Response.status(Status.NOT_FOUND) + .entity("Could not find a leaderboard with name '" + StringEscapeUtils.escapeHtml(leaderboardName) + "'.") + .type(MediaType.TEXT_PLAIN).build(); + } + + RaceColumn raceColumn = leaderboard.getRaceColumnByName(raceColumnName); + if (raceColumn == null) { + return Response + .status(Status.NOT_FOUND) + .entity("Could not find a race column '" + StringEscapeUtils.escapeHtml(raceColumnName) + "' in leaderboard '" + + StringEscapeUtils.escapeHtml(leaderboardName) + "'.").type(MediaType.TEXT_PLAIN).build(); + } + Fleet fleet = raceColumn.getFleetByName(fleetName); + if (fleet == null) { + return Response + .status(Status.NOT_FOUND) + .entity("Could not find a fleet '" + StringEscapeUtils.escapeHtml(fleetName) + "' in raceColumn '" + + StringEscapeUtils.escapeHtml(raceColumnName) + "'.").type(MediaType.TEXT_PLAIN).build(); + } + final TrackedRace trackedRace = raceColumn.getTrackedRace(fleet); + if (trackedRace == null) { + return Response.status(Status.NOT_FOUND) + .entity("Could not find a tracked race for raceColumn '" + StringEscapeUtils.escapeHtml(raceColumnName) + + " and fleet '" + StringEscapeUtils.escapeHtml(fleetName) + "'.").type(MediaType.TEXT_PLAIN) + .build(); + }; + + Course course = trackedRace.getRace().getCourse(); + final List competitors = new ArrayList<>(); + final Waypoint firstWaypoint = course.getFirstWaypoint(); + final TimePoint timeToUseForStart; + if (trackedRace.getStartOfRace() != null) { + timeToUseForStart = trackedRace.getStartOfRace(); + } else { + timeToUseForStart = MillisecondsTimePoint.now(); + } + if (firstWaypoint != null) { + final List startWaypointMarkPositions = + StreamSupport.stream(firstWaypoint.getMarks().spliterator(), /* parallel */ false). + map(mark->trackedRace.getTrack(mark)).filter(markTrack->markTrack!=null). + map(markTrack->markTrack.getEstimatedPosition(timeToUseForStart, /* extrapolate */ false)). + collect(Collectors.toList()); + Util.addAll(trackedRace.getRace().getCompetitors(), competitors); + competitors.sort((a, b)->{ + final MarkPassing aStartMarkPassing = trackedRace.getMarkPassing(a, firstWaypoint); + final MarkPassing bStartMarkPassing = trackedRace.getMarkPassing(b, firstWaypoint); + final int result; + if (aStartMarkPassing == null) { + if (bStartMarkPassing == null) { + result = compareDistanceFromStartLine(trackedRace, startWaypointMarkPositions, timeToUseForStart, a, b); + } else { + result = 1; // b consider less than a because it has a start mark passing and therefore is assumed to have started before a + } + } else if (bStartMarkPassing == null) { + result = -1; // a consider less than b because it has a start mark passing and therefore is assumed to have started before b + } else { + // both have a start mark passing; compare time points + result = aStartMarkPassing.getTimePoint().compareTo(bStartMarkPassing.getTimePoint()); + } + return result; + }); + } + CompetitorJsonSerializer serializer = new CompetitorJsonSerializer(); + JSONArray result = new JSONArray(); + for (final Competitor c : competitors) { + JSONObject jsonCompetitor = serializer.serialize(c); + result.add(jsonCompetitor); + } + String json = result.toJSONString(); + return Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build(); + } + + private int compareDistanceFromStartLine(TrackedRace trackedRace, Iterable startWaypointMarkPositions, + TimePoint timeToUseForStart, Competitor a, Competitor b) { + final Position aPos = trackedRace.getTrack(a).getEstimatedPosition(timeToUseForStart, /* extrapolate */ true); + final Position bPos = trackedRace.getTrack(b).getEstimatedPosition(timeToUseForStart, /* extrapolate */ true); + final Distance aDist = aPos==null?null:getDistanceFromStartWaypoint(aPos, startWaypointMarkPositions); + final Distance bDist = bPos==null?null:getDistanceFromStartWaypoint(bPos, startWaypointMarkPositions); + return Comparator.nullsLast(Comparator.naturalOrder()).compare(aDist, bDist); + } + + private Distance getDistanceFromStartWaypoint(Position pos, Iterable startWaypointMarkPositions) { + final Distance result; + if (Util.isEmpty(startWaypointMarkPositions)) { + result = null; + } else if (Util.size(startWaypointMarkPositions) == 1) { + // single mark start; strange, but possible: + result = startWaypointMarkPositions.iterator().next().getDistance(pos); + } else { + final Position first = Util.get(startWaypointMarkPositions, 0); + final Position second = Util.get(startWaypointMarkPositions, 1); + result = pos.getDistanceToLine(first, second).abs(); + } + return result; + } + @GET @Produces(MediaType.APPLICATION_JSON) @Path("{leaderboardName}/marks/{markId}") diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java index a5ca653598e..0de42199cf2 100644 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/jaxrs/api/RegattasResource.java @@ -3,7 +3,6 @@ package com.sap.sailing.server.gateway.jaxrs.api; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -14,8 +13,6 @@ import java.util.Set; import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; @@ -48,7 +45,6 @@ import com.sap.sailing.domain.base.impl.PersonImpl; import com.sap.sailing.domain.base.impl.TeamImpl; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.NoWindException; -import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.RegattaName; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.SpeedWithBearing; @@ -64,7 +60,6 @@ import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException; import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.GPSFixTrack; -import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sailing.domain.tracking.TrackedRace; @@ -1074,102 +1069,6 @@ public class RegattasResource extends AbstractSailingServerResource { return response; } - /** - * @return the actual or anticipated start order for the race identified by {@coode regattaName} and {@code raceName}. - * Those competitors for which a start mark passing is already known are sorted by those start mark passings. All other - * boats are ordered by their geometric distance from the start line or the windward distance from the start mark - * if the start for some reason is defined by a single mark. See {@link #compareDistanceFromStartLine(Competitor, Competitor)}. - */ - @GET - @Produces("application/json;charset=UTF-8") - @Path("{regattaname}/races/{racename}/startorder") - public Response getStartOrder(@PathParam("regattaname") String regattaName, @PathParam("racename") String raceName) { - Response response; - Regatta regatta = findRegattaByName(regattaName); - if (regatta == null) { - response = Response.status(Status.NOT_FOUND) - .entity("Could not find a regatta with name '" + StringEscapeUtils.escapeHtml(regattaName) + "'.").type(MediaType.TEXT_PLAIN) - .build(); - } else { - RaceDefinition race = findRaceByName(regatta, raceName); - if (race == null) { - response = Response.status(Status.NOT_FOUND) - .entity("Could not find a race with name '" + StringEscapeUtils.escapeHtml(raceName) + "'.").type(MediaType.TEXT_PLAIN) - .build(); - } else { - final TrackedRace trackedRace = findTrackedRace(regattaName, raceName); - Course course = trackedRace.getRace().getCourse(); - final List competitors = new ArrayList<>(); - final Waypoint firstWaypoint = course.getFirstWaypoint(); - final TimePoint timeToUseForStart; - if (trackedRace.getStartOfRace() != null) { - timeToUseForStart = trackedRace.getStartOfRace(); - } else { - timeToUseForStart = MillisecondsTimePoint.now(); - } - if (firstWaypoint != null) { - final List startWaypointMarkPositions = - StreamSupport.stream(firstWaypoint.getMarks().spliterator(), /* parallel */ false). - map(mark->trackedRace.getTrack(mark)).filter(markTrack->markTrack!=null). - map(markTrack->markTrack.getEstimatedPosition(timeToUseForStart, /* extrapolate */ false)). - collect(Collectors.toList()); - Util.addAll(trackedRace.getRace().getCompetitors(), competitors); - competitors.sort((a, b)->{ - final MarkPassing aStartMarkPassing = trackedRace.getMarkPassing(a, firstWaypoint); - final MarkPassing bStartMarkPassing = trackedRace.getMarkPassing(b, firstWaypoint); - final int result; - if (aStartMarkPassing == null) { - if (bStartMarkPassing == null) { - result = compareDistanceFromStartLine(trackedRace, startWaypointMarkPositions, timeToUseForStart, a, b); - } else { - result = 1; // b consider less than a because it has a start mark passing and therefore is assumed to have started before a - } - } else if (bStartMarkPassing == null) { - result = -1; // a consider less than b because it has a start mark passing and therefore is assumed to have started before b - } else { - // both have a start mark passing; compare time points - result = aStartMarkPassing.getTimePoint().compareTo(bStartMarkPassing.getTimePoint()); - } - return result; - }); - } - CompetitorJsonSerializer serializer = new CompetitorJsonSerializer(); - JSONArray result = new JSONArray(); - for (final Competitor c : competitors) { - JSONObject jsonCompetitor = serializer.serialize(c); - result.add(jsonCompetitor); - } - String json = result.toJSONString(); - return Response.ok(json).header("Content-Type", MediaType.APPLICATION_JSON + ";charset=UTF-8").build(); - } - } - return response; - } - - private int compareDistanceFromStartLine(TrackedRace trackedRace, Iterable startWaypointMarkPositions, - TimePoint timeToUseForStart, Competitor a, Competitor b) { - final Position aPos = trackedRace.getTrack(a).getEstimatedPosition(timeToUseForStart, /* extrapolate */ true); - final Position bPos = trackedRace.getTrack(b).getEstimatedPosition(timeToUseForStart, /* extrapolate */ true); - final Distance aDist = aPos==null?null:getDistanceFromStartWaypoint(aPos, startWaypointMarkPositions); - final Distance bDist = bPos==null?null:getDistanceFromStartWaypoint(bPos, startWaypointMarkPositions); - return Comparator.nullsLast(Comparator.naturalOrder()).compare(aDist, bDist); - } - - private Distance getDistanceFromStartWaypoint(Position pos, Iterable startWaypointMarkPositions) { - final Distance result; - if (Util.isEmpty(startWaypointMarkPositions)) { - result = null; - } else if (Util.size(startWaypointMarkPositions) == 1) { - // single mark start; strange, but possible: - result = startWaypointMarkPositions.iterator().next().getDistance(pos); - } else { - final Position first = Util.get(startWaypointMarkPositions, 0); - final Position second = Util.get(startWaypointMarkPositions, 1); - result = pos.getDistanceToLine(first, second).abs(); - } - return result; - } - @GET @Produces("application/json;charset=UTF-8") @Path("{regattaname}/races") 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 b0a9904648c..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 @@ -185,6 +185,10 @@ name to make sure you get to the event you're interested in. /api/v1/leaderboards/{name}/marks Gets the marks of a race (from the RaceLog and if possible the TrackedRace) + + /api/v1/leaderboards/{name}/startorder + Gets the order in which competitors have started or are expected to start for a race + /api/v1/leaderboards/{name}/settrackingtimes Modifies the race's tracking interval @@ -217,10 +221,6 @@ name to make sure you get to the event you're interested in. /api/v1/competitors/{id}/team/image Sets the team image - - /api/v1/regattas/{regattaname}/races/{racename}/startorder - Gets the order in which competitors have started or are expected to start - /api/v1/regattas/{regattaname}/races/{racename}/maneuvers Gets all maneuvers performed by all competitors of a race diff --git a/java/com.sap.sailing.server.gateway/webservices/api/v1/startorderGetDoc.html b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardRaceStartorderGetDoc.html similarity index 78% rename from java/com.sap.sailing.server.gateway/webservices/api/v1/startorderGetDoc.html rename to java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardRaceStartorderGetDoc.html index d95afdecbac..4581612874c 100755 --- a/java/com.sap.sailing.server.gateway/webservices/api/v1/startorderGetDoc.html +++ b/java/com.sap.sailing.server.gateway/webservices/api/v1/leaderboardRaceStartorderGetDoc.html @@ -9,10 +9,10 @@

    SAP Sailing Analytics Webservices API Version 1.0

    -

    URL: /api/v1/regattas/{regattaname}/races/{racename}/startorder

    +

    URL: /api/v1/leaderboards/{name}/startorder?race_column={name}&fleet={name}

    Description: -

    Gets the order in which competitors have started or are expected to start. The output +

    Gets the order in which competitors have started or are expected to start for a race. The output is a JSON array that contains the race competitors in the order in which they have passed the start waypoint or are expected to do so. Those competitors for which a start mark passing is already known are sorted by those start mark passings. All other @@ -31,7 +31,8 @@ Mandatory parameters: - None + race_column +
    fleet Optional parameters: @@ -43,7 +44,7 @@ Example: - https://dev.sapsailing.com/sailingserver/api/v1/regattas/ESS%202016%20Cardiff/races/Race%2016/startorder + https://dev.sapsailing.com/sailingserver/api/v1/leaderboards/ESS%202016%20Cardiff/startorder?race_column=R1&fleet=Default

    diff --git a/java/com.sap.sailing.server.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.server.test/META-INF/MANIFEST.MF index 07ac54ed0b5..3d98f2193fc 100644 --- a/java/com.sap.sailing.server.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.server.test/META-INF/MANIFEST.MF @@ -19,7 +19,7 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.domain.test, org.mockito.mockito-core;bundle-version="1.10.14", com.sap.sse.common, - org.eclipse.jetty.server;bundle-version="9.2.2", + org.eclipse.jetty.server;bundle-version="9.4.8", com.sap.sailing.domain.shared.android, com.sap.sailing.domain.racelogtrackingadapter.testsupport, com.sap.sailing.server.testsupport diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/statistics/StatisticsTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/statistics/StatisticsTest.java index b23212f6999..46bbae978ea 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/statistics/StatisticsTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/statistics/StatisticsTest.java @@ -38,6 +38,8 @@ import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.RaceListener; +import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; import com.sap.sailing.domain.tracking.impl.EmptyWindStore; @@ -82,14 +84,28 @@ public class StatisticsTest { regatta.addTrackedRace(trackedRace); } - private TrackedRaceStatisticsCacheImpl getStatisticsCacheWithRegattaAdded() { + private TrackedRaceStatisticsCacheImpl getStatisticsCacheWithRegattaAdded() throws Exception { TrackedRaceStatisticsCacheImpl trackedRaceStatisticsCache = new TrackedRaceStatisticsCacheImpl(); trackedRaceStatisticsCache.regattaAdded(regatta); + + // The following lines ensure that we received all necessary events from the TrackedRegatta + // When removing a listener we get a Future whose get method will block until all previously triggered events are processed. + RaceListener raceListener = new RaceListener() { + @Override + public void raceRemoved(TrackedRace trackedRace) { + } + @Override + public void raceAdded(TrackedRace trackedRace) { + } + }; + regatta.addRaceListener(raceListener); + regatta.removeRaceListener(raceListener).get(); + return trackedRaceStatisticsCache; } @Test - public void testTrackedRaceStatisticsCacheWithoutFixes() { + public void testTrackedRaceStatisticsCacheWithoutFixes() throws Exception { TrackedRaceStatisticsCacheImpl trackedRaceStatisticsCache = getStatisticsCacheWithRegattaAdded(); TrackedRaceStatistics statisticsForRace = trackedRaceStatisticsCache.getStatisticsWaitingForLatest(trackedRace); @@ -99,7 +115,7 @@ public class StatisticsTest { } @Test - public void testTrackedRaceStatisticsCacheWithFixes() { + public void testTrackedRaceStatisticsCacheWithFixes() throws Exception { trackedRace.recordFix(comp, new GPSFixMovingImpl(new DegreePosition(49.295970, 8.638958), new MillisecondsTimePoint(START_OF_RACE), new KilometersPerHourSpeedWithBearingImpl(1, new DegreeBearingImpl(100)))); @@ -159,5 +175,4 @@ public class StatisticsTest { double distanceInMeters = statisticsForRace.getDistanceTraveled().getMeters(); assertTrue(distanceInMeters > 53 && distanceInMeters < 57); } - } diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/ApplyScoresFromRaceLogTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/ApplyScoresFromRaceLogTest.java index 939c618ad4e..a1851511ceb 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/ApplyScoresFromRaceLogTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/ApplyScoresFromRaceLogTest.java @@ -112,7 +112,7 @@ public class ApplyScoresFromRaceLogTest extends LeaderboardScoringAndRankingTest f1RaceState.setFinishPositioningListChanged(now, results); final List rankedCompetitorsBeforeApplying = leaderboard.getCompetitorsFromBestToWorst(later); assertEquals(competitors, rankedCompetitorsBeforeApplying); // no effects of preliminary results list yet - f1RaceState.setFinishPositioningConfirmed(now); + f1RaceState.setFinishPositioningConfirmed(now, results); final Function expectedPoints = (c)->scores.get(c)==null?(mprs.get(c) == null || mprs.get(c) == MaxPointsReason.NONE ? competitors.indexOf(c)+1 : competitors.size()+1):scores.get(c); @@ -159,7 +159,7 @@ public class ApplyScoresFromRaceLogTest extends LeaderboardScoringAndRankingTest f1RaceState.setFinishPositioningListChanged(now, results); final List rankedCompetitorsBeforeApplying = leaderboard.getCompetitorsFromBestToWorst(later); assertEquals(competitors, rankedCompetitorsBeforeApplying); // no effects of preliminary results list yet - f1RaceState.setFinishPositioningConfirmed(now); + f1RaceState.setFinishPositioningConfirmed(now, results); assertScoreCorrections(leaderboard, f1Column, competitors.get(0), MaxPointsReason.DNF, 0, /* score is corrected */ false, later); assertScoreCorrections(leaderboard, f1Column, competitors.get(1), MaxPointsReason.OCS, 0, /* score is corrected */ false, later); @@ -201,7 +201,7 @@ public class ApplyScoresFromRaceLogTest extends LeaderboardScoringAndRankingTest f1RaceState.setFinishPositioningListChanged(now, results); final List rankedCompetitorsBeforeApplying = leaderboard.getCompetitorsFromBestToWorst(later); assertEquals(competitors, rankedCompetitorsBeforeApplying); // no effects of preliminary results list yet - f1RaceState.setFinishPositioningConfirmed(now); + f1RaceState.setFinishPositioningConfirmed(now, results); // validate that it arrived in leaderboard assertScoreCorrections(leaderboard, f1Column, competitors.get(0), MaxPointsReason.OCS, numberOfCompetitors+1, /* score is corrected */ false, later); assertScoreCorrections(leaderboard, f1Column, competitors.get(1), MaxPointsReason.OCS, numberOfCompetitors+1, /* score is corrected */ false, later); @@ -210,14 +210,14 @@ public class ApplyScoresFromRaceLogTest extends LeaderboardScoringAndRankingTest final CompetitorResults resultsWithOCSCleared = new CompetitorResultsImpl(); setResultForCompetitor(competitors.get(1), /* one-based rank */ 0, resultsWithOCSCleared, MaxPointsReason.OCS, /* explicit score */ null); f1RaceState.setFinishPositioningListChanged(later, resultsWithOCSCleared); - f1RaceState.setFinishPositioningConfirmed(later); + f1RaceState.setFinishPositioningConfirmed(later, resultsWithOCSCleared); // validate that it did not get cleared in leaderboard (changed by bug 4167): assertScoreCorrections(leaderboard, f1Column, competitors.get(1), MaxPointsReason.OCS, numberOfCompetitors+1, /* score is corrected */ false, later); assertScoreCorrections(leaderboard, f1Column, competitors.get(0), MaxPointsReason.OCS, numberOfCompetitors+1, /* score is corrected */ false, later); // ranking first, one point in low-point scheme // now explicitly overwrite with a 0/null/null result, validating that this will clear the corrections for competitor 0: setResultForCompetitor(competitors.get(0), /* one-based rank */ 0, resultsWithOCSCleared, /* maxPointsReason */ null, /* explicit score */ null); f1RaceState.setFinishPositioningListChanged(yetLater, resultsWithOCSCleared); - f1RaceState.setFinishPositioningConfirmed(yetLater); + f1RaceState.setFinishPositioningConfirmed(yetLater, resultsWithOCSCleared); assertScoreCorrections(leaderboard, f1Column, competitors.get(1), MaxPointsReason.OCS, numberOfCompetitors+1, /* score is corrected */ false, yetLater); assertScoreCorrections(leaderboard, f1Column, competitors.get(0), MaxPointsReason.NONE, 1, /* score is corrected */ false, yetLater); // ranking first, one point in low-point scheme } 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 347ccc9c0a2..50ddbbe9d46 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy OSX).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy OSX).launch @@ -30,9 +30,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (No Proxy).launch b/java/com.sap.sailing.server/SailingServer (No Proxy).launch index 0a323bd78fe..9c666ad1841 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy).launch @@ -32,9 +32,9 @@ - + - + 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 8b6711543ed..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 @@ -29,9 +29,9 @@ - + - + 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 73eaccf695b..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 @@ -23,10 +23,10 @@ - + - + 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 4ad15da213b..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 @@ -23,10 +23,10 @@ - + - + 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 caf29b6304b..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 @@ -23,10 +23,10 @@ - + - + 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 dd0241b2c75..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 @@ -25,10 +25,10 @@ - + - + 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 bbe59eca851..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 @@ -23,10 +23,10 @@ - + - + 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 a3578a7ed6a..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 @@ -23,9 +23,9 @@ - + - + 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 7cfe423142f..5979fcc875d 100644 --- a/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (No Proxy, winddbTest).launch @@ -29,9 +29,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy).launch b/java/com.sap.sailing.server/SailingServer (Proxy).launch index dd36f97dae2..5ef2a34f8ae 100644 --- a/java/com.sap.sailing.server/SailingServer (Proxy).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy).launch @@ -32,10 +32,10 @@ - + - + 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 5c308b7e960..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 @@ -23,10 +23,10 @@ - + - + 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 098014ae706..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 @@ -23,9 +23,9 @@ - + - + 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 73d478f2cff..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 @@ -22,9 +22,9 @@ - + - + diff --git a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch index 67539b2c380..23bddcb66ed 100644 --- a/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch +++ b/java/com.sap.sailing.server/SailingServer (Proxy, winddbTest).launch @@ -24,10 +24,10 @@ - + - + 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.base/site.xml b/java/com.sap.sailing.targetplatform.base/site.xml index 176c5f65dda..d8787556f2f 100644 --- a/java/com.sap.sailing.targetplatform.base/site.xml +++ b/java/com.sap.sailing.targetplatform.base/site.xml @@ -9,9 +9,6 @@ - - - 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 fe0bcdef426..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 @@ - + @@ -7,11 +7,7 @@ - - - - - + @@ -90,6 +86,10 @@ + + + + @@ -165,8 +165,6 @@ - - @@ -174,23 +172,12 @@ - - - - - - - - - - - @@ -200,18 +187,7 @@ - - - - - - - - - - - @@ -224,14 +200,6 @@ - - - - - - - - diff --git a/java/com.sap.sailing.www/.classpath b/java/com.sap.sailing.www/.classpath index 6de27b66c3b..01836c4842f 100644 --- a/java/com.sap.sailing.www/.classpath +++ b/java/com.sap.sailing.www/.classpath @@ -1,5 +1,6 @@ + diff --git a/java/com.sap.sailing.www/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.www/.settings/org.eclipse.core.resources.prefs new file mode 100755 index 00000000000..8b0e2a308d5 --- /dev/null +++ b/java/com.sap.sailing.www/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/release_notes_admin.html=UTF-8 diff --git a/java/com.sap.sailing.www/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.www/.settings/org.eclipse.jdt.core.prefs index deec0d0e770..a698e59674f 100644 --- a/java/com.sap.sailing.www/.settings/org.eclipse.jdt.core.prefs +++ b/java/com.sap.sailing.www/.settings/org.eclipse.jdt.core.prefs @@ -1,8 +1,12 @@ -#Thu Jan 19 09:59:11 CET 2012 eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sailing.www/META-INF/MANIFEST.MF b/java/com.sap.sailing.www/META-INF/MANIFEST.MF index de904e852a3..ce7ab905e0b 100644 --- a/java/com.sap.sailing.www/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.www/META-INF/MANIFEST.MF @@ -6,6 +6,7 @@ Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Web-ContextPath: / -Import-Package: javax.servlet;version="2.6.0", - javax.servlet.http;version="2.6.0" -Bundle-ClassPath: WEB-INF/classes/ +Import-Package: javax.servlet;version="3.1.0", + javax.servlet.http;version="3.1.0" +Bundle-ClassPath: . +Bundle-ActivationPolicy: lazy diff --git a/java/com.sap.sailing.www/WEB-INF/web.xml b/java/com.sap.sailing.www/WEB-INF/web.xml index 340b9e60850..d2ef9d1b532 100644 --- a/java/com.sap.sailing.www/WEB-INF/web.xml +++ b/java/com.sap.sailing.www/WEB-INF/web.xml @@ -9,9 +9,18 @@ org.eclipse.jetty.servlet.DefaultServlet - useFileMappedBuffer - false - + useFileMappedBuffer + false + + + CORSFilter + com.sap.sailing.www.CORSFilter + + + + CORSFilter + /fonts/* + diff --git a/java/com.sap.sailing.www/build.properties b/java/com.sap.sailing.www/build.properties index d12c66365ef..39b6b517605 100644 --- a/java/com.sap.sailing.www/build.properties +++ b/java/com.sap.sailing.www/build.properties @@ -1,4 +1,7 @@ +source.. = src/ +output.. = bin/ bin.includes = META-INF/,\ + .,\ WEB-INF/web.xml,\ index.html,\ release_notes.css,\ @@ -18,4 +21,5 @@ bin.includes = META-INF/,\ EULA_Android_BuoyPinger.html,\ EULA_Android_SailingBoatTracker.html,\ EULA_Android_SailingRaceManager.html,\ - EULA_iOS_SailingBoatTracker.html \ No newline at end of file + EULA_iOS_SailingBoatTracker.html +jre.compilation.profile = JavaSE-1.8 diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index 6883aba4ca2..c34c904bc85 100755 --- a/java/com.sap.sailing.www/release_notes_admin.html +++ b/java/com.sap.sailing.www/release_notes_admin.html @@ -22,21 +22,47 @@

    Release Notes - Administration Console

    +

    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. +

    • +
    +

    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

    • 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 an 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.sailing.www/src/com/sap/sailing/www/CORSFilter.java b/java/com.sap.sailing.www/src/com/sap/sailing/www/CORSFilter.java new file mode 100644 index 00000000000..7b14a17f0c4 --- /dev/null +++ b/java/com.sap.sailing.www/src/com/sap/sailing/www/CORSFilter.java @@ -0,0 +1,30 @@ +package com.sap.sailing.www; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; + +public class CORSFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + final HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.setHeader("Access-Control-Allow-Origin", "*"); + chain.doFilter(request, response); + } + + @Override + public void destroy() { + } +} 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 bb941469664..7d0e5d0a476 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 @@ -780,4 +780,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.datamining/META-INF/MANIFEST.MF b/java/com.sap.sse.datamining/META-INF/MANIFEST.MF index 2add8ffac32..d4a9e064d7b 100644 --- a/java/com.sap.sse.datamining/META-INF/MANIFEST.MF +++ b/java/com.sap.sse.datamining/META-INF/MANIFEST.MF @@ -5,8 +5,7 @@ Bundle-SymbolicName: com.sap.sse.datamining Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.8 -Import-Package: org.eclipse.jetty.util;version="9.2.2", - org.osgi.framework;version="1.7.0" +Import-Package: org.osgi.framework;version="1.7.0" Export-Package: com.sap.sse.datamining, com.sap.sse.datamining.components, com.sap.sse.datamining.components.management, diff --git a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedDataCountDistinctAggregationProcessor.java b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedDataCountDistinctAggregationProcessor.java index 70f9c46942c..51d93364c19 100755 --- a/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedDataCountDistinctAggregationProcessor.java +++ b/java/com.sap.sse.datamining/src/com/sap/sse/datamining/impl/components/aggregators/ParallelGroupedDataCountDistinctAggregationProcessor.java @@ -8,8 +8,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; -import org.eclipse.jetty.util.ConcurrentHashSet; - import com.sap.sse.common.Util; import com.sap.sse.datamining.components.AdditionalResultDataBuilder; import com.sap.sse.datamining.components.AggregationProcessorDefinition; @@ -55,7 +53,7 @@ public class ParallelGroupedDataCountDistinctAggregationProcessor Util.addToValueSet(countMap, key, element.getDataEntry(), new Util.ValueSetConstructor() { @Override public Set createSet() { - return new ConcurrentHashSet(); + return ConcurrentHashMap.newKeySet(); } }); } diff --git a/java/com.sap.sse.feature.runtime/feature.xml b/java/com.sap.sse.feature.runtime/feature.xml index 28e87763f9f..a7faaa74964 100644 --- a/java/com.sap.sse.feature.runtime/feature.xml +++ b/java/com.sap.sse.feature.runtime/feature.xml @@ -21,14 +21,7 @@ id="org.eclipse.jetty.osgi.boot" download-size="0" install-size="0" - version="9.2.2.v20140723" - unpack="false"/> - - - - - - + + 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 4d2b37b513f..02bfdddb31d 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) @@ -93,6 +94,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.security.ui/Security UI sdm.launch b/java/com.sap.sse.security.ui/Security UI sdm.launch index 633ad54780f..db91c43b97e 100644 --- a/java/com.sap.sse.security.ui/Security UI sdm.launch +++ b/java/com.sap.sse.security.ui/Security UI sdm.launch @@ -48,7 +48,7 @@ - + diff --git a/java/com.sap.sse.security.ui/Security UI.launch b/java/com.sap.sse.security.ui/Security UI.launch index 607b86530a3..61ec9497ff6 100644 --- a/java/com.sap.sse.security.ui/Security UI.launch +++ b/java/com.sap.sse.security.ui/Security UI.launch @@ -47,7 +47,7 @@ - + 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.sap.sse/src/com/sap/sse/util/apachelog/UniqueIPsPerReferrer.java b/java/com.sap.sse/src/com/sap/sse/util/apachelog/UniqueIPsPerReferrer.java index 7c7f17258db..0b15d0fc8e0 100755 --- a/java/com.sap.sse/src/com/sap/sse/util/apachelog/UniqueIPsPerReferrer.java +++ b/java/com.sap.sse/src/com/sap/sse/util/apachelog/UniqueIPsPerReferrer.java @@ -52,6 +52,7 @@ public class UniqueIPsPerReferrer { private static final Logger logger = Logger.getLogger(UniqueIPsPerReferrer.class.getName()); private static final String HOSTNAME_FILE_EXTENSION = ".ips"; private static final String UNIQUE_SUFFIX = ".unique"; + private static final String MONTH_AND_YEAR_TOTALS_SUFFIX = "_totals"; private static final char YEAR_MONTH_SEPARATOR = '_'; private final File CACHE; private final File VISITED_FILES; @@ -160,6 +161,8 @@ public class UniqueIPsPerReferrer { private void computePerMonthPerYearPerEventAndTotals() throws IOException, ParseException { final Map monthFileWritersPerFileBaseName = new HashMap<>(); final Map yearFileWritersPerFileBaseName = new HashMap<>(); + final Map monthTotalsFileWritersPerFileBaseName = new HashMap<>(); + final Map yearTotalsFileWritersPerFileBaseName = new HashMap<>(); final Writer eventTotalsWriter = new OutputStreamWriter(new GZIPOutputStream(new FileOutputStream(EVENTTOTALS))); final Writer totalsWriter = new OutputStreamWriter(new GZIPOutputStream(new FileOutputStream(TOTALS))); for (final File perHostnameFile : getAllHostnameFiles()) { @@ -170,16 +173,22 @@ public class UniqueIPsPerReferrer { String line; while ((line=r.readLine()) != null) { final PerHostnameEntry entry = new PerHostnameEntry(line); + final String combinedLine = hostname+" "+line+"\n"; if (entry.getYear() != 0) { final String monthFileBasename = String.format("%04d"+YEAR_MONTH_SEPARATOR+"%02d", entry.getYear(), entry.getZeroBasedMonth()+1); final Writer monthFileWriter = getFileWriter(monthFileBasename, monthFileWritersPerFileBaseName, this::getMonthFileCompressed, /* gzipCompressed */ true, /* append */ false); - final String combinedLine = hostname+" "+line+"\n"; monthFileWriter.write(combinedLine); + final String monthTotalsFileBasename = monthFileBasename+MONTH_AND_YEAR_TOTALS_SUFFIX; + final Writer monthTotalsFileWriter = getFileWriter(monthTotalsFileBasename, monthTotalsFileWritersPerFileBaseName, this::getMonthFileCompressed, /* gzipCompressed */ true, /* append */ false); + monthTotalsFileWriter.write(line+"\n"); final String yearFileBasename = String.format("%04d", entry.getYear()); final Writer yearFileWriter = getFileWriter(yearFileBasename, yearFileWritersPerFileBaseName, this::getYearFileCompressed, /* gzipCompressed */ true, /* append */ false); yearFileWriter.write(combinedLine); + final String yearTotalsFileBasename = yearFileBasename+MONTH_AND_YEAR_TOTALS_SUFFIX; + final Writer yearTotalsFileWriter = getFileWriter(yearTotalsFileBasename, yearTotalsFileWritersPerFileBaseName, this::getYearFileCompressed, /* gzipCompressed */ true, /* append */ false); + yearTotalsFileWriter.write(line+"\n"); } - eventTotalsWriter.write(hostname+" "+line+"\n"); + eventTotalsWriter.write(combinedLine); totalsWriter.write(line+"\n"); } } finally { @@ -192,6 +201,12 @@ public class UniqueIPsPerReferrer { for (final Writer yearFileWriter : yearFileWritersPerFileBaseName.values()) { yearFileWriter.close(); } + for (final Writer monthTotalsFileWriter : monthTotalsFileWritersPerFileBaseName.values()) { + monthTotalsFileWriter.close(); + } + for (final Writer yearTotalsFileWriter : yearTotalsFileWritersPerFileBaseName.values()) { + yearTotalsFileWriter.close(); + } eventTotalsWriter.close(); totalsWriter.close(); } @@ -249,9 +264,21 @@ public class UniqueIPsPerReferrer { } private void countTotalHits() throws IOException { - logger.info("Counting total number of unique visitors"); - final FileWriter totalsCountWriter = new FileWriter(TOTALS_UNIQUE); - final Set uniqueTotalLines = readDistinctLinesFromGzippedFile(TOTALS); + logger.info("Counting total numbers of unique visitors for all, months, and years"); + countUniqueTotals(TOTALS, TOTALS_UNIQUE); + for (final File monthGzippedTotals : MONTHS.listFiles((dir, filename)->filename.endsWith(MONTH_AND_YEAR_TOTALS_SUFFIX+GZIP_EXTENSION))) { + countUniqueTotals(monthGzippedTotals, new File(monthGzippedTotals.getParentFile(), + monthGzippedTotals.getName().substring(0, monthGzippedTotals.getName().length()-GZIP_EXTENSION.length())+UNIQUE_SUFFIX)); + } + for (final File yearGzippedTotals : YEARS.listFiles((dir, filename)->filename.endsWith(MONTH_AND_YEAR_TOTALS_SUFFIX+GZIP_EXTENSION))) { + countUniqueTotals(yearGzippedTotals, new File(yearGzippedTotals.getParentFile(), + yearGzippedTotals.getName().substring(0, yearGzippedTotals.getName().length()-GZIP_EXTENSION.length())+UNIQUE_SUFFIX)); + } + } + + private void countUniqueTotals(File gzippedEntriesWithoutHostname, File uniqueCount) throws IOException { + final FileWriter totalsCountWriter = new FileWriter(uniqueCount); + final Set uniqueTotalLines = readDistinctLinesFromGzippedFile(gzippedEntriesWithoutHostname); totalsCountWriter.write(uniqueTotalLines.size()+"\n"); totalsCountWriter.close(); } diff --git a/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF b/java/com.tractrac.clientmodule/META-INF/MANIFEST.MF index 6da9ddc6be7..b1b13765e20 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 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/configuration/jetty/etc/jetty-deployer.xml b/java/target/configuration/jetty/etc/jetty-deployer.xml index c3e40388e2f..1f322b2bdd2 100644 --- a/java/target/configuration/jetty/etc/jetty-deployer.xml +++ b/java/target/configuration/jetty/etc/jetty-deployer.xml @@ -14,23 +14,6 @@ - false - - - - - - - - - - - - - - - - diff --git a/java/target/configuration/jetty/etc/jetty-http.xml b/java/target/configuration/jetty/etc/jetty-http.xml new file mode 100644 index 00000000000..d6dc7a76785 --- /dev/null +++ b/java/target/configuration/jetty/etc/jetty-http.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/target/configuration/jetty/etc/jetty-selector.xml b/java/target/configuration/jetty/etc/jetty-selector.xml deleted file mode 100644 index 4502e101193..00000000000 --- a/java/target/configuration/jetty/etc/jetty-selector.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/java/target/configuration/jetty/etc/jetty.xml b/java/target/configuration/jetty/etc/jetty.xml index 503d02032a0..34ceb7ca681 100644 --- a/java/target/configuration/jetty/etc/jetty.xml +++ b/java/target/configuration/jetty/etc/jetty.xml @@ -110,7 +110,6 @@ SailingServerRealm /etc/realm.properties - 0 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/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_18dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_18dp.png new file mode 100644 index 00000000000..b13df8025eb Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_18dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_24dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_24dp.png new file mode 100644 index 00000000000..f883634807d Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_24dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_36dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_36dp.png new file mode 100644 index 00000000000..d87b60ae8ca Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_36dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_48dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_48dp.png new file mode 100644 index 00000000000..6387d674d3e Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_48dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_64dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_64dp.png new file mode 100644 index 00000000000..c1f188975a1 Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_64dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_96dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_96dp.png new file mode 100644 index 00000000000..53517c18f4f Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-hdpi/ic_warning_red_96dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_18dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_18dp.png new file mode 100644 index 00000000000..6b152355746 Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_18dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_24dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_24dp.png new file mode 100644 index 00000000000..09565344ae1 Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_24dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_36dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_36dp.png new file mode 100644 index 00000000000..f883634807d Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_36dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_48dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_48dp.png new file mode 100644 index 00000000000..0800ad9f8a8 Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_48dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_64dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_64dp.png new file mode 100644 index 00000000000..d4ab6684789 Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_64dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_96dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_96dp.png new file mode 100644 index 00000000000..c1f188975a1 Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-mdpi/ic_warning_red_96dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-sw720dp/ic_warning_red.xml b/mobile/com.sap.sailing.racecommittee.app/res/drawable-sw720dp/ic_warning_red.xml new file mode 100644 index 00000000000..cf27c617aab --- /dev/null +++ b/mobile/com.sap.sailing.racecommittee.app/res/drawable-sw720dp/ic_warning_red.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-sw720dp/ic_warning_red_small.xml b/mobile/com.sap.sailing.racecommittee.app/res/drawable-sw720dp/ic_warning_red_small.xml new file mode 100644 index 00000000000..6df76d19a78 --- /dev/null +++ b/mobile/com.sap.sailing.racecommittee.app/res/drawable-sw720dp/ic_warning_red_small.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_18dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_18dp.png new file mode 100644 index 00000000000..f883634807d Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_18dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_24dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_24dp.png new file mode 100644 index 00000000000..0800ad9f8a8 Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_24dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_36dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_36dp.png new file mode 100644 index 00000000000..6387d674d3e Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_36dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_48dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_48dp.png new file mode 100644 index 00000000000..c1f188975a1 Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_48dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_64dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_64dp.png new file mode 100644 index 00000000000..fcb54ee5b8f Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_64dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_96dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_96dp.png new file mode 100644 index 00000000000..fdd51d6c0be Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xhdpi/ic_warning_red_96dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_18dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_18dp.png new file mode 100644 index 00000000000..d87b60ae8ca Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_18dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_24dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_24dp.png new file mode 100644 index 00000000000..6387d674d3e Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_24dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_36dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_36dp.png new file mode 100644 index 00000000000..5fd5031dffc Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_36dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_48dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_48dp.png new file mode 100644 index 00000000000..53517c18f4f Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_48dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_64dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_64dp.png new file mode 100644 index 00000000000..fdd51d6c0be Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_64dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_96dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_96dp.png new file mode 100644 index 00000000000..5cc1d99a115 Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxhdpi/ic_warning_red_96dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_18dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_18dp.png new file mode 100644 index 00000000000..6387d674d3e Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_18dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_24dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_24dp.png new file mode 100644 index 00000000000..c1f188975a1 Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_24dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_36dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_36dp.png new file mode 100644 index 00000000000..53517c18f4f Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_36dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_48dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_48dp.png new file mode 100644 index 00000000000..fdd51d6c0be Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_48dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_64dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_64dp.png new file mode 100644 index 00000000000..68acaa46d6a Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_64dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_96dp.png b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_96dp.png new file mode 100644 index 00000000000..02daf260c0b Binary files /dev/null and b/mobile/com.sap.sailing.racecommittee.app/res/drawable-xxxhdpi/ic_warning_red_96dp.png differ diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable/ic_warning_red.xml b/mobile/com.sap.sailing.racecommittee.app/res/drawable/ic_warning_red.xml new file mode 100644 index 00000000000..6df76d19a78 --- /dev/null +++ b/mobile/com.sap.sailing.racecommittee.app/res/drawable/ic_warning_red.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/res/drawable/ic_warning_red_small.xml b/mobile/com.sap.sailing.racecommittee.app/res/drawable/ic_warning_red_small.xml new file mode 100644 index 00000000000..b7f82fdb9d6 --- /dev/null +++ b/mobile/com.sap.sailing.racecommittee.app/res/drawable/ic_warning_red_small.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/panel_button_setup.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/panel_button_setup.xml index 52cb32f10da..9ff20880824 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/panel_button_setup.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/panel_button_setup.xml @@ -65,7 +65,7 @@ android:layout_alignRight="@id/panel_image" android:visibility="gone" tools:src="@drawable/ic_warning_yellow" - tools:visibility="gone" /> + tools:visibility="visible" /> + android:visibility="gone" + tools:visibility="visible" /> + android:src="@drawable/ic_warning_yellow" + android:visibility="gone" + tools:visibility="visible" /> + android:layout_height="match_parent" + tools:ignore="MissingId" /> \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_main_xlarge.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_main_xlarge.xml index da6adccfead..bd1d8f8189c 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_main_xlarge.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_main_xlarge.xml @@ -1,20 +1,20 @@ - + android:layout_height="match_parent" /> + android:layout_alignParentRight="true" /> + android:orientation="vertical" /> + android:layout_weight="3" /> + android:layout_height="@dimen/side_button" /> + android:layout_height="match_parent" /> + android:layout_height="match_parent" + tools:ignore="MissingId" /> + diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_penalty_row_item.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_penalty_row_item.xml index 6d6691a75ae..992c1c896d5 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_penalty_row_item.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_penalty_row_item.xml @@ -25,6 +25,14 @@ android:layout_marginLeft="16dp" android:layout_marginRight="16dp"> + + @@ -54,6 +63,8 @@ android:layout_marginLeft="24dp" android:layout_toLeftOf="@id/item_right" android:layout_toRightOf="@id/item_check" + android:ellipsize="end" + android:maxLines="3" tools:text="ABD 1234 - Angel Dunn" /> diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_tracking_list_competitor_edit_normal.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_tracking_list_competitor_edit_normal.xml index b497b52dd65..5580597fe3e 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_tracking_list_competitor_edit_normal.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_tracking_list_competitor_edit_normal.xml @@ -1,11 +1,30 @@ + + + + + + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingLeft="24dp" + android:paddingRight="24dp"> + + + + + + android:textColor="@color/dialog_color_text" /> + android:spinnerMode="dropdown" /> + android:textColor="@color/dialog_color_text" /> + android:spinnerMode="dropdown" /> + android:textColorHint="@color/dialog_color_text" /> @@ -90,7 +109,7 @@ android:layout_height="wrap_content" android:paddingBottom="8dp" android:text="@string/finishing_time" - android:textColor="@color/dialog_color_text"/> + android:textColor="@color/dialog_color_text" /> + android:textColorHint="@color/dialog_color_text" /> \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_tracking_list_draggable_item.xml b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_tracking_list_draggable_item.xml index 6f0c705ebe5..77ac047cc80 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/layout/race_tracking_list_draggable_item.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/layout/race_tracking_list_draggable_item.xml @@ -29,20 +29,35 @@ android:layout_weight="12" android:orientation="horizontal"> - + android:paddingTop="4dp"> + + + + + Ergebnisliste veröffentlicht %d + Datenänderung + Die Daten der folgenden Teilnehmer haben sich geändert. Bitte überprüfen:\n\n%s + @android:string/ok Keine Verbindung zu SAP Die App konnte keine Verbindung zu SAP Sailing hergestellt.\nPrüfen Sie bitte, ob Sie eine stabile Mobilfunkverbindung haben.\nWollen Sie es erneut versuchen? Wiederholen Beenden + Dieser Teilnehmer wurde an einem anderen Gerät verändert und hat überschneidende Änderungen. Bitte stellen Sie sicher, dass die Daten korrekt sind. + \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml b/mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml index 257f7d6e553..cd8ec47c008 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/values/strings.xml @@ -558,9 +558,14 @@ Results published %d + Data changed + The data of following competitors has changed. Please review:\n\n%s + @android:string/ok Can\'t connect to SAP The App couldn\'t establish a connection to SAP Sailing.\nPlease check if you have a stable mobile connection.\nDo you want to retry? Retry Exit + This competitor was changed from another device and you have changed the same data. Please be sure, that this data is correct now. + \ No newline at end of file diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/CompetitorResultEditableImpl.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/CompetitorResultEditableImpl.java index fbfb53c2b8a..03e6f9674b0 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/CompetitorResultEditableImpl.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/domain/impl/CompetitorResultEditableImpl.java @@ -119,6 +119,17 @@ public class CompetitorResultEditableImpl implements CompetitorResult { this.mMergeState = mergeState; } + public void setValue(CompetitorResult result) { + mCompetitorId = result.getCompetitorId(); + mCompetitorDisplayName = result.getCompetitorDisplayName(); + mOneBasedRank = result.getOneBasedRank(); + mMaxPointsReason = result.getMaxPointsReason(); + mScore = result.getScore(); + mFinishingTime = result.getFinishingTime(); + mComment = result.getComment(); + mMergeState = result.getMergeState(); + } + @Override public boolean equals(Object o) { if (this == o) diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java index a785f811db4..e2576c4cf8a 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/activities/RacingActivity.java @@ -440,6 +440,12 @@ public class RacingActivity extends SessionActivity implements RaceListCallbacks public void onClick(DialogInterface dialog, int which) { ExLog.i(RacingActivity.this, LogEvent.RACE_RESET_YES, mSelectedRace.getId()); ExLog.w(RacingActivity.this, TAG, String.format("Race %s is selected for reset.", mSelectedRace.getId())); + Fragment fragment = getFragmentManager().findFragmentById(R.id.race_content); + if (fragment != null) { + FragmentTransaction transaction = getFragmentManager().beginTransaction(); + transaction.remove(fragment); + transaction.commit(); + } mSelectedRace.getState().setAdvancePass(MillisecondsTimePoint.now()); } }); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/FinishListAdapter.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/FinishListAdapter.java index dbead4d89eb..13154400a4c 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/FinishListAdapter.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/adapters/FinishListAdapter.java @@ -14,10 +14,12 @@ import com.sap.sailing.racecommittee.app.utils.ThemeHelper; import android.content.Context; import android.graphics.drawable.Drawable; +import android.support.v4.content.ContextCompat; import android.support.v4.view.ViewCompat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.TextView; public class FinishListAdapter extends BaseDraggableSwipeAdapter { @@ -61,6 +63,21 @@ public class FinishListAdapter extends BaseDraggableSwipeAdapter { @@ -81,6 +84,21 @@ public class PenaltyAdapter extends RecyclerView.Adapter imple for (final ManagedRace race : mAllRaces) { viewItemsRaces.put(race, new RaceListDataTypeRace(race, mInflater)); final SeriesBase series = race.getSeries(); - boolean hasConflict = race.getState().getConfirmedFinishPositioningList() != null && race.getState().getConfirmedFinishPositioningList().hasConflicts(); + RaceState state = race.getState(); + boolean draft = state.getFinishPositioningList() != null && state.getFinishPositioningList().hasConflicts(); + boolean confirmed = state.getConfirmedFinishPositioningList() != null && state.getConfirmedFinishPositioningList().hasConflicts(); + boolean hasConflict = draft || confirmed; if (!viewItemsSeriesHeaders.containsKey(series)) { viewItemsSeriesHeaders.put(series, new RaceListDataTypeHeader(new RaceGroupSeries(race), mInflater, hasConflict)); } else { @@ -200,10 +203,9 @@ public class ManagedRaceListAdapter extends ArrayAdapter imple holder.race_name.setText(RaceHelper.getReverseRaceFleetName(race.getRace())); RaceState state = race.getRace().getState(); if (state != null) { - CompetitorResults results = state.getConfirmedFinishPositioningList(); - if (results != null) { - holder.warning_sign.setVisibility(results.hasConflicts() ? View.VISIBLE : View.GONE); - } + CompetitorResults draft = state.getFinishPositioningList(); + CompetitorResults confirmed = state.getConfirmedFinishPositioningList(); + holder.warning_sign.setVisibility(((draft != null && draft.hasConflicts()) || confirmed != null && confirmed.hasConflicts()) ? View.VISIBLE : View.GONE); if (state.getStartTime() != null) { int startRes = R.string.race_started; if (state.getFinishedTime() == null) { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/LoginBackdrop.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/LoginBackdrop.java index 67fa87eab27..36936a68b62 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/LoginBackdrop.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/LoginBackdrop.java @@ -1,5 +1,6 @@ package com.sap.sailing.racecommittee.app.ui.fragments; +import java.io.FileNotFoundException; import java.io.IOException; import java.net.MalformedURLException; @@ -378,8 +379,8 @@ public class LoginBackdrop extends Fragment implements BackPressListener { server = null; if (exception instanceof UnauthorizedException) { // wrong credentials (access token) server = exception.getMessage().split("=")[1]; - } - if (exception instanceof IOException) { // connection error + BroadcastManager.getInstance(getActivity()).addIntent(new Intent(AppConstants.INTENT_ACTION_SHOW_LOGIN)); + } else if (exception instanceof IOException && !(exception instanceof FileNotFoundException)) { // connection error AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.AppTheme_AlertDialog); builder.setTitle(R.string.hello_call_error_title); builder.setMessage(R.string.hello_call_error_message); @@ -397,8 +398,6 @@ public class LoginBackdrop extends Fragment implements BackPressListener { }); builder.setCancelable(false); builder.show(); - } else { // unknown error -> show user credential input - BroadcastManager.getInstance(getActivity()).addIntent(new Intent(AppConstants.INTENT_ACTION_SHOW_LOGIN)); } } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java index f03fb6ae7df..841adbad68b 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/RaceListFragment.java @@ -91,21 +91,42 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis private final Set mAllRaces; private BaseRaceStateChangedListener stateListener = new BaseRaceStateChangedListener() { + + @Override + public void onFinishingPositioningsChanged(ReadonlyRaceState state) { + super.onFinishingPositioningsChanged(state); + + update(state); + } + + @Override + public void onFinishingPositionsConfirmed(ReadonlyRaceState state) { + super.onFinishingPositionsConfirmed(state); + + update(state); + } + @Override public void onStartTimeChanged(ReadonlyRaceState state) { + super.onStartTimeChanged(state); + update(state); } @Override public void onStatusChanged(ReadonlyRaceState state) { + super.onStatusChanged(state); + update(state); } - public void update(ReadonlyRaceState state) { + void update(ReadonlyRaceState state) { dataChanged(state); filterChanged(); + updateConflictSign(); } }; + public RaceListFragment() { mFilterMode = FilterMode.ACTIVE; mSelectedRace = null; @@ -424,10 +445,10 @@ public class RaceListFragment extends LoggableFragment implements OnItemClickLis mAdapter.onRacesChanged(); mAdapter.notifyDataSetChanged(); filterChanged(); - showConflictSign(); + updateConflictSign(); } - private void showConflictSign() { + private void updateConflictSign() { boolean showSign = false; for (ManagedRace race : mAllRaces) { CompetitorResults results = race.getState().getFinishPositioningList(); diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java index 90682500dd0..46b40a93408 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/panels/TimePanelFragment.java @@ -247,10 +247,9 @@ public class TimePanelFragment extends BasePanelFragment { } private void checkWarnings(ReadonlyRaceState state) { - CompetitorResults results = state.getConfirmedFinishPositioningList(); - if (results != null) { - mCompetitorList.showAdditionalImage(results.hasConflicts()); - } + CompetitorResults draft = state.getFinishPositioningList(); + CompetitorResults confirmed = state.getConfirmedFinishPositioningList(); + mCompetitorList.showAdditionalImage((draft != null && draft.hasConflicts()) || (confirmed != null && confirmed.hasConflicts())); } private class RaceStateChangedListener extends BaseRaceStateChangedListener { diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/BaseRaceInfoRaceFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/BaseRaceInfoRaceFragment.java index ed723ee0df2..44cac094e89 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/BaseRaceInfoRaceFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/BaseRaceInfoRaceFragment.java @@ -1,10 +1,5 @@ package com.sap.sailing.racecommittee.app.ui.fragments.raceinfo; -import android.app.FragmentTransaction; -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.IdRes; - import com.sap.sailing.android.shared.util.AppUtils; import com.sap.sailing.android.shared.util.BroadcastManager; import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; @@ -12,7 +7,6 @@ import com.sap.sailing.domain.abstractlog.race.state.impl.BaseRaceStateChangedLi import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.RacingProcedure; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.ReadonlyRacingProcedure; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.impl.BaseRacingProcedureChangedListener; -import com.sap.sailing.domain.common.racelog.RaceLogRaceStatus; import com.sap.sailing.racecommittee.app.AppConstants; import com.sap.sailing.racecommittee.app.R; import com.sap.sailing.racecommittee.app.ui.activities.RacingActivity; @@ -21,6 +15,11 @@ import com.sap.sailing.racecommittee.app.ui.fragments.panels.FlagPanelFragment; import com.sap.sailing.racecommittee.app.ui.fragments.panels.SetupPanelFragment; import com.sap.sailing.racecommittee.app.ui.fragments.panels.TimePanelFragment; +import android.app.FragmentTransaction; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.IdRes; + public abstract class BaseRaceInfoRaceFragment extends RaceFragment { private final RaceStateChangedListener mRaceStateChangedListener; @@ -107,6 +106,9 @@ public abstract class BaseRaceInfoRaceFragment mCompetitorResults; - private CompetitorResultsList mLastSend; - private CompetitorResults mLastPublished; + private CompetitorResults mDraftData; + private CompetitorResults mConfirmedData; private View mListButtonLayout; private ImageView mListButton; private HeaderLayout mHeader; + private StateChangeListener mStateChangeListener; public static PenaltyFragment newInstance() { Bundle args = new Bundle(); @@ -106,9 +112,12 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte @TargetApi(Build.VERSION_CODES.KITKAT) public View onCreateView(final LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { View layout = inflater.inflate(R.layout.race_penalty_fragment, container, false); + + mStateChangeListener = new StateChangeListener(this); + mCompetitorResults = new CompetitorResultsList<>(new ArrayList()); - mLastSend = new CompetitorResultsList<>(new ArrayList()); - mLastPublished = new CompetitorResultsImpl(); + mDraftData = new CompetitorResultsImpl(); + mConfirmedData = new CompetitorResultsImpl(); SearchView searchView = ViewHelper.get(layout, R.id.competitor_search); if (searchView != null) { searchView.setSearchTextWatcher(this); @@ -119,6 +128,7 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte mListButtonLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { + sendUnconfirmed(); RaceFragment fragment = TrackingListFragment.newInstance(new Bundle(), 1); int viewId = R.id.race_content; switch (getRaceState().getStatus()) { @@ -190,11 +200,10 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte mPublishButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - confirmData(); + sendConfirmed(); } }); } - setPublishButton(); mEntryCount = ViewHelper.get(layout, R.id.competitor_entry_count); mAdapter = new PenaltyAdapter(getActivity(), this); RecyclerView recyclerView = ViewHelper.get(layout, R.id.competitor_list); @@ -208,6 +217,7 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte private void setReason(String reason) { for (CompetitorResultEditableImpl item : mCompetitorResults) { if (item.isChecked()) { + item.setMergeState(MergeState.OK); item.setMaxPointsReason(MaxPointsReason.valueOf(reason)); item.setChecked(false); item.setDirty(true); @@ -220,6 +230,7 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); + if (mPenaltyDropDown != null) { int selection = mPenaltyAdapter.getPosition(MaxPointsReason.OCS.name()); RacingProcedure procedure = getRaceState().getRacingProcedure(); @@ -273,25 +284,40 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte mListButtonLayout.setVisibility(View.GONE); } } + initLocalData(); + loadCompetitors(); + } + private void initLocalData() { + mDraftData.clear(); if (getRaceState().getFinishPositioningList() != null) { for (CompetitorResult item : getRaceState().getFinishPositioningList()) { - mLastSend.add(new CompetitorResultEditableImpl(item)); + mDraftData.add(new CompetitorResultImpl(item.getCompetitorId(), item.getCompetitorDisplayName(), item.getOneBasedRank(), item + .getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item.getComment(), item.getMergeState())); } } + + initConfirmedData(); + } + + private void initConfirmedData() { + mConfirmedData.clear(); if (getRaceState().getConfirmedFinishPositioningList() != null) { for (CompetitorResult item : getRaceState().getConfirmedFinishPositioningList()) { - mLastPublished.add(new CompetitorResultImpl(item.getCompetitorId(), item.getCompetitorDisplayName(), item.getOneBasedRank(), item - .getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item.getComment(), MergeState.OK)); // TODO handle merge state + mConfirmedData.add(new CompetitorResultImpl(item.getCompetitorId(), item.getCompetitorDisplayName(), item.getOneBasedRank(), item + .getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item.getComment(), item.getMergeState())); } } - loadCompetitors(); } @Override public void onStart() { super.onStart(); + if (getRace() != null && getRaceState() != null) { + getRaceState().addChangedListener(mStateChangeListener); + } + Intent intent = new Intent(AppConstants.INTENT_ACTION_ON_LIFECYCLE); intent.putExtra(AppConstants.INTENT_ACTION_EXTRA_LIFECYCLE, AppConstants.INTENT_ACTION_EXTRA_START); intent.putExtra(AppConstants.INTENT_ACTION_EXTRA, AppConstants.INTENT_ACTION_TOGGLE_COMPETITOR); @@ -302,6 +328,10 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte public void onStop() { super.onStop(); + if (getRace() != null && getRaceState() != null) { + getRaceState().removeChangedListener(mStateChangeListener); + } + boolean dirty = false; for (CompetitorResultEditableImpl item : mCompetitorResults) { if (item.isDirty()) { @@ -320,18 +350,18 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte } private void sendUnconfirmed() { - CompetitorResults results = getCompetitorResultsDiff(); - getRaceState().setFinishPositioningListChanged(MillisecondsTimePoint.now(), results); - for (CompetitorResultEditableImpl item : mCompetitorResults) { - item.setDirty(false); + CompetitorResultsImpl results = getCompetitorResultsDiff(mDraftData); + if (results.size() > 0) { + getRaceState().setFinishPositioningListChanged(MillisecondsTimePoint.now(), results); } } - private void confirmData() { - sendUnconfirmed(); - getRaceState().setFinishPositioningConfirmed(MillisecondsTimePoint.now()); + private void sendConfirmed() { + CompetitorResultsImpl results = getCompetitorResultsDiff(mDraftData); + getRaceState().setFinishPositioningConfirmed(MillisecondsTimePoint.now(), results); Toast.makeText(getActivity(), R.string.publish_clicked, Toast.LENGTH_SHORT).show(); - loadCompetitors(); + initLocalData(); + setPublishButton(); } private String[] getAllMaxPointsReasons() { @@ -393,7 +423,7 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte } name += " - " + item.getName(); CompetitorResult result = new CompetitorResultImpl(item.getId(), name, 0, MaxPointsReason.NONE, - /* score */ null, /* finishingTime */ null, /* comment */ null, MergeState.OK); // TODO handle merge state + /* score */ null, /* finishingTime */ null, /* comment */ null, MergeState.OK); mCompetitorResults.add(new CompetitorResultEditableImpl(result)); } if (getRaceState() != null && getRaceState().getFinishPositioningList() != null) { // mix with finish position list @@ -466,18 +496,152 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte setPublishButton(); } + private void mergeData(CompetitorResults results, boolean checkDirty) { + Map changedCompetitor = new HashMap<>(); + for (CompetitorResult result : results) { + CompetitorResultEditableImpl item = null; + CompetitorResult draft = null; + for (CompetitorResultEditableImpl edited : mCompetitorResults) { + if (result.getCompetitorId().equals(edited.getCompetitorId())) { + item = edited; + break; + } + } + for (CompetitorResult saved : mDraftData) { + if (result.getCompetitorId().equals(saved.getCompetitorId())) { + draft = saved; + break; + } + } + if (item != null) { // result is in list + if (checkDirty & item.isDirty()) { + // check max point reason + if (!item.getMaxPointsReason().equals(result.getMaxPointsReason())) { + if (draft != null) { + if (item.getMaxPointsReason().equals(draft.getMaxPointsReason())) { + setMergeState(item, MergeState.WARNING); + } else { + setMergeState(item, MergeState.ERROR); + } + } else { + setMergeState(item, MergeState.ERROR); + } + item.setMaxPointsReason(result.getMaxPointsReason()); + changedCompetitor.put(item.getCompetitorId(), item.getCompetitorDisplayName()); + } + + // check score + if (item.getScore() != null) { + if (!item.getScore().equals(result.getScore())) { + if (draft != null) { + if (item.getScore().equals(draft.getScore())) { + setMergeState(item, MergeState.WARNING); + } else { + setMergeState(item, MergeState.ERROR); + } + } else { + setMergeState(item, MergeState.ERROR); + } + item.setScore(result.getScore()); + changedCompetitor.put(item.getCompetitorId(), item.getCompetitorDisplayName()); + } + } else if (result.getScore() != null) { + setMergeState(item, MergeState.ERROR); + item.setScore(result.getScore()); + changedCompetitor.put(item.getCompetitorId(), item.getCompetitorDisplayName()); + } + + // check score + if (item.getComment() != null) { + if (!item.getComment().equals(result.getComment())) { + if (draft != null) { + if (item.getComment().equals(draft.getComment())) { + setMergeState(item, MergeState.WARNING); + } else { + setMergeState(item, MergeState.ERROR); + } + } else { + setMergeState(item, MergeState.ERROR); + } + item.setComment(item.getComment() + " ## " + result.getComment()); + changedCompetitor.put(item.getCompetitorId(), item.getCompetitorDisplayName()); + } + } else if (result.getComment() != null) { + setMergeState(item, MergeState.ERROR); + item.setComment(result.getComment()); + changedCompetitor.put(item.getCompetitorId(), item.getCompetitorDisplayName()); + } + + // check merge state + if (!item.getMergeState().equals(result.getMergeState())) { + setMergeState(item, result.getMergeState()); + changedCompetitor.put(item.getCompetitorId(), item.getCompetitorDisplayName()); + } + } else { + item.setValue(result); + } + } else { // unknown result, so it will be added + mCompetitorResults.add(new CompetitorResultEditableImpl(result)); + } + } + mAdapter.notifyDataSetChanged(); + setPublishButton(); + if (changedCompetitor.size() > 0) { // show message to user + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.AppTheme_AlertDialog); + builder.setTitle(R.string.refresh_title); + StringBuilder string = new StringBuilder(1024); + for (String competitor : changedCompetitor.values()) { + string.append(competitor); + string.append("\n"); + } + builder.setMessage(getString(R.string.refresh_message, string.toString())); + builder.setPositiveButton(R.string.refresh_positive, null); + builder.show(); + } + } + + private void setMergeState(CompetitorResultEditableImpl item, MergeState newState) { + switch (item.getMergeState()) { + case ERROR: + if (newState != MergeState.ERROR) { + break; + } + item.setDirty(true); + item.setMergeState(newState); + break; + + case WARNING: + if (newState != MergeState.WARNING && newState != MergeState.ERROR) { + break; + } + item.setDirty(true); + item.setMergeState(newState); + break; + + case OK: + item.setDirty(true); + item.setMergeState(newState); + break; + } + } + private void setPublishButton() { Set changed = new HashSet<>(); boolean isChecked = false; + boolean hasError = false; for (CompetitorResultEditableImpl item : mCompetitorResults) { if (item.isChecked()) { isChecked = true; } + if (item.getMergeState() != MergeState.OK) { + hasError = true; + } } - for (CompetitorResult item : getCompetitorResultsDiff()) { + CompetitorResults list = getCompetitorResultsDiff(mDraftData); + for (CompetitorResult item : list) { boolean found = false; - for (CompetitorResult server : mLastPublished) { + for (CompetitorResult server : mConfirmedData) { if (server.equals(item)) { found = true; break; @@ -492,7 +656,9 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte text = getString(R.string.publish_button_other, changed.size()); } mPublishButton.setText(text); - mPublishButton.setEnabled(changed.size() != 0); + int warningSign = hasError ? R.drawable.ic_warning_red_small : 0; + mPublishButton.setCompoundDrawablesWithIntrinsicBounds(warningSign, 0, 0, 0); + mPublishButton.setEnabled(changed.size() != 0 && !hasError); mButtonBar.setVisibility((changed.size() != 0 || isChecked) ? View.VISIBLE : View.GONE); } @@ -505,14 +671,15 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte context = actionBar.getThemedContext(); } } + competitor.setMergeState(MergeState.OK); CompetitorResultWithIdImpl item = new CompetitorResultWithIdImpl(0, competitor.getCompetitorId(), competitor .getCompetitorDisplayName(), competitor.getOneBasedRank(), competitor.getMaxPointsReason(), competitor.getScore(), competitor - .getFinishingTime(), competitor.getComment(), MergeState.OK); // TODO handle merge state + .getFinishingTime(), competitor.getComment(), competitor.getMergeState()); AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.AppTheme_AlertDialog); builder.setTitle(item.getCompetitorDisplayName()); final CompetitorEditLayout layout = new CompetitorEditLayout(getActivity(), item, mCompetitorResults.getFirstRankZeroPosition() + /* allow for setting rank as the new last in the list in case the competitor did not have a rank so far */ - (item.getOneBasedRank() == 0 ? 1 : 0)); + (item.getOneBasedRank() == 0 ? 1 : 0), competitor.getMergeState() == MergeState.ERROR); builder.setView(layout); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override @@ -531,13 +698,23 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte competitor.setScore(item.getScore()); competitor.setDirty(true); } - if (competitor.isDirty()) { - mAdapter.notifyDataSetChanged(); - } + mAdapter.notifyDataSetChanged(); setPublishButton(); } }); - builder.setNegativeButton(android.R.string.cancel, null); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + setPublishButton(); + mAdapter.notifyDataSetChanged(); + } + }); AlertDialog dialog = builder.create(); dialog.show(); if (AppUtils.with(getActivity()).isTablet()) { @@ -548,26 +725,35 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte } } - private CompetitorResults getCompetitorResultsDiff() { + private CompetitorResultsImpl getCompetitorResultsDiff(CompetitorResults results) { CompetitorResultsImpl result = new CompetitorResultsImpl(); for (CompetitorResultEditableImpl item : mCompetitorResults) { - if (item.isDirty()) { + boolean found = false; + for (CompetitorResult published : results) { + if (item.getCompetitorId().equals(published.getCompetitorId())) { + result.add(new CompetitorResultImpl(item.getCompetitorId(), item.getCompetitorDisplayName(), item.getOneBasedRank(), item + .getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item.getComment(), item.getMergeState())); + found = true; + break; + } + } + if (!found && item.isDirty()) { result.add(new CompetitorResultImpl(item.getCompetitorId(), item.getCompetitorDisplayName(), item.getOneBasedRank(), item - .getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item.getComment(), MergeState.OK)); // TODO handle merge state + .getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item.getComment(), item.getMergeState())); } } - for (CompetitorResult item : mLastSend) { + for (CompetitorResult item : results) { boolean found = false; - for (CompetitorResult dirtyItem : result) { - if (item.getCompetitorId().equals(dirtyItem.getCompetitorId())) { + for (CompetitorResult current : mCompetitorResults) { + if (item.getCompetitorId().equals(current.getCompetitorId())) { found = true; } } if (!found) { result.add(new CompetitorResultImpl(item.getCompetitorId(), item.getCompetitorDisplayName(), item.getOneBasedRank(), item - .getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item.getComment(), MergeState.OK)); // TODO handle merge state + .getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item.getComment(), item.getMergeState())); } } @@ -589,4 +775,35 @@ public class PenaltyFragment extends BaseFragment implements PopupMenu.OnMenuIte } } } + + private static class StateChangeListener extends BaseRaceStateChangedListener { + + private WeakReference mReference; + + StateChangeListener(PenaltyFragment fragment) { + mReference = new WeakReference<>(fragment); + } + + @Override + public void onFinishingPositionsConfirmed(ReadonlyRaceState state) { + super.onFinishingPositionsConfirmed(state); + + PenaltyFragment fragment = mReference.get(); + if (fragment != null) { + fragment.initConfirmedData(); + fragment.mergeData(state.getConfirmedFinishPositioningList(), true); + fragment.setPublishButton(); + } + } + + @Override + public void onFinishingPositioningsChanged(ReadonlyRaceState state) { + super.onFinishingPositioningsChanged(state); + + PenaltyFragment fragment = mReference.get(); + if (fragment != null) { + fragment.mergeData(state.getFinishPositioningList(), true); + } + } + } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/TrackingListFragment.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/TrackingListFragment.java index 3b2c0002cb5..25ea2a93083 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/TrackingListFragment.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/fragments/raceinfo/TrackingListFragment.java @@ -1,10 +1,16 @@ package com.sap.sailing.racecommittee.app.ui.fragments.raceinfo; +import java.io.Serializable; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import com.h6ah4i.android.widget.advrecyclerview.animator.SwipeDismissItemAnimator; import com.h6ah4i.android.widget.advrecyclerview.draggable.RecyclerViewDragDropManager; @@ -20,6 +26,8 @@ import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState; import com.sap.sailing.domain.abstractlog.race.CompetitorResults; 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.state.ReadonlyRaceState; +import com.sap.sailing.domain.abstractlog.race.state.impl.BaseRaceStateChangedListener; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CompetitorStore; import com.sap.sailing.domain.base.SharedDomainFactory; @@ -44,6 +52,7 @@ import com.sap.sailing.racecommittee.app.ui.layouts.HeaderLayout; import com.sap.sailing.racecommittee.app.ui.views.SearchView; import com.sap.sailing.racecommittee.app.utils.StringHelper; import com.sap.sailing.racecommittee.app.utils.ThemeHelper; +import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; @@ -54,6 +63,7 @@ import android.content.Loader; import android.graphics.drawable.NinePatchDrawable; import android.os.Build; import android.os.Bundle; +import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; @@ -66,6 +76,7 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.Window; import android.widget.AdapterView; import android.widget.Button; import android.widget.ImageView; @@ -94,7 +105,8 @@ public class TrackingListFragment extends BaseFragment private RecyclerView.Adapter mFinishedAdapter; private CompetitorAdapter mCompetitorAdapter; private CompetitorResultsList mFinishedData; - private CompetitorResults mLastPublished; + private CompetitorResults mDraftData; + private CompetitorResults mConfirmedData; private List mCompetitorData; private List mFilteredCompetitorData; private int mId = 0; @@ -105,6 +117,8 @@ public class TrackingListFragment extends BaseFragment private String mFilter; private View mTools; + private StateChangeListener mStateChangeListener; + public TrackingListFragment() { mCompetitorData = Collections.synchronizedList(new ArrayList()); mFilteredCompetitorData = Collections.synchronizedList(new ArrayList()); @@ -120,6 +134,9 @@ public class TrackingListFragment extends BaseFragment @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View layout = inflater.inflate(R.layout.race_tracking_list, container, false); + + mStateChangeListener = new StateChangeListener(this); + mHeader = ViewHelper.get(layout, R.id.header); mDots = new ArrayList<>(); mPanels = new ArrayList<>(); @@ -210,6 +227,8 @@ public class TrackingListFragment extends BaseFragment mComparators.add(SORT_NAME, new NaturalNamedComparator()); mComparator = mComparators.get(SORT_SAIL_NUMBER); mFinishedData = initializeFinishList(); + mConfirmedData = new CompetitorResultsImpl(); + mDraftData = new CompetitorResultsImpl(); loadCompetitors(); if (getView() != null) { RecyclerView competitorView = (RecyclerView) getView().findViewById(R.id.list_positioning_all); @@ -233,11 +252,11 @@ public class TrackingListFragment extends BaseFragment LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); mAdapter = new FinishListAdapter(getActivity(), mFinishedData); mAdapter.setListener(this); - @SuppressWarnings("unchecked") RecyclerView.Adapter dragManager = mDragDropManager - .createWrappedAdapter(mAdapter); + @SuppressWarnings("unchecked") + RecyclerView.Adapter dragManager = mDragDropManager.createWrappedAdapter(mAdapter); mFinishedAdapter = dragManager; - @SuppressWarnings("unchecked") RecyclerView.Adapter swipeManager = mSwipeManager - .createWrappedAdapter(mFinishedAdapter); + @SuppressWarnings("unchecked") + RecyclerView.Adapter swipeManager = mSwipeManager.createWrappedAdapter(mFinishedAdapter); mFinishedAdapter = swipeManager; mFinishView.setLayoutManager(layoutManager); mFinishView.setAdapter(mFinishedAdapter); @@ -253,9 +272,11 @@ public class TrackingListFragment extends BaseFragment mConfirm.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - sendUnconfirmed(); - getRaceState().setFinishPositioningConfirmed(MillisecondsTimePoint.now()); - initLastPublished(); + TimePoint now = MillisecondsTimePoint.now(); + CompetitorResults result = getCompetitorResultsDiff(mConfirmedData); + getRaceState().setFinishPositioningListChanged(now, result); + getRaceState().setFinishPositioningConfirmed(now, result); + initLocalData(); Toast.makeText(getActivity(), R.string.publish_clicked, Toast.LENGTH_SHORT).show(); sendIntent(AppConstants.INTENT_ACTION_CLEAR_TOGGLE); sendIntent(AppConstants.INTENT_ACTION_SHOW_SUMMARY_CONTENT); @@ -274,15 +295,22 @@ public class TrackingListFragment extends BaseFragment sortCompetitors(); mCompetitorAdapter.notifyDataSetChanged(); - initLastPublished(); + initLocalData(); } - private void initLastPublished() { - mLastPublished = new CompetitorResultsImpl(); + private void initLocalData() { + mDraftData.clear(); + if (getRaceState().getFinishPositioningList() != null) { + for (CompetitorResult item : getRaceState().getFinishPositioningList()) { + mDraftData.add(item); + } + } + + mConfirmedData.clear(); if (getRaceState().getConfirmedFinishPositioningList() != null) { for (CompetitorResult item : getRaceState().getConfirmedFinishPositioningList()) { - mLastPublished.add(new CompetitorResultImpl(item.getCompetitorId(), item.getCompetitorDisplayName(), item.getOneBasedRank(), item - .getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item.getComment(), item.getMergeState())); // TODO handle merge state + mConfirmedData.add(new CompetitorResultImpl(item.getCompetitorId(), item.getCompetitorDisplayName(), item.getOneBasedRank(), item + .getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item.getComment(), item.getMergeState())); } } } @@ -302,6 +330,10 @@ public class TrackingListFragment extends BaseFragment public void onStart() { super.onStart(); + if (getRace() != null && getRaceState() != null) { + getRaceState().addChangedListener(mStateChangeListener); + } + Intent intent = new Intent(AppConstants.INTENT_ACTION_ON_LIFECYCLE); intent.putExtra(AppConstants.INTENT_ACTION_EXTRA_LIFECYCLE, AppConstants.INTENT_ACTION_EXTRA_START); intent.putExtra(AppConstants.INTENT_ACTION_EXTRA, AppConstants.INTENT_ACTION_TOGGLE_COMPETITOR); @@ -312,7 +344,13 @@ public class TrackingListFragment extends BaseFragment public void onStop() { super.onStop(); - sendUnconfirmed(); + if (getRace() != null && getRaceState() != null) { + getRaceState().removeChangedListener(mStateChangeListener); + } + + if (isDirty()) { + sendUnconfirmed(); + } Intent intent = new Intent(AppConstants.INTENT_ACTION_ON_LIFECYCLE); intent.putExtra(AppConstants.INTENT_ACTION_EXTRA_LIFECYCLE, AppConstants.INTENT_ACTION_EXTRA_STOP); @@ -374,8 +412,51 @@ public class TrackingListFragment extends BaseFragment return true; } + private boolean isDirty() { + CompetitorResultsImpl diff = (CompetitorResultsImpl) getCompetitorResultsDiff(mConfirmedData); + boolean dirty = diff.size() != mDraftData.size(); + if (!dirty) { + for (CompetitorResult item : getCompetitorResultsDiff(mConfirmedData)) { + for (CompetitorResult last : mDraftData) { + if (item.getCompetitorId().equals(last) && !item.equals(last)) { + dirty = true; + break; + } + } + if (dirty) { + break; + } + } + } + return dirty; + } + + private void setPublishButton() { + Set positions = new HashSet<>(); + boolean multiplePositions = false; + Set errors = new HashSet<>(); + + for (CompetitorResult item : mFinishedData) { + // check for multiple positions + if (item.getOneBasedRank() != 0 && positions.contains(item.getOneBasedRank())) { + multiplePositions = true; + } + positions.add(item.getOneBasedRank()); + + // check for merge errors + if (item.getMergeState() != MergeState.OK) { + errors.add(item.getCompetitorId()); + } + } + + boolean error = multiplePositions || errors.size() > 0; + int warningSign = error ? R.drawable.ic_warning_red_small : 0; + mConfirm.setEnabled(!error); + mConfirm.setCompoundDrawablesWithIntrinsicBounds(warningSign, 0, 0, 0); + } + private void sendUnconfirmed() { - getRaceState().setFinishPositioningListChanged(MillisecondsTimePoint.now(), getCompetitorResultsDiff()); + getRaceState().setFinishPositioningListChanged(MillisecondsTimePoint.now(), getCompetitorResultsDiff(mConfirmedData)); } private void loadCompetitors() { @@ -464,20 +545,18 @@ public class TrackingListFragment extends BaseFragment } private void deleteCompetitorsFromFinishedList(Collection validCompetitors) { - List positions = new ArrayList<>(); List toBeDeleted = new ArrayList<>(); for (CompetitorResultWithIdImpl item : mFinishedData) { if (!validCompetitors.contains(getCompetitorStore().getExistingCompetitorById(item.getCompetitorId()))) { - positions.add(mFinishedData.indexOf(item)); toBeDeleted.add(item); continue; } if (item.getOneBasedRank() == 0 && item.getMaxPointsReason() == MaxPointsReason.NONE) { - positions.add(mFinishedData.indexOf(item)); toBeDeleted.add(item); } } mFinishedData.removeAll(toBeDeleted); + setPublishButton(); mFinishedAdapter.notifyDataSetChanged(); } @@ -527,8 +606,9 @@ public class TrackingListFragment extends BaseFragment } } mFinishedData.add(pos, new CompetitorResultWithIdImpl(mId, competitor.getId(), name, greatestOneBasedRankSoFar + 1, MaxPointsReason.NONE, - /* score */ null, /* finishingTime */ null, /* comment */ null, MergeState.OK)); // TODO handle merge state + /* score */ null, /* finishingTime */ null, /* comment */ null, MergeState.OK)); mId++; + setPublishButton(); mFinishedAdapter.notifyItemInserted(pos); if (mDots.size() > 0) { Toast.makeText(getActivity(), getString(R.string.added_to_result_list, name, pos + 1), Toast.LENGTH_SHORT).show(); @@ -569,6 +649,7 @@ public class TrackingListFragment extends BaseFragment final int firstPositionChanged = Math.min(getFirstRankZeroPosition(), Math.min(fromPosition, toPosition)); final int lastPositionChanged = Math.min(getFirstRankZeroPosition(), Math.max(fromPosition, toPosition) + 1); adjustRanks(firstPositionChanged, lastPositionChanged); + setPublishButton(); mFinishedAdapter.notifyItemRangeChanged(firstPositionChanged, lastPositionChanged - firstPositionChanged); } @@ -578,6 +659,7 @@ public class TrackingListFragment extends BaseFragment if (position >= 0) { // found mFinishedData.remove(position); adjustRanks(position, getFirstRankZeroPosition()); + setPublishButton(); mFinishedAdapter.notifyItemRemoved(position); } Competitor competitor = getCompetitorStore().getExistingCompetitorById(item.getCompetitorId()); @@ -609,7 +691,7 @@ public class TrackingListFragment extends BaseFragment .getCompetitorId(), competitorToReplaceWithAdjustedPosition .getCompetitorDisplayName(), newOneBasedRank, competitorToReplaceWithAdjustedPosition .getMaxPointsReason(), competitorToReplaceWithAdjustedPosition.getScore(), competitorToReplaceWithAdjustedPosition - .getFinishingTime(), competitorToReplaceWithAdjustedPosition.getComment(), MergeState.OK); // TODO handle merge state + .getFinishingTime(), competitorToReplaceWithAdjustedPosition.getComment(), MergeState.OK); } private void addNewCompetitorToCompetitorList(Competitor competitor) { @@ -627,6 +709,7 @@ public class TrackingListFragment extends BaseFragment builder.setItems(maxPointsReasons, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int position) { setMaxPointsReasonForItem(item, maxPointsReasons[position]); + setPublishButton(); mFinishedAdapter.notifyItemChanged(mFinishedData.indexOf(item)); } }); @@ -647,7 +730,7 @@ public class TrackingListFragment extends BaseFragment final CompetitorEditLayout layout = new CompetitorEditLayout(getActivity(), getRace().getState().getFinishingTime(), item, mAdapter.getFirstRankZeroPosition() + /* allow for setting rank as the new last in the list in case the competitor did not have a rank so far */ - (item.getOneBasedRank() == 0 ? 1 : 0), false); + (item.getOneBasedRank() == 0 ? 1 : 0), false, item.getMergeState() == MergeState.ERROR); builder.setView(layout); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override @@ -656,11 +739,28 @@ public class TrackingListFragment extends BaseFragment updateItem(item, newItem); } }); - builder.setNegativeButton(android.R.string.cancel, null); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + builder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + CompetitorResultWithIdImpl newItem = new CompetitorResultWithIdImpl(item.getId(), item.getCompetitorId(), item + .getCompetitorDisplayName(), item.getOneBasedRank(), item.getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item + .getComment(), MergeState.OK); + updateItem(item, newItem); + } + }); AlertDialog dialog = builder.create(); dialog.show(); if (AppUtils.with(getActivity()).isTablet()) { - dialog.getWindow().setLayout(getResources().getDimensionPixelSize(R.dimen.competitor_dialog_width), ViewGroup.LayoutParams.WRAP_CONTENT); + Window window = dialog.getWindow(); + if (window != null) { + window.setLayout(getResources().getDimensionPixelSize(R.dimen.competitor_dialog_width), ViewGroup.LayoutParams.WRAP_CONTENT); + } } } @@ -677,8 +777,15 @@ public class TrackingListFragment extends BaseFragment * */ private void updateItem(final CompetitorResultWithIdImpl item, CompetitorResultWithIdImpl newItem) { - int index = mFinishedData.indexOf(item); - mFinishedData.set(index, newItem); // update the item in the list already; then check where to move it and adjust other elements + int index = -1; + for (int i = 0; i < mFinishedData.size(); i++) { + if (mFinishedData.get(i).getCompetitorId().equals(item.getCompetitorId())) { + index = i; + } + } + if (index >= 0) { + mFinishedData.set(index, newItem); // update the item in the list already; then check where to move it and adjust other elements + } if (item.getOneBasedRank() != 0 && newItem.getOneBasedRank() == 0 && newItem.getMaxPointsReason() != MaxPointsReason.NONE) { // move to the end of the area of "penalized" competitors; may also be an unpenalized competitor that hasn't // been removed yet (e.g., in order to force a score correction reset on the server) @@ -690,6 +797,7 @@ public class TrackingListFragment extends BaseFragment } else { mFinishedAdapter.notifyItemChanged(index); } + setPublishButton(); } private CharSequence[] getAllMaxPointsReasons() { @@ -703,7 +811,8 @@ public class TrackingListFragment extends BaseFragment protected void setMaxPointsReasonForItem(CompetitorResultWithIdImpl item, CharSequence maxPointsReasonName) { MaxPointsReason maxPointsReason = MaxPointsReason.valueOf(maxPointsReasonName.toString()); CompetitorResultWithIdImpl newItem = new CompetitorResultWithIdImpl(item.getId(), item.getCompetitorId(), item - .getCompetitorDisplayName(), item.getOneBasedRank(), maxPointsReason, item.getScore(), item.getFinishingTime(), item.getComment(), MergeState.OK); // TODO handle merge state + .getCompetitorDisplayName(), item.getOneBasedRank(), maxPointsReason, item.getScore(), item.getFinishingTime(), item.getComment(), item + .getMergeState()); updateItem(item, newItem); getRaceState().setFinishPositioningListChanged(MillisecondsTimePoint.now(), getCompetitorResults()); } @@ -712,22 +821,24 @@ public class TrackingListFragment extends BaseFragment CompetitorResults result = new CompetitorResultsImpl(); for (CompetitorResultWithIdImpl item : mFinishedData) { result.add(new CompetitorResultImpl(item.getCompetitorId(), item.getCompetitorDisplayName(), item.getOneBasedRank(), item - .getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item.getComment(), MergeState.OK)); // TODO handle merge state + .getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item.getComment(), item.getMergeState())); } return result; } - private CompetitorResults getCompetitorResultsDiff() { + private CompetitorResults getCompetitorResultsDiff(CompetitorResults results) { CompetitorResults result = new CompetitorResultsImpl(); // all changed items - for (CompetitorResult oldItem : mLastPublished) { + for (CompetitorResult oldItem : results) { boolean found = false; for (CompetitorResult newItem : mFinishedData) { if (oldItem.getCompetitorId().equals(newItem.getCompetitorId())) { - if (!oldItem.equals(newItem)) { - result.add(new CompetitorResultImpl(newItem.getCompetitorId(), newItem.getCompetitorDisplayName(), newItem - .getOneBasedRank(), newItem.getMaxPointsReason(), newItem.getScore(), newItem.getFinishingTime(), newItem.getComment(), MergeState.OK)); // TODO handle merge state + CompetitorResult temp = new CompetitorResultImpl(newItem.getCompetitorId(), newItem.getCompetitorDisplayName(), newItem + .getOneBasedRank(), newItem.getMaxPointsReason(), newItem.getScore(), newItem.getFinishingTime(), newItem + .getComment(), newItem.getMergeState()); + if (!oldItem.equals(temp)) { + result.add(temp); } found = true; break; @@ -735,14 +846,14 @@ public class TrackingListFragment extends BaseFragment } if (!found) { result.add(new CompetitorResultImpl(oldItem.getCompetitorId(), oldItem.getCompetitorDisplayName(), 0, oldItem - .getMaxPointsReason(), oldItem.getScore(), oldItem.getFinishingTime(), oldItem.getComment(), MergeState.OK)); // TODO handle merge state + .getMaxPointsReason(), oldItem.getScore(), oldItem.getFinishingTime(), oldItem.getComment(), oldItem.getMergeState())); } } // all new items for (CompetitorResultWithIdImpl newItem : mFinishedData) { boolean found = false; - for (CompetitorResult oldItem : mLastPublished) { + for (CompetitorResult oldItem : results) { if (oldItem.getCompetitorId().equals(newItem.getCompetitorId())) { found = true; break; @@ -750,7 +861,7 @@ public class TrackingListFragment extends BaseFragment } if (!found) { result.add(new CompetitorResultImpl(newItem.getCompetitorId(), newItem.getCompetitorDisplayName(), newItem.getOneBasedRank(), newItem - .getMaxPointsReason(), newItem.getScore(), newItem.getFinishingTime(), newItem.getComment(), MergeState.OK)); // TODO handle merge state + .getMaxPointsReason(), newItem.getScore(), newItem.getFinishingTime(), newItem.getComment(), newItem.getMergeState())); } } @@ -841,4 +952,217 @@ public class TrackingListFragment extends BaseFragment } } } + + private void mergeData(CompetitorResults results) { + Map changedCompetitor = new HashMap<>(); + for (CompetitorResult result : results) { + CompetitorResultWithIdImpl item = null; + CompetitorResult draft = null; + CompetitorResultWithIdImpl newItem; + MergeState state; + for (CompetitorResultWithIdImpl edited : mFinishedData) { + if (result.getCompetitorId().equals(edited.getCompetitorId())) { + item = edited; + break; + } + } + for (CompetitorResult saved : mDraftData) { + if (result.getCompetitorId().equals(saved.getCompetitorId())) { + draft = saved; + break; + } + } + if (item != null) { // result is in list + // check one based rank + if (item.getOneBasedRank() != result.getOneBasedRank()) { + if (draft != null) { + if (item.getOneBasedRank() == draft.getOneBasedRank()) { + state = MergeState.WARNING; + } else { + state = MergeState.ERROR; + } + } else { + state = MergeState.ERROR; + } + newItem = new CompetitorResultWithIdImpl(item.getId(), item.getCompetitorId(), item.getCompetitorDisplayName(), result + .getOneBasedRank(), item.getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item + .getComment(), getMergeState(item, state)); + item = updateChangedItem(changedCompetitor, item, newItem); + } + + // check max point reasons + if (!item.getMaxPointsReason().equals(result.getMaxPointsReason())) { + if (draft != null) { + if (item.getMaxPointsReason().equals(draft.getMaxPointsReason())) { + state = MergeState.WARNING; + } else { + state = MergeState.ERROR; + } + } else { + state = MergeState.ERROR; + } + newItem = new CompetitorResultWithIdImpl(item.getId(), item.getCompetitorId(), item.getCompetitorDisplayName(), item + .getOneBasedRank(), result.getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item + .getComment(), getMergeState(item, state)); + item = updateChangedItem(changedCompetitor, item, newItem); + } + + // check score + if (item.getScore() != null) { + if (!item.getScore().equals(result.getScore())) { + if (draft != null) { + if (item.getScore().equals(draft.getScore())) { + state = MergeState.WARNING; + } else { + state = MergeState.ERROR; + } + } else { + state = MergeState.ERROR; + } + newItem = new CompetitorResultWithIdImpl(item.getId(), item.getCompetitorId(), item.getCompetitorDisplayName(), item + .getOneBasedRank(), item.getMaxPointsReason(), result.getScore(), item.getFinishingTime(), item + .getComment(), getMergeState(item, state)); + item = updateChangedItem(changedCompetitor, item, newItem); + } + } else if (result.getScore() != null) { + newItem = new CompetitorResultWithIdImpl(item.getId(), item.getCompetitorId(), item.getCompetitorDisplayName(), item + .getOneBasedRank(), item.getMaxPointsReason(), result.getScore(), item.getFinishingTime(), item + .getComment(), getMergeState(item, MergeState.ERROR)); + item = updateChangedItem(changedCompetitor, item, newItem); + } + + // check finishing time + if (item.getFinishingTime() != null) { + if (!item.getFinishingTime().equals(result.getFinishingTime())) { + if (draft != null) { + if (item.getFinishingTime().equals(draft.getFinishingTime())) { + state = MergeState.WARNING; + } else { + state = MergeState.ERROR; + } + } else { + state = MergeState.ERROR; + } + newItem = new CompetitorResultWithIdImpl(item.getId(), item.getCompetitorId(), item.getCompetitorDisplayName(), item + .getOneBasedRank(), item.getMaxPointsReason(), item.getScore(), result.getFinishingTime(), item + .getComment(), getMergeState(item, state)); + item = updateChangedItem(changedCompetitor, item, newItem); + } + } else if (result.getFinishingTime() != null) { + newItem = new CompetitorResultWithIdImpl(item.getId(), item.getCompetitorId(), item.getCompetitorDisplayName(), item + .getOneBasedRank(), item.getMaxPointsReason(), item.getScore(), result.getFinishingTime(), item + .getComment(), getMergeState(item, MergeState.ERROR)); + item = updateChangedItem(changedCompetitor, item, newItem); + } + + // check comment + if (item.getComment() != null) { + if (!item.getComment().equals(result.getComment())) { + if (draft != null) { + if (item.getComment().equals(draft.getComment())) { + state = MergeState.WARNING; + } else { + state = MergeState.ERROR; + } + } else { + state = MergeState.ERROR; + } + newItem = new CompetitorResultWithIdImpl(item.getId(), item.getCompetitorId(), item.getCompetitorDisplayName(), item + .getOneBasedRank(), item.getMaxPointsReason(), item.getScore(), item.getFinishingTime(), + item.getComment() + " ## " + result.getComment(), getMergeState(item, state)); + item = updateChangedItem(changedCompetitor, item, newItem); + } + } else if (result.getComment() != null) { + newItem = new CompetitorResultWithIdImpl(item.getId(), item.getCompetitorId(), item.getCompetitorDisplayName(), item + .getOneBasedRank(), item.getMaxPointsReason(), item.getScore(), item.getFinishingTime(), result + .getComment(), getMergeState(item, MergeState.ERROR)); + item = updateChangedItem(changedCompetitor, item, newItem); + } + + // check merge state + if (!item.getMergeState().equals(result.getMergeState())) { + newItem = new CompetitorResultWithIdImpl(item.getId(), item.getCompetitorId(), item.getCompetitorDisplayName(), item + .getOneBasedRank(), item.getMaxPointsReason(), item.getScore(), item.getFinishingTime(), item + .getComment(), getMergeState(item, result.getMergeState())); + item = updateChangedItem(changedCompetitor, item, newItem); + } + } else { // unknown result, so it will be added + if (result.getOneBasedRank() != 0 || result.getMaxPointsReason() != MaxPointsReason.NONE) { + for (Competitor competitor : mCompetitorData) { + if (competitor.getId().equals(result.getCompetitorId())) { + removeCompetitorFromList(competitor); + break; + } + } + mFinishedData.add(new CompetitorResultWithIdImpl(mFinishedData.size(), result)); + Collections.sort(mFinishedData, new DefaultCompetitorResultComparator(/* lowPoint TODO where to get this from? */ true)); + } + } + } + setPublishButton(); + mAdapter.notifyDataSetChanged(); + if (changedCompetitor.size() > 0) { // show message to user + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.AppTheme_AlertDialog); + builder.setTitle(R.string.refresh_title); + StringBuilder string = new StringBuilder(1024); + for (String competitor : changedCompetitor.values()) { + string.append(competitor); + string.append("\n"); + } + builder.setMessage(getString(R.string.refresh_message, string.toString())); + builder.setPositiveButton(R.string.refresh_positive, null); + builder.show(); + } + } + + @NonNull + private CompetitorResultWithIdImpl updateChangedItem(Map changedCompetitor, CompetitorResultWithIdImpl item, + CompetitorResultWithIdImpl newItem) { + updateItem(item, newItem); + changedCompetitor.put(newItem.getCompetitorId(), newItem.getCompetitorDisplayName()); + return newItem; + } + + private MergeState getMergeState(CompetitorResultWithIdImpl item, MergeState newState) { + MergeState state = item.getMergeState(); + switch (item.getMergeState()) { + case ERROR: + if (newState != MergeState.ERROR) { + break; + } + state = newState; + break; + + case WARNING: + if (newState != MergeState.WARNING && newState != MergeState.ERROR) { + break; + } + state = newState; + break; + + case OK: + state = newState; + break; + } + return state; + } + + private static class StateChangeListener extends BaseRaceStateChangedListener { + + private WeakReference mReference; + + StateChangeListener(TrackingListFragment fragment) { + mReference = new WeakReference<>(fragment); + } + + @Override + public void onFinishingPositioningsChanged(ReadonlyRaceState state) { + super.onFinishingPositioningsChanged(state); + + TrackingListFragment fragment = mReference.get(); + if (fragment != null) { + fragment.mergeData(state.getFinishPositioningList()); + } + } + } } diff --git a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/layouts/CompetitorEditLayout.java b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/layouts/CompetitorEditLayout.java index 0d1a7ea7067..97fc67436ba 100644 --- a/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/layouts/CompetitorEditLayout.java +++ b/mobile/com.sap.sailing.racecommittee.app/src/com/sap/sailing/racecommittee/app/ui/layouts/CompetitorEditLayout.java @@ -49,12 +49,12 @@ public class CompetitorEditLayout extends ScrollView implements DatePickerDialog private boolean mRestricted; - public CompetitorEditLayout(Context context, CompetitorResultWithIdImpl competitor, int maxPos) { - this(context, null, competitor, maxPos, true); + public CompetitorEditLayout(Context context, CompetitorResultWithIdImpl competitor, int maxPos, boolean hasWarning) { + this(context, null, competitor, maxPos, true, hasWarning); } public CompetitorEditLayout(final Context context, final TimePoint startTime, CompetitorResultWithIdImpl competitor, int maxPos, - boolean restrictedView) { + boolean restrictedView, boolean hasWarning) { super(context); mRestricted = restrictedView; @@ -74,6 +74,10 @@ public class CompetitorEditLayout extends ScrollView implements DatePickerDialog if (position != null) { position.setVisibility(restrictedView ? GONE : VISIBLE); } + View warning = ViewHelper.get(layout, R.id.competitor_warning_message); + if (warning != null) { + warning.setVisibility(hasWarning ? VISIBLE : GONE); + } mCalendar = (GregorianCalendar) GregorianCalendar.getInstance(); if (mCompetitor.getFinishingTime() != null) { @@ -216,8 +220,7 @@ public class CompetitorEditLayout extends ScrollView implements DatePickerDialog comment = mComment.getText().toString(); } CompetitorResult result = new CompetitorResultImpl(mCompetitor.getCompetitorId(), mCompetitor - .getCompetitorDisplayName(), oneBaseRank, maxPointsReason, score, finishingTime, comment, - MergeState.OK); // TODO handle merge state + .getCompetitorDisplayName(), oneBaseRank, maxPointsReason, score, finishingTime, comment, MergeState.OK); return new CompetitorResultWithIdImpl(mCompetitor.getId(), result); } diff --git a/mobile/vectorDrawables/RCApp/ic_warning_red_18dp.xml b/mobile/vectorDrawables/RCApp/ic_warning_red_18dp.xml new file mode 100644 index 00000000000..a6b8ebb45d4 --- /dev/null +++ b/mobile/vectorDrawables/RCApp/ic_warning_red_18dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/mobile/vectorDrawables/RCApp/ic_warning_red_48dp.xml b/mobile/vectorDrawables/RCApp/ic_warning_red_48dp.xml new file mode 100644 index 00000000000..cd1607e5354 --- /dev/null +++ b/mobile/vectorDrawables/RCApp/ic_warning_red_48dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/mobile/vectorDrawables/RCApp/ic_warning_red_64dp.xml b/mobile/vectorDrawables/RCApp/ic_warning_red_64dp.xml new file mode 100644 index 00000000000..32a0ba1895e --- /dev/null +++ b/mobile/vectorDrawables/RCApp/ic_warning_red_64dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/mobile/vectorDrawables/RCApp/ic_warning_red_96dp.xml b/mobile/vectorDrawables/RCApp/ic_warning_red_96dp.xml new file mode 100644 index 00000000000..1807b1dac22 --- /dev/null +++ b/mobile/vectorDrawables/RCApp/ic_warning_red_96dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/static/jobs/RaceViewer2.jpg b/static/jobs/RaceViewer2.jpg new file mode 100644 index 00000000000..2e08eec3580 Binary files /dev/null and b/static/jobs/RaceViewer2.jpg differ diff --git a/static/jobs/index.html b/static/jobs/index.html new file mode 100644 index 00000000000..26275baa778 --- /dev/null +++ b/static/jobs/index.html @@ -0,0 +1,393 @@ + + + + + + + + + +SAP Sailing - Jobs + + + + + + + + + +
      +
      +
      +
      + SAP Sailing Analytics + +

      Sailing Analytics

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

      About Us

      +

      SAP is at the center of today’s technology revolution, + developing innovations that not only help our customers run at + their best, but also improve the lives of people everywhere.

      +

      As the market leader in enterprise application software, we + help organizations of all sizes and industries combat the damaging + effects of complexity, generate new opportunities for innovation + and growth, and stay ahead of the competition.

      +

      By delivering real time analytics around race rankings, + speeds, maneuvers, bearings etc., SAP is helping to simplify and + demystify the complex sport of sailing.

      +

      SAP Sailing Analytics provide insights and transparency to + the world of sailing by utilizing Cloud and In-Memory Technology, + processing GPS and wind measurement data in real time and + visualizing contents in various frontends accessible from + anywhere.

      +

      Jobs

      + » Student Backend Development +
      + +
      + + diff --git a/wiki/bug4232-review.md b/wiki/bug4232-review.md index 397cceec340..95d9ed32576 100644 --- a/wiki/bug4232-review.md +++ b/wiki/bug4232-review.md @@ -1,5 +1,99 @@ **This page has the purpose to prepare and track the progress of the bug4232 review** -(See https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4232) +(See [bugzilla](https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4232)) # Test Scenarios +## Prerequisites +* two devices (Smartphone and/or Tablet) +* device configuration: bug4232 (so all settings are correct - or manual) +* server: https://dev.sapsailing.com +* refresh time: 20 sec (because of faster local refreshes) +* event: Extreme Sailing Series 2016 Act 8 Sydney + +The edit dialog (open with the pencil) should always be closed if a warning (yellow or red) will be displayed. In case of a red warning, the publish button is always disabled. + +One device will be called **A** and the other **B**, so they have unique names while testing. + +## While starting + +### Test 1 +* 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 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 +* device A gives Omar Air OCS, device B sets DNS for Alinghi +* device A closes the penalty view (without publishing) +* device B should see both entries in the list after refresh + +### Test 5 +* same as [test 4](#test-scenarios_while-starting_test-4), but this time the result should be published +* same result as [test 4](#test-scenarios_while-starting_test-4). + +### Test 6 +* same as [test 4](#test-scenarios_while-starting_test-4), but after refresh device B should publish the merged result +* after the refresh device A also sees both entries + +## While finishing + +### Test 7 +* both devices open result list +* device A adds Omar Air, Alinghi and LR BAR into the list +* device A closes the result list +* after refresh device B see the three competitors in the result list + +### Test 8 +* both devices open result list +* both devices add Omar Air to the result list +* device A sets the penalty to OCS and closes the result list +* device B should see after the refresh a yellow warning sign + +### Test 9 +* both devices open result list +* both devices add Omar Air to the result list +* 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 +* both devices add Omar Air to the result list +* device A changes to penalty view and sets the value to DNF and changes back to result list +* device B should see a yellow warning sign after refresh + +### Test 11 +* both devices open result list +* device A add Omar Air and Alinghi +* device B add Alinghi and Omar Air +* device A closes the result list +* 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 +* 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/Manage-media-content.md b/wiki/howto/eventmanagers/Manage-media-content.md index 08207a32f40..1a0f33ef08b 100644 --- a/wiki/howto/eventmanagers/Manage-media-content.md +++ b/wiki/howto/eventmanagers/Manage-media-content.md @@ -1,38 +1,48 @@ -This page descrips how to manage the media content like images for the events at www.sapsailing.com, providing a guidance based on user's best experience. For information about where to get media content itself, please refer to the [[media|wiki/events/Sailing-events-media-content]] page. +This page descrips how to manage the media content like images and videos for the events on www.sapsailing.com, providing a guidance based on user's best experiences. For information about where to get media content itself, please refer to the [[media|wiki/events/Sailing-events-media-content]] page. [[_TOC_]] # Images ## Image types and their impact -There are four image types which can be added to an event carrying different roles: +There are four image types which can be added to an event where they carry different roles: -* **logo:** the sailing event or league logo which gets displayed e.g. in the upper left corner next to the name of the sailing event. -* **stage:** used as a banner for the sailing event and gets displayed e.g. on the landing page. -* **teaser:** used to give a preview about the sailing event and gets displayed e.g. in the event overview where all events are grouped by years. -* **galery:** representing the images which are displayed under the _media_ tab of each event. +* **Logo:** the sailing event or league logo which gets displayed e.g. in the upper left corner next to the name of the sailing event. +* **Stage:** used as a banner for the sailing event and gets displayed e.g. on the landing page. +* **Teaser:** used to give a preview about the sailing event and gets displayed e.g. in the event overview where all events are grouped by years. +* **Gallery:** representing the images which are displayed under the _media_ tab of each event. ## Scale guidelines for several purposes For performance reasons, images should be scaled before uploading them. The following image resolutions showed up as good manner: -* **logo:** about 150 x 150 pixel (file size < 100 kB); take a .png file with transparent background! -* **teaser:** about 640 x 480 pixel (file size < 100 kB) -* **stage:** about 1800 x 500 pixel (file size < 500 kB) -* **gallery:** about 1400 x 1000 pixel (file size < 350 kB) +* **logo.png:** about 150 x 150 pixel (file size < 100 kB) +* **teaser.jpeg:** about 640 x 480 pixel (file size < 100 kB) +* **stage.jpeg:** about 1800 x 500 pixel (file size < 500 kB) +* **gallery.jpeg:** about 1400 x 1000 pixel (file size < 350 kB) + +Note: the _logo_ requires the .png file format with transparent background. _Teaser_, _stage_ and _gallery_ images require the .jpg (.jpeg) file format. ## S3 folder structure -Before uploading the images an appropirate folder needs to be created at the S3 storage. For a good contribution please stick to the following naming conventions: +Before uploading the images an appropirate folder needs to be created at the S3 storage. For a good contribution please stick to the following conventions: -Build a event root folder for the event. Please add preceding number for providing a well grouped events order. -**pattern:** `event year`/`league name`/`event name with preceding number`/ -e.g. `2017`/`1. Segel-Bundesliga`/`01 Prien am Chiemsee`/ +Access the S3 folder of the corresponding event year. Create an event root folder and set its name to the name of the league or series of events (see **figure 1**). +![An exemplary event overview for 2017](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/media%20content/year-and-event.jpeg) +**Figure 1: An exemplary event overview for 2017** -Add a folder for the _logo_, _stage_ and _teaser_ images -**pattern:** `{event root folder}`/`Images_Homepage`/ -e.g. `2017`/`1. Segel-Bundesliga`/`01 Prien am Chiemsee`/`Images_Homepage`/ +Within this folder add a new subfolder, set its name to the event's name and add a preceding number which indicates the order of it (see **figure 2**). +![folder structure](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/media%20content/folder-structure.jpeg) +**Figure 2: folder structure with preceding event numbers within an event series** -Add a folder for the _galery_ images -**pattern:** `{event root folder}`/`Images_Photos`/ +According to this description result in following folder structure: +**pattern:** `~`/`event year`/`series name`/`event name with preceding number`/ +e.g. `~`/`2017`/`1. Segel-Bundesliga`/`01 Prien am Chiemsee`/ + +Access the corresponding event folder and create two subfolders: _Images\_Homepage_ and _Images\_Photos_. The first one is for the _stage_, _teaser_ and _logo_ images. The second one will carry _gallery_ images. + +**pattern:** `~`/`event year`/`series name`/`event name with preceding number`/`Images_Homepage`/ +e.g. `2017`/`1. Segel-Bundesliga`/`01 Prien am Chiemsee`/`Images_Homepage`/ + +**pattern:** `~`/`event year`/`series name`/`event name with preceding number`/`Images_Photos`/ e.g. `2017`/`1. Segel-Bundesliga`/`01 Prien am Chiemsee`/`Images_Photos`/ ## Image manipulation with GIMP @@ -43,7 +53,63 @@ The following guide is based on the tool [GIMP](https://www.gimp.org/) and its [ 3. Add -> Add a watermark -> leave defaults -> insert copyright information -> Ok 4. Add images -> ... -> Apply +## Exemplary instructions +1. Access the administration console _eventname(-master?)_.sapsailing.com/gwt/AdminConsle.html and login with your user credentials. +2. Select the corresponding event and click at the _edit_ button (see **figure 3**) +![Events overview on admin console](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/media%20content/events-overview-admin-console.JPG) +**Figure 3: the edit button on the admin console** +3. By selecting the _Images_ tab the possibilities to add the four image types like described [above](https://wiki.sapsailing.com/preview#images_image-types-and-their-impact) show up (see **figure 4**). +![The four image types](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/media%20content/edit-media-content.JPG) +**Figure 4: the four image types** +4. Click on the desired _add `image type` button_ and insert the _Image URL_ according to the memory location of the image. Usually, no further settings are required. +# Videos +A guidance about how to synchronize races with livestream videos can be found [here](https://wiki.sapsailing.com/wiki/howto/eventmanagers/linking-race-videos). +## Video types and their impact +We distinguish between _highlight_, _livestream_ and _gallery_ videos and a special _featured_ option. + +* **Highlight:** most event organizers provide highlight videos of their sailing events which summarize the best moments of the event. Such videos belong to the _highlight_ category. +* **Livestream:** some event organizers record livestreams of the races during the event. This videos belong to the _livestream_ category and can be linked to the _Race Analytics_ solution. +* **Gallery:** Some times interviews with sailors or other insights of the sailing event are provided by the organizers. These videos belong to the _gallery_ category. +* **Featured:** is a special option for an event video. This tag can be additionally added to one of the three categories _highlight_, _livestream_ and _gallery_. A video with the _featured_ tag is presented at the event's page instead of the _stage image_. +Note: Always define the most populare video to be _featured_ e.g. the current livestream during the event. Never define more than _one_ featured video. + +## Exemplary instructions +1. Access the administration console _eventname(-master?)_.sapsailing.com/gwt/AdminConsle.html and login with your user credentials. + +2. Select the corresponding event and click at the _edit_ button (see **figure 5**). +![Events overview on admin console](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/media%20content/events-overview-admin-console.JPG) +**Figure 5: the edit button on the admin console** + +3. By selecting the _Videos_ tab the possibilities to add the three video types like described [above](https://wiki.sapsailing.com/wiki/howto/eventmanagers/Manage-media-content#videos_video-types-and-their-impact) show up (see **figure 6**). +![Figure 6: the three video types](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/media%20content/edit-media-content-video.JPG) +**Figure 6: the three video types** + +4. Click on the desired _add_ `video type` _button_ and set the _Video URL_, the _Mime Type_, the _Title_ and the _Laguage_ of the video (see **figure 7**). +![insert a video](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/media%20content/add-video.JPG) +**Figure 7: link a video with _Video URL_, _Mime Type_, _Title_ and _Language_** + +5. In the case you want to set a _featured_ video, select the _Enter tags for the video_ input field and enter _Featured_ (a small popup supports your input). When you're done, you'll need to click the _Add button_ next to the text box that attaches this tag to the video (see **figure 8**). +![Add a featured video](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/media%20content/add-featured-video.JPG) +**Figure 8: add a featured video** + +# Competitor logos +The competitors of the sailing events can be equipped with their club flags. The following steps provide an exemplary guidance about how to add a club flag to its corresponding competitor. All available clubflags are located at the S3 storage under the folder `~`/`clubflags`/. + +## Required file format +* **File format:** .png with transparent background +* **File resolution:** about 100 x 50 pixel (file size < 25 kB) +* **Storage location:** `~`/`clubflags`/`country`/ +e.g. `~`/`clubflags`/`germany`/`clubflag1.png` + +## Exemplary instructions +1. Access the administration console _eventname(-master?)_.sapsailing.com/gwt/AdminConsle.html and login with your user credentials. + +2. Select _Tracked races_ and click on the _Competitors_ tab (see **figure 9**). + +3. Select the competitor, click on the _Edit_ button (see **figure 9**) and insert the Flag image URL of the corresponding club flag. +![Select the competitors tab within the admin console](https://s3-eu-west-1.amazonaws.com/media.sapsailing.com/wiki/how%20to/media%20content/clubflag-selection.JPG) +**Figure 9: competitors tab of the admin console** 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 diff --git a/wiki/howto/setup/webserver/VideoWorking13.JPG b/wiki/howto/setup/webserver/VideoWorking13.JPG index d0aed64f1ba..3922505fc67 100644 Binary files a/wiki/howto/setup/webserver/VideoWorking13.JPG and b/wiki/howto/setup/webserver/VideoWorking13.JPG differ diff --git a/wiki/howto/setup/webserver/addMedia11.JPG b/wiki/howto/setup/webserver/addMedia11.JPG deleted file mode 100644 index 715cb051fd1..00000000000 Binary files a/wiki/howto/setup/webserver/addMedia11.JPG and /dev/null differ diff --git a/wiki/howto/setup/webserver/addMedia_9.JPG b/wiki/howto/setup/webserver/addMedia_9.JPG deleted file mode 100644 index 3da6786d77d..00000000000 Binary files a/wiki/howto/setup/webserver/addMedia_9.JPG and /dev/null differ diff --git a/wiki/howto/setup/webserver/appstart4.JPG b/wiki/howto/setup/webserver/appstart4.JPG deleted file mode 100644 index 9f4fbf255e1..00000000000 Binary files a/wiki/howto/setup/webserver/appstart4.JPG and /dev/null differ diff --git a/wiki/howto/setup/webserver/choosingFolder6.JPG b/wiki/howto/setup/webserver/choosingFolder6.JPG deleted file mode 100644 index 1e762cf4536..00000000000 Binary files a/wiki/howto/setup/webserver/choosingFolder6.JPG and /dev/null differ diff --git a/wiki/howto/setup/webserver/chrome-webserver.md b/wiki/howto/setup/webserver/chrome-webserver.md deleted file mode 100644 index 1f99b6d7c55..00000000000 --- a/wiki/howto/setup/webserver/chrome-webserver.md +++ /dev/null @@ -1,61 +0,0 @@ -# Setup locally hosted 360° Videos -[[_TOC_]] - -### Installing the Webserver - -* Download the addon from [https://static.sapsailing.com/webserver-chrome/web-server-chrome.crx](https://static.sapsailing.com/webserver-chrome/web-server-chrome.crx),
      if downloading in chrome it might try to directly install, and give an error that this is not supported, ignore this -![](download0.JPG) -

      -* open the extensions page in chrome [chrome://extensions/](chrome://extensions/) -![](extensionPage1.JPG) -

      -* the next step will approximatly need 1 minute, there will be no visible response during that time, this is expected -* Drag the crx file from a file manager over the extensions page. A hover notification similar to "Drop to install" should appear -![](draginstall2.JPG) -

      -* A few moments later, a confirmation dialog should appear, confirm it to install the chrome app -![](dialog3.JPG) - -### Using the Webserver -* The webserver can be started from [chrome://apps/](chrome://apps/), it should have a title "Web Server" and a 200 ok logo -![](appstart4.JPG) -

      -* Upon clicking the button, a new Window with the Servers main view should open. Keep this open, as with default configuration the Server will be closed together with the config window -![](webserver5.JPG) -

      -* Ensure that under "Show Advanced Settings" the checkbox "Set CORS headers" is enabled -![](cors6.JPG) -

      -* Choose the folder with the videos -![](choosingFolder6.JPG) -* Under http://127.0.0.1:8887 (default) the files of the folder should be browseable -![](hostedFiles7.JPG) - -### adding a 360 Video -* After navigating into the RaceBoard -* Click manage Media, if this button does not appear, check that you are signed in (upper right corner) -![](managefiles8.JPG) -

      -* Click add at the bottom of the newly opened Media Manage dialog -* You should now see the following dialog: -![](addMedia8.JPG) -

      -* Copy the url of a video from the former part -![](addMedia_9.JPG) -* And paste it into the URL textbox -![](addMedia10.JPG) -

      -* Delete any trailing data, after the .MP4 -![](addMedia11.JPG) -

      -* wait for the autodetection (this will most likely fail, as the sapsailing server cannot reach your locally hosted file, this is ok. Dismiss the dialog warning about this)
      -1. manually set the MimeType to mp4panorama in case of a 360 video, use mp4 for a normal 2D video from other sources -2. set a proper Starttime. With the Set to default button, the starttime of the race will be selected, please adjust as required.
      It is later possible to properly synchronize the video, if a small delay exists, so this does not have to be perfect, the close this is the easier the synchronisazion will be -![](addMedia12.JPG) -

      -* Close the Dialog with OK, a small video player should appear (it's content might be black, this is ok), if it does not appear please try reloading the page -![](VideoWorking13.JPG) -* Using the edit button and the various +&- increment buttons, the video can be fine synchronized if required, do not forget to click save if adjusted -* Once the timeSlider is within a valid range (determined by the starttime and duration of the video) the player will start and play concurrently with the map. - - diff --git a/wiki/howto/setup/webserver/cors6.JPG b/wiki/howto/setup/webserver/cors6.JPG deleted file mode 100644 index b8833324737..00000000000 Binary files a/wiki/howto/setup/webserver/cors6.JPG and /dev/null differ diff --git a/wiki/howto/setup/webserver/dialog3.JPG b/wiki/howto/setup/webserver/dialog3.JPG deleted file mode 100644 index 95800789b29..00000000000 Binary files a/wiki/howto/setup/webserver/dialog3.JPG and /dev/null differ diff --git a/wiki/howto/setup/webserver/download0.JPG b/wiki/howto/setup/webserver/download0.JPG deleted file mode 100644 index 95237344713..00000000000 Binary files a/wiki/howto/setup/webserver/download0.JPG and /dev/null differ diff --git a/wiki/howto/setup/webserver/draginstall2.JPG b/wiki/howto/setup/webserver/draginstall2.JPG deleted file mode 100644 index 5b33237562d..00000000000 Binary files a/wiki/howto/setup/webserver/draginstall2.JPG and /dev/null differ diff --git a/wiki/howto/setup/webserver/extensionPage1.JPG b/wiki/howto/setup/webserver/extensionPage1.JPG deleted file mode 100644 index 0f49f10d729..00000000000 Binary files a/wiki/howto/setup/webserver/extensionPage1.JPG and /dev/null differ diff --git a/wiki/howto/setup/webserver/hostedFiles7.JPG b/wiki/howto/setup/webserver/hostedFiles7.JPG deleted file mode 100644 index 7a8967e52f2..00000000000 Binary files a/wiki/howto/setup/webserver/hostedFiles7.JPG and /dev/null differ diff --git a/wiki/howto/setup/webserver/nginx-1.JPG b/wiki/howto/setup/webserver/nginx-1.JPG new file mode 100644 index 00000000000..71a98b6ba96 Binary files /dev/null and b/wiki/howto/setup/webserver/nginx-1.JPG differ diff --git a/wiki/howto/setup/webserver/nginx-2.JPG b/wiki/howto/setup/webserver/nginx-2.JPG new file mode 100644 index 00000000000..bb96e96724f Binary files /dev/null and b/wiki/howto/setup/webserver/nginx-2.JPG differ diff --git a/wiki/howto/setup/webserver/nginx-3.JPG b/wiki/howto/setup/webserver/nginx-3.JPG new file mode 100644 index 00000000000..c0d57cc79d0 Binary files /dev/null and b/wiki/howto/setup/webserver/nginx-3.JPG differ diff --git a/wiki/howto/setup/webserver/nginx-4.JPG b/wiki/howto/setup/webserver/nginx-4.JPG new file mode 100644 index 00000000000..9b23520e473 Binary files /dev/null and b/wiki/howto/setup/webserver/nginx-4.JPG differ diff --git a/wiki/howto/setup/webserver/nginx-5-win.JPG b/wiki/howto/setup/webserver/nginx-5-win.JPG new file mode 100644 index 00000000000..e619ab8e662 Binary files /dev/null and b/wiki/howto/setup/webserver/nginx-5-win.JPG differ diff --git a/wiki/howto/setup/webserver/nginx-6.JPG b/wiki/howto/setup/webserver/nginx-6.JPG new file mode 100644 index 00000000000..d8921f4cc28 Binary files /dev/null and b/wiki/howto/setup/webserver/nginx-6.JPG differ diff --git a/wiki/howto/setup/webserver/nginx-7.JPG b/wiki/howto/setup/webserver/nginx-7.JPG new file mode 100644 index 00000000000..59b7352a4d0 Binary files /dev/null and b/wiki/howto/setup/webserver/nginx-7.JPG differ diff --git a/wiki/howto/setup/webserver/nginx-8.JPG b/wiki/howto/setup/webserver/nginx-8.JPG new file mode 100644 index 00000000000..83d5e90a79b Binary files /dev/null and b/wiki/howto/setup/webserver/nginx-8.JPG differ diff --git a/wiki/howto/setup/webserver/nginx-9.JPG b/wiki/howto/setup/webserver/nginx-9.JPG new file mode 100644 index 00000000000..24bec58d035 Binary files /dev/null and b/wiki/howto/setup/webserver/nginx-9.JPG differ diff --git a/wiki/howto/setup/webserver/nginx-a.JPG b/wiki/howto/setup/webserver/nginx-a.JPG new file mode 100644 index 00000000000..892c0a6cafc Binary files /dev/null and b/wiki/howto/setup/webserver/nginx-a.JPG differ diff --git a/wiki/howto/setup/webserver/nginx-b.JPG b/wiki/howto/setup/webserver/nginx-b.JPG new file mode 100644 index 00000000000..1d51f42f7fd Binary files /dev/null and b/wiki/howto/setup/webserver/nginx-b.JPG differ diff --git a/wiki/howto/setup/webserver/nginx-c.JPG b/wiki/howto/setup/webserver/nginx-c.JPG new file mode 100644 index 00000000000..0aff3f4ece7 Binary files /dev/null and b/wiki/howto/setup/webserver/nginx-c.JPG differ diff --git a/wiki/howto/setup/webserver/nginx-webserver.md b/wiki/howto/setup/webserver/nginx-webserver.md new file mode 100644 index 00000000000..8d09236e3cb --- /dev/null +++ b/wiki/howto/setup/webserver/nginx-webserver.md @@ -0,0 +1,86 @@ +# Setup locally hosted 360° Videos +[[_TOC_]] + +## Installing the Webserver + +* Download the zip from [https://static.sapsailing.com/webserver-nginx/nginx-videohost.zip](https://static.sapsailing.com/webserver-nginx/nginx-videohost.zip). +![](nginx-1.JPG) +

      +* Extract the zip into some easily reachable folder. (eg the Desktop) using the inbuild extractor of your operating system. +![](nginx-2.JPG) +

      +* The extracted folder should look like this: +![](nginx-3.JPG) + + +### Windows specific + +* If you are running windows, double click the start_windows.bat
      +![](nginx-4.JPG) +

      +* Upon this a security dialog appears, confirm it
      +![](nginx-5-win.JPG) +

      +* After this a cmd.exe window should open that looks similar to this one
      +![](nginx-6.JPG) +

      + +### Mac specific + +* If you are running MacOSX, execute start_maxos.command +* This script will start nginx with the correct configuration. +* If nginx is not already installed, it will be installed using homebrew +* If homebrew is not installed, it will also be installed prior +* If there are any dialogs requesting to install required software, confirm them. + +### Linux specific + +If you are on Linux you can:
      +* Try to start the supplied 64bit ubuntu static build ./nginx_ubuntu
      +* if it crashes/ does not work use your systems packagemanager to download nginx and start it with nginx -c movieHost.conf
      +* or use the supplied shell script "build_linux.sh" to download the required sources and compile nginx for your system.
      + +## Testing the Server + +* Browse to [http://127.0.0.1:8080/](http://127.0.0.1:8080/) the following website should appear. If you are on a different computer, replace the ip address with one the server can be reached with. +![](nginx-7.JPG) +

      +* To add new videos go to the folder place\_videos\_here +![](nginx-8.JPG) +

      +* And copy your files into it +![](nginx-9.JPG) +

      +* Following a refresh in the browser (F5) the video file should appear in the list: +![](nginx-a.JPG) +

      +* Click on it to verify it is reachable, note that depending on the browser 360° videos will look distorted. +![](nginx-b.JPG) +

      + +## adding a 360 Video + +* After navigating into the RaceBoard +* Click manage Media, if this button does not appear, check that you are signed in (upper right corner) +![](managefiles8.JPG) +

      +* Click add at the bottom of the newly opened Media Manage dialog +* You should now see the following dialog:
      +![](addMedia8.JPG) +

      +* Copy the url of a video from the former part
      +![](nginx-c.JPG) +* And paste it into the URL textbox
      +![](addMedia10.JPG) +

      +* Wait for the autodetection. The autodetection will prefill starttime and mimeType with information that can be derived from the video itself, please check that:
      +the MimeType is set to mp4panorama in case of a 360 video, or mp4 for a normal 2D video
      +Ensure a proper starttime is set, this should be the time, when the recording of the video was started.
      With the Set to default button, the starttime of the video can be set to the start of the race.
      It is later possible to properly synchronize the video, if a small delay exists, so this does not have to be perfect, but the fine synchronization is easier if it is close.

      +![](addMedia12.JPG) +

      +* Close the Dialog with OK, a small video player should appear (it's content might be black, this is ok), if it does not appear please try reloading the page +* Using the edit button and the various +&- increment buttons, the video can be fine synchronized if required, do not forget to click save if adjusted

      +![](VideoWorking13.JPG) +* Once the timeSlider is within a valid range (determined by the starttime and duration of the video) the player will start and play concurrently with the map. + + diff --git a/wiki/howto/setup/webserver/webserver5.JPG b/wiki/howto/setup/webserver/webserver5.JPG deleted file mode 100644 index 57bc8c68d90..00000000000 Binary files a/wiki/howto/setup/webserver/webserver5.JPG and /dev/null differ diff --git a/wiki/info/landscape/amazon-ec2.md b/wiki/info/landscape/amazon-ec2.md index 8ff007360ff..033b8f29024 100644 --- a/wiki/info/landscape/amazon-ec2.md +++ b/wiki/info/landscape/amazon-ec2.md @@ -200,6 +200,8 @@ To reach your multi instance via "ssv.sapsailing.com", perform the following ste Notice the overwritten health check port that is now pointing directly to the instance with its `SERVER_PORT` 8888. +BE CAREFUL please use for a live-server and live-master-server the traffic port for Health Checks. + 2. Add the "SL Multi-Instance Sailing Server" instance to the target group.