mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-13 22:15:26 +00:00
Merge branch 'master' into aws-automation
Change-Id: Iec97cffac19502d4f4b78f684a1ff9a78c04205b
This commit is contained in:
commit
10e0eec3be
510 files changed
+7742
-2112
No files matched your search
@@ -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/
|
||||
|
||||
Executable
+10
@@ -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; }'
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
+40
@@ -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
|
||||
+40
@@ -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
|
||||
+40
@@ -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
|
||||
+40
@@ -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
|
||||
+40
@@ -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 при наибольшем отношении к входной скорости
|
||||
+40
@@ -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=最高比率下驶入速度的绝对实际风向角
|
||||
+2
-3
@@ -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() {
|
||||
|
||||
+2
-1
@@ -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"
|
||||
+25
@@ -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
|
||||
|
||||
+17
@@ -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();
|
||||
}
|
||||
+8
-1
@@ -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;
|
||||
|
||||
|
||||
+4
@@ -27,4 +27,8 @@ public enum RaceLogTrackingState {
|
||||
public boolean isForTracking() {
|
||||
return this != NOT_A_RACELOG_TRACKED_RACE;
|
||||
}
|
||||
|
||||
public boolean isTracking() {
|
||||
return this == TRACKING;
|
||||
}
|
||||
}
|
||||
+1
@@ -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,
|
||||
|
||||
+4
-4
@@ -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
|
||||
|
||||
Executable
+57
@@ -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);
|
||||
}
|
||||
Executable
+118
@@ -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
|
||||
|
||||
+5
-9
@@ -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);
|
||||
|
||||
+1
@@ -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,
|
||||
|
||||
+5
-16
@@ -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);
|
||||
|
||||
-7
@@ -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);
|
||||
|
||||
+24
-5
@@ -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);
|
||||
|
||||
+2
@@ -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
|
||||
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package com.sap.sailing.domain.abstractlog.race;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public interface RaceLogCourseAreaChangedEvent extends RaceLogEvent {
|
||||
|
||||
Serializable getCourseAreaId();
|
||||
|
||||
}
|
||||
-2
@@ -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);
|
||||
|
||||
-5
@@ -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);
|
||||
}
|
||||
|
||||
-6
@@ -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) {
|
||||
|
||||
|
||||
-36
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -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.
|
||||
|
||||
+2
-2
@@ -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());
|
||||
}
|
||||
}
|
||||
+18
-16
@@ -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);
|
||||
}
|
||||
}
|
||||
+3
-5
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+122
@@ -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));
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -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>
|
||||
+7
@@ -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
|
||||
+3
@@ -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/,\
|
||||
.
|
||||
@@ -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>
|
||||
+1
@@ -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}]
|
||||
+17
@@ -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"
|
||||
}]
|
||||
+67
@@ -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()));
|
||||
}
|
||||
}
|
||||
+99
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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>
|
||||
+7
@@ -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
|
||||
+3
@@ -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
@@ -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>
|
||||
+56
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+109
@@ -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 + "]";
|
||||
}
|
||||
}
|
||||
+73
@@ -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");
|
||||
}
|
||||
}
|
||||
+76
@@ -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));
|
||||
}
|
||||
}
|
||||
+170
@@ -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());
|
||||
}
|
||||
}
|
||||
+116
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
-6
@@ -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);
|
||||
|
||||
+19
-4
@@ -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
|
||||
|
||||
-14
@@ -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);
|
||||
|
||||
}
|
||||
-22
@@ -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();
|
||||
|
||||
+54
-27
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Executable
+33
@@ -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;
|
||||
}
|
||||
+29
@@ -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;
|
||||
}
|
||||
Executable
+38
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+3
-5
@@ -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 "${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work" -war "${project_loc:com.sap.sailing.gwt.ui}" -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -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 "${project_loc:com.sap.sailing.gwt.ui}/.tmp/gwt-work" -war "${project_loc:com.sap.sailing.gwt.ui}" -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -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>
|
||||
+54
@@ -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());
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -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);
|
||||
}
|
||||
|
||||
+5
-4
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/EventWindFinderUtil.java
Executable
+46
@@ -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;
|
||||
}
|
||||
}
|
||||
+6
-4
@@ -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
@@ -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();
|
||||
|
||||
+17
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+5
-1
@@ -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
|
||||
|
||||
+30
@@ -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()) {
|
||||
|
||||
+8
-5
@@ -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);
|
||||
|
||||
}
|
||||
+1
-1
@@ -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
Reference in new issue
Block a user