Merge branch 'master' into aws-automation

Change-Id: Iec97cffac19502d4f4b78f684a1ff9a78c04205b
This commit is contained in:
Axel Uhl committed 2018-03-03 11:29:33 +01:00
commit 10e0eec3be
510 files changed
+7742 -2112

No files matched your search

+4 -4
View File
@@ -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 name=\"port\"><Property name=\"jetty.port\" default=\"$SERVER_PORT\"\/><\/Set>/g" "$ACDIR/configuration/jetty/etc/jetty-selector.xml"
sed -i "s/^.*jetty.port.*$/<Set name=\"port\"><Property name=\"jetty.port\" default=\"$SERVER_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/
+10
View File
@@ -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; }'
+6 -1
View File
@@ -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
+1 -1
View File
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 при наибольшем отношении к входной скорости
@@ -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=最高比率下驶入速度的绝对实际风向角
@@ -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() {
@@ -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
@@ -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
@@ -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"
@@ -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()+")";
}
}
@@ -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
@@ -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<String> getWindFinderReviewedSpotsCollectionIds();
}
@@ -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;
@@ -27,4 +27,8 @@ public enum RaceLogTrackingState {
public boolean isForTracking() {
return this != NOT_A_RACELOG_TRACKED_RACE;
}
public boolean isTracking() {
return this == TRACKING;
}
}
@@ -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,
@@ -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
@@ -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);
}
@@ -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;
}
}
@@ -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
@@ -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<String> windFinderReviewedSpotCollectionIds = (List<String>) 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<Competitor> 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<Competitor> competitors, DBObject dbObject) {
TimePoint ofFixedPassing = loadTimePoint(dbObject, FieldNames.TIMEPOINT_OF_FIXED_MARKPASSING);
@@ -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,
@@ -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);
@@ -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);
@@ -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);
@@ -7,6 +7,8 @@ package com.sap.sailing.domain.abstractlog.race;
*/
public interface CompetitorResults extends Iterable<CompetitorResult> {
boolean add(CompetitorResult result);
void clear();
int size();
/**
* @return {@code true} if there are more than one {@link CompetitorResult} contained with equal
@@ -1,9 +0,0 @@
package com.sap.sailing.domain.abstractlog.race;
import java.io.Serializable;
public interface RaceLogCourseAreaChangedEvent extends RaceLogEvent {
Serializable getCourseAreaId();
}
@@ -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);
@@ -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);
}
@@ -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) {
@@ -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);
}
}
@@ -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.
@@ -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
@@ -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
@@ -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());
}
}
@@ -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<Competitor, GPSFixMoving> 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);
}
}
@@ -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<Sideline> sidelines,
WindStore windStore, long delayToLiveInMillis,
@@ -620,7 +617,8 @@ public class MockedTrackedRace implements DynamicTrackedRace {
}
@Override
public void removeRaceListener(RaceListener listener) {
public Future<Boolean> removeRaceListener(RaceListener listener) {
return null;
}
};
}
@@ -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.<Sideline> emptyList(),
EmptyWindStore.INSTANCE, 0, 0, 0, /* useMarkPassingCalculator */ false, OneDesignRankingMetric::new,
mock(RaceLogResolver.class));
}
}
@@ -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) {
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="resources"/>
<classpathentry kind="output" path="bin"/>
</classpath>
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.sap.sailing.domain.windfinderadapter.test</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -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
@@ -0,0 +1,3 @@
eclipse.preferences.version=1
pluginProject.extensions=false
resolve.requirebundle=false
@@ -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"
@@ -0,0 +1,5 @@
source.. = src/,\
resources/
output.. = bin/
bin.includes = META-INF/,\
.
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.domain.windfinderadapter.test</artifactId>
<packaging>eclipse-test-plugin</packaging>
</project>
@@ -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}]
@@ -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"
}]
@@ -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<Pair<Wind, WindSource>> 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<Boolean>() {
@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<Boolean>() {
@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<Wind, WindSource> firstFix = wind.iterator().next();
assertTrue(new HashSet<>(Arrays.asList("de15", "10044N")).contains(((WindSourceWithAdditionalID) firstFix.getB()).getId()));
}
}
@@ -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<Spot> 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<Spot> spots = new ReviewedSpotsCollectionImpl("schilksee").getSpots(/* cached */ false);
final Set<String> 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);
}
}
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>com.sap.sailing.domain.windfinderadapter</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
@@ -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
@@ -0,0 +1,3 @@
eclipse.preferences.version=1
pluginProject.extensions=false
resolve.requirebundle=false
@@ -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
@@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>root</artifactId>
<groupId>com.sap.sailing</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.sap.sailing.domain.windfinderadapter</artifactId>
<packaging>eclipse-plugin</packaging>
</project>
@@ -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<ServiceRegistration<?>> 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;
}
}
@@ -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<ConcurrentMap<String, Spot>> spotsByIdCache;
public ReviewedSpotsCollectionImpl(String id) {
this.id = id;
this.parser = new WindFinderReportParser();
this.spotsByIdCache = ThreadPoolUtil.INSTANCE.getDefaultForegroundTaskThreadPoolExecutor().schedule(()->{
final ConcurrentMap<String, Spot> 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<Spot> getSpots(boolean cached) throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException {
final Iterable<Spot> 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<Spot> loadSpots() throws IOException, ParseException, MalformedURLException {
final Iterable<Spot> 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 + "]";
}
}
@@ -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<Wind> measurements = getAllMeasurements();
final Wind result;
if (measurements != null && !Util.isEmpty(measurements)) {
final List<Wind> 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<Wind> getAllMeasurementsAfter(TimePoint timePoint) throws MalformedURLException, IOException, ParseException, org.json.simple.parser.ParseException {
final List<Wind> result = new ArrayList<>();
for (final Wind measurement : getAllMeasurements()) {
if (timePoint == null || measurement.getTimePoint().after(timePoint)) {
result.add(measurement);
}
}
return result;
}
@Override
public Iterable<Wind> getAllMeasurements()
throws IOException, MalformedURLException, ParseException, org.json.simple.parser.ParseException {
final InputStream response = (InputStream) getMeasurementsUrl().getContent();
final Iterable<Wind> 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");
}
}
@@ -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<Wind> parse(Position position, JSONArray jsonOfSeveralMeasurements) throws NumberFormatException, ParseException {
final List<Wind> 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<Spot> parseSpots(JSONArray jsonOfMultipleSpots, ReviewedSpotsCollection reviewedSpotsCollection) {
return Util.map(jsonOfMultipleSpots, jsonOfSingleSpot->parseSpot((JSONObject) jsonOfSingleSpot, reviewedSpotsCollection));
}
}
@@ -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<RaceDefinition, WindTracker> 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<ConcurrentMap<String, ReviewedSpotsCollection>> 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<ReviewedSpotsCollection> reviewedSpotsCollections;
private final ServiceTracker<WindFinderReviewedSpotsCollectionIdProvider, WindFinderReviewedSpotsCollectionIdProvider> 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<String, ReviewedSpotsCollection> 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<String, ReviewedSpotsCollection> cache = new ConcurrentHashMap<>();
this.reviewedSpotsCollectionsByIdCache = new Future<ConcurrentMap<String,ReviewedSpotsCollection>>() {
@Override public boolean cancel(boolean mayInterruptIfRunning) { return true; }
@Override public boolean isCancelled() { return false; }
@Override public boolean isDone() { return true; }
@Override public ConcurrentMap<String, ReviewedSpotsCollection> get() { return cache; }
@Override public ConcurrentMap<String, ReviewedSpotsCollection> 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<ReviewedSpotsCollection> getReviewedSpotsCollections(boolean cached) throws InterruptedException, ExecutionException {
final Set<ReviewedSpotsCollection> 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<ReviewedSpotsCollection> loadSpotCollectionsFromProvider(boolean lookupInCache) throws InterruptedException, ExecutionException {
final Set<ReviewedSpotsCollection> 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());
}
}
@@ -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.
* <p>
*
* {@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<Spot, TimePoint> 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<ReviewedSpotsCollection> 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<Spot> usefulSpots = getUsefulSpots();
for (final Spot usefulSpot : usefulSpots) {
final Iterable<Wind> 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<Spot> getUsefulSpots() throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException {
final Set<Spot> 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());
}
}
@@ -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
@@ -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 <code>leaderboardGroups</code>.
*/
void setLeaderboardGroups(Iterable<LeaderboardGroup> 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<String> 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<String> 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<String> reviewedSpotsCollectionIds);
}
@@ -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<LeaderboardGroup> leaderboardGroups;
private ConcurrentMap<String, Boolean> 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<String> getWindFinderReviewedSpotsCollectionIds() {
return Collections.unmodifiableSet(windFinderReviewedSpotsCollectionIds.keySet());
}
@Override
public Iterable<String> getAllFinderSpotIdsUsedByTrackedRacesInEvent() {
final Set<String> 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<String> reviewedSpotsCollectionIds) {
windFinderReviewedSpotsCollectionIds.clear();
for (final String reviewedSpotsCollectionId : reviewedSpotsCollectionIds) {
windFinderReviewedSpotsCollectionIds.putIfAbsent(reviewedSpotsCollectionId, true);
}
}
}
@@ -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);
@@ -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
@@ -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.
@@ -1075,6 +1075,34 @@ public interface TrackedRace extends Serializable, IsManagedByCache<SharedDomain
return getVelocityMadeGood(competitor, timePoint, windPositionMode, new LeaderboardDTOCalculationReuseCache(timePoint));
}
/**
* 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.
*/
default Bearing getTWA(Competitor competitor, TimePoint timePoint, WindLegTypeAndLegBearingCache cache) {
Bearing twa = null;
final GPSFixTrack<Competitor, GPSFixMoving> 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<SharedDomain
*/
SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint, WindPositionMode windPositionMode,
WindLegTypeAndLegBearingCache cache);
}
@@ -1,6 +1,7 @@
package com.sap.sailing.domain.tracking;
import java.io.Serializable;
import java.util.concurrent.Future;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.base.Competitor;
@@ -89,10 +90,12 @@ public interface TrackedRegatta extends Serializable {
*/
void addRaceListener(RaceListener listener);
void removeRaceListener(RaceListener listener);
/**
* Removes the given listener and returns a {@link Future} that will be completed
* when it is guaranteed that no more events will be fired to the listener.
*/
Future<Boolean> removeRaceListener(RaceListener listener);
int getTotalPoints(Competitor competitor, TimePoint timePoint) throws NoWindException;
void removeTrackedRace(RaceDefinition raceDefinition);
}
@@ -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();
@@ -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<RaceDefinition, TrackedRace> trackedRaces;
private transient ConcurrentMap<RaceListener, RaceListener> 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:
* <ul>
* <li>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.</li>
* <li>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.</li>
* <li>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.
* </ul>
*/
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<RaceDefinition, TrackedRace>();
raceListeners = new ConcurrentHashMap<RaceListener, RaceListener>();
eventQueue = new WorkQueue();
}
private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException {
ois.defaultReadObject();
this.raceListeners = new ConcurrentHashMap<RaceListener, RaceListener>();
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<RaceListener> fireEventCallback) {
final Set<RaceListener> 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<TrackedRace> trackedRacesCopy = new ArrayList<>();
lockTrackedRacesForRead();
try {
raceListeners.put(listener, listener);
final List<TrackedRace> 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<Boolean> removeRaceListener(RaceListener listener) {
final CompletableFuture<Boolean> result = new CompletableFuture<Boolean>();
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
@@ -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<Runnable> 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);
}
}
}
}
}
@@ -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.<p>
*
* 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<Spot> getSpots(boolean cached) throws MalformedURLException, IOException, ParseException, InterruptedException, ExecutionException;
}
@@ -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<Wind> 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<Wind> getAllMeasurementsAfter(TimePoint timePoint) throws MalformedURLException, IOException, ParseException, org.json.simple.parser.ParseException;
}
@@ -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<ReviewedSpotsCollection> 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;
}
@@ -47,6 +47,7 @@
<plugin id="com.sap.sailing.domain.swisstimingreplayadapter" autoStart="true" startLevel="3" />
<plugin id="com.sap.sailing.domain.tractracadapter" autoStart="false" startLevel="4" />
<plugin id="com.sap.sailing.domain.tractracadapter.persistence" autoStart="true" startLevel="3" />
<plugin id="com.sap.sailing.domain.windfinderadapter" autoStart="true" startLevel="3" />
<plugin id="com.sap.sailing.ess40.resultimport" autoStart="true" startLevel="3" />
<plugin id="com.sap.sailing.expeditionconnector.persistence" autoStart="true" startLevel="3" />
<plugin id="com.sap.sailing.freg.resultimport" autoStart="true" startLevel="3" />
@@ -75,12 +76,10 @@
<plugin id="org.apache.felix.shell.remote" autoStart="true" startLevel="0" />
<plugin id="org.eclipse.equinox.common" autoStart="true" startLevel="2" />
<plugin id="org.eclipse.equinox.simpleconfigurator" autoStart="true" startLevel="2" />
<plugin id="org.eclipse.jetty.continuation" autoStart="true" startLevel="4" />
<plugin id="org.eclipse.jetty.deploy" autoStart="true" startLevel="4" />
<plugin id="org.eclipse.jetty.http" autoStart="true" startLevel="4" />
<plugin id="org.eclipse.jetty.io" autoStart="true" startLevel="4" />
<plugin id="org.eclipse.jetty.jmx" autoStart="true" startLevel="4" />
<plugin id="org.eclipse.jetty.nested" autoStart="true" startLevel="4" />
<plugin id="org.eclipse.jetty.osgi.boot" autoStart="true" startLevel="3" />
<plugin id="org.eclipse.jetty.security" autoStart="true" startLevel="4" />
<plugin id="org.eclipse.jetty.server" autoStart="true" startLevel="4" />
@@ -88,7 +87,6 @@
<plugin id="org.eclipse.jetty.util" autoStart="true" startLevel="4" />
<plugin id="org.eclipse.jetty.webapp" autoStart="true" startLevel="4" />
<plugin id="org.eclipse.jetty.xml" autoStart="true" startLevel="4" />
<plugin id="org.mortbay.jetty.util" autoStart="true" startLevel="4" />
<property name="eclipse.ignoreApp" value="true" />
<property name="osgi.noShutdown" value="true" />
</configurations>
+7
View File
@@ -67,6 +67,13 @@ Computes leaderboard information for sailing races and offers RESTful APIs to al
version="0.0.0"
unpack="false"/>
<plugin
id="com.sap.sailing.domain.windfinderadapter"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
<plugin
id="com.sap.sailing.server"
download-size="0"
@@ -139,9 +139,9 @@ public class SimpleGridFileReadingTest {
// alternatively for the 4-6 day forecast: http://tgftp.nws.noaa.gov/SL.us008001/ST.expr/DF.gr2/DC.ndfd/AR.oceanic/VP.004-007/
// validate graphically at http://tgftp.nws.noaa.gov/SL.us008001/ST.expr/DF.gr2/DC.ndfd/AR.oceanic/VP.004-007/
final Formatter errorLog = new Formatter(System.err);
Files.copy(new URL("http://tgftp.nws.noaa.gov/SL.us008001/ST.expr/DF.gr2/DC.ndfd/AR.oceanic/VP.001-003/ds.wdir.bin").openStream(),
Files.copy(new URL("http://tgftp.nws.noaa.gov/SL.us008001/ST.expr/DF.gr2/DC.ndfd/AR.conus/VP.001-003/ds.wdir.bin").openStream(),
new File("resources/ds.wdir.bin").toPath(), StandardCopyOption.REPLACE_EXISTING);
Files.copy(new URL("http://tgftp.nws.noaa.gov/SL.us008001/ST.expr/DF.gr2/DC.ndfd/AR.oceanic/VP.001-003/ds.wspd.bin").openStream(),
Files.copy(new URL("http://tgftp.nws.noaa.gov/SL.us008001/ST.expr/DF.gr2/DC.ndfd/AR.conus/VP.001-003/ds.wspd.bin").openStream(),
new File("resources/ds.wspd.bin").toPath(), StandardCopyOption.REPLACE_EXISTING);
FeatureDataset dataSetDirection = FeatureDatasetFactoryManager.open(FeatureType.ANY, "resources/ds.wdir.bin", /* task */ null, errorLog);
@@ -8,6 +8,7 @@ import java.util.Collections;
import java.util.List;
import java.util.NavigableSet;
import java.util.Set;
import java.util.concurrent.Future;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
@@ -320,10 +321,6 @@ public class MockedTrackedRace implements DynamicTrackedRace {
return null;
}
@Override
public void removeTrackedRace(RaceDefinition raceDefinition) {
}
@Override
public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable<Sideline> sidelines, WindStore windStore,
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
@@ -349,7 +346,8 @@ public class MockedTrackedRace implements DynamicTrackedRace {
}
@Override
public void removeRaceListener(RaceListener listener) {
public Future<Boolean> removeRaceListener(RaceListener listener) {
return null;
}
};
}
@@ -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<String, SeriesCreationParametersDTO> seriesCreationParameters = new LinkedHashMap<>();
final List<FleetDTO> 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<Pair<String, Integer>> 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<CompetitorDTO> 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<Triple<String, String, String>> 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);
}
}
@@ -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
@@ -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",
@@ -6,7 +6,7 @@
<stringAttribute key="com.google.gwt.eclipse.core.CLASSIC_DEVMODE_CODE_SERVER_PORT" value="9876"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9876"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.gwt.autoplay.AutoPlay"/>
<listEntry value="com.sap.sailing.gwt.ui.PairingList"/>
</listAttribute>
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/gwt/Home.html"/>
@@ -61,7 +61,7 @@
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-style PRETTY -incremental -workDir &quot;${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work&quot; -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.PairingList -startupUrl /gwt/PairingList.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/YoutubePopup.html com.sap.sailing.gwt.ui.YoutubePopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.autoplay.AutoPlay"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-style PRETTY -incremental -workDir &quot;${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work&quot; -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9876 -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/EmbeddedMapAndWindChart com.sap.sailing.gwt.ui.EmbeddedMapAndWindChart -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/YoutubePopup.html com.sap.sailing.gwt.ui.YoutubePopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.autoplay.AutoPlay -startupUrl /gwt/PairingList.html com.sap.sailing.gwt.ui.PairingList"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xms1024m -Xmx3072m -Dgwt.watchFileChanges=false"/>
</launchConfiguration>
@@ -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<SpotDTO> {
@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());
}
}
@@ -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<A
private LeaderboardWithZoomingPerspectiveLifecycle leaderboardLifecycle;
private RaceBoardPerspectiveLifecycle raceboardLifecycle;
public AutoplayPerspectiveLifecycle(AbstractLeaderboardDTO leaderboard) {
public AutoplayPerspectiveLifecycle(AbstractLeaderboardDTO leaderboard, UserService userService) {
leaderboardLifecycle = new LeaderboardWithZoomingPerspectiveLifecycle(leaderboard, StringMessages.INSTANCE);
//As we cannot know, if Bravo data is available later on, we will offer DetailTypes, that might be relevant despite not having data for them yet
raceboardLifecycle = new RaceBoardPerspectiveLifecycle(leaderboard, StringMessages.INSTANCE, DetailType.getAutoplayDetailTypesForChart());
raceboardLifecycle = new RaceBoardPerspectiveLifecycle(leaderboard, StringMessages.INSTANCE, DetailType.getAutoplayDetailTypesForChart(), userService);
addLifeCycle(leaderboardLifecycle);
addLifeCycle(raceboardLifecycle);
}
@@ -21,11 +21,12 @@ import com.sap.sse.gwt.client.shared.components.SettingsDialogForLinkSharing;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveCompositeSettings;
public class AutoPlayClassicConfiguration extends AutoPlayConfiguration {
private AutoPlayClientFactory cf;
@Override
public void startRootNode(AutoPlayClientFactory cf, AutoPlayContextDefinition context,
PerspectiveCompositeSettings<?> settings) {
this.cf = cf;
final UUID eventUUID = context.getEventId();
AsyncCallback<EventDTO> getEventByIdAsyncCallback = new AsyncCallback<EventDTO>() {
@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<AutoplayPerspectiveOwnSettings>) 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<AutoplayPerspectiveOwnSettings> autoplayPerspectiveSettings = autoplayLifecycle
.createDefaultSettings();
LinkWithSettingsGenerator<PerspectiveCompositeSettings<AutoplayPerspectiveOwnSettings>> 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());
}
@@ -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<SpotDTO> getWindFinderSpotsToConsider(Event event, WindFinderTrackerFactory windFinderTrackerFactory, boolean useCachedSpotsForTrackedRaces) {
final Set<SpotDTO> 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;
}
}
@@ -22,8 +22,12 @@
/* font-size: 0; */
margin-bottom: -1px;
}
.tabExtension {
float: right;
}
.tabExtension > * {
margin-left: 0.5em;
}
</ui:style>
@@ -33,12 +37,10 @@
<div class="{res.mediaCss.grid}">
<div class="{res.mediaCss.small12} {res.mediaCss.columns}">
<g:FlowPanel ui:field="tabExtension" addStyleNames="{style.tabExtension}" />
<c:TabBar ui:field="tabBar"
addStyleNames="{style.navigation}" />
<c:TabBar ui:field="tabBar" addStyleNames="{style.navigation}" />
</div>
</div>
</g:HTMLPanel>
<g:SimplePanel ui:field="tabContentPanelUi"
addStyleNames="{style.content}" />
<g:SimplePanel ui:field="tabContentPanelUi" addStyleNames="{style.content}" />
</g:FlowPanel>
</ui:UiBinder>
@@ -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();
@@ -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;
}
}
@@ -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<EventViewDTO>, 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
@@ -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<SpotDTO> 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<SpotDTO> getAllWindFinderSpotIdsUsedByEvent() {
final Iterable<SpotDTO> result;
if (allWindFinderSpotIdsUsedByEvent == null) {
result = Collections.emptySet();
} else {
result = allWindFinderSpotIdsUsedByEvent;
}
return result;
}
public void setAllWindFinderSpotsUsedByEvent(Iterable<SpotDTO> windFinderSpots) {
this.allWindFinderSpotIdsUsedByEvent = new ArrayList<>();
if (windFinderSpots != null) {
Util.addAll(windFinderSpots, this.allWindFinderSpotIdsUsedByEvent);
}
}
public String getLocationAndVenueAndCountry() {
String venue = getLocationAndVenue();
if(getVenueCountry() != null && !getVenueCountry().isEmpty()) {
@@ -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<SimpleRaceMetadataDTO, String, String> raceboardUrlFactory;
private final PopupPanel parent;
public RaceviewerLaunchPad(SimpleRaceMetadataDTO data, final PopupPanel parent) {
RaceviewerLaunchPad(SimpleRaceMetadataDTO data,
BiFunction<SimpleRaceMetadataDTO, String, String> 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);
}
@@ -55,7 +55,7 @@ public class RaceviewerLaunchPadCell<T extends RaceMetadataDTO<?>> 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;
}
Loaded 100 of 510 files, more files were not shown because too many files have changed in this diff. Show more