diff --git a/configuration/000-macros.conf b/configuration/000-macros.conf index 476674cf1a9..29a0a42de29 100755 --- a/configuration/000-macros.conf +++ b/configuration/000-macros.conf @@ -28,6 +28,7 @@ RewriteRule ^/(.*)$ http://$IP:$PORT/$1 [P,L] ProxyPassReverse / http://$IP:$PORT/ + ProxyPreserveHost On diff --git a/doc/SAPSailingAnalytics_Administrator_Training.pptx b/doc/SAPSailingAnalytics_Administrator_Training.pptx index 5975c9a410a..89daba9e0c9 100644 Binary files a/doc/SAPSailingAnalytics_Administrator_Training.pptx and b/doc/SAPSailingAnalytics_Administrator_Training.pptx differ diff --git a/home.md b/home.md index 8efa8dbcca3..0e4fd864502 100644 --- a/home.md +++ b/home.md @@ -70,6 +70,7 @@ SAP is at the center of today’s technology revolution, developing innovations ### Security * [[Fortify Tests|wiki/info/security/fortify]] * [[SSL / HTTPS Support|wiki/info/security/ssl-support]] + * [[Permission Concept|wiki/info/security/permission-concept]] ### Miscellaneous diff --git a/ios/SAPTracker/cfg/VERSION b/ios/SAPTracker/cfg/VERSION index afaf360d37f..c787b213b07 100644 --- a/ios/SAPTracker/cfg/VERSION +++ b/ios/SAPTracker/cfg/VERSION @@ -1 +1 @@ -1.0.0 \ No newline at end of file +1.0.22 diff --git a/ios/SAPTracker/src/xcode/SAPTracker/Base.lproj/InfoPlist.strings b/ios/SAPTracker/src/xcode/SAPTracker/Base.lproj/InfoPlist.strings index 5631ef6d48f..b658e7ad19f 100644 --- a/ios/SAPTracker/src/xcode/SAPTracker/Base.lproj/InfoPlist.strings +++ b/ios/SAPTracker/src/xcode/SAPTracker/Base.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSLocationAlwaysUsageDescription" = "In order to track your entire race your phone needs to continue tracking also when you switch off or lock the screen which helps save battery and avoids unintentionally stopping the tracking process."; "NSCameraUsageDescription" = "The camera is required to perform a check-in using QR code."; +"NSPhotoLibraryUsageDescription" = "To replace the Team Photo the app requires access to the photo library."; diff --git a/ios/SAPTracker/src/xcode/SAPTracker/de.lproj/InfoPlist.strings b/ios/SAPTracker/src/xcode/SAPTracker/de.lproj/InfoPlist.strings index 3555dd9d4a9..d9c04bd623d 100644 --- a/ios/SAPTracker/src/xcode/SAPTracker/de.lproj/InfoPlist.strings +++ b/ios/SAPTracker/src/xcode/SAPTracker/de.lproj/InfoPlist.strings @@ -8,3 +8,4 @@ "NSLocationAlwaysUsageDescription" = "Um das ganze Rennen mit Deinem Gerät zu tracken, muss das Tracking auch dann fortgesetzt werden, wenn der Bildschirm abgeschaltet oder gesperrt wird. Das hilft, die Batterielaufzeit zu verlängern und versehentlich das Tracking zu stoppen."; "NSCameraUsageDescription" = "Die Kamera wird benötigt, um per QR-Code einen Check-In auszuführen."; +"NSPhotoLibraryUsageDescription" = "Um das Team Photo zu ersetzen, benötigt die App den Zugriff auf ihr Fotoalbum."; diff --git a/java/com.google.gwt.dev/META-INF/MANIFEST.MF b/java/com.google.gwt.dev/META-INF/MANIFEST.MF index 599d78f8748..f1bad3cb695 100644 --- a/java/com.google.gwt.dev/META-INF/MANIFEST.MF +++ b/java/com.google.gwt.dev/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: gwt-dev Bundle-SymbolicName: com.google.gwt.dev -Bundle-Version: 2.8.1 +Bundle-Version: 2.8.2 Bundle-Vendor: GOOGLE Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-ClassPath: lib/gwt-dev.jar, diff --git a/java/com.google.gwt.dev/lib/gwt-dev.jar b/java/com.google.gwt.dev/lib/gwt-dev.jar index 1ddb4c5895e..30d16cb740e 100644 Binary files a/java/com.google.gwt.dev/lib/gwt-dev.jar and b/java/com.google.gwt.dev/lib/gwt-dev.jar differ diff --git a/java/com.google.gwt.dev/pom.xml b/java/com.google.gwt.dev/pom.xml index d98224f3ece..4f26a246167 100644 --- a/java/com.google.gwt.dev/pom.xml +++ b/java/com.google.gwt.dev/pom.xml @@ -8,6 +8,6 @@ 1.0.0-SNAPSHOT com.google.gwt.dev - 2.8.1 + 2.8.2 eclipse-plugin diff --git a/java/com.google.gwt.servlet/META-INF/MANIFEST.MF b/java/com.google.gwt.servlet/META-INF/MANIFEST.MF index 6c96b8d13c3..4998709609d 100644 --- a/java/com.google.gwt.servlet/META-INF/MANIFEST.MF +++ b/java/com.google.gwt.servlet/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Dev Bundle-SymbolicName: com.google.gwt.servlet -Bundle-Version: 2.8.1 +Bundle-Version: 2.8.2 Bundle-Vendor: GOOGLE Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-ClassPath: lib/gwt-servlet.jar, diff --git a/java/com.google.gwt.servlet/lib/gwt-servlet-deps.jar b/java/com.google.gwt.servlet/lib/gwt-servlet-deps.jar index be26accc5d3..430b627cd97 100644 Binary files a/java/com.google.gwt.servlet/lib/gwt-servlet-deps.jar and b/java/com.google.gwt.servlet/lib/gwt-servlet-deps.jar differ diff --git a/java/com.google.gwt.servlet/lib/gwt-servlet.jar b/java/com.google.gwt.servlet/lib/gwt-servlet.jar index 8088f0ab778..eee1e90525c 100644 Binary files a/java/com.google.gwt.servlet/lib/gwt-servlet.jar and b/java/com.google.gwt.servlet/lib/gwt-servlet.jar differ diff --git a/java/com.google.gwt.servlet/pom.xml b/java/com.google.gwt.servlet/pom.xml index 0375728464c..e4095b23ea9 100644 --- a/java/com.google.gwt.servlet/pom.xml +++ b/java/com.google.gwt.servlet/pom.xml @@ -8,6 +8,6 @@ 1.0.0-SNAPSHOT com.google.gwt.servlet - 2.8.1 + 2.8.2 eclipse-plugin diff --git a/java/com.google.gwt.user/META-INF/MANIFEST.MF b/java/com.google.gwt.user/META-INF/MANIFEST.MF index 970dfcb6845..75da7da6be4 100755 --- a/java/com.google.gwt.user/META-INF/MANIFEST.MF +++ b/java/com.google.gwt.user/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: gwt-user Bundle-SymbolicName: com.google.gwt.user -Bundle-Version: 2.8.1 +Bundle-Version: 2.8.2 Bundle-Vendor: GOOGLE Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-ClassPath: lib/gwt-user.jar, @@ -187,4 +187,4 @@ Export-Package: com.google.gwt.activity.shared, jsinterop.annotations Require-Bundle: javax.servlet;bundle-version="3.1.0", org.junit;bundle-version="4.8.2";resolution:=optional, - com.google.gwt.dev;bundle-version="2.8.1" + com.google.gwt.dev;bundle-version="2.8.2" diff --git a/java/com.google.gwt.user/lib/gwt-user.jar b/java/com.google.gwt.user/lib/gwt-user.jar index 5f3b97fb725..4e8f0dedee2 100644 Binary files a/java/com.google.gwt.user/lib/gwt-user.jar and b/java/com.google.gwt.user/lib/gwt-user.jar differ diff --git a/java/com.google.gwt.user/pom.xml b/java/com.google.gwt.user/pom.xml index 20d9c3a295d..1c2a3ce390d 100644 --- a/java/com.google.gwt.user/pom.xml +++ b/java/com.google.gwt.user/pom.xml @@ -8,6 +8,6 @@ 1.0.0-SNAPSHOT com.google.gwt.user - 2.8.1 + 2.8.2 eclipse-plugin diff --git a/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF b/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF index fef72b6dee0..afec7de52c2 100644 --- a/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.dashboards.gwt/META-INF/MANIFEST.MF @@ -8,9 +8,9 @@ Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Require-Bundle: com.sap.sailing.domain, com.sap.sailing.server, - com.google.gwt.dev;bundle-version="2.8.1";resolution:=optional, - com.google.gwt.user;bundle-version="2.8.1";resolution:=optional, - com.google.gwt.servlet;bundle-version="2.8.1", + com.google.gwt.dev;bundle-version="2.8.2";resolution:=optional, + com.google.gwt.user;bundle-version="2.8.2";resolution:=optional, + com.google.gwt.servlet;bundle-version="2.8.2", com.sap.sse.common, com.sap.sailing.domain.common, com.sap.sailing.domain.shared.android, diff --git a/java/com.sap.sailing.dashboards.gwt/WEB-INF/web.xml b/java/com.sap.sailing.dashboards.gwt/WEB-INF/web.xml index ad6c4ff7907..33797f9786f 100644 --- a/java/com.sap.sailing.dashboards.gwt/WEB-INF/web.xml +++ b/java/com.sap.sailing.dashboards.gwt/WEB-INF/web.xml @@ -28,6 +28,15 @@ /service/sailing + + LocaleInjectionFilter + com.sap.sse.security.ui.server.LocaleInjectionFilter + + + LocaleInjectionFilter + *.html + + Dashboard Dispatch Service dashboardDispatch diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/RibDashboard.gwt.xml b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/RibDashboard.gwt.xml index d1e3a6da979..58eefbbfd95 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/RibDashboard.gwt.xml +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/RibDashboard.gwt.xml @@ -39,6 +39,8 @@ + + diff --git a/java/com.sap.sailing.datamining.shared/META-INF/MANIFEST.MF b/java/com.sap.sailing.datamining.shared/META-INF/MANIFEST.MF index e7b8053e644..8503bc4bc6b 100644 --- a/java/com.sap.sailing.datamining.shared/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.datamining.shared/META-INF/MANIFEST.MF @@ -8,5 +8,6 @@ Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ActivationPolicy: lazy Bundle-ClassPath: . Require-Bundle: com.sap.sse.datamining.shared, - com.sap.sailing.domain.common + com.sap.sailing.domain.common, + com.sap.sse.common Export-Package: com.sap.sailing.datamining.shared diff --git a/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/FoilingSegmentsDataMiningSettings.java b/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/FoilingSegmentsDataMiningSettings.java new file mode 100755 index 00000000000..4b451b7a8bf --- /dev/null +++ b/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/FoilingSegmentsDataMiningSettings.java @@ -0,0 +1,51 @@ +package com.sap.sailing.datamining.shared; + +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.tracking.BravoFix; +import com.sap.sse.common.Duration; +import com.sap.sse.common.settings.SerializableSettings; + +public class FoilingSegmentsDataMiningSettings extends SerializableSettings { + private static final long serialVersionUID = 3239182650750480678L; + private final Duration minimumFoilingSegmentDuration; + private final Duration minimumDurationBetweenAdjacentFoilingSegments; + private final Speed minimumSpeedForFoiling; + private final Speed maximumSpeedNotFoiling; + private final Distance minimumRideHeight; + + public FoilingSegmentsDataMiningSettings(Duration minimumFoilingSegmentDuration, Duration minimumDurationBetweenAdjacentFoilingSegments, + Speed minimumSpeedForFoiling, Speed maximumSpeedNotFoiling, Distance minimumRideHeight) { + super(); + this.minimumFoilingSegmentDuration = minimumFoilingSegmentDuration; + this.minimumDurationBetweenAdjacentFoilingSegments = minimumDurationBetweenAdjacentFoilingSegments; + this.minimumSpeedForFoiling = minimumSpeedForFoiling; + this.maximumSpeedNotFoiling = maximumSpeedNotFoiling; + this.minimumRideHeight = minimumRideHeight; + } + + public Duration getMinimumFoilingSegmentDuration() { + return minimumFoilingSegmentDuration; + } + public Duration getMinimumDurationBetweenAdjacentFoilingSegments() { + return minimumDurationBetweenAdjacentFoilingSegments; + } + public Speed getMinimumSpeedForFoiling() { + return minimumSpeedForFoiling; + } + public Speed getMaximumSpeedNotFoiling() { + return maximumSpeedNotFoiling; + } + public Distance getMinimumRideHeight() { + return minimumRideHeight; + } + + public static FoilingSegmentsDataMiningSettings createDefaultSettings() { + return new FoilingSegmentsDataMiningSettings( + /* minimumFoilingSegmentDuration */ null, + /* minimumDurationBetweenAdjacentFoilingSegments */ null, + /* minimumSpeedForFoiling */ null, + /* maximumSpeedNotFoiling */ null, /* minimumRideHeight */ BravoFix.MIN_FOILING_HEIGHT_THRESHOLD); + } + +} diff --git a/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/SailingDataMiningSerializationDummy.java b/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/SailingDataMiningSerializationDummy.java index b2b8b929498..a2b7e0e7eb6 100644 --- a/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/SailingDataMiningSerializationDummy.java +++ b/java/com.sap.sailing.datamining.shared/src/com/sap/sailing/datamining/shared/SailingDataMiningSerializationDummy.java @@ -5,18 +5,23 @@ import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.LegType; import com.sap.sailing.domain.common.ManeuverType; import com.sap.sailing.domain.common.NauticalSide; +import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.Tack; import com.sap.sailing.domain.common.impl.CentralAngleDistance; import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.common.impl.NauticalMileDistance; +import com.sap.sse.common.Duration; import com.sap.sse.datamining.shared.SerializationDummy; +import com.sap.sse.datamining.shared.data.AverageWithStats; @SuppressWarnings("unused") public final class SailingDataMiningSerializationDummy implements SerializationDummy { - private static final long serialVersionUID = 2L; + private static final long serialVersionUID = 345L; private LegType legType; private Distance distance; + private Speed speed; + private Duration duration; private Bearing bearing; private CentralAngleDistance centralAngleDistance; private MeterDistance meterDistance; @@ -24,6 +29,8 @@ public final class SailingDataMiningSerializationDummy implements SerializationD private ManeuverType maneuverType; private Tack tack; private NauticalSide nauticalSide; + private FoilingSegmentsDataMiningSettings foilingSegmentsSettings; + private AverageWithStats averageWithStats; private SailingDataMiningSerializationDummy() { } diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties index 3dbacaae762..e2ef2372a0e 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties @@ -25,6 +25,7 @@ GPSFixSailingDomainRetrieverChain=GPS Fixes BravoFixSailingDomainRetrieverChain=Bravo Fixes BravoFixTrackSailingDomainRetrieverChain=Bravo Tracks FoilingSegmentsSailingDomainRetrieverChain=Foiled Segments +FoilingSegmentName=Foiled Segment Name WindFixSailingDomainRetrieverChain=Wind Fixes MarkPassingSailingDomainRetrieverChain=Mark Passings RaceResultSailingDomainRetrieverChain=Race Results diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties index ed2a6c71a3d..f88e7b0e8ab 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_de.properties @@ -23,6 +23,7 @@ GPSFixSailingDomainRetrieverChain=GPS-Fixes BravoFixSailingDomainRetrieverChain=Bravo-Fixes BravoFixTrackSailingDomainRetrieverChain=Bravo Messreihen FoilingSegmentsSailingDomainRetrieverChain=Gefoilte Abschnitte +FoilingSegmentName=Name des gefoilten Abschnitts WindFixSailingDomainRetrieverChain=Windmessungen MarkPassingSailingDomainRetrieverChain=Tonnenrundungen RaceResultSailingDomainRetrieverChain=Rennergebnisse diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_es.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_es.properties index 4362e8fbcc1..95ff91055b5 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_es.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_es.properties @@ -12,6 +12,7 @@ Regatta=Regata CourseArea=Zona de regata BoatClass=Clase de bote Fleet=Flota +RaceColumn=Columna de carrera Race=Carrera Competitor=Competidor Boat=Bote @@ -23,7 +24,8 @@ LegSailingDomainRetrieverChain=Tramos GPSFixSailingDomainRetrieverChain=Puntos de GPS BravoFixSailingDomainRetrieverChain=Puntos de Bravo BravoFixTrackSailingDomainRetrieverChain=Seguimientos Bravo -FoilingSegmentsSailingDomainRetrieverChain=Segmentos laminados +FoilingSegmentsSailingDomainRetrieverChain=Apéndices de foil +FoilingSegmentName=Nombre de apéndice de foil WindFixSailingDomainRetrieverChain=Puntos de viento MarkPassingSailingDomainRetrieverChain=Superaciones de marca RaceResultSailingDomainRetrieverChain=Resultados de la carrera @@ -99,16 +101,16 @@ windSpeedKnots=Velocidad del viento real (nudos) speedInKnots=Velocidad (nudos) bearing=Dirección (°T) WindTrack=Seguimiento del viento -timeSpentFoiling=Tiempo empleado en laminado +timeSpentFoiling=Tiempo empleado con foils yaw=Guiñada pitch=Paso heel=Escora rideHeight=Altura de circulación rideHeightPort=Altura de circulación del casco a babor rideHeightStarboard=Altura de circulación del casco a estribor -IsFoiling=Se está laminando -FoilingDuration=Duración del laminado -FoilingDistance=Distancia laminada +IsFoiling=Con foils +FoilingDuration=Duración con foils +FoilingDistance=Distancia con foils TakeoffSpeedInKnotsInKnots=Velocidad de despegue en nudos LandingSpeedInKnots=Velocidad de aterrizaje en nudos AbsoluteTrueWindAngleAtTakeoff=Ángulo absoluto del viento real (TWA) en despegue diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_fr.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_fr.properties index 692067303cf..3ce43dc3a30 100755 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_fr.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_fr.properties @@ -1,5 +1,5 @@ Speed=Vitesse -SpeedTenSecondsBefore=Vitesse 10s avant +SpeedTenSecondsBefore=Vitesse 10 s avant DistanceTraveled=Distance parcourue Name=Nom LegType=Type de portion de parcours @@ -12,6 +12,7 @@ Regatta=Régate CourseArea=Zone de course BoatClass=Catégorie de bateau Fleet=Flotte +RaceColumn=Colonne Course Race=Course Competitor=Concurrent Boat=Bateau @@ -24,6 +25,7 @@ GPSFixSailingDomainRetrieverChain=Positions GPS BravoFixSailingDomainRetrieverChain=Positions Bravo BravoFixTrackSailingDomainRetrieverChain=Enregistrements Bravo FoilingSegmentsSailingDomainRetrieverChain=Tronçons en planing +FoilingSegmentName=Nom du tronçon en planing WindFixSailingDomainRetrieverChain=Points vent MarkPassingSailingDomainRetrieverChain=Passages des marques RaceResultSailingDomainRetrieverChain=Résultats de course diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ja.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ja.properties index ef868bd9663..f717f279487 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ja.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ja.properties @@ -12,6 +12,7 @@ Regatta=レガッタ CourseArea=コースエリア BoatClass=艇種 Fleet=フリート +RaceColumn=レース列 Race=レース Competitor=競技者 Boat=艇 @@ -24,6 +25,7 @@ GPSFixSailingDomainRetrieverChain=GPS フィックス BravoFixSailingDomainRetrieverChain=Bravo フィックス BravoFixTrackSailingDomainRetrieverChain=Bravo トラック FoilingSegmentsSailingDomainRetrieverChain=フォイリングしたセグメント +FoilingSegmentName=フォイリングしたセグメント名 WindFixSailingDomainRetrieverChain=風フィックス MarkPassingSailingDomainRetrieverChain=マーク通過 RaceResultSailingDomainRetrieverChain=レース結果 diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_pt.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_pt.properties index e139ce21998..85f42265561 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_pt.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_pt.properties @@ -12,6 +12,7 @@ Regatta=Regata CourseArea=Área do percurso BoatClass=Classe de barcos Fleet=Frota +RaceColumn=Coluna da corrida Race=Corrida Competitor=Competidor Boat=Barco @@ -24,6 +25,7 @@ GPSFixSailingDomainRetrieverChain=Pontos fixos de GPS BravoFixSailingDomainRetrieverChain=Pontos fixos Bravo BravoFixTrackSailingDomainRetrieverChain=Pistas Bravo FoilingSegmentsSailingDomainRetrieverChain=Segmentos com hidrofólio +FoilingSegmentName=Nome do segmento com hidrofólio WindFixSailingDomainRetrieverChain=Pontos fixos de vento MarkPassingSailingDomainRetrieverChain=Passagens na marca RaceResultSailingDomainRetrieverChain=Resultados da corrida diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ru.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ru.properties index 6c3d05ecee6..85f6623a1fa 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ru.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_ru.properties @@ -12,6 +12,7 @@ Regatta=Регата CourseArea=Зона гонки BoatClass=Класс лодок Fleet=Флот +RaceColumn=Столбец гонки Race=Гонка Competitor=Участник Boat=Лодка @@ -24,6 +25,7 @@ GPSFixSailingDomainRetrieverChain=Замеры GPS BravoFixSailingDomainRetrieverChain=Замеры Bravo BravoFixTrackSailingDomainRetrieverChain=Отслеживания Bravo FoilingSegmentsSailingDomainRetrieverChain=Сегменты в крыльевом режиме +FoilingSegmentName=Имя сегмента в крыльевом режиме WindFixSailingDomainRetrieverChain=Замеры ветра MarkPassingSailingDomainRetrieverChain=Прохождения отметок RaceResultSailingDomainRetrieverChain=Результаты гонки diff --git a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_zh.properties b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_zh.properties index a64008c0791..3ef6c3ac327 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_zh.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages_zh.properties @@ -12,6 +12,7 @@ Regatta=比赛 CourseArea=场地区域 BoatClass=船只级别 Fleet=船队 +RaceColumn=比赛轮次列 Race=比赛轮次 Competitor=参赛队 Boat=船只 @@ -24,6 +25,7 @@ GPSFixSailingDomainRetrieverChain=GPS 修复 BravoFixSailingDomainRetrieverChain=Bravo 修复 BravoFixTrackSailingDomainRetrieverChain=Bravo 跟踪 FoilingSegmentsSailingDomainRetrieverChain=水翼腾空段 +FoilingSegmentName=水翼腾空段名称 WindFixSailingDomainRetrieverChain=风力修复 MarkPassingSailingDomainRetrieverChain=绕标 RaceResultSailingDomainRetrieverChain=比赛轮次结果 diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrievalChainDefinitions.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrievalChainDefinitions.java index f18f6e03604..1f0efb4adb4 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrievalChainDefinitions.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingDataRetrievalChainDefinitions.java @@ -33,6 +33,7 @@ import com.sap.sailing.datamining.impl.components.TrackedRaceRetrievalProcessor; import com.sap.sailing.datamining.impl.components.WindFixRetrievalProcessor; import com.sap.sailing.datamining.impl.components.WindTrackRetrievalProcessor; import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContext; +import com.sap.sailing.datamining.shared.FoilingSegmentsDataMiningSettings; import com.sap.sailing.server.RacingEventService; import com.sap.sse.datamining.components.DataRetrieverChainDefinition; import com.sap.sse.datamining.impl.components.SimpleDataRetrieverChainDefinition; @@ -75,7 +76,7 @@ public class SailingDataRetrievalChainDefinitions { final DataRetrieverChainDefinition foilingSegmentsRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( raceOfCompetitorRetrieverChainDefinition, HasFoilingSegmentContext.class, "FoilingSegmentsSailingDomainRetrieverChain"); foilingSegmentsRetrieverChainDefinition.endWith(RaceOfCompetitorRetrievalProcessor.class, FoilingSegmentRetrievalProcessor.class, - HasFoilingSegmentContext.class, "FoilingSegments"); + HasFoilingSegmentContext.class, FoilingSegmentsDataMiningSettings.class, FoilingSegmentsDataMiningSettings.createDefaultSettings(), "FoilingSegments"); dataRetrieverChainDefinitions.add(foilingSegmentsRetrieverChainDefinition); final DataRetrieverChainDefinition legOfCompetitorRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasFoilingSegmentContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasFoilingSegmentContext.java index 346eda3d464..8ae200b223d 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasFoilingSegmentContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasFoilingSegmentContext.java @@ -23,6 +23,9 @@ public interface HasFoilingSegmentContext { TimePoint getEndOfFoilingSegment(); + @Dimension(messageKey="FoilingSegmentName") + String getName(); + @Statistic(messageKey="FoilingDuration") Duration getDuration(); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/FoilingSegmentRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/FoilingSegmentRetrievalProcessor.java index d5c64b06cbd..fbfa62cd126 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/FoilingSegmentRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/FoilingSegmentRetrievalProcessor.java @@ -2,12 +2,14 @@ package com.sap.sailing.datamining.impl.components; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.concurrent.ExecutorService; import com.sap.sailing.datamining.data.HasFoilingSegmentContext; import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext; import com.sap.sailing.datamining.impl.data.BravoFixTrackWithContext; import com.sap.sailing.datamining.impl.data.FoilingSegmentWithContext; +import com.sap.sailing.datamining.shared.FoilingSegmentsDataMiningSettings; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.tracking.BravoFix; import com.sap.sailing.domain.tracking.BravoFixTrack; @@ -18,14 +20,17 @@ import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.components.AbstractRetrievalProcessor; public class FoilingSegmentRetrievalProcessor extends AbstractRetrievalProcessor { + private final FoilingSegmentsDataMiningSettings settings; - public FoilingSegmentRetrievalProcessor(ExecutorService executor, Collection> resultReceivers, int retrievalLevel) { + public FoilingSegmentRetrievalProcessor(ExecutorService executor, Collection> resultReceivers, + FoilingSegmentsDataMiningSettings settings, int retrievalLevel) { super(HasRaceOfCompetitorContext.class, HasFoilingSegmentContext.class, executor, resultReceivers, retrievalLevel); + this.settings = settings; } @Override protected Iterable retrieveData(HasRaceOfCompetitorContext element) { - Collection foilingSegments = new ArrayList<>(); + List foilingSegments = new ArrayList<>(); final TrackedRace trackedRace = element.getTrackedRaceContext().getTrackedRace(); final TimePoint startOfRace = element.getTrackedRaceContext().getTrackedRace().getStartOfRace(); if (startOfRace != null) { @@ -45,19 +50,24 @@ public class FoilingSegmentRetrievalProcessor extends AbstractRetrievalProcessor bravoFixTrack.lockForRead(); try { for (final BravoFix bravoFix : bravoFixTrack.getFixes(startOfRace, /* fromInclusive */ true, end, /* toInclusive */ false)) { - if (isFoiling) { - if (!bravoFix.isFoiling()) { - isFoiling = false; - foilingSegments.add(createFoilingSegment(startOfSegment, - last /* don't include the last interval ending at the non-foiling fix */, - element, bravoFixTrack)); + final boolean currentFixIsFoiling = + (bravoFix.isFoiling(settings.getMinimumRideHeight()) && + (settings.getMinimumSpeedForFoiling() == null || settings.getMinimumSpeedForFoiling().compareTo( + element.getTrackedRaceContext().getTrackedRace().getTrack(element.getCompetitor()).getEstimatedSpeed(bravoFix.getTimePoint())) <= 0)) || + (settings.getMaximumSpeedNotFoiling() != null && settings.getMaximumSpeedNotFoiling().compareTo( + element.getTrackedRaceContext().getTrackedRace().getTrack(element.getCompetitor()).getEstimatedSpeed(bravoFix.getTimePoint())) <= 0); + if (currentFixIsFoiling != isFoiling) { + if (currentFixIsFoiling) { + startOfSegment = bravoFix.getTimePoint(); + } else { + if (settings.getMinimumFoilingSegmentDuration() == null || + startOfSegment.until(last).compareTo(settings.getMinimumFoilingSegmentDuration()) >= 0) { + addOrMergeFoilingSegment(element, foilingSegments, bravoFixTrack, startOfSegment, + last /* don't include the last interval ending at the non-foiling fix */); + } startOfSegment = null; } - } else { - if (bravoFix.isFoiling()) { - isFoiling = true; - startOfSegment = bravoFix.getTimePoint(); - } + isFoiling = currentFixIsFoiling; } last = bravoFix.getTimePoint(); } @@ -65,13 +75,31 @@ public class FoilingSegmentRetrievalProcessor extends AbstractRetrievalProcessor bravoFixTrack.unlockAfterRead(); } if (isFoiling) { - foilingSegments.add(createFoilingSegment(startOfSegment, end, element, bravoFixTrack)); + addOrMergeFoilingSegment(element, foilingSegments, bravoFixTrack, startOfSegment, end); } } } return foilingSegments; } + private void addOrMergeFoilingSegment(HasRaceOfCompetitorContext element, + List foilingSegments, final BravoFixTrack bravoFixTrack, + TimePoint startOfSegment, TimePoint endOfSegment) { + if (foilingSegments.isEmpty() || settings.getMinimumDurationBetweenAdjacentFoilingSegments() == null) { + foilingSegments.add(createFoilingSegment(startOfSegment, endOfSegment, element, bravoFixTrack)); + } else { + final HasFoilingSegmentContext previousSegment = foilingSegments.get(foilingSegments.size()-1); + final TimePoint previousEnd = previousSegment.getEndOfFoilingSegment(); + if (previousEnd.until(startOfSegment).compareTo(settings.getMinimumDurationBetweenAdjacentFoilingSegments()) < 0) { + // merge: + foilingSegments.set(foilingSegments.size()-1, createFoilingSegment(previousSegment.getStartOfFoilingSegment(), endOfSegment, element, bravoFixTrack)); + } else { + // add; duration between the segments is large enough + foilingSegments.add(createFoilingSegment(startOfSegment, endOfSegment, element, bravoFixTrack)); + } + } + } + private HasFoilingSegmentContext createFoilingSegment(TimePoint startOfSegment, TimePoint endOfSegment, HasRaceOfCompetitorContext raceOfCompetitorContext, BravoFixTrack bravoFixTrack) { return new FoilingSegmentWithContext(new BravoFixTrackWithContext(raceOfCompetitorContext, bravoFixTrack), startOfSegment, endOfSegment); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/AbstractParallelAverageAggregationProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/AbstractParallelAverageAggregationProcessor.java new file mode 100755 index 00000000000..18cd52840b7 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/AbstractParallelAverageAggregationProcessor.java @@ -0,0 +1,80 @@ +package com.sap.sailing.datamining.impl.components.aggregators; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ExecutorService; + +import com.sap.sse.datamining.components.AggregationProcessorDefinition; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.impl.components.GroupedDataEntry; +import com.sap.sse.datamining.impl.components.aggregators.AbstractParallelComparableMaxAggregationProcessor; +import com.sap.sse.datamining.impl.components.aggregators.AbstractParallelComparableMinAggregationProcessor; +import com.sap.sse.datamining.impl.components.aggregators.AbstractParallelGroupedDataStoringAggregationProcessor; +import com.sap.sse.datamining.shared.GroupKey; +import com.sap.sse.datamining.shared.data.AverageWithStats; +import com.sap.sse.datamining.shared.impl.AverageWithStatsImpl; + +public abstract class AbstractParallelAverageAggregationProcessor> + extends AbstractParallelGroupedDataStoringAggregationProcessor> { + + private final AbstractParallelSumAggregationProcessor sumAggregationProcessor; + private final AbstractParallelComparableMinAggregationProcessor minAggregationProcessor; + private final AbstractParallelComparableMaxAggregationProcessor maxAggregationProcessor; + private final Map elementAmountPerKey; + + public AbstractParallelAverageAggregationProcessor(ExecutorService executor, + Collection>, ?>> resultReceivers, + AbstractParallelSumAggregationProcessor sumAggregationProcessor, + AbstractParallelComparableMinAggregationProcessor minAggregationProcessor, + AbstractParallelComparableMaxAggregationProcessor maxAggregationProcessor) { + super(executor, resultReceivers, "Average"); + elementAmountPerKey = new HashMap<>(); + this.sumAggregationProcessor = sumAggregationProcessor; + this.minAggregationProcessor = minAggregationProcessor; + this.maxAggregationProcessor = maxAggregationProcessor; + } + + public abstract AggregationProcessorDefinition> getProcessorDefinition(); + + @Override + protected void storeElement(GroupedDataEntry element) { + incrementElementAmount(element); + sumAggregationProcessor.handleElement(element); + minAggregationProcessor.handleElement(element); + maxAggregationProcessor.handleElement(element); + } + + private void incrementElementAmount(GroupedDataEntry element) { + GroupKey key = element.getKey(); + if (!elementAmountPerKey.containsKey(key)) { + elementAmountPerKey.put(key, 0); + } + Integer currentAmount = elementAmountPerKey.get(key); + elementAmountPerKey.put(key, currentAmount + 1); + } + + protected abstract T divide(T sum, long elementCount); + + @Override + protected Map> aggregateResult() { + Map> result = new HashMap<>(); + Map sumAggregation = sumAggregationProcessor.getResult(); + Map minAggregation = minAggregationProcessor.getResult(); + Map maxAggregation = maxAggregationProcessor.getResult(); + for (Entry sumAggregationEntry : sumAggregation.entrySet()) { + GroupKey key = sumAggregationEntry.getKey(); + result.put(key, new AverageWithStatsImpl<>( + /* average */ divide(sumAggregationEntry.getValue(), elementAmountPerKey.get(key).longValue()), + /* min */ minAggregation.get(key), + /* max */ maxAggregation.get(key), + /* median */ null, + /* standardDeviation */ null, + /* count */ elementAmountPerKey.get(key).longValue(), + getProcessorDefinition().getExtractedType().getName())); + } + return result; + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/AbstractParallelSumAggregationProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/AbstractParallelSumAggregationProcessor.java new file mode 100755 index 00000000000..a2a9f27cdc6 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/AbstractParallelSumAggregationProcessor.java @@ -0,0 +1,39 @@ +package com.sap.sailing.datamining.impl.components.aggregators; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; + +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.impl.components.GroupedDataEntry; +import com.sap.sse.datamining.impl.components.aggregators.AbstractParallelGroupedDataAggregationProcessor; +import com.sap.sse.datamining.shared.GroupKey; + +public abstract class AbstractParallelSumAggregationProcessor extends + AbstractParallelGroupedDataAggregationProcessor { + + private final Map results; + + public AbstractParallelSumAggregationProcessor(ExecutorService executor, Collection, ?>> resultReceivers) { + super(executor, resultReceivers, "Sum"); + results = new HashMap<>(); + } + + protected abstract T add(T t1, T t2); + + @Override + protected void handleElement(GroupedDataEntry element) { + GroupKey key = element.getKey(); + if (!results.containsKey(key)) { + results.put(key, element.getDataEntry()); + } else { + results.put(key, add(results.get(key), element.getDataEntry())); + } + } + + @Override + protected Map getResult() { + return results; + } +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelBearingMinAggregationProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelBearingMinAggregationProcessor.java index 4cdaf0364cf..40769033a7b 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelBearingMinAggregationProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelBearingMinAggregationProcessor.java @@ -8,11 +8,11 @@ import com.sap.sailing.domain.common.Bearing; import com.sap.sse.datamining.components.AggregationProcessorDefinition; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.components.SimpleAggregationProcessorDefinition; -import com.sap.sse.datamining.impl.components.aggregators.AbstractParallelComparableMaxAggregationProcessor; +import com.sap.sse.datamining.impl.components.aggregators.AbstractParallelComparableMinAggregationProcessor; import com.sap.sse.datamining.shared.GroupKey; public class ParallelBearingMinAggregationProcessor extends - AbstractParallelComparableMaxAggregationProcessor { + AbstractParallelComparableMinAggregationProcessor { private static final AggregationProcessorDefinition DEFINITION = new SimpleAggregationProcessorDefinition<>(Bearing.class, Bearing.class, "Minimum", ParallelBearingMinAggregationProcessor.class); diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDistanceAverageAggregationProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDistanceAverageAggregationProcessor.java index de4acb69a79..f79264ad57d 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDistanceAverageAggregationProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDistanceAverageAggregationProcessor.java @@ -1,63 +1,44 @@ package com.sap.sailing.datamining.impl.components.aggregators; import java.util.Collection; -import java.util.HashMap; +import java.util.Collections; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.ExecutorService; import com.sap.sailing.domain.common.Distance; import com.sap.sse.datamining.components.AggregationProcessorDefinition; import com.sap.sse.datamining.components.Processor; -import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.impl.components.SimpleAggregationProcessorDefinition; -import com.sap.sse.datamining.impl.components.aggregators.AbstractParallelGroupedDataStoringAggregationProcessor; import com.sap.sse.datamining.shared.GroupKey; +import com.sap.sse.datamining.shared.data.AverageWithStats; public class ParallelDistanceAverageAggregationProcessor - extends AbstractParallelGroupedDataStoringAggregationProcessor { + extends AbstractParallelAverageAggregationProcessor { + private static final Class _c = AverageWithStats.class; + @SuppressWarnings("unchecked") + private static final Class> _cc = (Class>) _c; + + private static final AggregationProcessorDefinition> DEFINITION = + new SimpleAggregationProcessorDefinition<>(Distance.class, _cc, "Average", ParallelDistanceAverageAggregationProcessor.class); - private static final AggregationProcessorDefinition DEFINITION = - new SimpleAggregationProcessorDefinition<>(Distance.class, Distance.class, "Average", ParallelDistanceAverageAggregationProcessor.class); - - public static AggregationProcessorDefinition getDefinition() { + public ParallelDistanceAverageAggregationProcessor(ExecutorService executor, + Collection>, ?>> resultReceivers) { + super(executor, resultReceivers, new ParallelDistanceSumAggregationProcessor(executor, /* resultReceivers */ Collections.emptyList()), + new ParallelDistanceMinAggregationProcessor(executor, /* resultReceivers */ Collections.emptyList()), + new ParallelDistanceMaxAggregationProcessor(executor, /* resultReceivers */ Collections.emptyList())); + } + + public static AggregationProcessorDefinition> getDefinition() { return DEFINITION; } - private final ParallelDistanceSumAggregationProcessor sumAggregationProcessor; - private final Map elementAmountPerKey; - - public ParallelDistanceAverageAggregationProcessor(ExecutorService executor, - Collection, ?>> resultReceivers) { - super(executor, resultReceivers, "Average"); - elementAmountPerKey = new HashMap<>(); - sumAggregationProcessor = new ParallelDistanceSumAggregationProcessor(executor, resultReceivers); + @Override + public AggregationProcessorDefinition> getProcessorDefinition() { + return DEFINITION; } @Override - protected void storeElement(GroupedDataEntry element) { - incrementElementAmount(element); - sumAggregationProcessor.handleElement(element); + protected Distance divide(Distance sum, long elementCount) { + return sum.scale(1. / (double) elementCount); } - - private void incrementElementAmount(GroupedDataEntry element) { - GroupKey key = element.getKey(); - if (!elementAmountPerKey.containsKey(key)) { - elementAmountPerKey.put(key, 0); - } - Integer currentAmount = elementAmountPerKey.get(key); - elementAmountPerKey.put(key, currentAmount + 1); - } - - @Override - protected Map aggregateResult() { - Map result = new HashMap<>(); - Map sumAggregation = sumAggregationProcessor.getResult(); - for (Entry sumAggregationEntry : sumAggregation.entrySet()) { - GroupKey key = sumAggregationEntry.getKey(); - result.put(key, sumAggregationEntry.getValue().scale(1. / elementAmountPerKey.get(key).doubleValue())); - } - return result; - } - } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDistanceSumAggregationProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDistanceSumAggregationProcessor.java index b5d067050d1..32a9d42c741 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDistanceSumAggregationProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDistanceSumAggregationProcessor.java @@ -1,20 +1,17 @@ package com.sap.sailing.datamining.impl.components.aggregators; import java.util.Collection; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import com.sap.sailing.domain.common.Distance; import com.sap.sse.datamining.components.AggregationProcessorDefinition; import com.sap.sse.datamining.components.Processor; -import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.impl.components.SimpleAggregationProcessorDefinition; -import com.sap.sse.datamining.impl.components.aggregators.AbstractParallelGroupedDataAggregationProcessor; import com.sap.sse.datamining.shared.GroupKey; public class ParallelDistanceSumAggregationProcessor extends - AbstractParallelGroupedDataAggregationProcessor { + AbstractParallelSumAggregationProcessor { private static final AggregationProcessorDefinition DEFINITION = new SimpleAggregationProcessorDefinition<>(Distance.class, Distance.class, "Sum", ParallelDistanceSumAggregationProcessor.class); @@ -23,25 +20,12 @@ public class ParallelDistanceSumAggregationProcessor extends return DEFINITION; } - private final Map results; - public ParallelDistanceSumAggregationProcessor(ExecutorService executor, Collection, ?>> resultReceivers) { - super(executor, resultReceivers, "Sum"); - results = new HashMap<>(); + super(executor, resultReceivers); } @Override - protected void handleElement(GroupedDataEntry element) { - GroupKey key = element.getKey(); - if (!results.containsKey(key)) { - results.put(key, element.getDataEntry()); - } else { - results.put(key, results.get(key).add(element.getDataEntry())); - } - } - - @Override - protected Map getResult() { - return results; + protected Distance add(Distance t1, Distance t2) { + return t1.add(t2); } } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDurationAverageAggregationProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDurationAverageAggregationProcessor.java index 252d67e7105..2c0ef499035 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDurationAverageAggregationProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDurationAverageAggregationProcessor.java @@ -1,63 +1,46 @@ package com.sap.sailing.datamining.impl.components.aggregators; import java.util.Collection; -import java.util.HashMap; +import java.util.Collections; import java.util.Map; -import java.util.Map.Entry; import java.util.concurrent.ExecutorService; import com.sap.sse.common.Duration; import com.sap.sse.datamining.components.AggregationProcessorDefinition; import com.sap.sse.datamining.components.Processor; -import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.impl.components.SimpleAggregationProcessorDefinition; -import com.sap.sse.datamining.impl.components.aggregators.AbstractParallelGroupedDataStoringAggregationProcessor; import com.sap.sse.datamining.shared.GroupKey; +import com.sap.sse.datamining.shared.data.AverageWithStats; public class ParallelDurationAverageAggregationProcessor - extends AbstractParallelGroupedDataStoringAggregationProcessor { + extends AbstractParallelAverageAggregationProcessor { - private static final AggregationProcessorDefinition DEFINITION = - new SimpleAggregationProcessorDefinition<>(Duration.class, Duration.class, "Average", ParallelDurationAverageAggregationProcessor.class); + private static final Class _c = AverageWithStats.class; + @SuppressWarnings("unchecked") + private static final Class> _cc = (Class>) _c; + + private static final AggregationProcessorDefinition> DEFINITION = + new SimpleAggregationProcessorDefinition<>(Duration.class, _cc, "Average", ParallelDurationAverageAggregationProcessor.class); - public static AggregationProcessorDefinition getDefinition() { + public static AggregationProcessorDefinition> getDefinition() { return DEFINITION; } - private final ParallelDurationSumAggregationProcessor sumAggregationProcessor; - private final Map elementAmountPerKey; - public ParallelDurationAverageAggregationProcessor(ExecutorService executor, - Collection, ?>> resultReceivers) { - super(executor, resultReceivers, "Average"); - elementAmountPerKey = new HashMap<>(); - sumAggregationProcessor = new ParallelDurationSumAggregationProcessor(executor, resultReceivers); + Collection>, ?>> resultReceivers) { + super(executor, resultReceivers, new ParallelDurationSumAggregationProcessor(executor, /* resultReceivers */ Collections.emptyList()), + new ParallelDurationMinAggregationProcessor(executor, /* resultReceivers */ Collections.emptyList()), + new ParallelDurationMaxAggregationProcessor(executor, /* resultReceivers */ Collections.emptyList())); } @Override - protected void storeElement(GroupedDataEntry element) { - incrementElementAmount(element); - sumAggregationProcessor.handleElement(element); - } - - private void incrementElementAmount(GroupedDataEntry element) { - GroupKey key = element.getKey(); - if (!elementAmountPerKey.containsKey(key)) { - elementAmountPerKey.put(key, 0); - } - Integer currentAmount = elementAmountPerKey.get(key); - elementAmountPerKey.put(key, currentAmount + 1); + public AggregationProcessorDefinition> getProcessorDefinition() { + return DEFINITION; } @Override - protected Map aggregateResult() { - Map result = new HashMap<>(); - Map sumAggregation = sumAggregationProcessor.getResult(); - for (Entry sumAggregationEntry : sumAggregation.entrySet()) { - GroupKey key = sumAggregationEntry.getKey(); - result.put(key, sumAggregationEntry.getValue().divide(elementAmountPerKey.get(key).doubleValue())); - } - return result; + protected Duration divide(Duration sum, long elementCount) { + return sum.divide((double) elementCount); } } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDurationSumAggregationProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDurationSumAggregationProcessor.java index 2b47767d9de..9bbcf206201 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDurationSumAggregationProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/aggregators/ParallelDurationSumAggregationProcessor.java @@ -1,20 +1,17 @@ package com.sap.sailing.datamining.impl.components.aggregators; import java.util.Collection; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import com.sap.sse.common.Duration; import com.sap.sse.datamining.components.AggregationProcessorDefinition; import com.sap.sse.datamining.components.Processor; -import com.sap.sse.datamining.impl.components.GroupedDataEntry; import com.sap.sse.datamining.impl.components.SimpleAggregationProcessorDefinition; -import com.sap.sse.datamining.impl.components.aggregators.AbstractParallelGroupedDataAggregationProcessor; import com.sap.sse.datamining.shared.GroupKey; public class ParallelDurationSumAggregationProcessor extends - AbstractParallelGroupedDataAggregationProcessor { + AbstractParallelSumAggregationProcessor { private static final AggregationProcessorDefinition DEFINITION = new SimpleAggregationProcessorDefinition<>(Duration.class, Duration.class, "Sum", ParallelDurationSumAggregationProcessor.class); @@ -23,25 +20,12 @@ public class ParallelDurationSumAggregationProcessor extends return DEFINITION; } - private final Map results; - public ParallelDurationSumAggregationProcessor(ExecutorService executor, Collection, ?>> resultReceivers) { - super(executor, resultReceivers, "Sum"); - results = new HashMap<>(); + super(executor, resultReceivers); } @Override - protected void handleElement(GroupedDataEntry element) { - GroupKey key = element.getKey(); - if (!results.containsKey(key)) { - results.put(key, element.getDataEntry()); - } else { - results.put(key, results.get(key).plus(element.getDataEntry())); - } - } - - @Override - protected Map getResult() { - return results; + protected Duration add(Duration t1, Duration t2) { + return t1.plus(t2); } } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/BravoFixTrackWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/BravoFixTrackWithContext.java index 46fd49161f5..6349f289fd7 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/BravoFixTrackWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/BravoFixTrackWithContext.java @@ -56,7 +56,7 @@ public class BravoFixTrackWithContext implements HasBravoFixTrackContext { end = endOfRace; } return bravoFixTrack.getDistanceSpentFoiling( - getRaceOfCompetitorContext().getTrackedRaceContext().getTrackedRace().getTrack(getRaceOfCompetitorContext().getCompetitor()), - startOfRace, end); + startOfRace, + end); } } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/FoilingSegmentWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/FoilingSegmentWithContext.java index 79cd0f7580e..402d123aec7 100755 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/FoilingSegmentWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/FoilingSegmentWithContext.java @@ -1,5 +1,6 @@ package com.sap.sailing.datamining.impl.data; +import java.text.SimpleDateFormat; import java.util.Locale; import com.sap.sailing.datamining.Activator; @@ -28,6 +29,7 @@ public class FoilingSegmentWithContext implements HasFoilingSegmentContext { private final HasBravoFixTrackContext bravoFixTrackContext; private final TimePoint startOfFoilingSegment; private final TimePoint endOfFoilingSegment; + private static final SimpleDateFormat TIMEPOINT_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); public FoilingSegmentWithContext(HasBravoFixTrackContext bravoFixTrackContext, TimePoint startOfFoilingSegment, TimePoint endOfFoilingSegment) { @@ -37,6 +39,12 @@ public class FoilingSegmentWithContext implements HasFoilingSegmentContext { this.endOfFoilingSegment = endOfFoilingSegment; } + @Override + public String getName() { + return bravoFixTrackContext.getRaceOfCompetitorContext().getCompetitor().getName() + "@" + + TIMEPOINT_FORMATTER.format(startOfFoilingSegment.asDate()); + } + @Override public HasBravoFixTrackContext getBravoFixTrackContext() { return bravoFixTrackContext; diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceResultOfCompetitorWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceResultOfCompetitorWithContext.java index 18b2a19f88a..96f465a8b9c 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceResultOfCompetitorWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceResultOfCompetitorWithContext.java @@ -174,7 +174,14 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti double competitorCount = Util.size(leaderboard.getCompetitors()); Double points = leaderboard.getTotalPoints(competitor, raceColumn, now); if (points != null) { - double relativeLowPoints = leaderboard.getScoringScheme().isHigherBetter() ? competitorCount - points + double relativeLowPoints = leaderboard.getScoringScheme().isHigherBetter() ? + // calculate the points for first rank (doesn't necessarily need to be the number + // of competitors!) and subtract the points + leaderboard.getScoringScheme().getScoreForRank(leaderboard, raceColumn, competitor, + /* rank */ 1, // which score does the winner get in this high-point scheme? + /* numberOfCompetitorsInRaceFetcher */ ()->Util.size(raceColumn.getAllCompetitors()), + /* numberOfCompetitorsInLeaderboardFetcher */ leaderboard.getNumberOfCompetitorsInLeaderboardFetcher(), + now) - points : points; final double result = relativeLowPoints / competitorCount; return result; diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedLegOfCompetitorWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedLegOfCompetitorWithContext.java index fdad2c9116e..b7c85fb721c 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedLegOfCompetitorWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedLegOfCompetitorWithContext.java @@ -88,7 +88,7 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti public Duration getTimeSpentFoiling() { return getSomethingForLegTrackingInterval((start, end) -> { BravoFixTrack bravoFixTrack = getTrackedLegContext().getTrackedLeg().getTrackedRace().getSensorTrack(getCompetitor(), BravoFixTrack.TRACK_NAME); - return bravoFixTrack.getTimeSpentFoiling(start, end); + return bravoFixTrack == null ? Duration.NULL : bravoFixTrack.getTimeSpentFoiling(start, end); }); } @@ -96,7 +96,7 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti public Distance getDistanceSpentFoiling() { return getSomethingForLegTrackingInterval((start, end) -> { BravoFixTrack bravoFixTrack = getTrackedLegContext().getTrackedLeg().getTrackedRace().getSensorTrack(getCompetitor(), BravoFixTrack.TRACK_NAME); - return bravoFixTrack.getDistanceSpentFoiling(getTrackedLegContext().getTrackedLeg().getTrackedRace().getTrack(getCompetitor()), start, end); + return bravoFixTrack == null ? Distance.NULL : bravoFixTrack.getDistanceSpentFoiling(start, end); }); } diff --git a/java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/NOAAImporter.java b/java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/NOAAImporter.java index 2be88f09250..2e265682a56 100755 --- a/java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/NOAAImporter.java +++ b/java/com.sap.sailing.declination/src/com/sap/sailing/declination/impl/NOAAImporter.java @@ -8,6 +8,8 @@ import java.text.ParseException; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; @@ -30,6 +32,7 @@ import com.sap.sse.common.TimePoint; * */ public class NOAAImporter { + private static final Logger logger = Logger.getLogger(NOAAImporter.class.getName()); private static final String QUERY_URL = "https://www.ngdc.noaa.gov/geomag-web/calculators/calculateDeclination"; private static final String REGEXP_DECLINATION = "

Declination = ([0-9]*)° ([0-9]*)' *([EW])"; private static final String REGEXP_ANNUAL_CHANGE = "changing by *([0-9]*)° *([0-9]*)' ([EW])/year *

"; @@ -102,7 +105,7 @@ public class NOAAImporter { result.notifyAll(); } } catch (IOException | ParserConfigurationException | SAXException e) { - throw new RuntimeException(e); + logger.log(Level.FINE, "Exception while trying to load magnetic declination online", e); } } }; diff --git a/java/com.sap.sailing.domain.bravoadapter/.classpath b/java/com.sap.sailing.domain.bravoadapter/.classpath new file mode 100755 index 00000000000..eca7bdba8f0 --- /dev/null +++ b/java/com.sap.sailing.domain.bravoadapter/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.sap.sailing.domain.bravoadapter/.project b/java/com.sap.sailing.domain.bravoadapter/.project new file mode 100755 index 00000000000..b9978a06950 --- /dev/null +++ b/java/com.sap.sailing.domain.bravoadapter/.project @@ -0,0 +1,28 @@ + + + com.sap.sailing.domain.bravoadapter + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.sap.sailing.domain.bravoadapter/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.domain.bravoadapter/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..0c68a61dca8 --- /dev/null +++ b/java/com.sap.sailing.domain.bravoadapter/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sailing.domain.bravoadapter/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.domain.bravoadapter/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..f29e940a005 --- /dev/null +++ b/java/com.sap.sailing.domain.bravoadapter/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sailing.domain.bravoadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.bravoadapter/META-INF/MANIFEST.MF new file mode 100755 index 00000000000..596f332169f --- /dev/null +++ b/java/com.sap.sailing.domain.bravoadapter/META-INF/MANIFEST.MF @@ -0,0 +1,14 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Bravoadapter +Bundle-SymbolicName: com.sap.sailing.domain.bravoadapter +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Import-Package: org.osgi.framework;version="1.8.0" +Require-Bundle: com.sap.sailing.domain, + com.sap.sailing.server.trackfiles, + com.sap.sailing.domain.common, + com.sap.sse.common, + com.sap.sailing.server.gateway +Bundle-Activator: com.sap.sailing.domain.bravoadapter.impl.Activator diff --git a/java/com.sap.sailing.domain.bravoadapter/build.properties b/java/com.sap.sailing.domain.bravoadapter/build.properties new file mode 100755 index 00000000000..34d2e4d2dad --- /dev/null +++ b/java/com.sap.sailing.domain.bravoadapter/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sailing.domain.bravoadapter/pom.xml b/java/com.sap.sailing.domain.bravoadapter/pom.xml new file mode 100755 index 00000000000..52715b513f2 --- /dev/null +++ b/java/com.sap.sailing.domain.bravoadapter/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sailing.domain.bravoadapter + eclipse-plugin + diff --git a/java/com.sap.sailing.domain.bravoadapter/src/com/sap/sailing/domain/bravoadapter/impl/Activator.java b/java/com.sap.sailing.domain.bravoadapter/src/com/sap/sailing/domain/bravoadapter/impl/Activator.java new file mode 100755 index 00000000000..5c87aa644fc --- /dev/null +++ b/java/com.sap.sailing.domain.bravoadapter/src/com/sap/sailing/domain/bravoadapter/impl/Activator.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.bravoadapter.impl; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +import com.sap.sailing.server.trackfiles.common.GPSFixImporterRegistration; + +public class Activator implements BundleActivator { + @Override + public void start(BundleContext context) throws Exception { + GPSFixImporterRegistration.register(new BravoGPSFixImporter(), context); + } + + @Override + public void stop(BundleContext context) throws Exception { + } +} diff --git a/java/com.sap.sailing.domain.bravoadapter/src/com/sap/sailing/domain/bravoadapter/impl/BravoGPSFixImporter.java b/java/com.sap.sailing.domain.bravoadapter/src/com/sap/sailing/domain/bravoadapter/impl/BravoGPSFixImporter.java new file mode 100755 index 00000000000..4803b280f54 --- /dev/null +++ b/java/com.sap.sailing.domain.bravoadapter/src/com/sap/sailing/domain/bravoadapter/impl/BravoGPSFixImporter.java @@ -0,0 +1,50 @@ +package com.sap.sailing.domain.bravoadapter.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Date; + +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.sensordata.BravoExtendedSensorDataMetadata; +import com.sap.sailing.domain.common.tracking.DoubleVectorFix; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl; +import com.sap.sailing.domain.trackimport.FormatNotSupportedException; +import com.sap.sailing.domain.trackimport.GPSFixImporter; +import com.sap.sailing.server.gateway.windimport.bravo.FunnyDegreeConverter; +import com.sap.sailing.server.trackfiles.impl.BravoExtendedDataImporterImpl; + +public class BravoGPSFixImporter implements GPSFixImporter { + @Override + public void importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing, final String filename) + throws FormatNotSupportedException, IOException { + TrackFileImportDeviceIdentifier device = new TrackFileImportDeviceIdentifierImpl(filename, getType() + "@" + new Date()); + new BravoExtendedDataImporterImpl().importFixes(inputStream, + (Iterable fixes, TrackFileImportDeviceIdentifier deviceIdentifier)->{ + for (final DoubleVectorFix fix : fixes) { + GPSFixMoving gpsFix = new GPSFixMovingImpl( + new DegreePosition(FunnyDegreeConverter.funnyLatLng(fix.get(BravoExtendedSensorDataMetadata.LAT.getColumnIndex())), + FunnyDegreeConverter.funnyLatLng(fix.get(BravoExtendedSensorDataMetadata.LON.getColumnIndex()))), + fix.getTimePoint(), + new KnotSpeedWithBearingImpl(fix.get(BravoExtendedSensorDataMetadata.SOG.getColumnIndex()), + new DegreeBearingImpl(fix.get(BravoExtendedSensorDataMetadata.COG.getColumnIndex())))); + callback.addFix(gpsFix, device); + } + }, filename, /* sourceName */ getType(), /* downsample */ false); + } + + @Override + public Iterable getSupportedFileExtensions() { + return Arrays.asList(new String[] { "csv", "log", "txt" }); + } + + @Override + public String getType() { + return "Bravo"; + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java index 5019df064cd..ddf0f98404a 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatClassMasterdata.java @@ -19,6 +19,7 @@ public enum BoatClassMasterdata { _470 ("470", true, 4.70, 1.68, BoatHullType.MONOHULL, true, "470er", "470M", "470W", "470 - M", "470 - W"), _5O5 ("5O5", true, 5.03, 1.88, BoatHullType.MONOHULL, true, "505", "5o5", "505er", "5o5er"), _5_5MR ("5.5mR", true, 9.50, 1.92, BoatHullType.MONOHULL, true, "5.5 Meter", "5.5 Metre", "5.5", "5.5M", "5.5-metre", "5.5 metre"), + _6MR ("6mR", true, 11, 2.2, BoatHullType.MONOHULL, true, "6 Meter", "6 Metre", "6", "6M", "6-metre", "6 metre"), _8MR ("8mR", true, 9.62, 2.45, BoatHullType.MONOHULL, true, "8 Meter", "8 Metre", "8M", "8-metre"), A_CAT ("A-Catamaran", true, 5.49, 2.30, BoatHullType.CATAMARAN, false, "A-Cat", "ACat", "A-Class Catamaran"), ALBIN_EXPRESS ("Albin Express", true, 7.77, 2.50, BoatHullType.MONOHULL, false), @@ -92,7 +93,7 @@ public enum BoatClassMasterdata { RS500 ("RS 500", true, 4.34, 1.58, BoatHullType.MONOHULL, true, "RS500", "RS_500"), RS800 ("RS 800", true, 4.80, 1.88, BoatHullType.MONOHULL, true, "RS800", "RS_800"), RS_AERO ("RS Aero", true, 4.00, 1.40, BoatHullType.MONOHULL, false, "RSAERO", "RS_Aero"), - RS_X ("RS:X", true, 2.86, 0.93, BoatHullType.SURFERBOARD, false, "RS-X", "RSX", "RS:X", "RS:X Men", "RS:X Woman"), + RS_X ("RS:X", true, 2.86, 0.93, BoatHullType.SURFERBOARD, false, "RS-X", "RSX", "RS:X", "RS:X Men", "RS:X Woman", "RS:X Women"), RS_FEVA ("RS Feva", true, 3.64, 1.42, BoatHullType.MONOHULL, true, "RSFeva"), SKUD_18 ("SKUD 18", true, 5.8, 2.29, BoatHullType.MONOHULL, true), SONAR ("Sonar", true, 7.01, 2.39, BoatHullType.MONOHULL, true), @@ -111,6 +112,7 @@ public enum BoatClassMasterdata { TORNADO ("Tornado Catamaran", true, 6.10, 3.02, BoatHullType.CATAMARAN, true, "Tornado", "Tornado Cat"), TOM_28_MAX ("Tom 28 MAX", true, 8.48, 2.48, BoatHullType.MONOHULL, true, "Tom 28"), TRIAS ("Trias", true, 9.20, 2.12, BoatHullType.MONOHULL, true), + TP52 ("TP52", true, 15.85, 4.35, BoatHullType.MONOHULL, true, "TP 52", "Transpac 52", "Transpac52"), VIPER_640 ("Viper 640", true, 6.43, 2.49, BoatHullType.MONOHULL, true), VO60 ("VO60", true, 19.5, 5.25, BoatHullType.MONOHULL, true, "Volvo Ocean 60", "VolvoOcean60", "W60", "Whitbread60", "Whitbread 60"), VX_ONE ("VX ONE", true, 5.79, 2.19, BoatHullType.MONOHULL, true, "VX-ONE"), @@ -123,7 +125,10 @@ public enum BoatClassMasterdata { ORC_INTERNATIONAL ("ORC International", true, 13.83, 3.91, BoatHullType.MONOHULL, true, "ORC Int."), // Performance Handicap Racing Fleet (PHRF) is a handicap system used for racing in North America - PHRF ("PHRF", true, 13.83, 3.91, BoatHullType.MONOHULL, true); + PHRF ("PHRF", true, 13.83, 3.91, BoatHullType.MONOHULL, true), + + // a 'boat class' to track runners at runnng events + RUNNING ("Runner", true, 1.0, 1.0, BoatHullType.NO_HULL, false, "Running"); private final String displayName; private final String[] alternativeNames; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatHullType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatHullType.java index e793bf5c32e..ed09686af61 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatHullType.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/BoatHullType.java @@ -1,5 +1,5 @@ package com.sap.sailing.domain.common; public enum BoatHullType { - MONOHULL, CATAMARAN, TRIMARAN, SURFERBOARD + MONOHULL, CATAMARAN, TRIMARAN, SURFERBOARD, NO_HULL } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java index db65d043bd6..7ad1ac96f7b 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DetailType.java @@ -5,6 +5,8 @@ import static com.sap.sailing.domain.common.SortingOrder.DESCENDING; import static com.sap.sailing.domain.common.SortingOrder.NONE; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; /** * Identifies details that can be requested from the racing service. Optionally, the details can specify a precision @@ -17,12 +19,32 @@ import java.io.Serializable; public enum DetailType implements Serializable { DISTANCE_TRAVELED(0, ASCENDING), DISTANCE_TRAVELED_INCLUDING_GATE_START(0, ASCENDING), AVERAGE_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING), RACE_RANK(0, ASCENDING), REGATTA_RANK(0, ASCENDING), OVERALL_RANK(0, ASCENDING), - RANK_GAIN(0, ASCENDING), - NUMBER_OF_MANEUVERS(0, ASCENDING), - CURRENT_SPEED_OVER_GROUND_IN_KNOTS(2,DESCENDING), - CURRENT_HEEL_IN_DEGREES(2,DESCENDING), - CURRENT_PITCH_IN_DEGREES(2,DESCENDING), + RANK_GAIN(0, ASCENDING), + NUMBER_OF_MANEUVERS(0, ASCENDING), + CURRENT_SPEED_OVER_GROUND_IN_KNOTS(2,DESCENDING), + CURRENT_HEEL_IN_DEGREES(2,DESCENDING), + CURRENT_PITCH_IN_DEGREES(2,DESCENDING), CURRENT_RIDE_HEIGHT_IN_METERS(2,DESCENDING), + CURRENT_DISTANCE_FOILED_IN_METERS(0, DESCENDING), + CURRENT_DURATION_FOILED_IN_SECONDS(0, DESCENDING), + CURRENT_PORT_DAGGERBOARD_RAKE(2,DESCENDING), + CURRENT_STBD_DAGGERBOARD_RAKE(2,DESCENDING), + CURRENT_PORT_RUDDER_RAKE(2,DESCENDING), + CURRENT_STBD_RUDDER_RAKE(2,DESCENDING), + CURRENT_MAST_ROTATION_IN_DEGREES(2,DESCENDING), + CURRENT_LEEWAY_IN_DEGREES(1, ASCENDING), + CURRENT_SET(1, ASCENDING), + CURRENT_DRIFT_IN_DEGREES(1, ASCENDING), + CURRENT_DEPTH_IN_METERS(1, ASCENDING), + CURRENT_RUDDER_IN_DEGREES(2, ASCENDING), + CURRENT_TACK_ANGLE_IN_DEGREES(2, ASCENDING), + CURRENT_DEFLECTOR_PERCENTAGE(2, ASCENDING), + CURRENT_DEFLECTOR_IN_MILLIMETERS(2, ASCENDING), + CURRENT_RAKE_IN_DEGREES(2, ASCENDING), + CURRENT_TARGET_HEEL_ANGLE_IN_DEGREES(2, ASCENDING), + CURRENT_FORESTAY_LOAD(2, ASCENDING), + CURRENT_FORESTAY_PRESSURE(2, ASCENDING), + CURRENT_TARGET_BOATSPEED_PERCENTAGE(2, ASCENDING), ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS(1, ASCENDING), VELOCITY_MADE_GOOD_IN_KNOTS(2, DESCENDING), GAP_TO_LEADER_IN_SECONDS(0, ASCENDING), GAP_CHANGE_SINCE_LEG_START_IN_SECONDS(0, ASCENDING), SIDE_TO_WHICH_MARK_AT_LEG_START_WAS_ROUNDED(0, ASCENDING), WINDWARD_DISTANCE_TO_GO_IN_METERS(0, ASCENDING), @@ -44,8 +66,10 @@ public enum DetailType implements Serializable { MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS(1, DESCENDING), TIME_ON_TIME_FACTOR(4, DESCENDING), TIME_ON_DISTANCE_ALLOWANCE_IN_SECONDS_PER_NAUTICAL_MILE(0, ASCENDING), TOTAL_DISTANCE_TRAVELED(0, ASCENDING), TOTAL_AVERAGE_SPEED_OVER_GROUND(2, DESCENDING), - TOTAL_TIME_SAILED_IN_SECONDS(1, ASCENDING), RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS(2, - DESCENDING), RACE_CURRENT_RIDE_HEIGHT_IN_METERS(2, DESCENDING), + TOTAL_TIME_SAILED_IN_SECONDS(1, ASCENDING), + TOTAL_DURATION_FOILED_IN_SECONDS(0, DESCENDING), TOTAL_DISTANCE_FOILED_IN_METERS(0, DESCENDING), + RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING), RACE_CURRENT_RIDE_HEIGHT_IN_METERS(2, DESCENDING), + RACE_CURRENT_DISTANCE_FOILED_IN_METERS(0, DESCENDING), RACE_CURRENT_DURATION_FOILED_IN_SECONDS(0, DESCENDING), RACE_NET_POINTS(2, ASCENDING), REGATTA_NET_POINTS(2, ASCENDING), REGATTA_NET_POINTS_SUM(2, ASCENDING), RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL(1, ASCENDING), RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_RACE_START(1, ASCENDING), RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START(2, DESCENDING), @@ -69,4 +93,35 @@ public enum DetailType implements Serializable { public SortingOrder getDefaultSortingOrder() { return defaultSortingOrder; } + + /** + * Default set of DetailTypes for charts, this list contains all commonly available data, without the use of extra + * sensors. + */ + public static List getDefaultDetailTypesForChart() { + List availableDetailsTypes = new ArrayList<>(); + availableDetailsTypes.add(DetailType.WINDWARD_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD); + availableDetailsTypes.add(DetailType.DISTANCE_TRAVELED); + availableDetailsTypes.add(DetailType.DISTANCE_TRAVELED_INCLUDING_GATE_START); + availableDetailsTypes.add(DetailType.VELOCITY_MADE_GOOD_IN_KNOTS); + availableDetailsTypes.add(DetailType.GAP_TO_LEADER_IN_SECONDS); + availableDetailsTypes.add(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS); + availableDetailsTypes.add(DetailType.RACE_RANK); + availableDetailsTypes.add(DetailType.REGATTA_RANK); + availableDetailsTypes.add(DetailType.DISTANCE_TO_START_LINE); + availableDetailsTypes.add(DetailType.BEAT_ANGLE); + availableDetailsTypes.add(DetailType.COURSE_OVER_GROUND_TRUE_DEGREES); + return availableDetailsTypes; + } + + /** + * Special List of DetailTypes, that allows operators to select for example the RideHeight, that is usually only selectable, if it already has data. + */ + public static List getAutoplayDetailTypesForChart() { + List availableDetailsTypes = getDefaultDetailTypesForChart(); + availableDetailsTypes.add(DetailType.RACE_CURRENT_RIDE_HEIGHT_IN_METERS); + availableDetailsTypes.add(DetailType.CURRENT_HEEL_IN_DEGREES); + availableDetailsTypes.add(DetailType.CURRENT_PITCH_IN_DEGREES); + return availableDetailsTypes; + } } \ No newline at end of file diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/DeviceIdentifier.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DeviceIdentifier.java similarity index 96% rename from java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/DeviceIdentifier.java rename to java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DeviceIdentifier.java index 5471253c027..b29ebc77bbf 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/DeviceIdentifier.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/DeviceIdentifier.java @@ -1,9 +1,7 @@ -package com.sap.sailing.domain.racelogtracking; +package com.sap.sailing.domain.common; import java.io.Serializable; -import com.sap.sailing.domain.abstractlog.race.RaceLog; - /** * Identifies any kind of tracking device. Should be implemented accordingly for different tracking * adapters, e.g. for smartphones, Igtimi trackers etc.

@@ -37,6 +35,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog; * {@link Object#equals(Object)} and {@link Object#hashCode()} should be implemented so that multiple * deserializations of same device identifier (conceptually) result in objects that are be identified * as being equal through these two methods. + * * @author Fredrik Teschke * */ diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/LegIdentifier.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/LegIdentifier.java index 73ca7a2e740..4398e6c2f2e 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/LegIdentifier.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/LegIdentifier.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.common; public interface LegIdentifier extends RegattaAndRaceIdentifier { - RaceIdentifier getRaceIdentifier(); + RegattaAndRaceIdentifier getRaceIdentifier(); String getLegName(); int getLegNumber(); } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/LegIdentifierImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/LegIdentifierImpl.java index 9800153f114..dbef906d0eb 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/LegIdentifierImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/LegIdentifierImpl.java @@ -2,7 +2,7 @@ package com.sap.sailing.domain.common; public class LegIdentifierImpl extends RegattaNameAndRaceName implements LegIdentifier { private static final long serialVersionUID = 3599904513673776450L; - private RaceIdentifier raceIdentifier = null; + private RegattaAndRaceIdentifier raceIdentifier = null; private String legName; LegIdentifierImpl() {} @@ -14,11 +14,10 @@ public class LegIdentifierImpl extends RegattaNameAndRaceName implements LegIden } @Override - public RaceIdentifier getRaceIdentifier() { + public RegattaAndRaceIdentifier getRaceIdentifier() { return this.raceIdentifier; } - @Override public String getLegName() { return legName; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java index 875734f00c2..c419b8d1bd1 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/ScoringSchemeType.java @@ -2,7 +2,7 @@ package com.sap.sailing.domain.common; public enum ScoringSchemeType { // make sure to also add new ones to ScoringSchemeTypeFormatter - LOW_POINT, HIGH_POINT, HIGH_POINT_ESS_OVERALL,HIGH_POINT_ESS_OVERALL_12, HIGH_POINT_LAST_BREAKS_TIE, HIGH_POINT_FIRST_GETS_TEN, + LOW_POINT, LOW_POINT_WITH_AUTOMATIC_RDG, HIGH_POINT, HIGH_POINT_ESS_OVERALL,HIGH_POINT_ESS_OVERALL_12, HIGH_POINT_LAST_BREAKS_TIE, HIGH_POINT_FIRST_GETS_TEN, HIGH_POINT_FIRST_GETS_ONE, LOW_POINT_WINNER_GETS_ZERO, HIGH_POINT_WINNER_GETS_SIX, HIGH_POINT_WINNER_GETS_FIVE, HIGH_POINT_WINNER_GETS_EIGHT, HIGH_POINT_WINNER_GETS_EIGHT_AND_INTERPOLATION, HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT, HIGH_POINT_FIRST_GETS_TWELVE_OR_EIGHT, HIGH_POINT_FIRST_GETS_TWELVE_OR_EIGHT_2017, LOW_POINT_WITH_ELIMINATIONS_AND_ROUNDS_WINNER_GETS_07, LOW_POINT_LEAGUE_OVERALL, HIGH_POINT_MATCH_RACING, LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/TrackedRaceStatusEnum.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/TrackedRaceStatusEnum.java index 572e618db8d..c5b8201eaf7 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/TrackedRaceStatusEnum.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/TrackedRaceStatusEnum.java @@ -1,6 +1,6 @@ package com.sap.sailing.domain.common; -public enum TrackedRaceStatusEnum { PREPARED(0), LOADING(1), TRACKING(2), FINISHED(3), ERROR(4); +public enum TrackedRaceStatusEnum { PREPARED(0), LOADING(1), TRACKING(2), FINISHED(3), ERROR(4), REMOVED(5); private final int order; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AnniversaryType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AnniversaryType.java new file mode 100644 index 00000000000..d3fa095916a --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/AnniversaryType.java @@ -0,0 +1,5 @@ +package com.sap.sailing.domain.common.dto; + +public enum AnniversaryType { + REPEATED_DIGIT, QUARTER +} \ No newline at end of file diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java index 16bceb8a47a..cc14be28357 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/CompetitorDTO.java @@ -21,7 +21,8 @@ public interface CompetitorDTO extends Serializable, MappableToDevice { String getSearchTag(); /** - * If the {@code searchTag} is not contained in {@link #getSearchTag()}, appends it to the search tag, separated by a space character + * If the {@code searchTag} is not {@link String#contains(CharSequence) contained} in {@link #getSearchTag()}, + * appends it to the search tag, separated by a space character */ void addToSearchTag(String searchTag); diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/EventType.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/EventType.java new file mode 100644 index 00000000000..f6a7104ff4e --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/EventType.java @@ -0,0 +1,5 @@ +package com.sap.sailing.domain.common.dto; + +public enum EventType { + SINGLE_REGATTA, MULTI_REGATTA, SERIES +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java index 150ec7d4087..4435b336e9f 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java @@ -138,7 +138,7 @@ public class LeaderboardEntryDTO implements Serializable { return totalPointsCorrected || (reasonForMaxPoints != null && reasonForMaxPoints != MaxPointsReason.NONE); } - public Duration getTimeSailedInMillis() { + public Duration getTimeSailed() { final Duration result; if (legDetails != null) { long timeInMilliseconds = 0; @@ -159,7 +159,7 @@ public class LeaderboardEntryDTO implements Serializable { return result; } - public Distance getDistanceTraveledInMeters() { + public Distance getDistanceTraveled() { Distance result = null; if (legDetails != null) { for (LegEntryDTO legDetail : legDetails) { @@ -176,6 +176,47 @@ public class LeaderboardEntryDTO implements Serializable { return result; } + public Duration getDurationFoiled() { + final Duration result; + if (legDetails != null) { + Double acc = null; + for (LegEntryDTO legDetail : legDetails) { + if (legDetail != null) { + if (legDetail.currentDurationFoiledInSeconds != null) { + if (acc == null) { + acc = 0.0; + } + acc += legDetail.currentDurationFoiledInSeconds; + } else { + acc = null; + break; + } + } + } + result = acc == null ? null : new MillisecondsDurationImpl((long) (acc*1000.)); + } else { + result = null; + } + return result; + } + + public Distance getDistanceFoiled() { + Distance result = null; + if (legDetails != null) { + for (LegEntryDTO legDetail : legDetails) { + if (legDetail != null) { + if (legDetail.currentDistanceFoiledInMeters != null) { + if (result == null) { + result = Distance.NULL; + } + result = result.add(new MeterDistance(legDetail.currentDistanceFoiledInMeters)); + } + } + } + } + return result; + } + public int getOneBasedCurrentLegNumber() { int result = 0; if (legDetails != null && !legDetails.isEmpty()) { diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java index 4e60ad845c6..5ca9daaf00e 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java @@ -26,6 +26,8 @@ public class LeaderboardRowDTO implements Serializable { public Double totalTimeSailedUpwindInSeconds; public Double totalTimeSailedReachingInSeconds; public Double totalDistanceTraveledInMeters; + public Double totalDurationFoiledInSeconds; + public Double totalDistanceFoiledInMeters; @Override public int hashCode() { @@ -38,6 +40,10 @@ public class LeaderboardRowDTO implements Serializable { + ((maximumSpeedOverGroundInKnots == null) ? 0 : maximumSpeedOverGroundInKnots.hashCode()); result = prime * result + ((totalDistanceTraveledInMeters == null) ? 0 : totalDistanceTraveledInMeters.hashCode()); + result = prime * result + + ((totalDurationFoiledInSeconds == null) ? 0 : totalDurationFoiledInSeconds.hashCode()); + result = prime * result + + ((totalDistanceFoiledInMeters == null) ? 0 : totalDistanceFoiledInMeters.hashCode()); result = prime * result + ((netPoints == null) ? 0 : netPoints.hashCode()); result = prime * result + ((totalTimeSailedDownwindInSeconds == null) ? 0 : totalTimeSailedDownwindInSeconds.hashCode()); @@ -87,6 +93,16 @@ public class LeaderboardRowDTO implements Serializable { return false; } else if (!totalDistanceTraveledInMeters.equals(other.totalDistanceTraveledInMeters)) return false; + if (totalDurationFoiledInSeconds == null) { + if (other.totalDurationFoiledInSeconds != null) + return false; + } else if (!totalDurationFoiledInSeconds.equals(other.totalDurationFoiledInSeconds)) + return false; + if (totalDistanceFoiledInMeters == null) { + if (other.totalDistanceFoiledInMeters != null) + return false; + } else if (!totalDistanceFoiledInMeters.equals(other.totalDistanceFoiledInMeters)) + return false; if (netPoints == null) { if (other.netPoints != null) return false; @@ -120,11 +136,11 @@ public class LeaderboardRowDTO implements Serializable { return true; } - public Distance getDistanceTraveledInMeters(String raceColumnName) { + public Distance getDistanceTraveled(String raceColumnName) { final Distance result; LeaderboardEntryDTO fieldsForRace = fieldsByRaceColumnName.get(raceColumnName); if (fieldsForRace != null) { - result = fieldsForRace.getDistanceTraveledInMeters(); + result = fieldsForRace.getDistanceTraveled(); } else { result = null; } @@ -135,7 +151,29 @@ public class LeaderboardRowDTO implements Serializable { final Duration result; LeaderboardEntryDTO fieldsForRace = fieldsByRaceColumnName.get(raceColumnName); if (fieldsForRace != null) { - result = fieldsForRace.getTimeSailedInMillis(); + result = fieldsForRace.getTimeSailed(); + } else { + result = null; + } + return result; + } + + public Distance getDistanceFoiled(String raceColumnName) { + final Distance result; + LeaderboardEntryDTO fieldsForRace = fieldsByRaceColumnName.get(raceColumnName); + if (fieldsForRace != null) { + result = fieldsForRace.getDistanceFoiled(); + } else { + result = null; + } + return result; + } + + public Duration getDurationFoiled(String raceColumnName) { + final Duration result; + LeaderboardEntryDTO fieldsForRace = fieldsByRaceColumnName.get(raceColumnName); + if (fieldsForRace != null) { + result = fieldsForRace.getDurationFoiled(); } else { result = null; } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LegEntryDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LegEntryDTO.java index 255fbbb3a00..e545f1fe01a 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LegEntryDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LegEntryDTO.java @@ -44,6 +44,8 @@ public class LegEntryDTO implements Serializable { public Double currentHeelInDegrees; public Double currentPitchInDegrees; + public Double currentDistanceFoiledInMeters; + public Double currentDurationFoiledInSeconds; /** * If the competitor has already finished the leg at the time point for which this entry was created, contains the @@ -81,6 +83,8 @@ public class LegEntryDTO implements Serializable { result = prime * result + ((currentHeelInDegrees == null) ? 0 : currentHeelInDegrees.hashCode()); result = prime * result + ((currentPitchInDegrees == null) ? 0 : currentPitchInDegrees.hashCode()); result = prime * result + ((currentRideHeightInMeters == null) ? 0 : currentRideHeightInMeters.hashCode()); + result = prime * result + ((currentDurationFoiledInSeconds == null) ? 0 : currentDurationFoiledInSeconds.hashCode()); + result = prime * result + ((currentDistanceFoiledInMeters == null) ? 0 : currentDistanceFoiledInMeters.hashCode()); result = prime * result + ((currentSpeedOverGroundInKnots == null) ? 0 : currentSpeedOverGroundInKnots.hashCode()); result = prime * result + ((distanceTraveledInMeters == null) ? 0 : distanceTraveledInMeters.hashCode()); @@ -150,6 +154,16 @@ public class LegEntryDTO implements Serializable { return false; } else if (!currentRideHeightInMeters.equals(other.currentRideHeightInMeters)) return false; + if (currentDurationFoiledInSeconds == null) { + if (other.currentDurationFoiledInSeconds != null) + return false; + } else if (!currentDurationFoiledInSeconds.equals(other.currentDurationFoiledInSeconds)) + return false; + if (currentDistanceFoiledInMeters == null) { + if (other.currentDistanceFoiledInMeters != null) + return false; + } else if (!currentDistanceFoiledInMeters.equals(other.currentDistanceFoiledInMeters)) + return false; if (currentSpeedOverGroundInKnots == null) { if (other.currentSpeedOverGroundInKnots != null) return false; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/DegreeBearingImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/DegreeBearingImpl.java index 70e37a8717f..5ea889222d9 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/DegreeBearingImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/impl/DegreeBearingImpl.java @@ -9,7 +9,7 @@ public class DegreeBearingImpl extends AbstractBearing implements Bearing { private final double bearingDeg; /** * - * @param bearingDeg if a mount degrees over 360, then it will be mod 360 degrees + * @param bearingDeg if amount degrees over 360, then it will be mod 360 degrees */ public DegreeBearingImpl(double bearingDeg) { super(); diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogServletConstants.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogServletConstants.java index dd7a29543fd..7c0541fd099 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogServletConstants.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/racelog/RaceLogServletConstants.java @@ -11,5 +11,7 @@ public interface RaceLogServletConstants { static final String PARAMS_RACE_COLUMN_NAME = "race_column"; static final String PARAMS_RACE_FLEET_NAME = "fleet"; static final String PARAMS_CLIENT_UUID = "clientuuid"; - static final String PARAM_COURSE_AREA_FILTER = "courseArea"; + static final String PARAMS_COURSE_AREA_FILTER = "courseArea"; + static final String PARAMS_TRACK_WIND = "trackWind"; + static final String PARAMS_CORRECT_WIND_DIRECTION_BY_MAGNETIC_DECLINATION = "correctWindDirectionByMagneticDeclination"; } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalableDistance.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalableDistance.java new file mode 100755 index 00000000000..065b6f4899f --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/scalablevalue/impl/ScalableDistance.java @@ -0,0 +1,44 @@ +package com.sap.sailing.domain.common.scalablevalue.impl; + +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.impl.MeterDistance; +import com.sap.sse.common.scalablevalue.ScalableValue; +import com.sap.sse.common.scalablevalue.ScalableValueWithDistance; + +public class ScalableDistance implements ScalableValueWithDistance { + private final double meters; + + public ScalableDistance(Distance distance) { + this.meters = distance.getMeters(); + } + + private ScalableDistance(double meters) { + this.meters = meters; + } + + @Override + public ScalableDistance multiply(double factor) { + return new ScalableDistance(factor*meters); + } + + @Override + public ScalableDistance add(ScalableValue t) { + return new ScalableDistance(meters+t.getValue()); + } + + @Override + public Distance divide(double divisor) { + return new MeterDistance(meters / divisor); + } + + @Override + public Double getValue() { + return meters; + } + + @Override + public double getDistance(Distance other) { + return Math.abs(meters-other.getMeters()); + } + +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Permission.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Permission.java index b0e3272131a..40a5d7e7893 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Permission.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/security/Permission.java @@ -14,6 +14,7 @@ public enum Permission implements com.sap.sse.security.shared.Permission { MANAGE_COURSE_LAYOUT, MANAGE_WIND, MANAGE_IGTIMI_ACCOUNTS, + MANAGE_EXPEDITION_DEVICE_CONFIGURATIONS, MANAGE_LEADERBOARDS, MANAGE_LEADERBOARD_RESULTS, MANAGE_LEADERBOARD_GROUPS, diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoExtendedSensorDataMetadata.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoExtendedSensorDataMetadata.java new file mode 100644 index 00000000000..efd9c9a863d --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoExtendedSensorDataMetadata.java @@ -0,0 +1,115 @@ +package com.sap.sailing.domain.common.sensordata; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import com.sap.sailing.domain.common.tracking.DoubleVectorFix; + +/** + * Metadata that defines the column structure of BravoExtended fixes when imported as + * {@link com.sap.sailing.domain.common.tracking.DoubleVectorFix}. + *

+ * + * The current implementation only stores a subset of the information available during the import. + *

+ * + * The first columns need to be consistent with {@link BravoSensorDataMetadata}. Furthermore, + * once data has been stored in a sensor store, the order of literals must remain constant, + * and no literals must be deleted anymore because the ordinal is used to determine the index + * into the {@link DoubleVectorFix}. + */ +public enum BravoExtendedSensorDataMetadata implements ColumnMetadata { + RIDE_HEIGHT_PORT_HULL("RideHeightPort", /* expectedInLogfile */ true), // + RIDE_HEIGHT_STBD_HULL("RideHeightStbd", /* expectedInLogfile */ true), // + HEEL("Heel", /* expectedInLogfile */ true), // + PITCH("PitchRate", /* expectedInLogfile */ true), // + DB_RAKE_PORT("DaggerBoardRakeAnglePort", /* expectedInLogfile */ true), // + DB_RAKE_STBD("DaggerBoardRakeAngleStbd", /* expectedInLogfile */ true), // + RUDDER_RAKE_PORT("RudderRakeAnglePort", /* expectedInLogfile */ true), // + RUDDER_RAKE_STBD("RudderRakeAngleStbd", /* expectedInLogfile */ true), // + MAST_ROTATION("MastRotation", /* expectedInLogfile */ true), // + LEEWAY(null, /* expectedInLogfile */ false), // + SET(null, /* expectedInLogfile */ false), // + DRIFT(null, /* expectedInLogfile */ false), // + DEPTH(null, /* expectedInLogfile */ false), // + RUDDER(null, /* expectedInLogfile */ false), // + FORESTAY_LOAD(null, /* expectedInLogfile */ false), // + FORESTAY_PRESSURE(null, /* expectedInLogfile */ false), // + TACK_ANGLE(null, /* expectedInLogfile */ false), // + RAKE_DEG(null, /* expectedInLogfile */ false), // + DEFLECTOR_PERCENTAGE(null, /* expectedInLogfile */ false), // + TARGET_HEEL(null, /* expectedInLogfile */ false), // + DEFLECTOR_MILLIMETERS(null, /* expectedInLogfile */ false), // + TARGET_BOATSPEED_P(null, /* expectedInLogfile */ false), // + LAT("Lat", /* expectedInLogfile */ true), // + LON("Lon", /* expectedInLogfile */ true), // + COG("COG", /* expectedInLogfile */ true), // + SOG("SOG", /* expectedInLogfile */ true), // + ; + + private final String columnName; + + /** + * Some values seem to only appear in the UDP stream but so far not in the log file. Therefore, + * those columns shall not be part of the response to {@link #getColumnNamesToIndexInDoubleFix()}, + * indicated by this field being {@code false} for those attributes. + */ + private final boolean expectedInLogFile; + + private BravoExtendedSensorDataMetadata(String columnName, boolean expectedInLogfile) { + this.columnName = columnName; + this.expectedInLogFile = expectedInLogfile; + } + + public String getColumnName() { + return columnName; + } + + public boolean isExpectedInLogFile() { + return expectedInLogFile; + } + + /** + * The index in the {@link DoubleVectorFix} where this data item will be stored + */ + public int getColumnIndex() { + return this.ordinal(); + } + + public static final int HEADER_COLUMN_OFFSET = 3; + + public static BravoExtendedSensorDataMetadata byColumnName(String valueName) { + BravoExtendedSensorDataMetadata[] values = BravoExtendedSensorDataMetadata.values(); + for (BravoExtendedSensorDataMetadata item : values) { + if (Objects.equals(item.getColumnName(), valueName)) { + return item; + } + } + return null; + } + + public static int getTrackColumnCount() { + int result = 0; + for (final BravoExtendedSensorDataMetadata m : BravoExtendedSensorDataMetadata.values()) { + if (m.isExpectedInLogFile()) { + result++; + } + } + return result; + } + + /** + * @return namens of those columns with {@link #isExpectedInLogFile()} being {@code true}, mapped to their + * {@link #getColumnIndex() column index} + */ + public static Map getColumnNamesToIndexInDoubleFix() { + final Map columnNamesToIndexInDoubleFix = new HashMap<>(); + for (final BravoExtendedSensorDataMetadata column : BravoExtendedSensorDataMetadata.values()) { + if (column.isExpectedInLogFile()) { + columnNamesToIndexInDoubleFix.put(column.getColumnName(), column.getColumnIndex()); + } + } + return columnNamesToIndexInDoubleFix; + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoSensorDataMetadata.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoSensorDataMetadata.java index 091e6329eff..f7c7a853a37 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoSensorDataMetadata.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/BravoSensorDataMetadata.java @@ -1,23 +1,24 @@ package com.sap.sailing.domain.common.sensordata; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; /** - * Metadata that defines the column structure of {@link com.sap.sailing.domain.common.tracking.DoubleVectorFix}es when - * imported. + * Metadata that defines the column structure of Bravo fixes when imported as + * {@link com.sap.sailing.domain.common.tracking.DoubleVectorFix}. + *

* * The current implementation only stores a subset of the information available during the import. */ -public enum BravoSensorDataMetadata { - +public enum BravoSensorDataMetadata implements ColumnMetadata { RIDE_HEIGHT_PORT_HULL("RideHeightPortHull"), // RIDE_HEIGHT_STBD_HULL("RideHeightStbdHull"), // HEEL("Heel"), // PITCH("ImuSensor_Pitch"); - private String columnName; private BravoSensorDataMetadata(String columnName) { @@ -51,8 +52,16 @@ public enum BravoSensorDataMetadata { public static List getTrackColumnNames() { ArrayList colNames = new ArrayList<>(getTrackColumnCount()); for (BravoSensorDataMetadata item : BravoSensorDataMetadata.values()) { - colNames.add(item.columnName); + colNames.add(item.getColumnName()); } return colNames; } + + public static Map getColumnNamesToIndexInDoubleFix() { + final Map columnNamesToIndexInDoubleFix = new HashMap<>(); + for (final BravoSensorDataMetadata column : BravoSensorDataMetadata.values()) { + columnNamesToIndexInDoubleFix.put(column.getColumnName(), column.getColumnIndex()); + } + return columnNamesToIndexInDoubleFix; + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/ColumnMetadata.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/ColumnMetadata.java new file mode 100644 index 00000000000..5380fa447be --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/ColumnMetadata.java @@ -0,0 +1,14 @@ +package com.sap.sailing.domain.common.sensordata; + +import com.sap.sailing.domain.common.tracking.DoubleVectorFix; +import com.sap.sailing.domain.common.tracking.SensorFix; + +/** + * Definition of column metadata used by {@link SensorFix} implementations to resolve columns in underlying + * {@link DoubleVectorFix DoubleVectorFixes}. Once data has been stored in a sensor store according to such a + * specification, the order of literals in implementing {@code enum} types must remain constant, and no literals must be + * deleted anymore because the ordinal is used to determine the index into the {@link DoubleVectorFix}. + */ +public interface ColumnMetadata { + int getColumnIndex(); +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/ExpeditionExtendedSensorDataMetadata.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/ExpeditionExtendedSensorDataMetadata.java new file mode 100755 index 00000000000..3858832e652 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/sensordata/ExpeditionExtendedSensorDataMetadata.java @@ -0,0 +1,109 @@ +package com.sap.sailing.domain.common.sensordata; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.sap.sailing.domain.common.tracking.DoubleVectorFix; + +/** + * Metadata that defines the column structure of BravoExtended fixes when imported as + * {@link com.sap.sailing.domain.common.tracking.DoubleVectorFix}. + *

+ * + * The current implementation only stores a subset of the information available during the import. + *

+ * + * The first columns need to be consistent with {@link BravoSensorDataMetadata}. + */ +public enum ExpeditionExtendedSensorDataMetadata { + HEEL("Heel", BravoExtendedSensorDataMetadata.HEEL), // + TRIM("Trim", BravoExtendedSensorDataMetadata.PITCH), // + LEEWAY("Leeway", BravoExtendedSensorDataMetadata.LEEWAY, /* expected in logfile */ true), // + SET("Set", BravoExtendedSensorDataMetadata.SET, /* expected in logfile */ true), // + DRIFT("Drift", BravoExtendedSensorDataMetadata.DRIFT, /* expected in logfile */ true), // + DEPTH("Depth", BravoExtendedSensorDataMetadata.DEPTH, /* expected in logfile */ true), // + RUDDER("Rudder", BravoExtendedSensorDataMetadata.RUDDER, /* expected in logfile */ true), // + FORESTAY_LOAD("Forestay", BravoExtendedSensorDataMetadata.FORESTAY_LOAD, /* expected in logfile */ true), // + FORESTAY_PRESSURE("ForestayPres", BravoExtendedSensorDataMetadata.FORESTAY_PRESSURE, /* expected in logfile */ true), // + TACK_ANGLE(null, BravoExtendedSensorDataMetadata.TACK_ANGLE), // + RAKE_DEG("RakeDeg", BravoExtendedSensorDataMetadata.RAKE_DEG, /* expected in logfile */ true), // + DEFLECTOR_PERCENTAGE("DflectrPP", BravoExtendedSensorDataMetadata.DEFLECTOR_PERCENTAGE, /* expected in logfile */ true), // + TARGET_HEEL("TG Heell", BravoExtendedSensorDataMetadata.TARGET_HEEL, /* expected in logfile */ true), // + DEFLECTOR_MILLIMETERS("DflectrMM", BravoExtendedSensorDataMetadata.DEFLECTOR_MILLIMETERS, /* expected in logfile */ true), // + TARGET_BOATSPEED_P("BspTr", BravoExtendedSensorDataMetadata.TARGET_BOATSPEED_P, /* expected in logfile */ true), // + ; + + private String columnName; + + /** + * Expedition extended fixes are mapped to Bravo extended fixes whose metadata is described + * in {@link BravoExtendedSensorDataMetadata}. When parsing an Expedition file, the columns + * in the resulting {@link DoubleVectorFix} must be chosen such that they match up with the + * corresponding fields as described by {@link BravoExtendedSensorDataMetadata}. This turns + * the ordinals of this enum's literals insignificant; the position is taken solely from + * the Bravo metadata. + */ + private final BravoExtendedSensorDataMetadata mappedToBravoField; + + private final boolean expectedInLogfile; + + private ExpeditionExtendedSensorDataMetadata(String columnName, BravoExtendedSensorDataMetadata mappedToBravoField) { + this(columnName, mappedToBravoField, mappedToBravoField.isExpectedInLogFile()); + } + + private ExpeditionExtendedSensorDataMetadata(String columnName, BravoExtendedSensorDataMetadata mappedToBravoField, boolean expectedInLogfile) { + this.columnName = columnName; + this.mappedToBravoField = mappedToBravoField; + this.expectedInLogfile = expectedInLogfile; + } + + public String getColumnName() { + return columnName; + } + + /** + * The index in the {@link DoubleVectorFix} where this data item will be stored + */ + public int getColumnIndex() { + return mappedToBravoField.getColumnIndex(); + } + + public boolean isExpectedInLogFile() { + return expectedInLogfile; + } + + public static ExpeditionExtendedSensorDataMetadata byColumnName(String valueName) { + ExpeditionExtendedSensorDataMetadata[] values = ExpeditionExtendedSensorDataMetadata.values(); + for (ExpeditionExtendedSensorDataMetadata item : values) { + if (Objects.equals(item.getColumnName(), valueName)) { + return item; + } + } + return null; + } + + public static int getTrackColumnCount() { + return ExpeditionExtendedSensorDataMetadata.values().length; + } + + public static List getTrackColumnNames() { + ArrayList colNames = new ArrayList<>(getTrackColumnCount()); + for (ExpeditionExtendedSensorDataMetadata item : ExpeditionExtendedSensorDataMetadata.values()) { + colNames.add(item.getColumnName()); + } + return colNames; + } + + public static Map getColumnNamesToIndexInDoubleFix() { + final Map columnNamesToIndexInDoubleFix = new HashMap<>(); + for (final ExpeditionExtendedSensorDataMetadata column : ExpeditionExtendedSensorDataMetadata.values()) { + if (column.isExpectedInLogFile()) { + columnNamesToIndexInDoubleFix.put(column.getColumnName(), column.getColumnIndex()); + } + } + return columnNamesToIndexInDoubleFix; + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/BravoExtendedFix.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/BravoExtendedFix.java new file mode 100644 index 00000000000..d38e8be6b5d --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/BravoExtendedFix.java @@ -0,0 +1,28 @@ +package com.sap.sailing.domain.common.tracking; + +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Distance; + +/** + * Extended version of {@link BravoFix} that provides access to more measures found in the extended data format. + */ +public interface BravoExtendedFix extends BravoFix { + Double getPortDaggerboardRake(); + Double getStbdDaggerboardRake(); + Double getPortRudderRake(); + Double getStbdRudderRake(); + Bearing getMastRotation(); + Bearing getLeeway(); + Double getSet(); + Bearing getDrift(); + Distance getDepth(); + Bearing getRudder(); + Double getForestayLoad(); + Double getForestayPressure(); + Bearing getTackAngle(); + Bearing getRake(); + Double getDeflectorPercentage(); + Bearing getTargetHeel(); + Distance getDeflector(); + Double getTargetBoatspeedP(); +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/BravoFix.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/BravoFix.java index aea9ffd3615..3cb3a5e7302 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/BravoFix.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/BravoFix.java @@ -33,13 +33,17 @@ public interface BravoFix extends SensorFix { @Dimension(messageKey="IsFoiling") boolean isFoiling(); + + /** + * Same as {@link #isFoiling()}, only that instead of using the {@link #MIN_FOILING_HEIGHT_THRESHOLD} constant as + * the threshold value for the ride height, here the caller can provide a specific ride height that must be reached + * for a fix to be considered foiling. + */ + boolean isFoiling(Distance minimumRideHeight); @Statistic(messageKey="pitch", resultDecimals=1) Bearing getPitch(); @Statistic(messageKey = "heel", resultDecimals = 1) Bearing getHeel(); - - - } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/DoubleVectorFix.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/DoubleVectorFix.java index 4e955af6679..9367955be94 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/DoubleVectorFix.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/DoubleVectorFix.java @@ -3,10 +3,15 @@ package com.sap.sailing.domain.common.tracking; import com.sap.sse.common.Timed; /** - * A fix that simply holds an array of double values. The interpretation of the data depends on the concrete mapping in - * the RegattaLog. + * A fix that simply holds an array of {@link Double} values. The interpretation of the data depends on the concrete + * mapping in the RegattaLog. Asking beyond the end of the vector using {@link #get(int)} will return {@code null} but + * not throw an exception. */ public interface DoubleVectorFix extends Timed { - double[] get(); - double get(int index); + Double[] get(); + Double get(int index); + /** + * Tells whether at least one component is not {@code null} + */ + boolean hasValidData(); } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoExtendedFixImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoExtendedFixImpl.java new file mode 100644 index 00000000000..4fc24390061 --- /dev/null +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoExtendedFixImpl.java @@ -0,0 +1,126 @@ +package com.sap.sailing.domain.common.tracking.impl; + +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.MeterDistance; +import com.sap.sailing.domain.common.sensordata.BravoExtendedSensorDataMetadata; +import com.sap.sailing.domain.common.sensordata.ColumnMetadata; +import com.sap.sailing.domain.common.tracking.BravoExtendedFix; +import com.sap.sailing.domain.common.tracking.BravoFix; +import com.sap.sailing.domain.common.tracking.DoubleVectorFix; + +/** + * Implementation of {@link BravoExtendedFix}. {@link BravoExtendedFix} adds more measures compared to {@link BravoFix}. + */ +public class BravoExtendedFixImpl extends BravoFixImpl implements BravoExtendedFix { + private static final long serialVersionUID = 5622321493028301922L; + + public BravoExtendedFixImpl(DoubleVectorFix fix) { + super(fix); + } + + @Override + protected ColumnMetadata resolveMetadataFromValueName(String valueName) { + return BravoExtendedSensorDataMetadata.byColumnName(valueName); + } + + @Override + public Double getPortDaggerboardRake() { + return fix.get(BravoExtendedSensorDataMetadata.DB_RAKE_PORT.getColumnIndex()); + } + + @Override + public Double getStbdDaggerboardRake() { + return fix.get(BravoExtendedSensorDataMetadata.DB_RAKE_STBD.getColumnIndex()); + } + + @Override + public Double getPortRudderRake() { + return fix.get(BravoExtendedSensorDataMetadata.RUDDER_RAKE_PORT.getColumnIndex()); + } + + @Override + public Double getStbdRudderRake() { + return fix.get(BravoExtendedSensorDataMetadata.RUDDER_RAKE_STBD.getColumnIndex()); + } + + @Override + public Bearing getMastRotation() { + final Double bearingDeg = fix.get(BravoExtendedSensorDataMetadata.MAST_ROTATION.getColumnIndex()); + return bearingDeg == null ? null : new DegreeBearingImpl(bearingDeg); + } + + @Override + public Bearing getLeeway() { + final Double leeway = fix.get(BravoExtendedSensorDataMetadata.LEEWAY.getColumnIndex()); + return leeway == null ? null : new DegreeBearingImpl(leeway); + } + + @Override + public Double getSet() { + return fix.get(BravoExtendedSensorDataMetadata.SET.getColumnIndex()); + } + + @Override + public Bearing getDrift() { + final Double drift = fix.get(BravoExtendedSensorDataMetadata.DRIFT.getColumnIndex()); + return drift == null ? null : new DegreeBearingImpl(drift); + } + + @Override + public Distance getDepth() { + final Double depthInMeters = fix.get(BravoExtendedSensorDataMetadata.DEPTH.getColumnIndex()); + return depthInMeters == null ? null : new MeterDistance(depthInMeters); + } + + @Override + public Bearing getRudder() { + final Double rudderAngleDeg = fix.get(BravoExtendedSensorDataMetadata.RUDDER.getColumnIndex()); + return rudderAngleDeg == null ? null : new DegreeBearingImpl(rudderAngleDeg); + } + + @Override + public Double getForestayLoad() { + return fix.get(BravoExtendedSensorDataMetadata.FORESTAY_LOAD.getColumnIndex()); + } + + @Override + public Double getForestayPressure() { + return fix.get(BravoExtendedSensorDataMetadata.FORESTAY_PRESSURE.getColumnIndex()); + } + + @Override + public Bearing getTackAngle() { + final Double tackAngleDeg = fix.get(BravoExtendedSensorDataMetadata.TACK_ANGLE.getColumnIndex()); + return tackAngleDeg == null ? null : new DegreeBearingImpl(tackAngleDeg); + } + + @Override + public Bearing getRake() { + final Double rakeDeg = fix.get(BravoExtendedSensorDataMetadata.RAKE_DEG.getColumnIndex()); + return rakeDeg == null ? null : new DegreeBearingImpl(rakeDeg); + } + + @Override + public Double getDeflectorPercentage() { + return fix.get(BravoExtendedSensorDataMetadata.DEFLECTOR_PERCENTAGE.getColumnIndex()); + } + + @Override + public Bearing getTargetHeel() { + final Double targetHeelDeg = fix.get(BravoExtendedSensorDataMetadata.TARGET_HEEL.getColumnIndex()); + return targetHeelDeg == null ? null : new DegreeBearingImpl(targetHeelDeg); + } + + @Override + public Distance getDeflector() { + final Double deflectorMillimeters = fix.get(BravoExtendedSensorDataMetadata.DEFLECTOR_MILLIMETERS.getColumnIndex()); + return deflectorMillimeters == null ? null : new MeterDistance(deflectorMillimeters / 1000.); + } + + @Override + public Double getTargetBoatspeedP() { + return fix.get(BravoExtendedSensorDataMetadata.TARGET_BOATSPEED_P.getColumnIndex()); + } +} diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoFixImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoFixImpl.java index 296b472a1f2..7db57751513 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoFixImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoFixImpl.java @@ -5,43 +5,37 @@ import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata; +import com.sap.sailing.domain.common.sensordata.ColumnMetadata; import com.sap.sailing.domain.common.tracking.BravoFix; import com.sap.sailing.domain.common.tracking.DoubleVectorFix; -import com.sap.sse.common.TimePoint; /** * Implementation of {@link BravoFix} that wraps a {@link DoubleVectorFix} which holds the actual sensor data. The * mapping metadata is stored in the {@link BravoSensorDataMetadata} enum. */ -public class BravoFixImpl implements BravoFix { +public class BravoFixImpl extends SensorFixImpl implements BravoFix { private static final long serialVersionUID = 2033254212013221160L; - private final DoubleVectorFix fix; public BravoFixImpl(DoubleVectorFix fix) { - this.fix = fix; + super(fix); } - + @Override - public double get(String valueName) { - BravoSensorDataMetadata colDefEnum = BravoSensorDataMetadata.byColumnName(valueName); - if (colDefEnum == null) { - throw new IllegalArgumentException("Unknown value \"" + valueName + "\" for " + getClass().getSimpleName()); - } - int index = colDefEnum.getColumnIndex(); - return fix.get(index); - } - - @Override - public TimePoint getTimePoint() { - return fix.getTimePoint(); + protected ColumnMetadata resolveMetadataFromValueName(String valueName) { + return BravoSensorDataMetadata.byColumnName(valueName); } @Override public Distance getRideHeight() { - double rideHeightPortHullasDouble = fix.get(BravoSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()); - double rideHeightStarboardHullasDouble = fix + Double rideHeightPortHullasDouble = fix.get(BravoSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()); + Double rideHeightStarboardHullAsDouble = fix .get(BravoSensorDataMetadata.RIDE_HEIGHT_STBD_HULL.getColumnIndex()); - return new MeterDistance(Math.min(rideHeightPortHullasDouble, rideHeightStarboardHullasDouble)); + return rideHeightPortHullasDouble == null ? + rideHeightStarboardHullAsDouble == null ? null : + new MeterDistance(rideHeightStarboardHullAsDouble) : + rideHeightStarboardHullAsDouble == null ? + new MeterDistance(rideHeightPortHullasDouble) : + new MeterDistance(Math.min(rideHeightPortHullasDouble, rideHeightStarboardHullAsDouble)); } @Override @@ -59,16 +53,23 @@ public class BravoFixImpl implements BravoFix { @Override public boolean isFoiling() { - return getRideHeight().compareTo(MIN_FOILING_HEIGHT_THRESHOLD) >= 0; + return isFoiling(MIN_FOILING_HEIGHT_THRESHOLD); + } + + @Override + public boolean isFoiling(Distance minimumRideHeight) { + return getRideHeight().compareTo(minimumRideHeight) >= 0; } @Override public Bearing getPitch() { - return new DegreeBearingImpl(fix.get(BravoSensorDataMetadata.PITCH.getColumnIndex())); + final Double bearingDeg = fix.get(BravoSensorDataMetadata.PITCH.getColumnIndex()); + return bearingDeg == null ? null : new DegreeBearingImpl(bearingDeg); } @Override public Bearing getHeel() { - return new DegreeBearingImpl(fix.get(BravoSensorDataMetadata.HEEL.getColumnIndex())); + final Double bearingDeg = fix.get(BravoSensorDataMetadata.HEEL.getColumnIndex()); + return bearingDeg == null ? null : new DegreeBearingImpl(bearingDeg); } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/DoubleVectorFixImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/DoubleVectorFixImpl.java index 9c6b1ca99f3..031a5b197d9 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/DoubleVectorFixImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/DoubleVectorFixImpl.java @@ -1,12 +1,16 @@ package com.sap.sailing.domain.common.tracking.impl; import java.util.Arrays; +import java.util.BitSet; import com.sap.sailing.domain.common.tracking.DoubleVectorFix; import com.sap.sse.common.TimePoint; /** - * Implementation of {@link DoubleVectorFix}. + * Implementation of {@link DoubleVectorFix}. In order to save some space and reduce the number of object headers, + * instead of storing {@link Double} objects the structure internally uses an array of {@code double} values and + * additionally remembers in a {@link BitSet} which components are {@code null}. The {@link #get()} and {@link #get(int)} + * methods then translate back to a {@link Double} representation accordingly. */ public class DoubleVectorFixImpl implements DoubleVectorFix { @@ -14,10 +18,25 @@ public class DoubleVectorFixImpl implements DoubleVectorFix { private final double[] fixData; private final TimePoint timePoint; + + /** + * The constructor accepts a {@code Double[]} for the data components, allowing for {@code null} + * values to be used. To save space, internally a {@code double[]} is used to represent the data. + * In order to encode the {@code null} values, this bit set stores which array components had + * valid, non-{@code null} data. + */ + private final BitSet validComponents; - public DoubleVectorFixImpl(TimePoint timePoint, double[] fixData) { + public DoubleVectorFixImpl(TimePoint timePoint, Double[] fixData) { this.timePoint = timePoint; - this.fixData = fixData; + this.fixData = new double[fixData.length]; + this.validComponents = new BitSet(fixData.length); + for (int i=0; i + + + + + + diff --git a/java/com.sap.sailing.domain.expeditionadapter/.project b/java/com.sap.sailing.domain.expeditionadapter/.project new file mode 100755 index 00000000000..a413084a0cd --- /dev/null +++ b/java/com.sap.sailing.domain.expeditionadapter/.project @@ -0,0 +1,28 @@ + + + com.sap.sailing.domain.expeditionadapter + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.sap.sailing.domain.expeditionadapter/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.domain.expeditionadapter/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..0c68a61dca8 --- /dev/null +++ b/java/com.sap.sailing.domain.expeditionadapter/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sailing.domain.expeditionadapter/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.domain.expeditionadapter/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..f29e940a005 --- /dev/null +++ b/java/com.sap.sailing.domain.expeditionadapter/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sailing.domain.expeditionadapter/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.expeditionadapter/META-INF/MANIFEST.MF new file mode 100755 index 00000000000..43e36b46448 --- /dev/null +++ b/java/com.sap.sailing.domain.expeditionadapter/META-INF/MANIFEST.MF @@ -0,0 +1,13 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Expeditionadapter +Bundle-SymbolicName: com.sap.sailing.domain.expeditionadapter +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Import-Package: org.osgi.framework +Require-Bundle: com.sap.sailing.server.trackfiles, + com.sap.sailing.domain, + com.sap.sse.common, + com.sap.sailing.domain.common +Bundle-Activator: com.sap.sailing.domain.expeditionadapter.impl.Activator diff --git a/java/com.sap.sailing.domain.expeditionadapter/build.properties b/java/com.sap.sailing.domain.expeditionadapter/build.properties new file mode 100755 index 00000000000..34d2e4d2dad --- /dev/null +++ b/java/com.sap.sailing.domain.expeditionadapter/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sailing.domain.expeditionadapter/pom.xml b/java/com.sap.sailing.domain.expeditionadapter/pom.xml new file mode 100755 index 00000000000..420cb8c34f6 --- /dev/null +++ b/java/com.sap.sailing.domain.expeditionadapter/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sailing.domain.expeditionadapter + eclipse-plugin + diff --git a/java/com.sap.sailing.domain.expeditionadapter/src/com/sap/sailing/domain/expeditionadapter/impl/Activator.java b/java/com.sap.sailing.domain.expeditionadapter/src/com/sap/sailing/domain/expeditionadapter/impl/Activator.java new file mode 100755 index 00000000000..a73939a8ac8 --- /dev/null +++ b/java/com.sap.sailing.domain.expeditionadapter/src/com/sap/sailing/domain/expeditionadapter/impl/Activator.java @@ -0,0 +1,17 @@ +package com.sap.sailing.domain.expeditionadapter.impl; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +import com.sap.sailing.server.trackfiles.common.GPSFixImporterRegistration; + +public class Activator implements BundleActivator { + @Override + public void start(BundleContext context) throws Exception { + GPSFixImporterRegistration.register(new ExpeditionGPSFixImporter(), context); + } + + @Override + public void stop(BundleContext context) throws Exception { + } +} diff --git a/java/com.sap.sailing.domain.expeditionadapter/src/com/sap/sailing/domain/expeditionadapter/impl/ExpeditionGPSFixImporter.java b/java/com.sap.sailing.domain.expeditionadapter/src/com/sap/sailing/domain/expeditionadapter/impl/ExpeditionGPSFixImporter.java new file mode 100755 index 00000000000..be38baee40d --- /dev/null +++ b/java/com.sap.sailing.domain.expeditionadapter/src/com/sap/sailing/domain/expeditionadapter/impl/ExpeditionGPSFixImporter.java @@ -0,0 +1,61 @@ +package com.sap.sailing.domain.expeditionadapter.impl; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +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.tracking.GPSFixMoving; +import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl; +import com.sap.sailing.domain.trackimport.FormatNotSupportedException; +import com.sap.sailing.domain.trackimport.GPSFixImporter; +import com.sap.sailing.server.trackfiles.impl.ExpeditionExtendedDataImporterImpl; + +public class ExpeditionGPSFixImporter implements GPSFixImporter { + private static final String LAT_COLUMN_HEADING = ExpeditionExtendedDataImporterImpl.COL_NAME_LAT; + private static final String LON_COLUMN_HEADING = ExpeditionExtendedDataImporterImpl.COL_NAME_LON; + private static final String COG_COLUMN_HEADING = "Cog"; + private static final String SOG_COLUMN_HEADING = "Sog"; + @Override + public void importFixes(InputStream inputStream, Callback callback, boolean inferSpeedAndBearing, final String sourceName) + throws FormatNotSupportedException, IOException { + TrackFileImportDeviceIdentifier device = new TrackFileImportDeviceIdentifierImpl(sourceName, getType() + "@" + new Date()); + final BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); + final String headerLine = br.readLine(); + final Map columnDefinitions = ExpeditionExtendedDataImporterImpl.parseHeader(headerLine); + final AtomicInteger lineNr = new AtomicInteger(0); + br.lines().forEach(line->{ + if (!line.trim().isEmpty()) { + ExpeditionExtendedDataImporterImpl.parseLine(lineNr.incrementAndGet(), sourceName, line, columnDefinitions, + (timePoint, columnValues, columns)->{ + final double latDeg = Double.parseDouble(columnValues[columns.get(LAT_COLUMN_HEADING)]); + final double lonDeg = Double.parseDouble(columnValues[columns.get(LON_COLUMN_HEADING)]); + final double cogDeg = Double.parseDouble(columnValues[columns.get(COG_COLUMN_HEADING)]); + final double sogKnots = Double.parseDouble(columnValues[columns.get(SOG_COLUMN_HEADING)]); + final GPSFixMoving fix = new GPSFixMovingImpl(new DegreePosition(latDeg, lonDeg), timePoint, + new KnotSpeedWithBearingImpl(sogKnots, new DegreeBearingImpl(cogDeg))); + callback.addFix(fix, device); + }); + } + }); + } + + @Override + public Iterable getSupportedFileExtensions() { + return Arrays.asList(new String[] { "csv", "log", "txt" }); + } + + @Override + public String getType() { + return "Expedition"; + } +} diff --git a/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/FixFactoryTest.java b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/FixFactoryTest.java new file mode 100755 index 00000000000..808ba392812 --- /dev/null +++ b/java/com.sap.sailing.domain.igtimiadapter.test/src/com/sap/sailing/domain/igtimiadapter/test/FixFactoryTest.java @@ -0,0 +1,46 @@ +package com.sap.sailing.domain.igtimiadapter.test; + +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.igtimiadapter.datatypes.Fix; +import com.sap.sailing.domain.igtimiadapter.impl.FixFactory; +import com.sap.sse.common.Util; + +public class FixFactoryTest { + private FixFactory fixFactory; + + @Before + public void setUp() { + fixFactory = new FixFactory(); + } + + @Test + public void testUnknownFixTypes() throws ParseException { + final String[] messages = new String[] { + "{\"DC-FE-AAKG\":{\"1\":{\"t\":[1510174025500],\"1\":[170.5044476],\"2\":[-45.87697]}}}", + "{\"DC-FE-AAKG\":{\"2\":{\"t\":[1510174025500],\"1\":[1]}}}", + "{\"DC-FE-AAKG\":{\"3\":{\"t\":[1510174025500],\"1\":[10]}}}", + "{\"DC-FE-AAKG\":{\"7\":{\"t\":[1510174025500],\"1\":[187.7]}}}", + "{\"DC-FE-AAKG\":{\"11\":{\"t\":[1510174025500],\"1\":[54]}}}", + "{\"DC-FE-AAKG\":{\"12\":{\"t\":[1510174025500],\"1\":[1.852]}}}", + "{\"DC-FE-AAKG\":{\"22\":{\"t\":[1510174025500],\"1\":[263.4]}}}", + "{\"DC-FE-AAKG\":{\"23\":{\"t\":[1510174025500],\"1\":[1.852]}}}", + "{\"DC-FE-AAKG\":{\"6\":{\"t\":[1510174025500],\"1\":[0]}}}", + "{\"DC-FE-AAKG\":{\"9\":{\"t\":[1510174025500],\"1\":[0]}}}" }; + List fixes = new ArrayList<>(); + for (final String message : messages) { + JSONObject jsonObject = (JSONObject) new JSONParser().parse(message); + Util.addAll(fixFactory.createFixes(jsonObject), fixes); + } + assertTrue(!Util.isEmpty(fixes)); // this largely asserts that no exception was thrown although there were unknown fix types (22/23) + } +} diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiConnectionFactory.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiConnectionFactory.java index 04bc60e64b3..5d14e3206f4 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiConnectionFactory.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/IgtimiConnectionFactory.java @@ -1,6 +1,8 @@ package com.sap.sailing.domain.igtimiadapter; import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; import javax.xml.parsers.ParserConfigurationException; @@ -32,11 +34,8 @@ public interface IgtimiConnectionFactory { * Obtains a URL that a user agent (e.g., a web browser) can be sent to in order to allow that user to authenticate * and then authorize this factory's {@link Client} for accessing the user's Igtimi data. The URL is chosen such that * it redirects to the {@link AuthorizationCallback} with the code response type. - * @param redirectProtocol TODO - * @param redirectHost TODO - * @param redirectPort TODO */ - String getAuthorizationUrl(String redirectProtocol, String redirectHost, String redirectPort); + String getAuthorizationUrl(String redirectProtocol, String redirectHost, String redirectPort) throws MalformedURLException, UnsupportedEncodingException; /** * Using the "account" service, retrieves the {@link Account} information for this access token and stores the token diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/Activator.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/Activator.java index f9c2941f1e6..14c6ed4004c 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/Activator.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/Activator.java @@ -38,9 +38,15 @@ public class Activator implements BundleActivator { private static final String DEFAULT_CLIENT_ID = "d29eae61621af3057db0e638232a027e96b1d2291b1b89a1481dfcac075b0bf4"; private static final String DEFAULT_CLIENT_SECRET = "537dbd14a84fcb470c91d85e8c4f8f7a356ac5ffc8727594d1bfe900ee5942ef"; + private static final String DEFAULT_REDIRECT_PROTOCOL = "https"; + private static final String DEFAULT_REDIRECT_HOST = "www.sapsailing.com"; + private static final String DEFAULT_REDIRECT_PORT = null; // port==null means default private static final String CLIENT_REDIRECT_PATH = "/igtimi/oauth/v1/authorizationcallback"; private static final String CLIENT_ID_PROPERTY_NAME = "igtimi.client.id"; private static final String CLIENT_SECRET_PROPERTY_NAME = "igtimi.client.secret"; + private static final String CLIENT_REDIRECT_PROTOCOL_PROPERTY_NAME = "igtimi.client.redirect.protocol"; + private static final String CLIENT_REDIRECT_HOST_PROPERTY_NAME = "igtimi.client.redirect.host"; + private static final String CLIENT_REDIRECT_PORT_PROPERTY_NAME = "igtimi.client.redirect.port"; private final Future connectionFactory; private final Future windTrackerFactory; private final ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true)); @@ -49,7 +55,10 @@ public class Activator implements BundleActivator { logger.info(getClass().getName()+" constructor"); final String clientId = System.getProperty(CLIENT_ID_PROPERTY_NAME, DEFAULT_CLIENT_ID); final String clientSecret = System.getProperty(CLIENT_SECRET_PROPERTY_NAME, DEFAULT_CLIENT_SECRET); - final Client client = new ClientImpl(clientId, clientSecret, "https", "www.sapsailing.com", /* port==null means default */ null, CLIENT_REDIRECT_PATH); + final String clientRedirectProtocol = System.getProperty(CLIENT_REDIRECT_PROTOCOL_PROPERTY_NAME, DEFAULT_REDIRECT_PROTOCOL); + final String clientRedirectHost = System.getProperty(CLIENT_REDIRECT_HOST_PROPERTY_NAME, DEFAULT_REDIRECT_HOST); + final String clientRedirectPort = System.getProperty(CLIENT_REDIRECT_PORT_PROPERTY_NAME, DEFAULT_REDIRECT_PORT); + final Client client = new ClientImpl(clientId, clientSecret, clientRedirectProtocol, clientRedirectHost, clientRedirectPort, CLIENT_REDIRECT_PATH); final DomainObjectFactory domainObjectFactory = PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory(); final MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(); connectionFactory = executor.submit(new Callable() { diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java index 71700a8136e..83d1e869d5b 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/FixFactory.java @@ -54,9 +54,12 @@ public class FixFactory { Sensor sensor = new SensorImpl(deviceSerialNumber, fixTypeAndOptionalColonSeparatedSensorsSubId.length < 2 ? 0 : Long.valueOf(fixTypeAndOptionalColonSeparatedSensorsSubId[1])); - Fix fix = createFix(sensor, Type.valueOf(fixType), timePoint, valuesPerSubindex); - result.add(fix); - fixIndex++; + final Type ft = Type.valueOf(fixType); + if (ft != null) { + Fix fix = createFix(sensor, ft, timePoint, valuesPerSubindex); + result.add(fix); + fixIndex++; + } } } return result; diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java index f3ea520e9cb..b75b3f4c482 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/impl/IgtimiConnectionFactoryImpl.java @@ -3,8 +3,10 @@ package com.sap.sailing.domain.igtimiadapter.impl; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; +import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collections; @@ -31,7 +33,6 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.client.LaxRedirectStrategy; import org.apache.http.impl.client.SystemDefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.json.simple.JSONArray; @@ -51,6 +52,7 @@ import com.sap.sailing.domain.igtimiadapter.datatypes.Type; import com.sap.sailing.domain.igtimiadapter.persistence.DomainObjectFactory; import com.sap.sailing.domain.igtimiadapter.persistence.MongoObjectFactory; import com.sap.sse.common.TimePoint; +import com.sap.sse.util.LaxRedirectStrategyForAllRedirectResponseCodes; public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { private static final Logger logger = Logger.getLogger(IgtimiConnectionFactoryImpl.class.getName()); @@ -321,9 +323,17 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { } @Override - public String getAuthorizationUrl(String redirectProtocol, String redirectHost, String redirectPort) { + public String getAuthorizationUrl(String redirectProtocol, String redirectHost, String redirectPortAsString) throws MalformedURLException, UnsupportedEncodingException { + final URL redirectTarget; + int redirectPort; + if (redirectPortAsString == null || redirectPortAsString.trim().isEmpty() || (redirectPort=Integer.valueOf(redirectPortAsString)) == 0) { + redirectTarget = new URL(redirectProtocol, redirectHost, /* file */ ""); + } else { + redirectTarget = new URL(redirectProtocol, redirectHost, redirectPort, /* file */ ""); + } return getBaseUrl()+"/oauth/authorize?response_type=code&client_id="+getClient().getId()+ - "&redirect_uri="+client.getRedirectUri(redirectProtocol, redirectHost, redirectPort); + "&redirect_uri="+URLEncoder.encode(client.getDefaultRedirectUri(), "UTF-8") + + "&state="+URLEncoder.encode(redirectTarget.toString(), "UTF-8"); } private Client getClient() { @@ -373,7 +383,7 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory { DefaultHttpClient client = new SystemDefaultHttpClient(); CookieStore cookieStore = new BasicCookieStore(); client.setCookieStore(cookieStore); - client.setRedirectStrategy(new LaxRedirectStrategy()); + client.setRedirectStrategy(new LaxRedirectStrategyForAllRedirectResponseCodes()); HttpGet get = new HttpGet(getOauthAuthorizeUrl()); HttpResponse responseForAuthorize = client.execute(get); return signInAndReturnAuthorizationForm(client, responseForAuthorize, userEmail, userPassword); diff --git a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/oauth/AuthorizationCallback.java b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/oauth/AuthorizationCallback.java index f2aabc8176e..ce75312a129 100755 --- a/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/oauth/AuthorizationCallback.java +++ b/java/com.sap.sailing.domain.igtimiadapter/src/com/sap/sailing/domain/igtimiadapter/oauth/AuthorizationCallback.java @@ -1,7 +1,9 @@ package com.sap.sailing.domain.igtimiadapter.oauth; import java.io.IOException; +import java.net.URI; import java.net.URISyntaxException; +import java.net.URLEncoder; import java.util.logging.Level; import java.util.logging.Logger; @@ -33,22 +35,38 @@ public class AuthorizationCallback { connectionFactory = Activator.getInstance().getConnectionFactory(); } + /** + * When a non-empty {@code state} parameter is passed, it is assumed to hold the base URL of the server + * to which to re-direct this request. In this case we're probably running on www.sapsailing.com, and + * an authorization request redirect callback from the Igtimi Oauth provider has reached us. The original + * server that requested authentication is expected to have set its base URL in the {@code state} parameter + * and will then receive the same call, only without the {@code state} parameter.

+ * + * If the {@code state} parameter is empty, the call is meant for "us" (this server instance), and the + * access token will be obtained from the {@code code} passed with the request. + */ @GET @Produces("text/plain;charset=UTF-8") @Path(AUTHORIZATIONCALLBACK) - public Response obtainAccessToken(@QueryParam("code") String code) throws ClientProtocolException, IOException, IllegalStateException, ParseException, URISyntaxException { + public Response obtainAccessToken(@QueryParam("code") String code, @QueryParam("state") String state) + throws ClientProtocolException, IOException, IllegalStateException, ParseException, URISyntaxException { Response result; - if (code != null) { - try { - connectionFactory.obtainAccessTokenFromAuthorizationCode(code); - result = Response.ok().entity("SAP Sailing Analytics authorized successfully to access user's tracks").build(); - } catch (Exception e) { - result = Response.status(Status.UNAUTHORIZED).entity(e.getMessage()).build(); - logger.log(Level.SEVERE, "Error trying to obtain access token from authentication code " + code, e); - } + if (state != null) { + // redirect to the server whose base URL is contained in the state parameter + result = Response.temporaryRedirect(new URI(state+"/igtimi/oauth/v1"+AUTHORIZATIONCALLBACK+"?code="+URLEncoder.encode(code, "UTF-8"))).build(); } else { - result = Response.status(Status.UNAUTHORIZED).entity("code parameter not found in request").build(); - logger.log(Level.SEVERE, "Authentication code not found in request"); + if (code != null) { + try { + connectionFactory.obtainAccessTokenFromAuthorizationCode(code); + result = Response.ok().entity("SAP Sailing Analytics authorized successfully to access user's tracks").build(); + } catch (Exception e) { + result = Response.status(Status.UNAUTHORIZED).entity(e.getMessage()).build(); + logger.log(Level.SEVERE, "Error trying to obtain access token from authentication code " + code, e); + } + } else { + result = Response.status(Status.UNAUTHORIZED).entity("code parameter not found in request").build(); + logger.log(Level.SEVERE, "Authentication code not found in request"); + } } return result; } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java index 41925366e0e..f92b84e6a8a 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/DomainObjectFactory.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.persistence; +import java.net.MalformedURLException; import java.net.URL; import java.util.Collection; import java.util.Map; @@ -11,6 +12,7 @@ import java.util.function.Consumer; import com.mongodb.DBObject; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; +import com.sap.sailing.domain.anniversary.DetailedRaceInfo; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.base.Event; @@ -23,6 +25,7 @@ import com.sap.sailing.domain.base.configuration.DeviceConfiguration; import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.dto.AnniversaryType; import com.sap.sailing.domain.leaderboard.EventResolver; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; @@ -194,4 +197,9 @@ public interface DomainObjectFactory { RegattaLeaderboardWithEliminations loadRegattaLeaderboardWithEliminations(DBObject dbLeaderboard, String leaderboardName, String wrappedRegattaLeaderboardName, LeaderboardRegistry leaderboardRegistry); + + /** + * Loads all stored anniversary races. + */ + Map> getAnniversaryData() throws MalformedURLException; } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java index e196204e2e3..84ea757462e 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/MongoObjectFactory.java @@ -2,9 +2,11 @@ package com.sap.sailing.domain.persistence; import java.io.Serializable; import java.net.URL; +import java.util.concurrent.ConcurrentHashMap; import com.mongodb.DB; import com.mongodb.DBObject; +import com.sap.sailing.domain.anniversary.DetailedRaceInfo; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.RaceDefinition; @@ -15,6 +17,7 @@ import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.configuration.DeviceConfiguration; import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.dto.AnniversaryType; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sailing.domain.racelog.RaceLogIdentifier; @@ -22,6 +25,7 @@ import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier; import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRegatta; +import com.sap.sse.common.Util.Pair; /** * Offers methods to construct {@link DBObject MongoDB objects} from domain objects. @@ -181,4 +185,9 @@ public interface MongoObjectFactory { * objects obtained through {@link DomainObjectFactory#loadConnectivityParametersForRacesToRestore(Consumer)}. */ void removeAllConnectivityParametersForRacesToRestore(); + + /** + * Stores determined Anniversary races. + */ + void storeAnniversaryData(ConcurrentHashMap> knownAnniversaries); } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java index 2651cd72545..29f55619367 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/CollectionNames.java @@ -107,4 +107,9 @@ public enum CollectionNames { * way by corresponding implementations of the RaceTrackingConnectivityParametersMongoHandler} interface. */ CONNECTIVITY_PARAMS_FOR_RACES_TO_BE_RESTORED, + + /** + * Contains the known anniversaries + */ + ANNIVERSARIES } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java index db509e2f7a1..a8bdcb83c67 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java @@ -39,6 +39,7 @@ import com.mongodb.WriteResult; import com.mongodb.util.JSON; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; 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; @@ -101,14 +102,18 @@ import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogCloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDefineMarkEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorSensorDataMappingEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompetitorEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRevokeEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnTimeFactorEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.impl.AbstractRegattaLogDeviceCompetitorSensorDataMappingEventImpl; import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogCloseOpenEndedDeviceMappingEventImpl; import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDefineMarkEventImpl; +import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorBravoExtendedMappingEventImpl; import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorBravoMappingEventImpl; +import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorExpeditionExtendedMappingEventImpl; import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl; import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceMarkMappingEventImpl; import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRegisterCompetitorEventImpl; @@ -116,6 +121,7 @@ import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRevokeEv import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEventImpl; import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogSetCompetitorTimeOnTimeFactorEventImpl; import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl; +import com.sap.sailing.domain.anniversary.DetailedRaceInfo; import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.ControlPoint; @@ -150,6 +156,7 @@ import com.sap.sailing.domain.base.impl.SeriesImpl; import com.sap.sailing.domain.base.impl.VenueImpl; import com.sap.sailing.domain.base.impl.WaypointImpl; import com.sap.sailing.domain.common.CourseDesignerMode; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.MarkType; import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.PassingInstruction; @@ -163,6 +170,8 @@ import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.WindSourceType; +import com.sap.sailing.domain.common.dto.AnniversaryType; +import com.sap.sailing.domain.common.dto.EventType; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; @@ -199,7 +208,6 @@ import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongo import com.sap.sailing.domain.persistence.racelog.tracking.impl.PlaceHolderDeviceIdentifierMongoHandler; import com.sap.sailing.domain.racelog.RaceLogIdentifier; import com.sap.sailing.domain.racelog.RaceLogStore; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.impl.PlaceHolderDeviceIdentifierSerializationHandler; import com.sap.sailing.domain.ranking.OneDesignRankingMetric; import com.sap.sailing.domain.ranking.RankingMetricConstructor; @@ -1594,8 +1602,15 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { final Long finishingTimePointAsMillis = (Long) dbObject.get(FieldNames.RACE_LOG_FINISHING_TIME_AS_MILLIS.name()); final TimePoint finishingTime = finishingTimePointAsMillis == null ? null : new MillisecondsTimePoint(finishingTimePointAsMillis); final String comment = (String) dbObject.get(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name()); + final String mergeStateAsString = (String) dbObject.get(FieldNames.LEADERBOARD_SCORE_CORRECTION_MERGE_STATE.name()); + final MergeState mergeState; + if (mergeStateAsString == null) { + mergeState = MergeState.OK; + } else { + mergeState = MergeState.valueOf(mergeStateAsString); + } CompetitorResultImpl positionedCompetitor = new CompetitorResultImpl( - competitorId, competitorDisplayName, rank == null ? rankCounter : rank, maxPointsReason, score, finishingTime, comment); + competitorId, competitorDisplayName, rank == null ? rankCounter : rank, maxPointsReason, score, finishingTime, comment, mergeState); positionedCompetitors.add(positionedCompetitor); rankCounter++; } @@ -1704,6 +1719,12 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { } else if (eventClass.equals(RegattaLogDeviceCompetitorBravoMappingEventImpl.class.getSimpleName())) { return loadRegattaLogDeviceCompetitorBravoMappingEvent(createdAt, author, logicalTimePoint, id, dbObject, regattaLogIdentifier, o); + } else if (eventClass.equals(RegattaLogDeviceCompetitorBravoExtendedMappingEventImpl.class.getSimpleName())) { + return loadRegattaLogDeviceCompetitorBravoExtendedMappingEvent(createdAt, author, logicalTimePoint, id, dbObject, + regattaLogIdentifier, o); + } else if (eventClass.equals(RegattaLogDeviceCompetitorExpeditionExtendedMappingEventImpl.class.getSimpleName())) { + return loadRegattaLogDeviceCompetitorExpeditionExtendedMappingEvent(createdAt, author, logicalTimePoint, id, dbObject, + regattaLogIdentifier, o); } else if (eventClass.equals(RegattaLogDeviceMarkMappingEvent.class.getSimpleName())) { return loadRegattaLogDeviceMarkMappingEvent(createdAt, author, logicalTimePoint, id, dbObject, regattaLogIdentifier, o); } else if (eventClass.equals(RegattaLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName())) { @@ -1891,6 +1912,28 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { private RegattaLogDeviceCompetitorBravoMappingEventImpl loadRegattaLogDeviceCompetitorBravoMappingEvent( TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, final DBObject dbObject, RegattaLikeIdentifier regattaLogIdentifier, DBObject outerDBObject) { + return loadRegattaLogDeviceCompetitorSensorDataMappingEvent(createdAt, author, logicalTimePoint, id, dbObject, + regattaLogIdentifier, outerDBObject, RegattaLogDeviceCompetitorBravoMappingEventImpl::new); + } + + private RegattaLogDeviceCompetitorBravoExtendedMappingEventImpl loadRegattaLogDeviceCompetitorBravoExtendedMappingEvent( + TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, + final DBObject dbObject, RegattaLikeIdentifier regattaLogIdentifier, DBObject outerDBObject) { + return loadRegattaLogDeviceCompetitorSensorDataMappingEvent(createdAt, author, logicalTimePoint, id, dbObject, + regattaLogIdentifier, outerDBObject, RegattaLogDeviceCompetitorBravoExtendedMappingEventImpl::new); + } + + private RegattaLogDeviceCompetitorExpeditionExtendedMappingEventImpl loadRegattaLogDeviceCompetitorExpeditionExtendedMappingEvent( + TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, + final DBObject dbObject, RegattaLikeIdentifier regattaLogIdentifier, DBObject outerDBObject) { + return loadRegattaLogDeviceCompetitorSensorDataMappingEvent(createdAt, author, logicalTimePoint, id, dbObject, + regattaLogIdentifier, outerDBObject, RegattaLogDeviceCompetitorExpeditionExtendedMappingEventImpl::new); + } + + private MappingT loadRegattaLogDeviceCompetitorSensorDataMappingEvent( + TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable id, + final DBObject dbObject, RegattaLikeIdentifier regattaLogIdentifier, DBObject outerDBObject, + AbstractRegattaLogDeviceCompetitorSensorDataMappingEventImpl.Factory factory) { DeviceIdentifier device = null; try { device = loadDeviceId(deviceIdentifierServiceFinder, (DBObject) dbObject.get(FieldNames.DEVICE_ID.name())); @@ -1911,7 +1954,7 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { final TimePoint from = times.getA(); final TimePoint to = times.getB(); final boolean needsMigration = times.getC(); - final RegattaLogDeviceCompetitorBravoMappingEventImpl result = new RegattaLogDeviceCompetitorBravoMappingEventImpl( + final MappingT result = factory.create( createdAt, logicalTimePoint, author, id, mappedTo, device, from, to); if (needsMigration) { // remove old version of mapping event @@ -2366,4 +2409,57 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { } }; } + + @Override + public Map> getAnniversaryData() throws MalformedURLException { + HashMap> fromDb = new HashMap<>(); + DBCollection anniversarysStored = database.getCollection(CollectionNames.ANNIVERSARIES.name()); + DBCursor cursor = anniversarysStored.find(); + while (cursor.hasNext()) { + DBObject toLoad = cursor.next(); + String leaderboardName = toLoad.get(FieldNames.LEADERBOARD_NAME.name()).toString(); + String eventID = toLoad.get(FieldNames.EVENT_ID.name()).toString(); + + final Object mongoDisplayName = toLoad.get(FieldNames.LEADERBOARD_DISPLAY_NAME.name()); + final String leaderboardDisplayName; + if (mongoDisplayName == null) { + leaderboardDisplayName = null; + } else { + leaderboardDisplayName = mongoDisplayName.toString(); + } + final Object mongoEventName = toLoad.get(FieldNames.EVENT_NAME.name()); + final String eventName; + if (mongoEventName == null) { + eventName = null; + } else { + eventName = mongoEventName.toString(); + } + + TimePoint startOfRace = new MillisecondsTimePoint( + ((Number) toLoad.get(FieldNames.START_OF_RACE.name())).longValue()); + String race = toLoad.get(FieldNames.RACE_NAME.name()).toString(); + String regatta = toLoad.get(FieldNames.REGATTA_NAME.name()).toString(); + Object rurl = toLoad.get(FieldNames.REMOTE_URL.name()); + final URL remoteUrlOrNull; + if (rurl != null) { + remoteUrlOrNull = new URL(rurl.toString()); + } else { + remoteUrlOrNull = null; + } + final Object typeJson = toLoad.get(FieldNames.EVENT_TYPE.name()); + final EventType eventType; + if (typeJson == null) { + eventType = null; + } else { + eventType = EventType.valueOf(typeJson.toString()); + } + DetailedRaceInfo loadedAnniversary = new DetailedRaceInfo(new RegattaNameAndRaceName(regatta, race), + leaderboardName, leaderboardDisplayName, startOfRace, UUID.fromString(eventID), eventName, eventType, + remoteUrlOrNull); + int anniversary = ((Number) toLoad.get(FieldNames.ANNIVERSARY_NUMBER.name())).intValue(); + String type = toLoad.get(FieldNames.ANNIVERSARY_TYPE.name()).toString(); + fromDb.put(anniversary, new Pair<>(loadedAnniversary, AnniversaryType.valueOf(type))); + } + return fromDb; + } } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java index 0f0509aa82b..a3dfe0b6343 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java @@ -80,7 +80,8 @@ public enum FieldNames { LEADERBOARD_IS_MEDAL_RACE_COLUMN, LEADERBOARD_CARRIED_POINTS, LEADERBOARD_CARRIED_POINTS_BY_ID, LEADERBOARD_SCORE_CORRECTIONS, LEADERBOARD_DISCARDING_THRESHOLDS, LEADERBOARD_SCORE_CORRECTION_MAX_POINTS_REASON, LEADERBOARD_CORRECTED_SCORE, LEADERBOARD_SCORE_CORRECTION_TIMESTAMP, - LEADERBOARD_RANK, LEADERBOARD_SCORE_CORRECTION_COMMENT, LEADERBOARD_COLUMN_FACTORS, WRAPPED_REGATTA_LEADERBOARD_NAME, + LEADERBOARD_RANK, LEADERBOARD_SCORE_CORRECTION_COMMENT, LEADERBOARD_SCORE_CORRECTION_MERGE_STATE, + LEADERBOARD_COLUMN_FACTORS, WRAPPED_REGATTA_LEADERBOARD_NAME, ELMINATED_COMPETITORS, // leaderboard group @@ -161,5 +162,8 @@ public enum FieldNames { SAILORS_INFO_URL, SAILORS_INFO_LOCALE, // double vector fixes - FIX; + FIX, + + //anniversary fields + START_OF_RACE, ANNIVERSARY_NUMBER, REMOTE_URL, ANNIVERSARY_TYPE, EVENT_TYPE; } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java index 6bf58f9873f..feed1f28979 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -62,7 +63,7 @@ import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRegisterCompe import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRevokeEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnDistanceAllowancePerNauticalMileEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogSetCompetitorTimeOnTimeFactorEvent; -import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorBravoMappingEventImpl; +import com.sap.sailing.domain.anniversary.DetailedRaceInfo; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.ControlPoint; import com.sap.sailing.domain.base.ControlPointWithTwoMarks; @@ -85,6 +86,7 @@ import com.sap.sailing.domain.base.configuration.RegattaConfiguration; import com.sap.sailing.domain.base.configuration.impl.DeviceConfigurationMatcherSingle; import com.sap.sailing.domain.base.impl.FleetImpl; import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.domain.common.Positioned; @@ -93,6 +95,7 @@ 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.WindSource; +import com.sap.sailing.domain.common.dto.AnniversaryType; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard; import com.sap.sailing.domain.leaderboard.Leaderboard; @@ -106,7 +109,6 @@ import com.sap.sailing.domain.persistence.MongoObjectFactory; import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; import com.sap.sailing.domain.persistence.racelog.tracking.impl.PlaceHolderDeviceIdentifierMongoHandler; import com.sap.sailing.domain.racelog.RaceLogIdentifier; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.regattalike.RegattaLikeIdentifier; import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParameters; import com.sap.sailing.domain.tracking.RaceTrackingConnectivityParametersHandler; @@ -125,6 +127,7 @@ import com.sap.sse.common.Timed; import com.sap.sse.common.TypeBasedServiceFinder; import com.sap.sse.common.TypeBasedServiceFinderFactory; import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; import com.sap.sse.common.impl.MillisecondsTimePoint; import com.sap.sse.shared.media.ImageDescriptor; import com.sap.sse.shared.media.VideoDescriptor; @@ -1236,6 +1239,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { result.put(FieldNames.RACE_LOG_FINISHING_TIME_AS_MILLIS.name(), competitorResult.getFinishingTime() == null ? null : competitorResult.getFinishingTime().asMillis()); result.put(FieldNames.LEADERBOARD_RANK.name(), competitorResult.getOneBasedRank()); result.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_COMMENT.name(), competitorResult.getComment()); + result.put(FieldNames.LEADERBOARD_SCORE_CORRECTION_MERGE_STATE.name(), competitorResult.getMergeState().name()); return result; } @@ -1473,7 +1477,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { public void storeRegattaLogEvent(RegattaLikeIdentifier regattaLikeId, RegattaLogDeviceCompetitorSensorDataMappingEvent event) { DBObject result = createBasicRegattaLogEventDBObject(event); - result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), RegattaLogDeviceCompetitorBravoMappingEventImpl.class.getSimpleName()); + result.put(FieldNames.REGATTA_LOG_EVENT_CLASS.name(), event.getClass().getSimpleName()); storeDeviceMappingEvent(event, result, FieldNames.REGATTA_LOG_FROM, FieldNames.REGATTA_LOG_TO); result.put(FieldNames.COMPETITOR_ID.name(), event.getMappedTo().getId()); storeRegattaLogEvent(regattaLikeId, result); @@ -1630,4 +1634,35 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { database.getCollection(CollectionNames.CONNECTIVITY_PARAMS_FOR_RACES_TO_BE_RESTORED.name()).drop(); } + @Override + public void storeAnniversaryData( + ConcurrentHashMap> knownAnniversaries) { + try { + DBCollection anniversarysStored = database.getCollection(CollectionNames.ANNIVERSARIES.name()); + for (Entry> anniversary : knownAnniversaries.entrySet()) { + BasicDBObject currentProxy = new BasicDBObject(FieldNames.ANNIVERSARY_NUMBER.name(), + anniversary.getKey().intValue()); + BasicDBObject newValue = new BasicDBObject(FieldNames.ANNIVERSARY_NUMBER.name(), + anniversary.getKey().intValue()); + DetailedRaceInfo raceInfo = anniversary.getValue().getA(); + newValue.append(FieldNames.RACE_NAME.name(), raceInfo.getIdentifier().getRaceName()); + newValue.append(FieldNames.REGATTA_NAME.name(), raceInfo.getIdentifier().getRegattaName()); + newValue.append(FieldNames.LEADERBOARD_NAME.name(), raceInfo.getLeaderboardName()); + newValue.append(FieldNames.LEADERBOARD_DISPLAY_NAME.name(), raceInfo.getLeaderboardDisplayName()); + storeTimePoint(raceInfo.getStartOfRace(), newValue, (FieldNames.START_OF_RACE.name())); + newValue.append(FieldNames.EVENT_ID.name(), raceInfo.getEventID().toString()); + newValue.append(FieldNames.EVENT_NAME.name(), raceInfo.getEventName()); + newValue.append(FieldNames.EVENT_TYPE.name(), + raceInfo.getEventType() == null ? null : raceInfo.getEventType().name()); + + final URL remoteUrl = raceInfo.getRemoteUrl(); + newValue.append(FieldNames.REMOTE_URL.name(), remoteUrl == null ? null : remoteUrl.toExternalForm()); + newValue.append(FieldNames.ANNIVERSARY_TYPE.name(), anniversary.getValue().getB().toString()); + anniversarysStored.update(currentProxy, newValue, true, false); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/DoubleVectorFixMongoHandlerImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/DoubleVectorFixMongoHandlerImpl.java index c39f4e58069..7d4043b0e56 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/DoubleVectorFixMongoHandlerImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/DoubleVectorFixMongoHandlerImpl.java @@ -38,20 +38,20 @@ public class DoubleVectorFixMongoHandlerImpl implements FixMongoHandler dbValues = (List) dbObject; - double[] result = new double[dbValues.size()]; - for(int i = 0 ; i < dbValues.size() ; i++) { - result[i] = dbValues.get(i).doubleValue(); + Double[] result = new Double[dbValues.size()]; + for (int i = 0 ; i < dbValues.size() ; i++) { + result[i] = dbValues.get(i) == null ? null : dbValues.get(i).doubleValue(); } return result; } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoSensorFixStoreImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoSensorFixStoreImpl.java index d9cf59b0009..9459a46b8ed 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoSensorFixStoreImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/MongoSensorFixStoreImpl.java @@ -20,6 +20,7 @@ import com.mongodb.DBCollection; import com.mongodb.DBCursor; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.persistence.DomainObjectFactory; import com.sap.sailing.domain.persistence.MongoObjectFactory; @@ -30,7 +31,6 @@ import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongo import com.sap.sailing.domain.persistence.racelog.tracking.FixMongoHandler; import com.sap.sailing.domain.persistence.racelog.tracking.MongoSensorFixStore; import com.sap.sailing.domain.racelog.tracking.FixReceivedListener; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sse.common.Duration; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.TimePoint; @@ -325,9 +325,7 @@ public class MongoSensorFixStoreImpl implements MongoSensorFixStore { throws TransformationException, NoCorrespondingServiceRegisteredException { Map result = new HashMap<>(); for (final DeviceIdentifier deviceIdentifier : forDevices) { - final DBObject deviceQuery = new BasicDBObject( - FieldNames.DEVICE_ID.name() + "." + FieldNames.DEVICE_TYPE_SPECIFIC_ID.name(), - deviceIdentifier.getStringRepresentation()); + final DBObject deviceQuery = getDeviceQuery(deviceIdentifier); final DBObject orderBy = new BasicDBObject( FieldNames.GPSFIX.name() + "." + FieldNames.TIME_AS_MILLIS.name(), -1); DBCursor lastFixForDeviceCursor = fixesCollection.find(deviceQuery).sort(orderBy).limit(1); diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/PlaceHolderDeviceIdentifierMongoHandler.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/PlaceHolderDeviceIdentifierMongoHandler.java index ee3ee4fe186..ccf0364559e 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/PlaceHolderDeviceIdentifierMongoHandler.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/racelog/tracking/impl/PlaceHolderDeviceIdentifierMongoHandler.java @@ -1,8 +1,8 @@ package com.sap.sailing.domain.persistence.racelog.tracking.impl; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.impl.PlaceHolderDeviceIdentifierSerializationHandler; public class PlaceHolderDeviceIdentifierMongoHandler extends PlaceHolderDeviceIdentifierSerializationHandler diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java index 2a74622a15d..dcb29f6b6e5 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/AbstractGPSFixStoreTest.java @@ -24,6 +24,7 @@ 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.impl.RegattaImpl; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; @@ -38,7 +39,6 @@ import com.sap.sailing.domain.racelog.tracking.SensorFixStore; import com.sap.sailing.domain.racelog.tracking.test.mock.MockDeviceAndSessionIdentifierWithGPSFixesDeserializer; import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory; import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.ranking.OneDesignRankingMetric; import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java index 33176ca2846..4c9f475b7f3 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/CreateAndTrackWithRaceLogTest.java @@ -36,6 +36,7 @@ import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.base.impl.FleetImpl; import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; @@ -47,7 +48,6 @@ import com.sap.sailing.domain.leaderboard.impl.HighPoint; import com.sap.sailing.domain.racelog.tracking.SensorFixStore; import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory; import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter; import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory; import com.sap.sailing.domain.racelogtracking.impl.fixtracker.RaceLogFixTrackerManager; @@ -209,7 +209,7 @@ public class CreateAndTrackWithRaceLogTest extends RaceLogTrackingTestHelper { // stop tracking, then no more fixes arrive at race service.getRaceTrackerById(raceLog.getId()).stop(false); - raceLogFixTrackerManager.stop(false); + raceLogFixTrackerManager.stop(/* preemptive */ false, /* willBeRemoved */ false); addFixes3(race, comp1, dev1); } @@ -289,7 +289,7 @@ public class CreateAndTrackWithRaceLogTest extends RaceLogTrackingTestHelper { // stop tracking, then no more fixes arrive at race service.getRaceTrackerById(raceLog.getId()).stop(/* preemptive */ false); - raceLogFixTrackerManager.stop(false); + raceLogFixTrackerManager.stop(false, /* willBeRemoved */ false); addFixes3(race, comp1, dev1); } } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/DeviceMappingsAndSensorFixStoreLockingTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/DeviceMappingsAndSensorFixStoreLockingTest.java index 2a23fcdeda4..383942f7784 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/DeviceMappingsAndSensorFixStoreLockingTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/DeviceMappingsAndSensorFixStoreLockingTest.java @@ -16,9 +16,9 @@ import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCo import com.sap.sailing.domain.abstractlog.regatta.impl.RegattaLogImpl; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl; import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.impl.fixtracker.RegattaLogDeviceMappings; import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest; import com.sap.sse.common.MultiTimeRange; @@ -76,7 +76,7 @@ public class DeviceMappingsAndSensorFixStoreLockingTest extends AbstractGPSFixSt }; }.start(); - store.storeFix(device, new DoubleVectorFixImpl(new MillisecondsTimePoint(1), new double[]{0.0})); + store.storeFix(device, new DoubleVectorFixImpl(new MillisecondsTimePoint(1), new Double[]{0.0})); } } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/GPSFixStoreListenerTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/GPSFixStoreListenerTest.java index bab51469897..2174d0718e8 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/GPSFixStoreListenerTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/GPSFixStoreListenerTest.java @@ -9,10 +9,10 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.Timeout; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.persistence.racelog.tracking.impl.MongoSensorFixStoreImpl; import com.sap.sailing.domain.racelog.tracking.FixReceivedListener; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest; public class GPSFixStoreListenerTest extends AbstractGPSFixStoreTest { diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/RaceLogFixTrackerManagerTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/RaceLogFixTrackerManagerTest.java index bddfc87bf0b..5558db801d9 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/RaceLogFixTrackerManagerTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/RaceLogFixTrackerManagerTest.java @@ -31,6 +31,7 @@ import com.sap.sailing.domain.base.impl.CourseImpl; import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; @@ -45,7 +46,6 @@ import com.sap.sailing.domain.racelog.tracking.SensorFixStore; import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory; import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; import com.sap.sailing.domain.racelogsensortracking.SensorFixMapperFactory; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.impl.fixtracker.RaceLogFixTrackerManager; import com.sap.sailing.domain.ranking.OneDesignRankingMetric; import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; @@ -119,7 +119,7 @@ public class RaceLogFixTrackerManagerTest { throw new IllegalArgumentException("Unknown event type"); } }); - raceLogFixTrackerManager.stop(false); + raceLogFixTrackerManager.stop(/* preemptive */ false, /* willBeRemoved */ false); } /** @@ -140,6 +140,6 @@ public class RaceLogFixTrackerManagerTest { } }); trackedRace.attachRaceLog(raceLog2); - raceLogFixTrackerManager.stop(false); + raceLogFixTrackerManager.stop(/* preemptive */ false, /* willBeRemoved */ false); } } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreAndLoadTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreAndLoadTest.java index 3886aa96e8e..383919426b2 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreAndLoadTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreAndLoadTest.java @@ -54,7 +54,10 @@ import com.sap.sailing.domain.base.impl.CourseImpl; import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; @@ -75,12 +78,12 @@ import com.sap.sailing.domain.racelog.tracking.SensorFixStore; import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory; import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; import com.sap.sailing.domain.racelogsensortracking.SensorFixMapperFactory; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.impl.BravoDataFixMapper; import com.sap.sailing.domain.racelogtracking.impl.fixtracker.FixLoaderAndTracker; import com.sap.sailing.domain.ranking.OneDesignRankingMetric; import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; import com.sap.sailing.domain.tracking.BravoFixTrack; +import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.DynamicSensorFixTrack; import com.sap.sailing.domain.tracking.DynamicTrack; import com.sap.sailing.domain.tracking.DynamicTrackedRace; @@ -92,6 +95,7 @@ import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; import com.sap.sailing.domain.tracking.impl.EmptyWindStore; import com.sap.sailing.domain.tracking.impl.SensorFixTrackImpl; +import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Timed; import com.sap.sse.common.Util.Pair; @@ -178,95 +182,65 @@ public class SensorFixStoreAndLoadTest { public void testLoadAlreadyAddedFixes() throws InterruptedException { regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING))); - addBravoFixes(); - FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker(); - trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); - trackedRace.waitForLoadingToFinish(); - testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2); - - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test public void testAddFixesWhileTracking() throws InterruptedException { regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING))); - FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker(); - trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); - addBravoFixes(); - trackedRace.waitForLoadingToFinish(); - testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2); - - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test public void testNoFixesAreLoadedIfNoStoredFixIsInTimeRange() throws InterruptedException { regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(AFTER_LAST_FIX), new MillisecondsTimePoint(END_OF_TRACKING))); - addBravoFixes(); - FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker(); - trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); - trackedRace.waitForLoadingToFinish(); - testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 0); - - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test public void testFixesNotInMappedTimeRangeAreIgnoredWhileTracking() throws InterruptedException { regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(AFTER_LAST_FIX), new MillisecondsTimePoint(END_OF_TRACKING))); - FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker(); - trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); - addBravoFixes(); - trackedRace.waitForLoadingToFinish(); - testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 0); - - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test public void testCompetitorWithoutMappingHasNoTrack() throws InterruptedException { regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING))); - addBravoFixes(); - FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker(); - trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); - trackedRace.waitForLoadingToFinish(); - assertNull(trackedRace.getSensorTrack(comp2, BravoFixTrack.TRACK_NAME)); - - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test @@ -275,20 +249,14 @@ public class SensorFixStoreAndLoadTest { device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING))); regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp2, device, new MillisecondsTimePoint(MID_OF_TRACKING + 1), new MillisecondsTimePoint(END_OF_TRACKING))); - addBravoFixes(); - FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker(); - trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); - trackedRace.waitForLoadingToFinish(); - testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2); testNumberOfRawFixes(trackedRace.getSensorTrack(comp2, BravoFixTrack.TRACK_NAME), 1); - - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test @@ -297,19 +265,13 @@ public class SensorFixStoreAndLoadTest { device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING))); regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(MID_OF_TRACKING + 1), new MillisecondsTimePoint(END_OF_TRACKING))); - addBravoFixes(); - FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker(); - trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); - trackedRace.waitForLoadingToFinish(); - testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 3); - - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test @@ -319,19 +281,13 @@ public class SensorFixStoreAndLoadTest { new MillisecondsTimePoint(MID_OF_TRACKING)); regattaLog.add(mappingEvent); regattaLog.add(new RegattaLogRevokeEventImpl(author, mappingEvent, "Test purposes")); - FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker(); - trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); - addBravoFixes(); - trackedRace.waitForLoadingToFinish(); - assertNull(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME)); - - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test @@ -342,24 +298,17 @@ public class SensorFixStoreAndLoadTest { new MillisecondsTimePoint(END_OF_TRACKING)); regattaLog.add(mappingEvent); regattaLog.add(new RegattaLogRevokeEventImpl(author, mappingEvent, "Test purposes")); - // non-revoked mapping timestamp 100-200 regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING))); - FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker(); - trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); - addBravoFixes(); - trackedRace.waitForLoadingToFinish(); - // Only Fixes from 100 to 200 may be included testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2); - - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test @@ -371,26 +320,17 @@ public class SensorFixStoreAndLoadTest { RegattaLogCloseOpenEndedDeviceMappingEventImpl closeEvent = new RegattaLogCloseOpenEndedDeviceMappingEventImpl( new MillisecondsTimePoint(4), author, mappingEventId, new MillisecondsTimePoint(MID_OF_TRACKING)); regattaLog.add(closeEvent); - addBravoFixes(); - FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker(); - trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); - trackedRace.waitForLoadingToFinish(); - BravoFixTrack bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME); - testNumberOfRawFixes(bravoFixTrack, 2); - regattaLog.add(new RegattaLogRevokeEventImpl(author, closeEvent, "Test purposes")); - trackedRace.waitForLoadingToFinish(); testNumberOfRawFixes(bravoFixTrack, 3); - - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test @@ -405,7 +345,7 @@ public class SensorFixStoreAndLoadTest { BravoFixTrack bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME); assertEquals(FIX_RIDE_HEIGHT, bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getRideHeight()); - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test @@ -414,21 +354,15 @@ public class SensorFixStoreAndLoadTest { deviceTest, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING))); regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING))); - addTestFixes(); addBravoFixes(); - FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker(); - trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); - trackedRace.waitForLoadingToFinish(); - testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2); testNumberOfRawFixes(trackedRace.getSensorTrack(comp, TestFixTrackImpl.TRACK_NAME), 1); - - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test @@ -437,22 +371,15 @@ public class SensorFixStoreAndLoadTest { deviceTest, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING))); regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING))); - - FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker(); - trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); - addTestFixes(); addBravoFixes(); - trackedRace.waitForLoadingToFinish(); - testNumberOfRawFixes(trackedRace.getSensorTrack(comp, TestFixTrackImpl.TRACK_NAME), 1); testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 2); - - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test @@ -461,18 +388,12 @@ public class SensorFixStoreAndLoadTest { deviceTest, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(END_OF_TRACKING))); regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(END_OF_TRACKING))); - - FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker(); - trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); - addTestFixes(); addBravoFixes(); - trackedRace.waitForLoadingToFinish(); - BravoFixTrack bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME); assertEquals(FIX_RIDE_HEIGHT, bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getRideHeight()); @@ -480,12 +401,10 @@ public class SensorFixStoreAndLoadTest { bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP2)).getRideHeight()); assertEquals(FIX_RIDE_HEIGHT3, bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP3)).getRideHeight()); - TestFixTrackImpl testFixTrack = trackedRace.getSensorTrack(comp, TestFixTrackImpl.TRACK_NAME); assertEquals(FIX_TEST_VALUE, testFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getTestValue()); - - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } private void addBravoFixes() { @@ -501,7 +420,6 @@ public class SensorFixStoreAndLoadTest { } private FixLoaderAndTracker createFixLoaderAndTracker() { - return new FixLoaderAndTracker(trackedRace, store, new SensorFixMapperFactory() { private TestDataFixMapper testDataFixMapper = new TestDataFixMapper(); private BravoDataFixMapper bravoDataFixMapper = new BravoDataFixMapper(); @@ -542,7 +460,7 @@ public class SensorFixStoreAndLoadTest { } private DoubleVectorFix createBravoDoubleVectorFixWithRideHeight(long timestamp, double rideHeight) { - double[] fixData = new double[BravoSensorDataMetadata.getTrackColumnCount()]; + Double[] fixData = new Double[BravoSensorDataMetadata.getTrackColumnCount()]; // fill the port/starboard columns as well because their minimum defines the true ride height fixData[BravoSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()] = rideHeight; fixData[BravoSensorDataMetadata.RIDE_HEIGHT_STBD_HULL.getColumnIndex()] = rideHeight; @@ -554,7 +472,7 @@ public class SensorFixStoreAndLoadTest { } private DoubleVectorFix createTestDoubleVectorFixWithTestValue(long timestamp, double testValue) { - double[] fixData = new double[TestFixImpl.COLUMNS.size()]; + Double[] fixData = new Double[TestFixImpl.COLUMNS.size()]; fixData[TestFixImpl.TEST_COLUMN_INDEX] = testValue; return new DoubleVectorFixImpl(new MillisecondsTimePoint(timestamp), fixData); } @@ -679,7 +597,7 @@ public class SensorFixStoreAndLoadTest { trackedRace.waitForLoadingToFinish(); assertNotNull(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME)); assertNotNull(trackedRace.getSensorTrack(comp2, BravoFixTrack.TRACK_NAME)); - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } /** Regression test for bug 4052 - https://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=4052 */ @@ -693,7 +611,7 @@ public class SensorFixStoreAndLoadTest { trackedRace.attachRegattaLog(regattaLog); trackedRace.waitForLoadingToFinish(); assertNull(trackedRace.getSensorTrack(compNotPartOfRace, BravoFixTrack.TRACK_NAME)); - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test @@ -712,7 +630,7 @@ public class SensorFixStoreAndLoadTest { trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(START_OF_TRACKING)); trackedRace.waitForLoadingToFinish(); testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 3); - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test @@ -725,7 +643,7 @@ public class SensorFixStoreAndLoadTest { trackedRace.attachRegattaLog(regattaLog); addBravoFixes(); assertNull(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME)); - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test @@ -746,7 +664,51 @@ public class SensorFixStoreAndLoadTest { trackedRace.waitForLoadingToFinish(); // only the initial 3 fixes are available testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 3); - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); + final DynamicGPSFixTrack gpsFixTrack = trackedRace.getTrack(comp); + final DegreePosition pos1 = new DegreePosition(0, 0); + final DegreeBearingImpl course = new DegreeBearingImpl(0); + final KnotSpeedWithBearingImpl speed = new KnotSpeedWithBearingImpl(10, course); + final MillisecondsTimePoint timePoint = new MillisecondsTimePoint(FIX_TIMESTAMP); + final MillisecondsTimePoint timePoint2 = new MillisecondsTimePoint(FIX_TIMESTAMP2); + final MillisecondsTimePoint timePoint3 = new MillisecondsTimePoint(FIX_TIMESTAMP3); + final TimePoint timePointBetween2And3 = new MillisecondsTimePoint((FIX_TIMESTAMP2+FIX_TIMESTAMP3)/2); + final GPSFixMoving fix1 = new GPSFixMovingImpl(pos1, timePoint, speed); + final Position pos2 = pos1.translateGreatCircle(course, speed.travel(timePoint.until(timePoint2))); + final GPSFixMoving fix2 = new GPSFixMovingImpl(pos2, timePoint2, speed); + final Position pos3 = pos2.translateGreatCircle(course, speed.travel(timePoint2.until(timePoint3))); + final GPSFixMoving fix3 = new GPSFixMovingImpl(pos3, timePoint3, speed); + gpsFixTrack.add(fix1); + gpsFixTrack.add(fix2); + gpsFixTrack.add(fix3); + final BravoFixTrack bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME); + final Distance foilingDistance = bravoFixTrack.getDistanceSpentFoiling(timePoint, timePoint3); + final Distance distanceTraveled = gpsFixTrack.getDistanceTraveled(timePoint, timePoint3); + assertEquals(distanceTraveled.getMeters(), foilingDistance.getMeters(), 0.001); + final Duration foilingDuration = bravoFixTrack.getTimeSpentFoiling(timePoint, timePoint3); + assertEquals(timePoint.until(timePoint3).asMillis(), foilingDuration.asMillis()); + + // now insert a GPS fix which leads to an extended distance traveled as it is not exactly on the previous track + final DegreeBearingImpl temporaryCourse = new DegreeBearingImpl(90); + final GPSFixMoving fixBetween2And3 = new GPSFixMovingImpl( + pos2.translateGreatCircle(temporaryCourse, speed.add(new KnotSpeedWithBearingImpl(5, temporaryCourse)).travel(timePoint2.until(timePointBetween2And3))), + timePointBetween2And3, speed); + gpsFixTrack.add(fixBetween2And3); + final Distance distanceTraveledDifferently = gpsFixTrack.getDistanceTraveled(timePoint, timePoint3); + assertTrue(distanceTraveledDifferently.compareTo(distanceTraveled) > 0); + final Distance foilingDistanceDifferent = bravoFixTrack.getDistanceSpentFoiling(timePoint, timePoint3); + assertEquals(distanceTraveledDifferently.getMeters(), foilingDistanceDifferent.getMeters(), 0.001); + + // now overwrite fix3 by a new one with increased speed, therefore extended distance: + final SpeedWithBearing doubledSpeed = speed.add(speed); + final GPSFixMoving fix3Faster = new GPSFixMovingImpl( + pos2.translateGreatCircle(course, doubledSpeed.travel(timePoint2.until(timePoint3))), + timePoint3, doubledSpeed); + gpsFixTrack.add(fix3Faster, /* replace */ true); + final Distance distanceTraveledFaster = gpsFixTrack.getDistanceTraveled(timePoint, timePoint3); + assertTrue(distanceTraveledFaster.compareTo(distanceTraveled) > 0); + final Distance foilingDistanceFaster = bravoFixTrack.getDistanceSpentFoiling(timePoint, timePoint3); + assertEquals(distanceTraveledFaster.getMeters(), foilingDistanceFaster.getMeters(), 0.001); } @Test @@ -771,7 +733,7 @@ public class SensorFixStoreAndLoadTest { trackedRace.waitForLoadingToFinish(); // only the initial 3 fixes are available testNumberOfRawFixes(trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME), 3); - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test(timeout=10_000) @@ -809,7 +771,7 @@ public class SensorFixStoreAndLoadTest { }; }.start(); // When the bug is triggered, this call would hang until the test timeout is reached - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); } @Test @@ -824,7 +786,7 @@ public class SensorFixStoreAndLoadTest { trackedRace.attachRaceLog(raceLog); trackedRace.attachRegattaLog(regattaLog); trackedRace.waitForLoadingToFinish(); - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); statusTransitionListener.assertTransitions(TrackedRaceStatusEnum.PREPARED, TrackedRaceStatusEnum.TRACKING, TrackedRaceStatusEnum.LOADING, TrackedRaceStatusEnum.TRACKING, TrackedRaceStatusEnum.FINISHED); } @@ -840,7 +802,7 @@ public class SensorFixStoreAndLoadTest { regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp, device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(END_OF_TRACKING))); trackedRace.waitForLoadingToFinish(); - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); statusTransitionListener.assertTransitions(TrackedRaceStatusEnum.PREPARED, TrackedRaceStatusEnum.TRACKING, TrackedRaceStatusEnum.LOADING, TrackedRaceStatusEnum.TRACKING, TrackedRaceStatusEnum.FINISHED); } @@ -857,7 +819,7 @@ public class SensorFixStoreAndLoadTest { trackedRace.attachRegattaLog(regattaLog); trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(START_OF_TRACKING)); trackedRace.waitForLoadingToFinish(); - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); statusTransitionListener.assertTransitions(TrackedRaceStatusEnum.PREPARED, TrackedRaceStatusEnum.TRACKING, TrackedRaceStatusEnum.LOADING, TrackedRaceStatusEnum.TRACKING, TrackedRaceStatusEnum.FINISHED); } @@ -876,7 +838,7 @@ public class SensorFixStoreAndLoadTest { trackedRace.waitForLoadingToFinish(); trackedRace.setStartOfTrackingReceived(new MillisecondsTimePoint(START_OF_TRACKING)); trackedRace.waitForLoadingToFinish(); - fixLoaderAndTracker.stop(true); + fixLoaderAndTracker.stop(true, /* willBeRemoved */ false); statusTransitionListener.assertTransitions(TrackedRaceStatusEnum.PREPARED, TrackedRaceStatusEnum.TRACKING, TrackedRaceStatusEnum.LOADING, TrackedRaceStatusEnum.TRACKING, TrackedRaceStatusEnum.LOADING, TrackedRaceStatusEnum.TRACKING, TrackedRaceStatusEnum.FINISHED); } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreTest.java index 50f0204d706..1b11a7f9f3f 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/SensorFixStoreTest.java @@ -14,6 +14,7 @@ import org.mockito.Mockito; import com.mongodb.DB; import com.mongodb.MongoException; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; @@ -30,7 +31,6 @@ import com.sap.sailing.domain.racelog.tracking.FixReceivedListener; import com.sap.sailing.domain.racelog.tracking.SensorFixStore; import com.sap.sailing.domain.racelog.tracking.test.mock.MockSmartphoneImeiServiceFinderFactory; import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.Timed; import com.sap.sse.common.impl.MillisecondsTimePoint; @@ -384,7 +384,7 @@ public class SensorFixStoreTest { } private DoubleVectorFix createBravoDoubleVectorFixWithRideHeight(long timestamp, double rideHeight) { - double[] fixData = new double[BravoSensorDataMetadata.getTrackColumnCount()]; + Double[] fixData = new Double[BravoSensorDataMetadata.getTrackColumnCount()]; fixData[BravoSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()] = rideHeight; fixData[BravoSensorDataMetadata.RIDE_HEIGHT_STBD_HULL.getColumnIndex()] = rideHeight; return new DoubleVectorFixImpl(new MillisecondsTimePoint(timestamp), fixData); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java index f449b0dc1b0..56bc80be5bc 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java @@ -24,11 +24,11 @@ import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.impl.CourseImpl; import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.impl.fixtracker.FixLoaderAndTracker; import com.sap.sailing.domain.racelogtracking.test.AbstractGPSFixStoreTest; import com.sap.sailing.domain.tracking.DynamicTrackedRace; diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiIdentifier.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiIdentifier.java index 9bfdebc3545..f0ec889b7ab 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiIdentifier.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiIdentifier.java @@ -1,6 +1,6 @@ package com.sap.sailing.domain.racelog.tracking.test.mock; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.common.DeviceIdentifier; public class SmartphoneImeiIdentifier implements DeviceIdentifier { diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiJsonHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiJsonHandler.java index 3a770d9dfbb..71a6c7cfcc2 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiJsonHandler.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiJsonHandler.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.racelog.tracking.test.mock; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdentifierJsonHandler; public class SmartphoneImeiJsonHandler extends SmartphoneImeiSerializationHandler diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiMongoHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiMongoHandler.java index 1ac23ea7e8f..d102c5e0280 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiMongoHandler.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiMongoHandler.java @@ -1,8 +1,8 @@ package com.sap.sailing.domain.racelog.tracking.test.mock; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; public class SmartphoneImeiMongoHandler extends SmartphoneImeiSerializationHandler implements DeviceIdentifierMongoHandler { diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiSerializationHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiSerializationHandler.java index eaa539c68f6..bddce674e82 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiSerializationHandler.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.testsupport/src/com/sap/sailing/domain/racelog/tracking/test/mock/SmartphoneImeiSerializationHandler.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.racelog.tracking.test.mock; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sse.common.Util; public class SmartphoneImeiSerializationHandler { diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_fr.properties b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_fr.properties index 70b07c43666..00a58568345 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_fr.properties +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/resources/stringmessages/TrackingStringMessages_fr.properties @@ -2,10 +2,10 @@ trackingInvitationFor=Invitation de suivi pour scanQRCodeOrVisitUrlToRegisterAs=Si vous avez bien installé l''application {0}, scannez le code QR suivant ou cliquez sur le lien sur votre smartphone pour vous enregistrer comme welcomeTo=Bienvenue dans iOSUsers=Lancer l''application iOS -androidUsers=Lancer l’application Android +androidUsers=Lancer l''application Android alternativelyVisitThisLink=Si vous consultez ce mail sur l''appareil sur lequel l''application est déjà installée, cliquez simplement sur le bouton ci-dessous pour commencer : buoyTender=Navire baliseur -appStoreInstallText=Si vous n''avez pas encore l''application, installez-la : +appStoreInstallText=Si vous n''avez pas encore l''application, voici les liens pour l''installer : appIos=Installer l''application iOS appAndroid=Installer l''application Android buoyPingerAppName=SAP Sailing Buoy Pinger diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/PingDeviceIdentifier.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/PingDeviceIdentifier.java index 9312eb9f762..2aa2d8778f5 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/PingDeviceIdentifier.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/PingDeviceIdentifier.java @@ -2,6 +2,8 @@ package com.sap.sailing.domain.racelogtracking; import java.util.UUID; +import com.sap.sailing.domain.common.DeviceIdentifier; + /** * A device identifier used to identify a non-existent, virtual device, which is used only once as an * identifier to add a single fix to a track (i.e. pinging the location of that track's item). diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/PingDeviceIdentifierImpl.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/PingDeviceIdentifierImpl.java index d08d7f72ff8..9c8f284f018 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/PingDeviceIdentifierImpl.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/PingDeviceIdentifierImpl.java @@ -4,8 +4,6 @@ import java.util.UUID; public class PingDeviceIdentifierImpl implements PingDeviceIdentifier { private static final long serialVersionUID = -4049961972156611640L; - - private final UUID id; public PingDeviceIdentifierImpl() { diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java index bacd7172c58..1d8c2160bfe 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapter.java @@ -39,8 +39,6 @@ public interface RaceLogTrackingAdapter { *

  • Is a {@link RaceLogRaceTracker} already listening for this racelog? If not, add one.
  • *
  • Is a {@link RaceLogStartTrackingEvent} present in the racelog? If not, add one
  • * - * @param trackWind TODO - * @param correctWindDirectionByMagneticDeclination TODO */ RaceHandle startTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet, boolean trackWind, boolean correctWindDirectionByMagneticDeclination) throws NotDenotedForRaceLogTrackingException, Exception; diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapterFactory.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapterFactory.java index bcaf268825b..abda88a56e9 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapterFactory.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/RaceLogTrackingAdapterFactory.java @@ -4,6 +4,11 @@ import com.sap.sailing.domain.base.DomainFactory; import com.sap.sailing.domain.racelogtracking.impl.RaceLogTrackingAdapterFactoryImpl; public interface RaceLogTrackingAdapterFactory { + /** + * A singleton instance; to be used for testing purposes only; when the containing + * bundle has been activates, an instance can be found in the OSGi service registry. + */ RaceLogTrackingAdapterFactory INSTANCE = RaceLogTrackingAdapterFactoryImpl.INSTANCE; + RaceLogTrackingAdapter getAdapter(DomainFactory baseDomainFactory); } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java index c2b99f36dc3..8be4cc22076 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/Activator.java @@ -88,6 +88,8 @@ public class Activator implements BundleActivator { registrations.add(context.registerService(MasterDataImportClassLoaderService.class, new MasterDataImportClassLoaderServiceImpl(), null)); registrations.add(context.registerService(SensorFixMapper.class, new BravoDataFixMapper(), null)); + registrations.add(context.registerService(SensorFixMapper.class, new BravoExtendedDataFixMapper(), null)); + registrations.add(context.registerService(SensorFixMapper.class, new ExpeditionExtendedDataFixMapper(), null)); sensorFixMapperTracker = createSensorFixMapperServiceTracker(context); racingEventServiceTracker = ServiceTrackerFactory.createAndOpen(context, RacingEventService.class); diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/BravoDataFixMapper.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/BravoDataFixMapper.java index 4469caa01f8..3415d44f166 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/BravoDataFixMapper.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/BravoDataFixMapper.java @@ -20,7 +20,8 @@ public class BravoDataFixMapper implements SensorFixMapper getTrack(DynamicTrackedRace race, Competitor key) { return race.getOrCreateSensorTrack(key, BravoFixTrack.TRACK_NAME, - () -> new BravoFixTrackImpl(key, BravoFixTrack.TRACK_NAME)); + () -> new BravoFixTrackImpl(key, BravoFixTrack.TRACK_NAME, /* hasExtendedFixes */ false, + race.getTrack(key))); } @Override diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/BravoExtendedDataFixMapper.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/BravoExtendedDataFixMapper.java new file mode 100644 index 00000000000..f35509b8ec3 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/BravoExtendedDataFixMapper.java @@ -0,0 +1,37 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorBravoExtendedMappingEventImpl; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.tracking.BravoExtendedFix; +import com.sap.sailing.domain.common.tracking.BravoFix; +import com.sap.sailing.domain.common.tracking.DoubleVectorFix; +import com.sap.sailing.domain.common.tracking.impl.BravoExtendedFixImpl; +import com.sap.sailing.domain.racelog.tracking.SensorFixMapper; +import com.sap.sailing.domain.tracking.BravoFixTrack; +import com.sap.sailing.domain.tracking.DynamicSensorFixTrack; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.impl.BravoFixTrackImpl; + +/** + * {@link SensorFixMapper} implementation to handle {@link BravoExtendedFix}es. + */ +public class BravoExtendedDataFixMapper implements SensorFixMapper, Competitor> { + + @Override + public DynamicSensorFixTrack getTrack(DynamicTrackedRace race, Competitor key) { + return race.getOrCreateSensorTrack(key, BravoFixTrack.TRACK_NAME, + () -> new BravoFixTrackImpl(key, BravoFixTrack.TRACK_NAME, /* hasExtendedFixes */ true, + race.getTrack(key))); + } + + @Override + public BravoExtendedFix map(DoubleVectorFix fix) { + return new BravoExtendedFixImpl(fix); + } + + @Override + public boolean isResponsibleFor(Class> eventType) { + return RegattaLogDeviceCompetitorBravoExtendedMappingEventImpl.class.isAssignableFrom(eventType); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/ExpeditionExtendedDataFixMapper.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/ExpeditionExtendedDataFixMapper.java new file mode 100755 index 00000000000..5dcf9db8119 --- /dev/null +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/ExpeditionExtendedDataFixMapper.java @@ -0,0 +1,41 @@ +package com.sap.sailing.domain.racelogtracking.impl; + +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorExpeditionExtendedMappingEventImpl; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.tracking.BravoExtendedFix; +import com.sap.sailing.domain.common.tracking.BravoFix; +import com.sap.sailing.domain.common.tracking.DoubleVectorFix; +import com.sap.sailing.domain.common.tracking.impl.BravoExtendedFixImpl; +import com.sap.sailing.domain.racelog.tracking.SensorFixMapper; +import com.sap.sailing.domain.tracking.BravoFixTrack; +import com.sap.sailing.domain.tracking.DynamicSensorFixTrack; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.impl.BravoFixTrackImpl; + +/** + * {@link SensorFixMapper} implementation to product {@link BravoExtendedFix}es from {@link DoubleVectorFix}es + * interpreted as "Expedition" data. The data has significant overlap with what we typically receive from the + * "Bravo" devices. Ultimately, the naming may need to be adjusted, better reflecting that the "Bravo" extended + * tracks now assume general responsibility for a broader set of sensor values that may be assigned to a + * competitor, beyond just GPS tracking data. + */ +public class ExpeditionExtendedDataFixMapper implements SensorFixMapper, Competitor> { + + @Override + public DynamicSensorFixTrack getTrack(DynamicTrackedRace race, Competitor key) { + return race.getOrCreateSensorTrack(key, BravoFixTrack.TRACK_NAME, + () -> new BravoFixTrackImpl(key, BravoFixTrack.TRACK_NAME, /* hasExtendedFixes */ true, + race.getTrack(key))); + } + + @Override + public BravoExtendedFix map(DoubleVectorFix fix) { + return new BravoExtendedFixImpl(fix); + } + + @Override + public boolean isResponsibleFor(Class> eventType) { + return RegattaLogDeviceCompetitorExpeditionExtendedMappingEventImpl.class.isAssignableFrom(eventType); + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierJsonHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierJsonHandler.java index 60d1b57dfb7..072e91a1e4f 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierJsonHandler.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierJsonHandler.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.racelogtracking.impl; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdentifierJsonHandler; public class PingDeviceIdentifierJsonHandler extends PingDeviceIdentifierSerializationHandler diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierMongoHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierMongoHandler.java index efb7ecb08dc..9a5a7d37764 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierMongoHandler.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierMongoHandler.java @@ -1,8 +1,8 @@ package com.sap.sailing.domain.racelogtracking.impl; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; public class PingDeviceIdentifierMongoHandler extends PingDeviceIdentifierSerializationHandler implements DeviceIdentifierMongoHandler { diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierSerializationHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierSerializationHandler.java index 415af995c85..2d1e5578407 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierSerializationHandler.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/PingDeviceIdentifierSerializationHandler.java @@ -2,8 +2,8 @@ package com.sap.sailing.domain.racelogtracking.impl; import java.util.UUID; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.PingDeviceIdentifier; import com.sap.sailing.domain.racelogtracking.PingDeviceIdentifierImpl; import com.sap.sse.common.Util; diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java index 7ed7c2c6c5d..dff27e3ab36 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java @@ -161,7 +161,7 @@ public class RaceLogRaceTracker extends AbstractRaceTrackerBaseImpl { } @Override - protected void onStop(boolean preemptive) { + protected void onStop(boolean preemptive, boolean willBeRemoved) { RaceLog raceLog = params.getRaceLog(); final Pair trackingTimes = new TrackingTimesFinder(raceLog).analyze(); if (!trackedRegatta.getRegatta().isControlTrackingFromStartAndFinishTimes() && diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/SmartphoneUUIDMongoHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/SmartphoneUUIDMongoHandler.java index 8646b044541..e11f27f1d8e 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/SmartphoneUUIDMongoHandler.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/SmartphoneUUIDMongoHandler.java @@ -1,8 +1,8 @@ package com.sap.sailing.domain.racelogtracking.impl; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.SmartphoneUUIDSerializationHandler; public class SmartphoneUUIDMongoHandler extends SmartphoneUUIDSerializationHandler diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierJsonHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierJsonHandler.java index fcdb4f4876e..bff372f9a57 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierJsonHandler.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierJsonHandler.java @@ -4,8 +4,8 @@ import java.util.UUID; import org.json.simple.JSONObject; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl; import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdentifierJsonHandler; diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierMongoHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierMongoHandler.java index a9b1e2cd8b5..1218089453f 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierMongoHandler.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierMongoHandler.java @@ -4,9 +4,9 @@ import java.util.UUID; import com.mongodb.BasicDBObject; import com.mongodb.DBObject; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl; import com.sap.sse.common.TimePoint; diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierStringSerializationHandler.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierStringSerializationHandler.java index e121abd0502..9f66f61d8e1 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierStringSerializationHandler.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/TrackFileImportDeviceIdentifierStringSerializationHandler.java @@ -2,8 +2,8 @@ package com.sap.sailing.domain.racelogtracking.impl; import java.util.UUID; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceIdentifierStringSerializationHandler; import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl; diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java index 7776003a4d6..d83e9517bb5 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/FixLoaderAndTracker.java @@ -23,6 +23,7 @@ import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMap import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.common.tracking.DoubleVectorFix; @@ -33,7 +34,6 @@ import com.sap.sailing.domain.racelog.tracking.FixReceivedListener; import com.sap.sailing.domain.racelog.tracking.SensorFixMapper; import com.sap.sailing.domain.racelog.tracking.SensorFixStore; import com.sap.sailing.domain.racelogsensortracking.SensorFixMapperFactory; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.DynamicSensorFixTrack; @@ -93,7 +93,7 @@ import com.sap.sse.util.ThreadPoolUtil; * * * - * There is a corner cases that is assumed to be acceptable for the specific semantic of a {@link Mark}'s fixes:
    + * There is a corner case that is assumed to be acceptable for the specific semantic of a {@link Mark}'s fixes:
    * If a Marks is tracked and not pinged, any new fix transferred from the tracking device is treated as being better * than the one before. So all fixes are being recorded starting at the time point when the operator starts tracking for * a race and startOfTracking is in the future.
    @@ -122,7 +122,20 @@ public class FixLoaderAndTracker implements TrackingDataLoader { */ private final Set loadingJobs = ConcurrentHashMap.newKeySet(); private final SensorFixMapperFactory sensorFixMapperFactory; + + /** + * This flag is used to tell the loaders/trackers whether preemptive stopping has been requested. If switched + * to {@code true}, running loaders will stop loading fixes and return immediately. + */ private AtomicBoolean preemptiveStopRequested = new AtomicBoolean(false); + + /** + * When {@link #stop(boolean, boolean)} is called with {@code willBeStopped==true}, the fact will be recorded + * in this attribute. This is then evaluated when updating general progress so as to set the race status to + * {@link TrackedRaceStatusEnum#REMOVED} instead of {@link TrackedRaceStatusEnum#FINISHED}. + */ + private AtomicBoolean willBeRemovedAfterStopping = new AtomicBoolean(false); + private AtomicBoolean stopRequested = new AtomicBoolean(false); private final AbstractRaceChangeListener raceChangeListener = new AbstractRaceChangeListener() { @Override @@ -263,7 +276,6 @@ public class FixLoaderAndTracker implements TrackingDataLoader { this.sensorFixStore = sensorFixStore; this.sensorFixMapperFactory = sensorFixMapperFactory; this.trackedRace = trackedRace; - startTracking(); } @@ -362,7 +374,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader { */ private void loadFixes(TimeRange timeRangeToLoad, RegattaLogDeviceMappingEvent mappingEvent, final Consumer progressConsumer, final BooleanSupplier stopCallback) { - if (timeRangeToLoad != null && !preemptiveStopRequested.get()) { + if (timeRangeToLoad != null && !stopCallback.getAsBoolean()) { mappingEvent.accept(new MappingEventVisitor() { @Override public void visit(RegattaLogDeviceCompetitorSensorDataMappingEvent event) { @@ -496,15 +508,22 @@ public class FixLoaderAndTracker implements TrackingDataLoader { * TracTrac for archived races, loading is automatically stopped. Finishing already started loading jobs ensures, * that e.g. bravo fixes are completely loaded even if loading from TracTrac is faster.
    * If stopping preemptively, the call will block until all already started loading jobs are aborted or finished. + * + * @param willBeRemoved + * if {@code true} then not only will tracking for the race be stopped, but the race is expected to be + * removed shortly by the caller; therefore it does not make sense to resume cache calculations for + * the race. The race status will then be set to {@link TrackedRaceStatusEnum#REMOVED} instead of + * {@link TrackedRaceStatusEnum#FINISHED}. */ - public void stop(boolean preemptive) { + public void stop(boolean preemptive, boolean willBeRemoved) { preemptiveStopRequested.set(preemptive); + willBeRemovedAfterStopping.set(willBeRemoved); stopRequested.set(true); trackedRace.removeListener(raceChangeListener); deviceMappings.stop(); synchronized (loadingJobs) { if (loadingJobs.isEmpty()) { - setStatusAndProgress(TrackedRaceStatusEnum.FINISHED, 1.0); + setStatusAndProgress(willBeRemoved ? TrackedRaceStatusEnum.REMOVED : TrackedRaceStatusEnum.FINISHED, 1.0); } } sensorFixStore.removeListener(listener); @@ -544,7 +563,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader { } /** - * Updates the {@link FixLoaderAndTracker}'s overall state on the {@link TrackedRace} based on the progresses of + * Updates the {@link FixLoaderAndTracker}'s overall status on the {@link TrackedRace} based on the progresses of * {@link #loadingJobs}. */ private void updateStatusAndProgress() { @@ -560,15 +579,14 @@ public class FixLoaderAndTracker implements TrackingDataLoader { } if (allFinished) { loadingJobs.clear(); - status = stopRequested.get() ? TrackedRaceStatusEnum.FINISHED : TrackedRaceStatusEnum.TRACKING; + status = stopRequested.get() ? willBeRemovedAfterStopping.get() ? TrackedRaceStatusEnum.REMOVED : TrackedRaceStatusEnum.FINISHED : TrackedRaceStatusEnum.TRACKING; progress = 1.0; } else { progress = progressSum / loadingJobs.size(); status = TrackedRaceStatusEnum.LOADING; } - } else { - status = stopRequested.get() ? TrackedRaceStatusEnum.FINISHED : TrackedRaceStatusEnum.TRACKING; + status = stopRequested.get() ? willBeRemovedAfterStopping.get() ? TrackedRaceStatusEnum.REMOVED : TrackedRaceStatusEnum.FINISHED : TrackedRaceStatusEnum.TRACKING; progress = 1.0; } setStatusAndProgress(status, progress); @@ -629,9 +647,7 @@ public class FixLoaderAndTracker implements TrackingDataLoader { @Override public final void run() { - updateStatusAndProgressWithErrorHandling(); - try { load(this::updateProgress, preemptiveStopRequested::get); } finally { diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RaceLogFixTrackerManager.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RaceLogFixTrackerManager.java index dfef30b0ae9..d8c29226b0e 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RaceLogFixTrackerManager.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RaceLogFixTrackerManager.java @@ -88,7 +88,7 @@ public class RaceLogFixTrackerManager implements TrackingDataLoader { if (isForTracking()) { startTrackerIfNotAlreadyStarted(); } else { - stopTrackerIfStillRunning(false); + stopTrackerIfStillRunning(/* preemptive */ false, /* willBeRemoved */ false); } } @@ -116,8 +116,8 @@ public class RaceLogFixTrackerManager implements TrackingDataLoader { knownRaceLogs.remove(raceLog); } - public void stop(boolean preemptive) { - stopTrackerIfStillRunning(preemptive); + public void stop(boolean preemptive, boolean willBeRemoved) { + stopTrackerIfStillRunning(preemptive, willBeRemoved); trackedRace.removeListener(raceChangeListener); synchronized (knownRaceLogs) { final Set tempSet = new HashSet<>(knownRaceLogs); @@ -134,10 +134,10 @@ public class RaceLogFixTrackerManager implements TrackingDataLoader { } } - private synchronized void stopTrackerIfStillRunning(boolean preemptive) { + private synchronized void stopTrackerIfStillRunning(boolean preemptive, boolean willBeRemoved) { if (tracker != null) { logger.fine("Stopping fix tracker for TrackedRace: " + trackedRace.getRaceIdentifier()); - tracker.stop(preemptive); + tracker.stop(preemptive, willBeRemoved); tracker = null; } } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java index 07cc2cefb8b..b837aa38419 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogDeviceMappings.java @@ -22,8 +22,8 @@ import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMap import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogRevokeEvent; import com.sap.sailing.domain.abstractlog.regatta.impl.BaseRegattaLogEventVisitor; import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceMappingFinder; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.DoesNotHaveRegattaLogException; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceMapping; import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent; import com.sap.sailing.domain.tracking.DynamicTrack; diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogFixTrackerRegattaListener.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogFixTrackerRegattaListener.java index e48ea0c6a9b..8cbe6daaef9 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogFixTrackerRegattaListener.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/fixtracker/RegattaLogFixTrackerRegattaListener.java @@ -74,9 +74,9 @@ public class RegattaLogFixTrackerRegattaListener extends AbstractTrackedRegattaA if (raceTracker != null) { boolean added = raceTracker.add(new RaceTracker.Listener() { @Override - public void onTrackerWillStop(boolean preemptive) { + public void onTrackerWillStop(boolean preemptive, boolean willBeRemoved) { raceTracker.remove(this); - removeRaceLogSensorDataTracker(raceIdentifier, preemptive); + removeRaceLogSensorDataTracker(raceIdentifier, preemptive, willBeRemoved); } }); // if !added, the RaceTracker is already stopped, so we are not allowed to start fix tracking @@ -89,7 +89,7 @@ public class RegattaLogFixTrackerRegattaListener extends AbstractTrackedRegattaA oldInstance = dataTrackers.put(raceIdentifier, trackerManager); } if (oldInstance != null) { - oldInstance.stop(true); + oldInstance.stop(/* preemptive */ true, /* willBeRemoved */ false); } } } @@ -102,13 +102,13 @@ public class RegattaLogFixTrackerRegattaListener extends AbstractTrackedRegattaA } private void removeRaceLogSensorDataTracker(RegattaAndRaceIdentifier raceIdentifier) { - removeRaceLogSensorDataTracker(raceIdentifier, false); + removeRaceLogSensorDataTracker(raceIdentifier, /* preemptive */ false, /* willBeRemoved */ false); } - private void removeRaceLogSensorDataTracker(RegattaAndRaceIdentifier raceIdentifier, boolean preemptive) { + private void removeRaceLogSensorDataTracker(RegattaAndRaceIdentifier raceIdentifier, boolean preemptive, boolean willBeRemoved) { RaceLogFixTrackerManager currentActiveDataTracker = dataTrackers.get(raceIdentifier); if (currentActiveDataTracker != null) { - currentActiveDataTracker.stop(preemptive); + currentActiveDataTracker.stop(preemptive, willBeRemoved); trackerStopped(raceIdentifier, currentActiveDataTracker); } } diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/ConfirmedFinishPositioningListFinderTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/ConfirmedFinishPositioningListFinderTest.java index bada0503c17..b818708212f 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/ConfirmedFinishPositioningListFinderTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/ConfirmedFinishPositioningListFinderTest.java @@ -2,19 +2,26 @@ package com.sap.sailing.domain.racelog.analyzing.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import org.junit.Test; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.abstractlog.race.CompetitorResult; +import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState; import com.sap.sailing.domain.abstractlog.race.CompetitorResults; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmedEvent; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.ConfirmedFinishPositioningListFinder; +import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultImpl; +import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultsImpl; +import com.sap.sailing.domain.common.MaxPointsReason; +import com.sap.sse.common.Util; public class ConfirmedFinishPositioningListFinderTest extends PassAwareRaceLogAnalyzerTest { @@ -27,9 +34,17 @@ public class ConfirmedFinishPositioningListFinderTest extends @Override protected TargetPair getTargetEventsAndResultForPassAwareTests(int passId, AbstractLogEventAuthor author) { RaceLogFinishPositioningConfirmedEvent event = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 1, passId, author); - when(event.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(mock(CompetitorResults.class)); + when(event.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(mockCompetitorResults()); return new TargetPair(Arrays.asList(event), event.getPositionedCompetitorsIDsNamesMaxPointsReasons()); } + + private CompetitorResults mockCompetitorResults(CompetitorResult... results) { + final CompetitorResultsImpl result = new CompetitorResultsImpl(); + for (final CompetitorResult r : results) { + result.add(r); + } + return result; + } @Test public void testNullForNone() { @@ -42,11 +57,58 @@ public class ConfirmedFinishPositioningListFinderTest extends public void testMostRecent() { RaceLogFinishPositioningConfirmedEvent event1 = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 1); RaceLogFinishPositioningConfirmedEvent event2 = createEvent(RaceLogFinishPositioningConfirmedEvent.class, 2); - when(event2.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(mock(CompetitorResults.class)); - + final CompetitorResultImpl oldResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", 1, MaxPointsReason.NONE, 1.0, /* finishing time */ null, /* comment */ null, MergeState.OK); + final CompetitorResultImpl oldResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.NONE, 2.0, /* finishing time */ null, /* comment */ null, MergeState.OK); + final CompetitorResults olderResults = mockCompetitorResults(oldResultC1, oldResultC2); + final CompetitorResultImpl newResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK); + final CompetitorResults newerResults = mockCompetitorResults(newResultC2); + when(event1.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(olderResults); + when(event2.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(newerResults); raceLog.add(event1); raceLog.add(event2); + assertCompetitorResultsEquals(mockCompetitorResults(oldResultC1, newResultC2), analyzer.analyze()); + } + + @Test + public void testMixtureOfPartialResultsOfDifferentPriority() { + RaceLogFinishPositioningConfirmedEvent event1 = createEvent(/* priority */ 1, RaceLogFinishPositioningConfirmedEvent.class, 1); + RaceLogFinishPositioningConfirmedEvent event2 = createEvent(/* priority */ 1, RaceLogFinishPositioningConfirmedEvent.class, 2); + RaceLogFinishPositioningConfirmedEvent event3 = createEvent(/* priority */ 2, RaceLogFinishPositioningConfirmedEvent.class, 3); + RaceLogFinishPositioningConfirmedEvent event4 = createEvent(/* priority */ 1, RaceLogFinishPositioningConfirmedEvent.class, 4); + final CompetitorResultImpl e1ResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", 1, MaxPointsReason.DNC, 6.0, /* finishing time */ null, /* comment */ null, MergeState.OK); + final CompetitorResultImpl e1ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.NONE, 2.0, /* finishing time */ null, /* comment */ null, MergeState.OK); + final CompetitorResults e1Results = mockCompetitorResults(e1ResultC1, e1ResultC2); + final CompetitorResultImpl e2ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK); + final CompetitorResults e2Results = mockCompetitorResults(e2ResultC2); + final CompetitorResultImpl e3ResultC2 = new CompetitorResultImpl("Comp2", "Comp 2", 2, MaxPointsReason.DSQ, 6.0, /* finishing time */ null, /* comment */ null, MergeState.OK); + final CompetitorResultImpl e3ResultC3 = new CompetitorResultImpl("Comp3", "Comp 3", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK); + final CompetitorResults e3Results = mockCompetitorResults(e3ResultC2, e3ResultC3); + final CompetitorResultImpl e4ResultC1 = new CompetitorResultImpl("Comp1", "Comp 1", 1, /* maxPointsReason */ null, /* score */ null, /* finishing time */ null, /* comment */ null, MergeState.OK); + final CompetitorResultImpl e4ResultC4 = new CompetitorResultImpl("Comp4", "Comp 4", 2, MaxPointsReason.DNF, 3.0, /* finishing time */ null, /* comment */ null, MergeState.OK); + final CompetitorResults e4Results = mockCompetitorResults(e4ResultC1, e4ResultC4); + when(event1.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(e1Results); + when(event2.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(e2Results); + when(event3.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(e3Results); + when(event4.getPositionedCompetitorsIDsNamesMaxPointsReasons()).thenReturn(e4Results); + // insert in more or less random order + raceLog.add(event4); + raceLog.add(event2); + raceLog.add(event1); + raceLog.add(event3); + // e1ResultC1 is expected to be overruled by e4ResultC1 because the latter has the same priority and comes later + // e1ResultC2 is expected to be overruled by e2ResultC2 due to same priority but later time; and e3ResultC2 has less priority + // e3ResultC3 and e4ResultC4 stand by themselves; they are not overruled by anything + assertCompetitorResultsEquals(mockCompetitorResults(e4ResultC1, e2ResultC2, e3ResultC3, e4ResultC4), analyzer.analyze()); + } - assertEquals(event2.getPositionedCompetitorsIDsNamesMaxPointsReasons(), analyzer.analyze()); + /** + * Results are considered equal if they contain equal sets of {@link CompetitorResult}s. + */ + private void assertCompetitorResultsEquals(CompetitorResults o1, CompetitorResults o2) { + Set o1Results = new HashSet<>(); + Util.addAll(o1, o1Results); + Set o2Results = new HashSet<>(); + Util.addAll(o2, o2Results); + assertEquals(o1Results, o2Results); } } diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceLogAnalyzerTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceLogAnalyzerTest.java index fea4c927df6..a40cda33219 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceLogAnalyzerTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/racelog/analyzing/test/RaceLogAnalyzerTest.java @@ -9,6 +9,7 @@ import java.util.UUID; import org.junit.Before; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogAnalyzer; @@ -29,7 +30,11 @@ public abstract class RaceLogAnalyzerTest T createEvent(Class type, long milliseconds) { - return createEvent(type, milliseconds, 0, UUID.randomUUID()); + return createEvent(/* priority */ 0, type, milliseconds); + } + + protected static T createEvent(int priority, Class type, long milliseconds) { + return createEvent(priority, type, milliseconds, 0, UUID.randomUUID()); } protected static T createEvent(Class type, long milliseconds, Serializable id) { @@ -41,7 +46,12 @@ public abstract class RaceLogAnalyzerTest T createEvent(Class type, long milliseconds, int passId, Serializable id) { - return createEvent(type, milliseconds, passId, id, mock(AbstractLogEventAuthor.class)); + return createEvent(/* priority */ 0, type, milliseconds, passId, id); + } + + protected static T createEvent(int priority, Class type, long milliseconds, int passId, Serializable id) { + final AbstractLogEventAuthor author = new LogEventAuthorImpl("Author", priority); + return createEvent(type, milliseconds, passId, id, author); } protected static T createEvent(Class type, long milliseconds, int passId, AbstractLogEventAuthor author) { diff --git a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/regattalog/tracking/analyzing/test/DeviceMappingFinderTest.java b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/regattalog/tracking/analyzing/test/DeviceMappingFinderTest.java index e9b28c32396..e43e8cece89 100644 --- a/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/regattalog/tracking/analyzing/test/DeviceMappingFinderTest.java +++ b/java/com.sap.sailing.domain.shared.android.test/src/com/sap/sailing/domain/regattalog/tracking/analyzing/test/DeviceMappingFinderTest.java @@ -17,9 +17,9 @@ import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCo import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceCompetitorMappingFinder; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.impl.CompetitorImpl; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.abstractlog.NotRevokableException; import com.sap.sailing.domain.racelog.tracking.test.mock.SmartphoneImeiIdentifier; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; diff --git a/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF index 73cbf568770..a0e1d5bcbb2 100644 --- a/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain.shared.android/META-INF/MANIFEST.MF @@ -40,6 +40,7 @@ Export-Package: com.sap.sailing.domain.abstractlog, com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl, com.sap.sailing.domain.abstractlog.shared.analyzing, com.sap.sailing.domain.abstractlog.shared.events, + com.sap.sailing.domain.anniversary, com.sap.sailing.domain.base, com.sap.sailing.domain.base.configuration, com.sap.sailing.domain.base.configuration.impl, diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/CompetitorResult.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/CompetitorResult.java index a1743691ef4..73e556c861e 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/CompetitorResult.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/CompetitorResult.java @@ -14,6 +14,30 @@ import com.sap.sse.common.TimePoint; * */ public interface CompetitorResult extends Serializable { + public static enum MergeState { + /** + * No merge was necessary at competitor level. + */ + OK, + + /** + * A field-wise merge was necessary because an update was received that affected this competitor result + * while locally this competitor result was in a "dirty" (modified) state. The merge was carried out + * automatically because the fields changed in the dirty state did not overlap with the fields changed + * in the version of the object received. + */ + WARNING, + + /** + * A field-wise merge was necessary because an update was received that affected this competitor result while + * locally this competitor result was in a "dirty" (modified) state. The fields changed in the dirty state + * overlap with the fields changed in the version of the object received. Field values from the local dirty state + * have been preferred or have been appended in case of {@link String}-typed fields and need manual + * intervention. + */ + ERROR + }; + Serializable getCompetitorId(); String getCompetitorDisplayName(); @@ -27,4 +51,9 @@ public interface CompetitorResult extends Serializable { TimePoint getFinishingTime(); String getComment(); + + /** + * Always returns a valid, non-{@code null} result. See {@link MergeState} for details. + */ + MergeState getMergeState(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/CompetitorResults.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/CompetitorResults.java index 9abbcae0b33..b248d2bf176 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/CompetitorResults.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/CompetitorResults.java @@ -7,4 +7,13 @@ package com.sap.sailing.domain.abstractlog.race; */ public interface CompetitorResults extends Iterable { boolean add(CompetitorResult result); + + /** + * @return {@code true} if there are more than one {@link CompetitorResult} contained with equal + * {@link CompetitorResult#getOneBasedRank() rank}. A client can take this as an indicator that + * there may be an inconsistency, caused, e.g., by merging results entered on different devices + * concurrently. The client shall use the {@link #iterator()} method to enumerate the + * {@link CompetitorResult} objects, finding the conflicting elements. + */ + boolean hasConflicts(); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/AbstractFinishPositioningListFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/AbstractFinishPositioningListFinder.java new file mode 100755 index 00000000000..14ad1d00d2f --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/AbstractFinishPositioningListFinder.java @@ -0,0 +1,60 @@ +package com.sap.sailing.domain.abstractlog.race.analyzing.impl; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import com.sap.sailing.domain.abstractlog.race.CompetitorResult; +import com.sap.sailing.domain.abstractlog.race.CompetitorResults; +import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningEvent; +import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultsImpl; + +/** + * Fetches competitor results from the current pass in a {@link RaceLog}. Results may be provided in multiple, partial + * race log events, all of potentially different priority, with time stamps and potential overlaps, providing different + * results for the same competitor at different times, with different priorities. This finder merges all these results + * from the current pass and prefers higher priorities (numerically less) over lower priorities (numerically greater), + * and later results over earlier results with equal priority. It can therefore rely on the order in which race log + * events are produced by {@link #getPassEventsDescending()}.

    + * + * The analyzer will return {@code null} if no result has been found at all. + * + * @author Axel Uhl (D043530) + * + */ +public class AbstractFinishPositioningListFinder extends RaceLogAnalyzer { + /** + * The type of event we're looking for in the log + */ + private final Class clz; + + public AbstractFinishPositioningListFinder(RaceLog raceLog, Class clz) { + super(raceLog); + this.clz = clz; + } + + @Override + protected CompetitorResults performAnalysis() { + boolean resultFound = false; + final Map resultsByCompetitor = new HashMap<>(); + for (RaceLogEvent event : getPassEventsDescending()) { + if (clz.isInstance(event)) { + RaceLogFinishPositioningEvent finishPositioningEvent = (RaceLogFinishPositioningEvent) event; + final CompetitorResults positionedCompetitorsIDsNamesMaxPointsReasons = finishPositioningEvent.getPositionedCompetitorsIDsNamesMaxPointsReasons(); + if (positionedCompetitorsIDsNamesMaxPointsReasons != null) { + resultFound = true; + for (final CompetitorResult r : positionedCompetitorsIDsNamesMaxPointsReasons) { + if (!resultsByCompetitor.containsKey(r.getCompetitorId())) { + resultsByCompetitor.put(r.getCompetitorId(), r); + } + } + } + } + } + final CompetitorResultsImpl result = new CompetitorResultsImpl(); + result.addAll(resultsByCompetitor.values()); + return resultFound ? result : null; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/ConfirmedFinishPositioningListFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/ConfirmedFinishPositioningListFinder.java index 381df5b1c36..5afc4304be9 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/ConfirmedFinishPositioningListFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/ConfirmedFinishPositioningListFinder.java @@ -1,24 +1,10 @@ package com.sap.sailing.domain.abstractlog.race.analyzing.impl; -import com.sap.sailing.domain.abstractlog.race.CompetitorResults; import com.sap.sailing.domain.abstractlog.race.RaceLog; -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmedEvent; -public class ConfirmedFinishPositioningListFinder extends RaceLogAnalyzer { - +public class ConfirmedFinishPositioningListFinder extends AbstractFinishPositioningListFinder { public ConfirmedFinishPositioningListFinder(RaceLog raceLog) { - super(raceLog); - } - - @Override - protected CompetitorResults performAnalysis() { - for (RaceLogEvent event : getPassEventsDescending()) { - if (event instanceof RaceLogFinishPositioningConfirmedEvent) { - RaceLogFinishPositioningConfirmedEvent finishPositioningEvent = (RaceLogFinishPositioningConfirmedEvent) event; - return finishPositioningEvent.getPositionedCompetitorsIDsNamesMaxPointsReasons(); - } - } - return null; + super(raceLog, RaceLogFinishPositioningConfirmedEvent.class); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/FinishPositioningListFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/FinishPositioningListFinder.java index 890748da8fa..5e51c98f0d0 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/FinishPositioningListFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/FinishPositioningListFinder.java @@ -1,25 +1,10 @@ package com.sap.sailing.domain.abstractlog.race.analyzing.impl; -import com.sap.sailing.domain.abstractlog.race.CompetitorResults; import com.sap.sailing.domain.abstractlog.race.RaceLog; -import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningListChangedEvent; -public class FinishPositioningListFinder extends RaceLogAnalyzer { - +public class FinishPositioningListFinder extends AbstractFinishPositioningListFinder { public FinishPositioningListFinder(RaceLog raceLog) { - super(raceLog); - } - - @Override - protected CompetitorResults performAnalysis() { - for (RaceLogEvent event : getPassEventsDescending()) { - if (event instanceof RaceLogFinishPositioningListChangedEvent) { - RaceLogFinishPositioningListChangedEvent finishPositioningEvent = (RaceLogFinishPositioningListChangedEvent) event; - return finishPositioningEvent.getPositionedCompetitorsIDsNamesMaxPointsReasons(); - } - } - - return null; + super(raceLog, RaceLogFinishPositioningListChangedEvent.class); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/CompetitorResultImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/CompetitorResultImpl.java index da8728f267b..d5221e451a4 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/CompetitorResultImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/CompetitorResultImpl.java @@ -23,8 +23,11 @@ public class CompetitorResultImpl implements CompetitorResult { private final String comment; + private final MergeState mergeState; + public CompetitorResultImpl(Serializable competitorId, String competitorDisplayName, int oneBasedRank, - MaxPointsReason maxPointsReason, Double score, TimePoint finishingTime, String comment) { + MaxPointsReason maxPointsReason, Double score, TimePoint finishingTime, String comment, + MergeState mergeState) { super(); this.competitorId = competitorId; this.competitorDisplayName = competitorDisplayName; @@ -33,6 +36,7 @@ public class CompetitorResultImpl implements CompetitorResult { this.score = score; this.finishingTime = finishingTime; this.comment = comment; + this.mergeState = mergeState; } @Override @@ -70,6 +74,11 @@ public class CompetitorResultImpl implements CompetitorResult { return comment; } + @Override + public MergeState getMergeState() { + return mergeState == null ? MergeState.OK : mergeState; // default in case of having de-serialized an old version + } + @Override public int hashCode() { final int prime = 31; @@ -81,6 +90,7 @@ public class CompetitorResultImpl implements CompetitorResult { result = prime * result + ((maxPointsReason == null) ? 0 : maxPointsReason.hashCode()); result = prime * result + oneBasedRank; result = prime * result + ((score == null) ? 0 : score.hashCode()); + result = prime * result + ((mergeState == null) ? 0 : mergeState.hashCode()); return result; } @@ -122,6 +132,11 @@ public class CompetitorResultImpl implements CompetitorResult { return false; } else if (!score.equals(other.score)) return false; + if (mergeState == null) { + if (other.mergeState != null) + return false; + } else if (!mergeState.equals(other.mergeState)) + return false; return true; } @@ -129,7 +144,7 @@ public class CompetitorResultImpl implements CompetitorResult { public String toString() { return "CompetitorResultImpl [competitorId=" + competitorId + ", competitorName=" + competitorDisplayName + ", rank=" + oneBasedRank + ", maxPointsReason=" + maxPointsReason + ", score=" + score + ", finishingTime=" - + finishingTime + ", comment=" + comment + "]"; + + finishingTime + ", comment=" + comment + ", mergeState=" + mergeState + "]"; } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/CompetitorResultsImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/CompetitorResultsImpl.java index 67b4e6cd896..0d60d044dec 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/CompetitorResultsImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/CompetitorResultsImpl.java @@ -1,10 +1,26 @@ package com.sap.sailing.domain.abstractlog.race.impl; import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; import com.sap.sailing.domain.abstractlog.race.CompetitorResult; import com.sap.sailing.domain.abstractlog.race.CompetitorResults; public class CompetitorResultsImpl extends ArrayList implements CompetitorResults { private static final long serialVersionUID = 4928351242700897387L; + + @Override + public boolean hasConflicts() { + final Set oneBasedRanks = new HashSet<>(); + for (final CompetitorResult r : this) { + final int oneBasedRank = r.getOneBasedRank(); + if (oneBasedRank != 0) { + if (!oneBasedRanks.add(oneBasedRank)) { + return true; + } + } + } + return false; + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/NoAddingRaceLogWrapper.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/NoAddingRaceLogWrapper.java index 269d14b692d..5a2a9e2c7dc 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/NoAddingRaceLogWrapper.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/NoAddingRaceLogWrapper.java @@ -15,6 +15,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor; import com.sap.sailing.domain.common.abstractlog.NotRevokableException; +import com.sap.sailing.domain.tracking.impl.TimeRangeCache; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util.Function; @@ -263,4 +264,11 @@ public class NoAddingRaceLogWrapper implements RaceLog { public int size() { return innerRaceLog.size(); } + + @Override + public T getValueSum(TimePoint from, TimePoint to, T nullElement, + com.sap.sailing.domain.tracking.Track.Adder adder, TimeRangeCache cache, + com.sap.sailing.domain.tracking.Track.TimeRangeValueCalculator valueCalculator) { + return innerRaceLog.getValueSum(from, to, nullElement, adder, cache, valueCalculator); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java index 20501c69678..2fbd998177e 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/state/impl/RaceStateImpl.java @@ -158,6 +158,7 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState { public void forceNewDependentStartTime(TimePoint now, final Duration startTimeDifference, final SimpleRaceLogIdentifier dependentOnRace) { raceLog.add(new RaceLogDependentStartTimeEventImpl(now, author, raceLog.getCurrentPassId(), dependentOnRace, startTimeDifference)); } + @Override public void setFinishingTime(TimePoint timePoint) { @@ -166,7 +167,10 @@ public class RaceStateImpl extends ReadonlyRaceStateImpl implements RaceState { @Override public void setFinishedTime(TimePoint timePoint) { - raceLog.add(new RaceLogRaceStatusEventImpl(timePoint, author, raceLog.getCurrentPassId(), RaceLogRaceStatus.FINISHED)); + raceLog.add(new RaceLogRaceStatusEventImpl(timePoint, author, raceLog.getCurrentPassId(), + RaceLogRaceStatus.FINISHED)); + // ensure caches are synched + forceUpdate(); } @Override diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/RegattaLogDeviceMappingEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/RegattaLogDeviceMappingEvent.java index 8895db1fbea..7cac1817803 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/RegattaLogDeviceMappingEvent.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/RegattaLogDeviceMappingEvent.java @@ -6,7 +6,7 @@ import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDefinedMarkAnalyzer; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceMapping; import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimeRange; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/RegattaLogDeviceMappingEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/RegattaLogDeviceMappingEventImpl.java index 88b011916d1..08f4e08c1d9 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/RegattaLogDeviceMappingEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/RegattaLogDeviceMappingEventImpl.java @@ -5,7 +5,7 @@ import java.io.Serializable; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; import com.sap.sailing.domain.abstractlog.impl.AbstractLogEventImpl; import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sse.common.TimePoint; import com.sap.sse.common.WithID; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/AbstractRegattaLogDeviceCompetitorSensorDataMappingEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/AbstractRegattaLogDeviceCompetitorSensorDataMappingEventImpl.java new file mode 100644 index 00000000000..dbe9f1e80ac --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/AbstractRegattaLogDeviceCompetitorSensorDataMappingEventImpl.java @@ -0,0 +1,47 @@ +package com.sap.sailing.domain.abstractlog.regatta.events.impl; + +import java.io.Serializable; + +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.abstractlog.regatta.MappingEventVisitor; +import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorSensorDataMappingEvent; +import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEventImpl; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sse.common.TimePoint; + +/** + * Base type for mapping event for devices mapped for specific Bravo type fixes. + */ +public abstract class AbstractRegattaLogDeviceCompetitorSensorDataMappingEventImpl extends RegattaLogDeviceMappingEventImpl + implements RegattaLogDeviceCompetitorSensorDataMappingEvent { + private static final long serialVersionUID = -1494030544804758753L; + + public interface Factory { + T create(TimePoint createdAt, TimePoint logicalTimePoint, + AbstractLogEventAuthor author, Serializable pId, Competitor mappedTo, DeviceIdentifier device, + TimePoint from, TimePoint to); + } + + public AbstractRegattaLogDeviceCompetitorSensorDataMappingEventImpl(TimePoint createdAt, TimePoint logicalTimePoint, + AbstractLogEventAuthor author, Serializable pId, Competitor mappedTo, DeviceIdentifier device, + TimePoint from, TimePoint to) { + super(createdAt, logicalTimePoint, author, pId, mappedTo, device, from, to); + } + + public AbstractRegattaLogDeviceCompetitorSensorDataMappingEventImpl(TimePoint logicalTimePoint, AbstractLogEventAuthor author, + Competitor mappedTo, DeviceIdentifier device, TimePoint from, TimePoint to) { + super(logicalTimePoint, author, mappedTo, device, from, to); + } + + @Override + public void accept(RegattaLogEventVisitor visitor) { + visitor.visit(this); + } + + @Override + public void accept(MappingEventVisitor visitor) { + visitor.visit(this); + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceCompetitorBravoExtendedMappingEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceCompetitorBravoExtendedMappingEventImpl.java new file mode 100644 index 00000000000..83fc2feb3e4 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceCompetitorBravoExtendedMappingEventImpl.java @@ -0,0 +1,27 @@ +package com.sap.sailing.domain.abstractlog.regatta.events.impl; + +import java.io.Serializable; + +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sse.common.TimePoint; + +/** + * Mapping event for devices mapped for Bravo extended type fixes. + */ +public class RegattaLogDeviceCompetitorBravoExtendedMappingEventImpl extends AbstractRegattaLogDeviceCompetitorSensorDataMappingEventImpl { + private static final long serialVersionUID = -1494030544804758753L; + + + public RegattaLogDeviceCompetitorBravoExtendedMappingEventImpl(TimePoint createdAt, TimePoint logicalTimePoint, + AbstractLogEventAuthor author, Serializable pId, Competitor mappedTo, DeviceIdentifier device, + TimePoint from, TimePoint to) { + super(createdAt, logicalTimePoint, author, pId, mappedTo, device, from, to); + } + + public RegattaLogDeviceCompetitorBravoExtendedMappingEventImpl(TimePoint logicalTimePoint, AbstractLogEventAuthor author, + Competitor mappedTo, DeviceIdentifier device, TimePoint from, TimePoint to) { + super(logicalTimePoint, author, mappedTo, device, from, to); + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceCompetitorBravoMappingEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceCompetitorBravoMappingEventImpl.java index 8ece02e1ce8..831aa75a927 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceCompetitorBravoMappingEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceCompetitorBravoMappingEventImpl.java @@ -3,19 +3,14 @@ package com.sap.sailing.domain.abstractlog.regatta.events.impl; import java.io.Serializable; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; -import com.sap.sailing.domain.abstractlog.regatta.MappingEventVisitor; -import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor; -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorSensorDataMappingEvent; -import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEventImpl; import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sse.common.TimePoint; /** * Mapping event for devices mapped for Bravo type fixes. */ -public class RegattaLogDeviceCompetitorBravoMappingEventImpl extends RegattaLogDeviceMappingEventImpl - implements RegattaLogDeviceCompetitorSensorDataMappingEvent { +public class RegattaLogDeviceCompetitorBravoMappingEventImpl extends AbstractRegattaLogDeviceCompetitorSensorDataMappingEventImpl { private static final long serialVersionUID = -1494030544804758753L; @@ -29,14 +24,4 @@ public class RegattaLogDeviceCompetitorBravoMappingEventImpl extends RegattaLogD Competitor mappedTo, DeviceIdentifier device, TimePoint from, TimePoint to) { super(logicalTimePoint, author, mappedTo, device, from, to); } - - @Override - public void accept(RegattaLogEventVisitor visitor) { - visitor.visit(this); - } - - @Override - public void accept(MappingEventVisitor visitor) { - visitor.visit(this); - } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceCompetitorExpeditionExtendedMappingEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceCompetitorExpeditionExtendedMappingEventImpl.java new file mode 100755 index 00000000000..eab4586d1f2 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceCompetitorExpeditionExtendedMappingEventImpl.java @@ -0,0 +1,28 @@ +package com.sap.sailing.domain.abstractlog.regatta.events.impl; + +import java.io.Serializable; + +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sse.common.TimePoint; + +/** + * Mapping event for devices mapped for "Expedition" extended type fixes, coming from the + * "Expedition" software that was originally used in the context of this solution only for + * handling wind data but now is also used to forward sensor data into competitor tracks. + */ +public class RegattaLogDeviceCompetitorExpeditionExtendedMappingEventImpl extends AbstractRegattaLogDeviceCompetitorSensorDataMappingEventImpl { + private static final long serialVersionUID = -7865925670153838864L; + + public RegattaLogDeviceCompetitorExpeditionExtendedMappingEventImpl(TimePoint createdAt, TimePoint logicalTimePoint, + AbstractLogEventAuthor author, Serializable pId, Competitor mappedTo, DeviceIdentifier device, + TimePoint from, TimePoint to) { + super(createdAt, logicalTimePoint, author, pId, mappedTo, device, from, to); + } + + public RegattaLogDeviceCompetitorExpeditionExtendedMappingEventImpl(TimePoint logicalTimePoint, AbstractLogEventAuthor author, + Competitor mappedTo, DeviceIdentifier device, TimePoint from, TimePoint to) { + super(logicalTimePoint, author, mappedTo, device, from, to); + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceCompetitorMappingEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceCompetitorMappingEventImpl.java index ef7aea4c628..b9c8cbce426 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceCompetitorMappingEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceCompetitorMappingEventImpl.java @@ -8,7 +8,7 @@ import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorMappingEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEventImpl; import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sse.common.TimePoint; public class RegattaLogDeviceCompetitorMappingEventImpl extends RegattaLogDeviceMappingEventImpl implements diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceMarkMappingEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceMarkMappingEventImpl.java index e2da1cdca93..61312f84ba3 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceMarkMappingEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/events/impl/RegattaLogDeviceMarkMappingEventImpl.java @@ -8,7 +8,7 @@ import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEventVisitor; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEventImpl; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent; import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sse.common.TimePoint; public class RegattaLogDeviceMarkMappingEventImpl extends RegattaLogDeviceMappingEventImpl implements diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/BaseRegattaLogDeviceMappingFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/BaseRegattaLogDeviceMappingFinder.java index 83cff11e8f6..62f138d79f5 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/BaseRegattaLogDeviceMappingFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/BaseRegattaLogDeviceMappingFinder.java @@ -10,8 +10,8 @@ import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogCloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.abstractlog.NotRevokableException; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceMapping; import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimeRange; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceCompetitorMappingFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceCompetitorMappingFinder.java index 6226305b798..c357cc9e26e 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceCompetitorMappingFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceCompetitorMappingFinder.java @@ -8,7 +8,7 @@ import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompeti import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent; import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorMappingEventImpl; import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java index 448947529dc..983684147c1 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMappingFinder.java @@ -13,7 +13,7 @@ import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogCloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceMapping; import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent; import com.sap.sailing.domain.racelogtracking.impl.DeviceMappingWithRegattaLogEventImpl; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMarkMappingFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMarkMappingFinder.java index b76ca947923..4e3d50a3cf8 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMarkMappingFinder.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/regatta/tracking/analyzing/impl/RegattaLogDeviceMarkMappingFinder.java @@ -8,7 +8,7 @@ import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMapping import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent; import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceMarkMappingEventImpl; import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/anniversary/DetailedRaceInfo.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/anniversary/DetailedRaceInfo.java new file mode 100644 index 00000000000..0a9608804c5 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/anniversary/DetailedRaceInfo.java @@ -0,0 +1,70 @@ +package com.sap.sailing.domain.anniversary; + +import java.net.URL; +import java.util.UUID; + +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.dto.EventType; +import com.sap.sse.common.TimePoint; + +/** + * Reference to an "anniversary" race, such that it can be used to obtain a link to the race on its hosting server. For + * this, the object hosts additional information about the leaderboard and the event. + * Object creation will fail with an exception if no leaderboardName and eventId is given. All other data is optional. + */ +public class DetailedRaceInfo extends SimpleRaceInfo { + private static final long serialVersionUID = 1L; + private final String eventName; + private final String leaderboardDisplayName; + private final String leaderboardName; + private final UUID eventID; + private EventType eventType; + + public DetailedRaceInfo(RegattaAndRaceIdentifier identifier, String leaderboardName, String leaderboardDisplayName, + TimePoint timePoint, UUID eventId, String eventName, EventType eventType, + URL remoteUrl) { + super(identifier, timePoint, remoteUrl); + if (leaderboardName == null || eventId == null) { + throw new IllegalStateException("DetailedRaceInfo Data is not allowed to contain any null values!"); + } + this.leaderboardName = leaderboardName; + this.leaderboardDisplayName = leaderboardDisplayName; + this.eventName = eventName; + this.eventID = eventId; + this.eventType = eventType; + } + + /** + * Copy constructor to easily switch remoteUrl value + */ + public DetailedRaceInfo(DetailedRaceInfo copy, URL remoteUrl) { + this(copy.getIdentifier(), copy.getLeaderboardName(), copy.getLeaderboardDisplayName(), copy.getStartOfRace(), + copy.getEventID(), copy.getEventName(), copy.getEventType(), remoteUrl); + } + + public String getLeaderboardDisplayName() { + return leaderboardDisplayName; + } + + public String getEventName() { + return eventName; + } + + public String getLeaderboardName() { + return leaderboardName; + } + + public UUID getEventID() { + return eventID; + } + + @Override + public String toString() { + return "DetailedRaceInfo [identifier=" + getIdentifier() + ", leaderboardName=" + leaderboardName + + ", startOfRace=" + getStartOfRace() + ", eventID=" + eventID + "]"; + } + + public EventType getEventType() { + return eventType; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/anniversary/SimpleRaceInfo.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/anniversary/SimpleRaceInfo.java new file mode 100644 index 00000000000..15cf9422074 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/anniversary/SimpleRaceInfo.java @@ -0,0 +1,85 @@ +package com.sap.sailing.domain.anniversary; + +import java.io.Serializable; +import java.net.URL; + +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sse.common.TimePoint; + +/** + * Used to capture information about a race for use in an "anniversary" feature that allows us to sort a set of races by + * their start time. Races are identified by a {@link RegattaAndRaceIdentifier}. Their start time is recorded in the + * {@link #startOfRace} field which must not be {@code null}, and the {@link URL} of the remote server reference is + * stored in {@link #remoteUrl}. Using clients shall make sure that the same {@link URL} object is used for larger sets + * of objects of this type in order not to waste memory on a massive replication of those equal {@link URL} objects + * identifying the same remote server. + */ +public class SimpleRaceInfo implements Serializable { + private static final long serialVersionUID = 1L; + + private final RegattaAndRaceIdentifier identifier; + private final TimePoint startOfRace; + private final URL remoteUrl; + + /** + * @param remoteUrl + * use {@code null} to mean "local"; a local server does not necessarily know under which URL it is being + * reached and therefore cannot provide this + */ + public SimpleRaceInfo(RegattaAndRaceIdentifier identifier, TimePoint startOfRace, URL remoteUrl) { + if (identifier == null || startOfRace == null) { + throw new IllegalStateException("SimpleRaceInfo Data is not allowed to contain any null values!"); + } + this.identifier = identifier; + this.startOfRace = startOfRace; + this.remoteUrl = remoteUrl; + } + + public RegattaAndRaceIdentifier getIdentifier() { + return identifier; + } + + /** + * The URL used in a remote server reference through which the record was obtained; or {@code null} in case the race + * identified by this object resides on the local server responding to a request. + */ + public URL getRemoteUrl() { + return remoteUrl; + } + + public TimePoint getStartOfRace() { + return startOfRace; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((identifier == null) ? 0 : identifier.hashCode()); + result = prime * result + ((startOfRace == null) ? 0 : startOfRace.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; + SimpleRaceInfo other = (SimpleRaceInfo) obj; + if (identifier == null) { + if (other.identifier != null) + return false; + } else if (!identifier.equals(other.identifier)) + return false; + if (startOfRace == null) { + if (other.startOfRace != null) + return false; + } else if (!startOfRace.equals(other.startOfRace)) + return false; + return true; + } + +} \ No newline at end of file diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorStore.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorStore.java index 029c3f50793..9aa1019ed12 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorStore.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/CompetitorStore.java @@ -19,6 +19,7 @@ import com.sap.sse.common.Duration; public interface CompetitorStore extends CompetitorFactory { public interface CompetitorUpdateListener { void competitorUpdated(Competitor competitor); + void competitorCreated(Competitor competitor); } /** @@ -68,7 +69,7 @@ public interface CompetitorStore extends CompetitorFactory { String newSailId, Nationality newNationality, URI newTeamImageUri, URI newFlagImageUri, Double timeOnTimeFactor, Duration timeOnDistanceAllowancePerNauticalMile, String searchTag); - void addCompetitors(Iterable competitors); + void addNewCompetitors(Iterable competitors); CompetitorDTO convertToCompetitorDTO(Competitor c); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java index 831bfdfb761..06abdb83f51 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/TransientCompetitorStoreImpl.java @@ -74,7 +74,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab DynamicTeam team, DynamicBoat boat, Double timeOnTimeFactor, Duration timeOnDistanceAllowancePerNauticalMile, String searchTag) { Competitor result = new CompetitorImpl(id, name, displayColor, email, flagImage, team, boat, timeOnTimeFactor, timeOnDistanceAllowancePerNauticalMile, searchTag); - addNewCompetitor(id, result); + addNewCompetitor(result); if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "Created competitor "+name+" with ID "+id, new Exception("Here is where it happened")); } @@ -86,14 +86,19 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab * {@link #getExistingCompetitorById(Serializable)}. Subclasses may override in case they need to take additional * measures such as durably storing the competitor. Overriding implementations must call this implementation. */ - protected void addNewCompetitor(Serializable id, Competitor competitor) { + protected void addNewCompetitor(Competitor competitor) { LockUtil.lockForWrite(lock); try { - competitorCache.put(id, competitor); - competitorsByIdAsString.put(id.toString(), competitor); + competitorCache.put(competitor.getId(), competitor); + competitorsByIdAsString.put(competitor.getId().toString(), competitor); } finally { LockUtil.unlockAfterWrite(lock); } + synchronized (listeners) { + for (final CompetitorUpdateListener listener : listeners) { + listener.competitorCreated(competitor); + } + } } @Override @@ -277,15 +282,9 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab } @Override - public void addCompetitors(Iterable competitors) { - LockUtil.lockForWrite(lock); - try { - for (Competitor competitor: competitors) { - competitorCache.put(competitor.getId(), competitor); - competitorsByIdAsString.put(competitor.getId().toString(), competitor); - } - } finally { - LockUtil.unlockAfterWrite(lock); + public void addNewCompetitors(Iterable competitors) { + for (Competitor competitor: competitors) { + addNewCompetitor(competitor); } } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/DeviceIdentifierSerializationHandler.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/DeviceIdentifierSerializationHandler.java index 57327658980..e3bc1a64e60 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/DeviceIdentifierSerializationHandler.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/DeviceIdentifierSerializationHandler.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.racelogtracking; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sse.common.Util; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/DeviceMapping.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/DeviceMapping.java index 19fd46a3fed..afd702b188d 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/DeviceMapping.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/DeviceMapping.java @@ -4,6 +4,7 @@ import java.io.Serializable; import java.util.List; import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceMappingFinder; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sse.common.TimeRange; import com.sap.sse.common.Timed; import com.sap.sse.common.WithID; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/PlaceHolderDeviceIdentifier.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/PlaceHolderDeviceIdentifier.java index 6adb0e30b79..1c4d9c2b16c 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/PlaceHolderDeviceIdentifier.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/PlaceHolderDeviceIdentifier.java @@ -1,6 +1,7 @@ package com.sap.sailing.domain.racelogtracking; import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.common.DeviceIdentifier; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/SmartphoneUUIDIdentifier.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/SmartphoneUUIDIdentifier.java index 05d6e8e3671..2b5655a8c58 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/SmartphoneUUIDIdentifier.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/SmartphoneUUIDIdentifier.java @@ -2,6 +2,8 @@ package com.sap.sailing.domain.racelogtracking; import java.util.UUID; +import com.sap.sailing.domain.common.DeviceIdentifier; + public interface SmartphoneUUIDIdentifier extends DeviceIdentifier { String TYPE = "smartphoneUUID"; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/SmartphoneUUIDSerializationHandler.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/SmartphoneUUIDSerializationHandler.java index 8a284d26c53..2661f799f5a 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/SmartphoneUUIDSerializationHandler.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/SmartphoneUUIDSerializationHandler.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.racelogtracking; import java.util.UUID; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.racelogtracking.impl.SmartphoneUUIDIdentifierImpl; import com.sap.sse.common.Util; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/impl/DeviceMappingImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/impl/DeviceMappingImpl.java index 125b7c6a8b3..1a7bebc997f 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/impl/DeviceMappingImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/impl/DeviceMappingImpl.java @@ -5,7 +5,7 @@ import java.util.ArrayList; import java.util.List; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceMapping; import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimeRange; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/impl/DeviceMappingWithRegattaLogEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/impl/DeviceMappingWithRegattaLogEventImpl.java index 21096c83a03..454d2055597 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/impl/DeviceMappingWithRegattaLogEventImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/impl/DeviceMappingWithRegattaLogEventImpl.java @@ -4,7 +4,7 @@ import java.io.Serializable; import java.util.List; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMappingEvent; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent; import com.sap.sse.common.TimeRange; import com.sap.sse.common.WithID; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/impl/PlaceHolderDeviceIdentifierSerializationHandler.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/impl/PlaceHolderDeviceIdentifierSerializationHandler.java index 383c40a7e5c..182fa4d18a5 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/impl/PlaceHolderDeviceIdentifierSerializationHandler.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/racelogtracking/impl/PlaceHolderDeviceIdentifierSerializationHandler.java @@ -1,7 +1,7 @@ package com.sap.sailing.domain.racelogtracking.impl; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.PlaceHolderDeviceIdentifier; import com.sap.sse.common.Util; diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/FixAcceptancePredicate.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/FixAcceptancePredicate.java new file mode 100755 index 00000000000..6788fc98915 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/FixAcceptancePredicate.java @@ -0,0 +1,14 @@ +package com.sap.sailing.domain.tracking; + +/** + * A predicate for a fix, for use in + * {@link Track#getInterpolatedValue(com.sap.sse.common.TimePoint, com.sap.sse.common.Util.Function)}, used, e.g., to + * provide a rule for when a fix shall be accepted during the search for surrounding fixes. + * + * @author Axel Uhl (d043530) + * + * @param + */ +public interface FixAcceptancePredicate { + boolean isAcceptFix(FixType fix); +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java index adada1b8209..7bffcbefe56 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java @@ -5,6 +5,7 @@ import java.util.Iterator; import java.util.concurrent.locks.ReadWriteLock; import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.impl.TimeRangeCache; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Timed; @@ -31,6 +32,29 @@ import com.sap.sse.common.scalablevalue.ScalableValue; * @author Axel Uhl (d043530) */ public interface Track extends Serializable { + /** + * An adding function to be used together with {@link Track#getValueSum(TimePoint, TimePoint, Object, Adder, TimeRangeCache, TimeRangeValueCalculator)}. + * + * @author Axel Uhl (D043530) + * + * @param + */ + static interface Adder { + /** + * Adds two elements of type {@code T}. Neither argument must be {@code null}. + */ + T add(T t1, T t2); + } + + static interface TimeRangeValueCalculator { + /** + * Calculates a value for fixes across a time range. When the method is called, + * a read lock will previously have been {@link Track#lockForRead obtained} before, + * so an implementing class does not need to worry about acquiring the lock. + */ + T calculate(TimePoint from, TimePoint to); + } + /** * Locks this track for reading by the calling thread. If the thread already holds the lock for this track, * the hold count will be incremented. Make sure to call {@link #unlockAfterRead()} in a finally @@ -209,6 +233,8 @@ public interface Track extends Serializable { */ Duration getAverageIntervalBetweenRawFixes(); + T getValueSum(TimePoint from, TimePoint to, T nullElement, Adder adder, TimeRangeCache cache, TimeRangeValueCalculator valueCalculator); + /** * @return the number of raw fixes contained in the Track. */ @@ -218,4 +244,5 @@ public interface Track extends Serializable { * Tells whether the collection of {@link #getRawFixes() raw fixes} (no outliers removed) is empty */ boolean isEmpty(); + } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TimeRangeCache.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TimeRangeCache.java new file mode 100755 index 00000000000..de43e8d22f7 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TimeRangeCache.java @@ -0,0 +1,221 @@ +package com.sap.sailing.domain.tracking.impl; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map.Entry; +import java.util.NavigableSet; + +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.concurrent.LockUtil; +import com.sap.sse.concurrent.NamedReentrantReadWriteLock; +import com.sap.sse.util.impl.ArrayListNavigableSet; + +/** + * This cache looks "backwards." It contains pairs whose first component represents a to parameter used in + * a calculation for a time range. It is ordered by this component. The second component is a navigable, ordered set of + * pairs where the first pair component represents a from parameter used in the calculation's time range + * and the second pair component represents the result of the calculation for this parameter combination. + *

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

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

    + * + * Cache use: When a result across a time range is to be computed the calculating method should first look for a + * cache entry for the to parameter. If one is found, the earliest entry in the navigable set for the + * navigable set of from and result values that is at or after the requested from time point + * is determined. If such an entry exists, the result is remembered and the algorithm is repeated recursively, using the + * from value found in the cache as the new to value, and the from value + * originally passed to the calculating method as from again. If no entry is found in the cache entry for + * to that is at or after the requested from time, the result has to be computed "from + * scratch." + *

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

    + * + * Once the calculating method has computed its value, it should {@link #cache(TimePoint, TimePoint, Object) add} the + * result to the cache. + * + * @author Axel Uhl (D043530) + */ +public class TimeRangeCache { + public static final int MAX_SIZE = 100; + + private final NavigableSet>>> timeRangeCache; + + /** + * The cache is to have limited size. Eviction shall happen based on a least-recently-used strategy. Usage is + * defined as having been returned by {@link #getEarliestFromAndResultAtOrAfterFrom(TimePoint, TimePoint)} or + * having been added by {@link #cache(TimePoint, TimePoint, Object)}. + *

    + * + * When an eldest entry is asked to be expunged from this map and the map has more than {@link #MAX_SIZE} elements, + * the expunging will be admitted, and the entry is removed from the {@link #timeRangeCache} core structure. Reading + * and writing this structure must happen under the {@link #lock write lock} because also reading the linked hash + * map that counts access as "use" has a modifying effect on its internal structures. + *

    + * + * The key pairs are from/to pairs. Note that this is in some sense "the opposite direction" compared to the + * alignment of the {@link #timeRangeCache} structure which has as its outer keys the "to" time point.

    + * + * Read access is to be synchronized using this field's mutex; write access only happens under the + * {@link #lock write lock} and therefore will have no contenders. + */ + private final LinkedHashMap, Void> lruCache; + + private final NamedReentrantReadWriteLock lock; + + private static final Comparator> timePointInPairComparator = new Comparator>() { + @Override + public int compare(Util.Pair o1, Util.Pair o2) { + return o1.getA().compareTo(o2.getA()); + } + }; + + public TimeRangeCache(String nameForLockLogging) { + lock = new NamedReentrantReadWriteLock("lock for TimeRangeCache for "+nameForLockLogging, /* fair */ true); + this.timeRangeCache = new ArrayListNavigableSet>>>(timePointInPairComparator); + this.lruCache = new LinkedHashMap, Void>(/* initial capacity */ 10, /* load factor */ 0.75f, + /* access-based ordering */ true) { + private static final long serialVersionUID = -6568235517111733193L; + + @Override + protected boolean removeEldestEntry(Entry, Void> eldest) { + final boolean expunge = size() > MAX_SIZE; + if (expunge) { + removeCacheEntry(eldest.getKey().getA(), eldest.getKey().getB()); + } + return expunge; + } + }; + } + + public int size() { + return lruCache.size(); + } + + private void removeCacheEntry(TimePoint from, TimePoint to) { + assert lock.getWriteHoldCount() == 1; // we can be sure we are alone here; this only happens when adding a new entry, holding the write lock + Util.Pair>> entryForTo = timeRangeCache.floor(createDummy(to)); + if (entryForTo.getA().equals(to)) { + Pair entryForFrom = entryForTo.getB().ceiling(new Util.Pair(from, null)); + if (entryForFrom.getA().equals(from)) { + entryForTo.getB().remove(entryForFrom); + if (entryForTo.getB().isEmpty()) { + timeRangeCache.remove(entryForTo); + } + } + } + } + + /** + * Looks up the entry closest to but no later than to. If not found, null is returned. If + * found, the earliest pair of from/result that is at or after from will be returned, together with + * the to value of the entry. If there is no entry that is at or after from, + * null is returned. + */ + public Util.Pair> getEarliestFromAndResultAtOrAfterFrom(TimePoint from, TimePoint to) { + LockUtil.lockForRead(lock); + try { + Util.Pair> result = null; + Util.Pair>> entryForTo = timeRangeCache.floor(createDummy(to)); + if (entryForTo != null) { + final Util.Pair fromCeiling = entryForTo.getB().ceiling(new Util.Pair(from, null)); + if (fromCeiling != null) { + result = new Util.Pair>(entryForTo.getA(), fromCeiling); + } + } + // no writer can be active because we're holding the read lock; read access on the lruCache is synchronized using + // the lruCache's mutex; this is necessary because we're using access-based LRU pinging where even getting an entry + // modifies the internal parts of the data structure which is not thread safe. + synchronized (lruCache) { + lruCache.get(new Util.Pair(from, to)); + } + return result; + } finally { + LockUtil.unlockAfterRead(lock); + } + } + + /** + * Removes all cache entries that have a to time point that is at or after timePoint. + */ + public void invalidateAllAtOrLaterThan(TimePoint timePoint) { + LockUtil.lockForWrite(lock); + try { + Util.Pair>> dummy = createDummy(timePoint); + NavigableSet>>> toRemove = timeRangeCache.tailSet(dummy, /* inclusive */ true); + for (Iterator>>> i=toRemove.iterator(); i.hasNext(); ) { + Util.Pair>> entryToRemove = i.next(); + assert entryToRemove.getA().compareTo(timePoint) >= 0; + for (Pair fromAndResult : entryToRemove.getB()) { + lruCache.remove(new Util.Pair<>(fromAndResult.getA(), entryToRemove.getA())); + } + i.remove(); + } + } finally { + LockUtil.unlockAfterWrite(lock); + } + } + + private NavigableSet> getEntryForTo(TimePoint to) { + NavigableSet> result = null; + Util.Pair>> dummyForTo = createDummy(to); + Util.Pair>> entryForTo = timeRangeCache.floor(dummyForTo); + if (entryForTo != null && entryForTo.getA().equals(to)) { + result = entryForTo.getB(); + } + return result; + } + + public void cache(TimePoint from, TimePoint to, T result) { + LockUtil.lockForWrite(lock); + try { + NavigableSet> entryForTo = getEntryForTo(to); + if (entryForTo == null) { + entryForTo = new ArrayListNavigableSet>(timePointInPairComparator); + Util.Pair>> pairForTo = new Util.Pair>>( + to, entryForTo); + timeRangeCache.add(pairForTo); + } + entryForTo.add(new Util.Pair(from, result)); + lruCache.put(new Util.Pair(from, to), null); + } finally { + LockUtil.unlockAfterWrite(lock); + } + } + + private Util.Pair>> createDummy(TimePoint to) { + return new Util.Pair>>(to, null); + } + + /** + * Removes all contents from this cache + */ + public void clear() { + LockUtil.lockForWrite(lock); + try { + timeRangeCache.clear(); + lruCache.clear(); + } finally { + LockUtil.unlockAfterWrite(lock); + } + + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java index 0685e176249..4f6a752bbcc 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/impl/TrackImpl.java @@ -6,10 +6,12 @@ import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.NavigableSet; +import com.sap.sailing.domain.tracking.FixAcceptancePredicate; import com.sap.sailing.domain.tracking.Track; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Timed; +import com.sap.sse.common.Util; import com.sap.sse.common.Util.Function; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.scalablevalue.ScalableValue; @@ -148,14 +150,25 @@ public class TrackImpl implements Track { @Override public FixType getLastFixAtOrBefore(TimePoint timePoint) { + return getLastFixAtOrBefore(timePoint, /* fixAcceptancePredicate == null means accept all */ null); + } + + private FixType getLastFixAtOrBefore(TimePoint timePoint, FixAcceptancePredicate fixAcceptancePredicate) { lockForRead(); try { - return (FixType) getInternalFixes().floor(getDummyFix(timePoint)); + final NavigableSet headSet = getInternalFixes().headSet(getDummyFix(timePoint), /* inclusive */ true); + for (final Iterator i=headSet.descendingIterator(); i.hasNext(); ) { + final FixType next = i.next(); + if (fixAcceptancePredicate == null || fixAcceptancePredicate.isAcceptFix(next)) { + return next; + } + } + return null; } finally { unlockAfterRead(); } } - + @Override public FixType getLastFixBefore(TimePoint timePoint) { lockForRead(); @@ -188,9 +201,20 @@ public class TrackImpl implements Track { @Override public FixType getFirstFixAtOrAfter(TimePoint timePoint) { + return getFirstFixAtOrAfter(timePoint, /* fixAcceptancePredicate==null means accept all fixes */ null); + } + + private FixType getFirstFixAtOrAfter(TimePoint timePoint, FixAcceptancePredicate fixAcceptancePredicate) { lockForRead(); try { - return (FixType) getInternalFixes().ceiling(getDummyFix(timePoint)); + final NavigableSet tailSet = getInternalFixes().tailSet(getDummyFix(timePoint), /* inclusive */ true); + for (final Iterator i=tailSet.iterator(); i.hasNext(); ) { + final FixType next = i.next(); + if (fixAcceptancePredicate == null || fixAcceptancePredicate.isAcceptFix(next)) { + return next; + } + } + return null; } finally { unlockAfterRead(); } @@ -254,9 +278,16 @@ public class TrackImpl implements Track { } } - private Pair getSurroundingFixes(TimePoint timePoint) { - FixType left = getLastFixAtOrBefore(timePoint); - FixType right = getFirstFixAtOrAfter(timePoint); + /** + * @param fixAcceptancePredicate + * if not {@code null}, adjacent fixes will be skipped as long as this predicate does not + * {@link FixAcceptancePredicate#isAcceptFix(Object) accept} the fix. This can, e.g., be used to skip + * fixes that don't have values in a dimension required. If {@code null}, the next fixes left and right + * (including the exact {@code timePoint} if a fix exists there) will be used without further check. + */ + private Pair getSurroundingFixes(TimePoint timePoint, FixAcceptancePredicate fixAcceptancePredicate) { + FixType left = getLastFixAtOrBefore(timePoint, fixAcceptancePredicate); + FixType right = getFirstFixAtOrAfter(timePoint, fixAcceptancePredicate); com.sap.sse.common.Util.Pair result = new com.sap.sse.common.Util.Pair<>(left, right); return result; } @@ -274,9 +305,15 @@ public class TrackImpl implements Track { } @Override - public ValueType getInterpolatedValue(TimePoint timePoint, Function> converter) { + public ValueType getInterpolatedValue(TimePoint timePoint, + Function> converter) { + return getInterpolatedValue(timePoint, converter, /* fixAcceptancePredicate==null means accept all */ null); + } + + protected ValueType getInterpolatedValue(TimePoint timePoint, + Function> converter, FixAcceptancePredicate fixAcceptancePredicate) { final ValueType result; - Pair fixPair = getSurroundingFixes(timePoint); + Pair fixPair = getSurroundingFixes(timePoint, fixAcceptancePredicate); if (fixPair.getA() == null) { if (fixPair.getB() == null) { result = null; @@ -423,6 +460,64 @@ public class TrackImpl implements Track { } } + @Override + public T getValueSum(TimePoint from, TimePoint to, T nullElement, Adder adder, TimeRangeCache cache, TimeRangeValueCalculator valueCalculator) { + return getValueSumRecursively(from, to, /* recursionLevel */ 0, nullElement, adder, cache, valueCalculator); + } + + private T getValueSumRecursively(TimePoint from, TimePoint to, int recursionDepth, T nullElement, + Adder adder, TimeRangeCache cache, TimeRangeValueCalculator valueCalculator) { + T result; + if (!from.before(to)) { + result = nullElement; + } else { + boolean perfectCacheHit = false; + lockForRead(); + try { + Util.Pair> bestCacheEntry = cache.getEarliestFromAndResultAtOrAfterFrom(from, to); + if (bestCacheEntry != null) { + perfectCacheHit = true; // potentially a cache hit; but if it doesn't span the full interval, it's not perfect; see below + // compute the missing stretches between best cache entry's "from" and our "from" and the cache + // entry's "to" and our "to" + T valueFromFromToBeginningOfCacheEntry = nullElement; + T valueFromEndOfCacheEntryToTo = nullElement; + if (!bestCacheEntry.getB().getA().equals(from)) { + assert bestCacheEntry.getB().getA().after(from); + perfectCacheHit = false; + valueFromFromToBeginningOfCacheEntry = getValueSumRecursively(from, bestCacheEntry + .getB().getA(), recursionDepth + 1, nullElement, adder, cache, valueCalculator); + } + if (!bestCacheEntry.getA().equals(to)) { + assert bestCacheEntry.getA().before(to); + perfectCacheHit = false; + valueFromEndOfCacheEntryToTo = getValueSumRecursively(bestCacheEntry.getA(), to, + recursionDepth + 1, nullElement, adder, cache, valueCalculator); + } + if (valueFromEndOfCacheEntryToTo == null || bestCacheEntry.getB().getB() == null) { + result = null; + } else { + result = adder.add(adder.add(valueFromFromToBeginningOfCacheEntry, bestCacheEntry.getB().getB()), + valueFromEndOfCacheEntryToTo); + } + } else { + if (from.compareTo(to) < 0) { + result = valueCalculator.calculate(from, to); + } else { + result = nullElement; + } + } + } finally { + unlockAfterRead(); + } + if (!perfectCacheHit && recursionDepth == 0) { + cache.cache(from, to, result); + } + } + return result; + } + + + @Override public int size() { return fixes.size(); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/RaceType.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/RaceType.java index c96d1a67920..995108fe34f 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/RaceType.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/RaceType.java @@ -26,11 +26,11 @@ public interface RaceType { _470_MEN("SAM005", "470", Gender.male), STAR("SAM007", "Star", Gender.male), _49ER("SAM009", "49er", Gender.male), - _49ERFX("SAW009", "49erFX", Gender.female), + _49ERFX("SAW019", "49erFX", Gender.female), ELLIOTT6M("SAW007", "Elliott 6M", Gender.female), RSX_WOMEN("SAW102", "RS:X", Gender.female), RSX_MEN("SAM102", "RS:X", Gender.male), - NACRA17("SAX017", "Nacra 17", Gender.mixed), + NACRA17("SAX016", "Nacra 17", Gender.mixed), LASER_RADIAL("SAW103", "Laser Radial", Gender.female); enum Gender { diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java index 3077f832731..9aef27aa605 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java @@ -142,7 +142,7 @@ public class DomainFactoryImpl implements DomainFactory { /* dateOfBirth */ null, teamMemberName.trim())); } DynamicTeam team = new TeamImpl(competitor.getName(), teamMembers, /* coach */ null); - result = baseDomainFactory.getCompetitorStore().getOrCreateCompetitor(getCompetitorID(competitor.getBoatID(), raceId, boatClass), + result = baseDomainFactory.getCompetitorStore().getOrCreateCompetitor(getCompetitorID(competitor.getBoatID(), competitor.getName(), raceId, boatClass), competitor.getName(), null /*displayColor*/, null /*email*/, null, team, boat, /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null); return result; @@ -153,19 +153,27 @@ public class DomainFactoryImpl implements DomainFactory { return createCompetitorWithoutID(new CompetitorWithoutID(boatID, threeLetterIOCCode, name), raceId, boatClass); } - private String getCompetitorID(String boatID, String raceId, BoatClass boatClass) { + private String getCompetitorID(String boatID, String name, String raceId, BoatClass boatClass) { String result = null; - if(boatClass != null) { - result = getCompetitorID(boatID, boatClass); + if (boatClass != null) { + result = getCompetitorID(boatID, name, boatClass); } else { RaceType raceType = getRaceTypeFromRaceID(raceId); if (raceType != null) { - result = getCompetitorID(boatID, raceType); + result = getCompetitorID(boatID, name, raceType); } } return result; } + private String getCompetitorID(String boatID, String name, BoatClass boatClass) { + return getCompetitorID(boatID, boatClass) + "/" + name; + } + + private String getCompetitorID(String boatID, String name, RaceType raceType) { + return getCompetitorID(boatID, raceType) + "/" + name; + } + @Override public String getCompetitorID(String boatID, RaceType raceType) { return boatID + "/" + raceType.getRaceCode(); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java index 924636d923b..18bd0aee11a 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java @@ -166,9 +166,9 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl } @Override - protected void onStop(boolean preemptive) throws MalformedURLException, IOException, InterruptedException { + protected void onStop(boolean preemptive, boolean willBeRemoved) throws MalformedURLException, IOException, InterruptedException { if (isTrackedRaceStillReachable()) { - TrackedRaceStatus newStatus = new TrackedRaceStatusImpl(TrackedRaceStatusEnum.FINISHED, 1.0); + TrackedRaceStatus newStatus = new TrackedRaceStatusImpl(willBeRemoved ? TrackedRaceStatusEnum.REMOVED : TrackedRaceStatusEnum.FINISHED, 1.0); trackedRace.onStatusChanged(this, newStatus); } connector.removeSailMasterListener(this); @@ -281,11 +281,11 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl case COMPETITOR: String boatID = fix.getBoatID(); Competitor competitor = getCompetitorByBoatIDAndRaceIDOrBoatClass(boatID, raceID, boatClass); - if(competitor == null) { - // TODO: read startlist again from Manage2Sail - // use competitorStore.isCompetitorToUpdateDuringGetOrCreate(result) + if (competitor == null) { + // TODO: read startlist again from Manage2Sail + // use competitorStore.isCompetitorToUpdateDuringGetOrCreate(result) } - if(competitor != null) { + if (competitor != null) { DynamicGPSFixTrack competitorTrack = trackedRace.getTrack(competitor); competitorTrack.addGPSFix(gpsFix); } else { @@ -321,8 +321,8 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl Competitor result = null; // first look into the temp cache result = competitorsByBoatId.get(boatID); - if(result == null) { - if(boatClass != null) { + if (result == null) { + if (boatClass != null) { result = getCompetitorByBoatIDAndBoatClass(boatID, boatClass); } else { RaceType raceType = domainFactory.getRaceTypeFromRaceID(raceID); diff --git a/java/com.sap.sailing.domain.test/resources/IncrementalLeaderboardDTO.ser b/java/com.sap.sailing.domain.test/resources/IncrementalLeaderboardDTO.ser index 8b0225928aa..435b8ce147b 100755 Binary files a/java/com.sap.sailing.domain.test/resources/IncrementalLeaderboardDTO.ser and b/java/com.sap.sailing.domain.test/resources/IncrementalLeaderboardDTO.ser differ diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java index daf57f907fc..6d81cdba6b7 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/common/test/LeaderboardDTODiffingTest.java @@ -45,12 +45,14 @@ import com.sap.sse.util.ClonerImpl; /** * Tests the compressing / de-compressing functionality of {@link LeaderboardDTO} and {@link IncrementalLeaderboardDTO}. - * See also bug 1417.

    + * See also bug 1417. + *

    * * The data of a meaningful and non-trivial {@link LeaderboardDTO} is obtained by using an instrumented version of * SailingServiceImpl.getLeaderboardByName(...) which serializes the leaderboard at the end of the method - * to a file used by this test. The leaderboard that this test wants to use is that of the 505 Worlds 2013, obtained - * for an expanded Race R9 at time 2013-05-03T17:21:40Z. + * to a file used by this test. The leaderboard that this test wants to use is that of the 505 Worlds 2013, obtained for + * an expanded Race R9 at time 2013-05-03T19:17:09Z after the last competitor tracked has finished the last leg. The + * total distance traveled in meters has to be expanded for this test to work. * * @author Axel Uhl (d043530) * diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BearingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BearingTest.java index 2beb3229a9b..8c82f95ade9 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BearingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BearingTest.java @@ -46,6 +46,16 @@ public class BearingTest { assertNull(new BearingWithConfidenceCluster(null).getAverage(null)); } + @Test + public void testNegativeAngle() { + assertEquals(-10., new DegreeBearingImpl(-10.).getDegrees(), 0.000001); + } + + @Test + public void testNegativeAngleOverflow() { + assertEquals(-10., new DegreeBearingImpl(-370.).getDegrees(), 0.000001); + } + @Test public void testZeroConfidenceLeadsToNullBearingAverage() { BearingWithConfidenceCluster cluster = new BearingWithConfidenceCluster(null); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BravoFixTrackInterpolationWithNullTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BravoFixTrackInterpolationWithNullTest.java new file mode 100755 index 00000000000..8fc789297ef --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/BravoFixTrackInterpolationWithNullTest.java @@ -0,0 +1,49 @@ +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertEquals; + +import java.util.UUID; + +import org.junit.Test; + +import com.sap.sailing.domain.base.CourseArea; +import com.sap.sailing.domain.base.impl.CourseAreaImpl; +import com.sap.sailing.domain.common.sensordata.BravoExtendedSensorDataMetadata; +import com.sap.sailing.domain.common.tracking.impl.BravoExtendedFixImpl; +import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl; +import com.sap.sailing.domain.tracking.DynamicBravoFixTrack; +import com.sap.sailing.domain.tracking.impl.BravoFixTrackImpl; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class BravoFixTrackInterpolationWithNullTest { + @Test + public void testFixesWithNonNullFieldsAreSelected() { + DynamicBravoFixTrack track = new BravoFixTrackImpl<>(new CourseAreaImpl("Test", UUID.randomUUID()), + "test", /* hasExtendedFixes */ true); + track.add(createFix(10l, 5., null)); + track.add(createFix(20l, null, 7.)); + track.add(createFix(30l, 7., null)); + assertEquals(6., track.getHeel(new MillisecondsTimePoint(20l)).getDegrees(), 0.00001); + } + + @Test + public void testFixesWithNonNullFieldsAreSelectedForMultipleNullsInARow() { + DynamicBravoFixTrack track = new BravoFixTrackImpl<>(new CourseAreaImpl("Test", UUID.randomUUID()), + "test", /* hasExtendedFixes */ true); + track.add(createFix(10l, 5., null)); + track.add(createFix(20l, null, 7.)); + track.add(createFix(30l, null, 7.)); + track.add(createFix(40l, null, 7.)); + track.add(createFix(50l, 7., null)); + assertEquals(6., track.getHeel(new MillisecondsTimePoint(30l)).getDegrees(), 0.00001); + assertEquals(5., track.getHeel(new MillisecondsTimePoint(10l)).getDegrees(), 0.00001); + } + + private BravoExtendedFixImpl createFix(long timePointAsMillis, Double heel, Double pitch) { + final Double[] fixData = new Double[Math.max(BravoExtendedSensorDataMetadata.HEEL.getColumnIndex(), + BravoExtendedSensorDataMetadata.PITCH.getColumnIndex())+1]; + fixData[BravoExtendedSensorDataMetadata.HEEL.getColumnIndex()] = heel; + fixData[BravoExtendedSensorDataMetadata.PITCH.getColumnIndex()] = pitch; + return new BravoExtendedFixImpl(new DoubleVectorFixImpl(new MillisecondsTimePoint(timePointAsMillis), fixData)); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java index cbcfa7f6b7c..61002947418 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CompetitorProviderCacheInvalidationTest.java @@ -207,27 +207,29 @@ public class CompetitorProviderCacheInvalidationTest extends AbstractLeaderboard for (Competitor c : compLists[1]) { raceLog.add(new RaceLogRegisterCompetitorEventImpl(MillisecondsTimePoint.now(), new LogEventAuthorImpl("Me", 0), 1, c)); } + Set allCompetitors = new HashSet<>(); + allCompetitors.addAll(compLists[0]); + allCompetitors.addAll(compLists[1]); // expected are only the competitors in the RaceLog, because only one RaceColumn is // registered, which has the competitors registered in the RaceLog. - assertRegattaAndRaceCompetitors(new HashSet<>(compLists[1])); + assertRegattaAndRaceCompetitors(allCompetitors, new HashSet<>(compLists[1])); // Now we revoke that the race log provides the competitors for R1; the competitors should then // snap back to those competitors taken from the regatta log for both, the rentire leaderboard // as well as for the race column raceLog.revokeEvent(author, usesCompetitorsFromRaceLogEvent); - assertRegattaAndRaceCompetitors(new HashSet<>(compLists[0])); + assertRegattaAndRaceCompetitors(new HashSet<>(compLists[0]), new HashSet<>(compLists[0])); // And now re-introduce per-race competitors and validate again that the cache adjusts properly: raceLog.add(new RaceLogUseCompetitorsFromRaceLogEventImpl(MillisecondsTimePoint.now(), author, MillisecondsTimePoint.now(), UUID.randomUUID(), passId)); - assertRegattaAndRaceCompetitors(new HashSet<>(compLists[1])); + assertRegattaAndRaceCompetitors(allCompetitors, new HashSet<>(compLists[1])); } - private void assertRegattaAndRaceCompetitors(Set expected) { + private void assertRegattaAndRaceCompetitors(Set expectedAllCompetitors, Set expectedForRace) { Set actual = new HashSet<>(); Util.addAll(competitorProviderFlexibleLeaderboard.getAllCompetitors(), actual); - assertEquals(expected, actual); + assertEquals(expectedAllCompetitors, actual); Set actualForRace = new HashSet<>(); - //For the race only the competitors registered on RaceLog as RaceLogUseCompetitorsFromRaceLogEvent is present - Set expectedForRace = new HashSet<>(expected); + // For the race only the competitors registered on RaceLog as RaceLogUseCompetitorsFromRaceLogEvent is present Util.addAll(competitorProviderFlexibleLeaderboard.getAllCompetitors( flexibleLeaderboard.getRaceColumnByName("R1"), flexibleLeaderboard.getFleet(LeaderboardNameConstants.DEFAULT_FLEET_NAME)), actualForRace); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java index 57d1229ceb8..a4ea30c26cc 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardScoringAndRankingTest.java @@ -105,6 +105,32 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki assertEquals(competitors, rankedCompetitors); } + @Test + public void testAutomaticRdgAdjustment() throws NoWindException { + List competitors = createCompetitors(10); + Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */4, + new String[] { "Default" }, + /* medal */false, /* medal */ 0, "testOneStartedRaceWithDifferentScores", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT_WITH_AUTOMATIC_RDG)); + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000); + TrackedRace f1 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors); + RaceColumn f1Column = series.get(1).getRaceColumnByName("F1"); + RaceColumn f2Column = series.get(1).getRaceColumnByName("F2"); + RaceColumn f3Column = series.get(1).getRaceColumnByName("F3"); + RaceColumn f4Column = series.get(1).getRaceColumnByName("F4"); + f1Column.setTrackedRace(f1Column.getFleets().iterator().next(), f1); + leaderboard.getScoreCorrection().setMaxPointsReason(competitors.get(3), f1Column, MaxPointsReason.RDG); + leaderboard.getScoreCorrection().correctScore(competitors.get(3), f2Column, 7); + assertEquals(7, leaderboard.getTotalPoints(competitors.get(3), f1Column, later), 0.0000001); + leaderboard.getScoreCorrection().correctScore(competitors.get(3), f3Column, 5); + assertEquals(6, leaderboard.getTotalPoints(competitors.get(3), f1Column, later), 0.0000001); + leaderboard.getScoreCorrection().setMaxPointsReason(competitors.get(3), f4Column, MaxPointsReason.RDG); + assertEquals(6, leaderboard.getTotalPoints(competitors.get(3), f1Column, later), 0.0000001); + assertEquals(6, leaderboard.getTotalPoints(competitors.get(3), f4Column, later), 0.0000001); + } + /** * Regarding bug 912, test adding a disqualification in the middle, with a high-point scoring scheme, and check that * all competitors ranked worse advance by one, including getting more points due to the high-point scoring @@ -307,6 +333,356 @@ public class LeaderboardScoringAndRankingTest extends LeaderboardScoringAndRanki assertEquals(47.0, leaderboard.getNetPoints(competitors.get(9), later), 0.00000001); } + /** + * Match races want to prefer a competitor over another with equal score sum based on the direct + * comparison. When the two competitors matched each other, the result(s) of these matches are to + * be considered in isolation to break a tie. If this score sum is still equal, the last of these + * matches shall take precedence, if any.

    + * + * If more than two competitors have equal score sum the comparator needs to ensure that all those + * competitors can be put in a consistent order. This may not always be possible. Consider three + * competitors A, B, and C such that A won against B, B won against C and C won against A, but all + * three having equal total scores. In this case, no consistent ordering of these three competitors + * is possible, and all three need to be ranked equal. + */ + @Test + public void testMatchRaceTieBreak() { + List competitors = createCompetitors(6); + Regatta regatta = createRegatta(/* qualifying */ 2, new String[] { "M1", "M2", "M3" }, /* final */0, + new String[] { "Default" }, + /* medal */false, /* medal */ 0, "testMatchRaceTieBreak", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT_FIRST_GETS_ONE)); + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + Series qualificationSeries; + Iterator seriesIter = regatta.getSeries().iterator(); + qualificationSeries = seriesIter.next(); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000); + TrackedRace q1M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(0, 2)); + TrackedRace q1M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(2, 4)); + TrackedRace q1M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(4, 6)); + RaceColumn q1Column = qualificationSeries.getRaceColumnByName("Q1"); + q1Column.setTrackedRace(q1Column.getFleetByName("M1"), q1M1); + q1Column.setTrackedRace(q1Column.getFleetByName("M2"), q1M2); + q1Column.setTrackedRace(q1Column.getFleetByName("M3"), q1M3); + + TrackedRace q2M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(0), competitors.get(2))); + TrackedRace q2M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(3), competitors.get(1))); + TrackedRace q2M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(5), competitors.get(4))); + RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2"); + q2Column.setTrackedRace(q2Column.getFleetByName("M1"), q2M1); + q2Column.setTrackedRace(q2Column.getFleetByName("M2"), q2M2); + q2Column.setTrackedRace(q2Column.getFleetByName("M3"), q2M3); + + // point sums for competitors 0..5: 2, 0, 1, 1, 1, 1 + // So it's clear-cut for (0) and (1); all others (2..5) need to be tie-broken based + // on their direct comparison. + // (2): < (3); ? (4); ? (5) + // (3): ? (4); ? (5) + // (4): > (5) because (4) won over (5) in Q1, but (5) won over (4) in Q2 which came last + // With this, multiple orders are possible: + // (2), (3), (5), (4) + // (2), (5), (3), (4) + // (5), (2), (3), (4) + // (5), (4), (2), (3) + // ... + // So we have two separate "ordered chains," one being (2)<(3) and the other being (5)<(4). In this + // case (2) and (5) shall be compared equal, and (3) and (4) shall be compared equal, leaving resolution + // to a lesser criterion, such as the fallback name-based ordering. + + List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(competitors.get(0), rankedCompetitors.get(0)); // 2 points; winner + assertEquals(competitors.get(1), rankedCompetitors.get(rankedCompetitors.size()-1)); // 0 points; loser + assertTrue(rankedCompetitors.indexOf(competitors.get(2)) < rankedCompetitors.indexOf(competitors.get(3))); + assertTrue(rankedCompetitors.indexOf(competitors.get(5)) < rankedCompetitors.indexOf(competitors.get(4))); + } + + @Test + public void testMatchRaceTieBreakWithMultipleDirectComparisonScoreDifference() { + List competitors = createCompetitors(6); + Regatta regatta = createRegatta(/* qualifying */ 4, new String[] { "M1", "M2", "M3" }, /* final */0, + new String[] { "Default" }, + /* medal */false, /* medal */ 0, "testMatchRaceTieBreak", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT_FIRST_GETS_ONE)); + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + Series qualificationSeries; + Iterator seriesIter = regatta.getSeries().iterator(); + qualificationSeries = seriesIter.next(); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000); + TrackedRace q1M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(0, 2)); + TrackedRace q1M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(2, 4)); + TrackedRace q1M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(4, 6)); + RaceColumn q1Column = qualificationSeries.getRaceColumnByName("Q1"); + q1Column.setTrackedRace(q1Column.getFleetByName("M1"), q1M1); + q1Column.setTrackedRace(q1Column.getFleetByName("M2"), q1M2); + q1Column.setTrackedRace(q1Column.getFleetByName("M3"), q1M3); + + TrackedRace q2M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(1), competitors.get(0))); + TrackedRace q2M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(2), competitors.get(3))); + TrackedRace q2M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(4), competitors.get(5))); + RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2"); + q2Column.setTrackedRace(q2Column.getFleetByName("M1"), q2M1); + q2Column.setTrackedRace(q2Column.getFleetByName("M2"), q2M2); + q2Column.setTrackedRace(q2Column.getFleetByName("M3"), q2M3); + + TrackedRace q3M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(0, 2)); + TrackedRace q3M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(2, 4)); + TrackedRace q3M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(4, 6)); + RaceColumn q3Column = qualificationSeries.getRaceColumnByName("Q3"); + q3Column.setTrackedRace(q2Column.getFleetByName("M1"), q3M1); + q3Column.setTrackedRace(q2Column.getFleetByName("M2"), q3M2); + q3Column.setTrackedRace(q2Column.getFleetByName("M3"), q3M3); + + TrackedRace q4M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(1), competitors.get(2))); + TrackedRace q4M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(3), competitors.get(0))); + TrackedRace q4M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(4), competitors.get(5))); + RaceColumn q4Column = qualificationSeries.getRaceColumnByName("Q4"); + q4Column.setTrackedRace(q2Column.getFleetByName("M1"), q4M1); + q4Column.setTrackedRace(q2Column.getFleetByName("M2"), q4M2); + q4Column.setTrackedRace(q2Column.getFleetByName("M3"), q4M3); + // point sums for competitors 0..5: 2, 2, 3, 1, 4, 0 + // The only tie to break is between (0) and (1) with the following direct comparisons: + // Q1: (0)->(1); Q2: (1)->(0); Q3: (0)->(1) + // This gives 2 points for (0) and 1 point for (1), so (0) shall be ranked better (lesser) than (1) + + List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(Arrays.asList(competitors.get(4), competitors.get(2), competitors.get(0), competitors.get(1), competitors.get(3), competitors.get(5)), + rankedCompetitors); + } + + @Test + public void testMatchRaceTieBreakWithMultipleDirectComparisonLastRace() { + List competitors = createCompetitors(6); + Regatta regatta = createRegatta(/* qualifying */ 4, new String[] { "M1", "M2", "M3" }, /* final */0, + new String[] { "Default" }, + /* medal */false, /* medal */ 0, "testMatchRaceTieBreak", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT_FIRST_GETS_ONE)); + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + Series qualificationSeries; + Iterator seriesIter = regatta.getSeries().iterator(); + qualificationSeries = seriesIter.next(); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000); + TrackedRace q1M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(0, 2)); + TrackedRace q1M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(2, 4)); + TrackedRace q1M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(4, 6)); + RaceColumn q1Column = qualificationSeries.getRaceColumnByName("Q1"); + q1Column.setTrackedRace(q1Column.getFleetByName("M1"), q1M1); + q1Column.setTrackedRace(q1Column.getFleetByName("M2"), q1M2); + q1Column.setTrackedRace(q1Column.getFleetByName("M3"), q1M3); + + TrackedRace q2M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(1), competitors.get(0))); + TrackedRace q2M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(2), competitors.get(3))); + TrackedRace q2M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(4), competitors.get(5))); + RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2"); + q2Column.setTrackedRace(q2Column.getFleetByName("M1"), q2M1); + q2Column.setTrackedRace(q2Column.getFleetByName("M2"), q2M2); + q2Column.setTrackedRace(q2Column.getFleetByName("M3"), q2M3); + + TrackedRace q3M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(0, 2)); + TrackedRace q3M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(2, 4)); + TrackedRace q3M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors.subList(4, 6)); + RaceColumn q3Column = qualificationSeries.getRaceColumnByName("Q3"); + q3Column.setTrackedRace(q2Column.getFleetByName("M1"), q3M1); + q3Column.setTrackedRace(q2Column.getFleetByName("M2"), q3M2); + q3Column.setTrackedRace(q2Column.getFleetByName("M3"), q3M3); + + TrackedRace q4M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(1), competitors.get(0))); + TrackedRace q4M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(2), competitors.get(3))); + TrackedRace q4M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(4), competitors.get(5))); + RaceColumn q4Column = qualificationSeries.getRaceColumnByName("Q4"); + q4Column.setTrackedRace(q2Column.getFleetByName("M1"), q4M1); + q4Column.setTrackedRace(q2Column.getFleetByName("M2"), q4M2); + q4Column.setTrackedRace(q2Column.getFleetByName("M3"), q4M3); + // point sums for competitors 0..5: 2, 2, 4, 0, 4, 0 + // The ties between (2) and (4) with both 4 points cannot be decided because they never met; + // the same holds for (3) and (5) with both 0 points. + // The tie between (0) and (1) shall be broken based on their last match because their + // direct comparison scores are equal, too (both 2). In the last match (1) won over (0). + List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(Arrays.asList(competitors.get(1), competitors.get(0)), rankedCompetitors.subList(2, 4)); + } + + /** + * Match races want to prefer a competitor over another with equal score sum based on the direct + * comparison. When the two competitors matched each other, the result(s) of these matches are to + * be considered in isolation to break a tie. If this score sum is still equal, the last of these + * matches shall take precedence, if any.

    + * + * If more than two competitors have equal score sum the comparator needs to ensure that all those + * competitors can be put in a consistent order. This may not always be possible. Consider three + * competitors A, B, and C such that A won against B, B won against C and C won against A, but all + * three having equal total scores. In this case, no consistent ordering of these three competitors + * is possible, and all three need to be ranked equal. + */ + @Test + public void testMatchRaceTieBreakWithCycle() { + List competitors = createCompetitors(6); + Regatta regatta = createRegatta(/* qualifying */ 2, new String[] { "M1", "M2", "M3" }, /* final */0, + new String[] { "Default" }, + /* medal */false, /* medal */ 0, "testMatchRaceTieBreak", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT_FIRST_GETS_ONE)); + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + Series qualificationSeries; + Iterator seriesIter = regatta.getSeries().iterator(); + qualificationSeries = seriesIter.next(); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000); + TrackedRace q1M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(0), competitors.get(1))); + TrackedRace q1M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(2), competitors.get(3))); + TrackedRace q1M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(4), competitors.get(5))); + RaceColumn q1Column = qualificationSeries.getRaceColumnByName("Q1"); + q1Column.setTrackedRace(q1Column.getFleetByName("M1"), q1M1); + q1Column.setTrackedRace(q1Column.getFleetByName("M2"), q1M2); + q1Column.setTrackedRace(q1Column.getFleetByName("M3"), q1M3); + + TrackedRace q2M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(1), competitors.get(2))); + TrackedRace q2M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(3), competitors.get(4))); + TrackedRace q2M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(5), competitors.get(0))); + RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2"); + q2Column.setTrackedRace(q2Column.getFleetByName("M1"), q2M1); + q2Column.setTrackedRace(q2Column.getFleetByName("M2"), q2M2); + q2Column.setTrackedRace(q2Column.getFleetByName("M3"), q2M3); + + // point sums for competitors 0..5: 1, 1, 1, 1, 1, 1 + // cyclic direct comparison; expect competitors to be ordered by their names + List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(competitors, rankedCompetitors); + } + + /** + * Match races want to prefer a competitor over another with equal score sum based on the direct + * comparison. When the two competitors matched each other, the result(s) of these matches are to + * be considered in isolation to break a tie. If this score sum is still equal, the last of these + * matches shall take precedence, if any.

    + * + * If more than two competitors have equal score sum the comparator needs to ensure that all those + * competitors can be put in a consistent order. This may not always be possible. Consider three + * competitors A, B, and C such that A won against B, B won against C and C won against A, but all + * three having equal total scores. In this case, no consistent ordering of these three competitors + * is possible, and all three need to be ranked equal. + */ + @Test + public void testMatchRaceTieBreakWithTwoRelatedCycles() { + List competitors = createCompetitors(6); + Regatta regatta = createRegatta(/* qualifying */ 3, new String[] { "M1", "M2", "M3" }, /* final */0, + new String[] { "Default" }, + /* medal */false, /* medal */ 0, "testMatchRaceTieBreak", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT_FIRST_GETS_ONE)); + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + Series qualificationSeries; + Iterator seriesIter = regatta.getSeries().iterator(); + qualificationSeries = seriesIter.next(); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000); + TrackedRace q1M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(0), competitors.get(1))); + TrackedRace q1M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(4), competitors.get(2))); + TrackedRace q1M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(3), competitors.get(5))); + RaceColumn q1Column = qualificationSeries.getRaceColumnByName("Q1"); + q1Column.setTrackedRace(q1Column.getFleetByName("M1"), q1M1); + q1Column.setTrackedRace(q1Column.getFleetByName("M2"), q1M2); + q1Column.setTrackedRace(q1Column.getFleetByName("M3"), q1M3); + + TrackedRace q2M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(1), competitors.get(2))); + TrackedRace q2M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(3), competitors.get(0))); + TrackedRace q2M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(5), competitors.get(4))); + RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2"); + q2Column.setTrackedRace(q2Column.getFleetByName("M1"), q2M1); + q2Column.setTrackedRace(q2Column.getFleetByName("M2"), q2M2); + q2Column.setTrackedRace(q2Column.getFleetByName("M3"), q2M3); + + TrackedRace q3M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(2), competitors.get(0))); + TrackedRace q3M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(5), competitors.get(1))); + TrackedRace q3M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(4), competitors.get(3))); + RaceColumn q3Column = qualificationSeries.getRaceColumnByName("Q3"); + q3Column.setTrackedRace(q1Column.getFleetByName("M1"), q3M1); + q3Column.setTrackedRace(q1Column.getFleetByName("M2"), q3M2); + q3Column.setTrackedRace(q1Column.getFleetByName("M3"), q3M3); + + // point sums for competitors 0..5: 1, 1, 1, 2, 2, 2 + // + // For 0..2 we have a cycle: (0)<(1)<(2)<(0), so they shall be treated equal, sorted by name + // Further, we have: (5)<(4)<(3)<(5), so this is another cycle over the remaining three competitors + // and (3) < (0) + // and (5) < (1) connect the chains + // which could be resolved by comparing all elements of {(3), (4), (5)} lesser than all elements of {(0), (1), (2)}; + // had the links between the chains been inconsistent then all elements of both chains would have to compare equal, + // resorting to name comparison + List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(Arrays.asList(competitors.get(3), competitors.get(4), competitors.get(5)), + Arrays.asList(rankedCompetitors.get(0), rankedCompetitors.get(1), rankedCompetitors.get(2))); + assertEquals(Arrays.asList(competitors.get(0), competitors.get(1), competitors.get(2)), + Arrays.asList(rankedCompetitors.get(3), rankedCompetitors.get(4), rankedCompetitors.get(5))); + } + + /** + * Match races want to prefer a competitor over another with equal score sum based on the direct + * comparison. When the two competitors matched each other, the result(s) of these matches are to + * be considered in isolation to break a tie. If this score sum is still equal, the last of these + * matches shall take precedence, if any.

    + * + * If more than two competitors have equal score sum the comparator needs to ensure that all those + * competitors can be put in a consistent order. This may not always be possible. Consider three + * competitors A, B, and C such that A won against B, B won against C and C won against A, but all + * three having equal total scores. In this case, no consistent ordering of these three competitors + * is possible, and all three need to be ranked equal. + */ + @Test + public void testMatchRaceTieBreakWithPartialCycle() { + List competitors = createCompetitors(6); + Regatta regatta = createRegatta(/* qualifying */ 3, new String[] { "M1", "M2", "M3" }, /* final */0, + new String[] { "Default" }, + /* medal */false, /* medal */ 0, "testMatchRaceTieBreak", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT_FIRST_GETS_ONE)); + Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + Series qualificationSeries; + Iterator seriesIter = regatta.getSeries().iterator(); + qualificationSeries = seriesIter.next(); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000); + TrackedRace q1M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(0), competitors.get(1))); + TrackedRace q1M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(4), competitors.get(2))); + TrackedRace q1M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(3), competitors.get(5))); + RaceColumn q1Column = qualificationSeries.getRaceColumnByName("Q1"); + q1Column.setTrackedRace(q1Column.getFleetByName("M1"), q1M1); + q1Column.setTrackedRace(q1Column.getFleetByName("M2"), q1M2); + q1Column.setTrackedRace(q1Column.getFleetByName("M3"), q1M3); + + TrackedRace q2M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(1), competitors.get(2))); + TrackedRace q2M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(3), competitors.get(0))); + TrackedRace q2M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(4), competitors.get(5))); + RaceColumn q2Column = qualificationSeries.getRaceColumnByName("Q2"); + q2Column.setTrackedRace(q2Column.getFleetByName("M1"), q2M1); + q2Column.setTrackedRace(q2Column.getFleetByName("M2"), q2M2); + q2Column.setTrackedRace(q2Column.getFleetByName("M3"), q2M3); + + TrackedRace q3M1 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(2), competitors.get(0))); + TrackedRace q3M2 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(5), competitors.get(1))); + TrackedRace q3M3 = new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(competitors.get(4), competitors.get(3))); + RaceColumn q3Column = qualificationSeries.getRaceColumnByName("Q3"); + q3Column.setTrackedRace(q1Column.getFleetByName("M1"), q3M1); + q3Column.setTrackedRace(q1Column.getFleetByName("M2"), q3M2); + q3Column.setTrackedRace(q1Column.getFleetByName("M3"), q3M3); + + // point sums for competitors 0..5: 1, 1, 1, 2, 3, 1 + // + // For 0..2 we have a cycle: (0)<(1)<(2)<(0), so they shall be treated equal, sorted by name + // (5) has equal points with (0), (1), and (2) but we have (5)<(1) (based on q3M2), so the + // "single-element chain" (5) is connected to the cycle and is "less" than the cycle. + // Further, we have: (4)<(3)<(all others) based on points (high point!) + // So the expected order is: (4), (3), (5), (0), (1), (2) (where the last three elements' order is resolved by their name) + List rankedCompetitors = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(Arrays.asList(competitors.get(4), competitors.get(3), competitors.get(5), competitors.get(0), competitors.get(1), competitors.get(2)), + rankedCompetitors); + } + /** * Regarding bug 961, test scoring in a leaderboard that has a qualification series with two unordered groups where for one * column only one group has raced (expressed by a mocked TrackedRace attached to the column). Those who already raced their diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/MarkPassingUpdateFromRaceLogFinishPositioingListTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/MarkPassingUpdateFromRaceLogFinishPositioingListTest.java index 51cb340ece3..c8fe26bb138 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/MarkPassingUpdateFromRaceLogFinishPositioingListTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/MarkPassingUpdateFromRaceLogFinishPositioingListTest.java @@ -24,6 +24,7 @@ import org.junit.Test; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; import com.sap.sailing.domain.abstractlog.race.CompetitorResult; +import com.sap.sailing.domain.abstractlog.race.CompetitorResult.MergeState; import com.sap.sailing.domain.abstractlog.race.CompetitorResults; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmedEvent; @@ -153,7 +154,7 @@ public class MarkPassingUpdateFromRaceLogFinishPositioingListTest extends Abstra int rank = 1; for (Pair finishingTime : finishingTimes) { results.add(new CompetitorResultImpl(finishingTime.getA().getId(), finishingTime.getA().getName(), rank++, /* maxPointsReason */ - null, /* score */null, finishingTime.getB(), /* comment */null)); + null, /* score */null, finishingTime.getB(), /* comment */null, MergeState.OK)); } return results; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PositionTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PositionTest.java index 35e28f8456c..385f92a4a8e 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PositionTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/PositionTest.java @@ -53,7 +53,7 @@ public class PositionTest { assertCompactFixHasEqualLatLng(now, -45., 45.); assertCompactFixHasEqualLatLng(now, 0., 45.); } - + private void assertCompactFixHasEqualLatLng(final TimePoint now, final double latDeg, final double lngDeg) { GPSFix fix = new VeryCompactGPSFixImpl(new DegreePosition(latDeg, lngDeg), now); assertEquals(latDeg, fix.getPosition().getLatDeg(), 0.0000001); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java index 165585f853c..f0ce8699455 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackTest.java @@ -51,7 +51,7 @@ import com.sap.sailing.domain.common.tracking.impl.GPSFixMovingImpl; import com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sailing.domain.tracking.GPSFixTrack; -import com.sap.sailing.domain.tracking.impl.DistanceCache; +import com.sap.sailing.domain.tracking.impl.TimeRangeCache; import com.sap.sailing.domain.tracking.impl.DynamicGPSFixMovingTrackImpl; import com.sap.sailing.domain.tracking.impl.DynamicGPSFixTrackImpl; import com.sap.sailing.domain.tracking.impl.GPSFixTrackImpl; @@ -304,7 +304,7 @@ public class TrackTest { // The following getMaximumSpeedOverGround call will trigger a computeMaxSpeed(...) and a cache(...) call new Thread(()-> assertEquals(1., track.getMaximumSpeedOverGround(new MillisecondsTimePoint(0), new MillisecondsTimePoint(7200000)). - getB().getKnots(), 0.001)).start(); // produces a cache entry that ends + getB().getKnots(), 0.01)).start(); // produces a cache entry that ends // now don't release the cacheBarrier as yet but add more fixes new Thread(() -> { GPSFixMoving fix2 = new GPSFixMovingImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(3600000), @@ -442,7 +442,7 @@ public class TrackTest { @Test public void testDistanceTraveledBackwardsQuery() { final Set> cacheEntries = new HashSet<>(); - final DistanceCache distanceCache = new DistanceCache("test-DistanceCache") { + final TimeRangeCache distanceCache = new TimeRangeCache("test-DistanceCache") { @Override public void cache(TimePoint from, TimePoint to, Distance distance) { super.cache(from, to, distance); @@ -453,7 +453,7 @@ public class TrackTest { DynamicGPSFixTrack track = new DynamicGPSFixTrackImpl(new Object(), /* millisecondsOverWhichToAverage */ 30000l) { private static final long serialVersionUID = -7277196393160609503L; @Override - protected DistanceCache getDistanceCache() { + protected TimeRangeCache getDistanceCache() { return distanceCache; } }; @@ -779,7 +779,7 @@ public class TrackTest { @Test public void testDistanceTraveledOnSmoothenedTrackThenAddingOutlier() { final Set invalidationCalls = new HashSet(); - final DistanceCache distanceCache = new DistanceCache("test-DistanceCache") { + final TimeRangeCache distanceCache = new TimeRangeCache("test-DistanceCache") { @Override public void invalidateAllAtOrLaterThan(TimePoint timePoint) { super.invalidateAllAtOrLaterThan(timePoint); @@ -789,7 +789,7 @@ public class TrackTest { DynamicGPSFixTrack track = new DynamicGPSFixTrackImpl(new Object(), /* millisecondsOverWhichToAverage */ 30000l) { private static final long serialVersionUID = -7277196393160609503L; @Override - protected DistanceCache getDistanceCache() { + protected TimeRangeCache getDistanceCache() { return distanceCache; } }; @@ -811,7 +811,7 @@ public class TrackTest { } invalidationCalls.clear(); assertEquals(speed.getMetersPerSecond()*(steps-1), track.getDistanceTraveled(now, start).getMeters(), 0.02); - final com.sap.sse.common.Util.Pair> fullIntervalCacheEntry = distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(now, start); + final com.sap.sse.common.Util.Pair> fullIntervalCacheEntry = distanceCache.getEarliestFromAndResultAtOrAfterFrom(now, start); assertNotNull(fullIntervalCacheEntry); // no more entry for "to"-value start in cache assertEquals(start, fullIntervalCacheEntry.getA()); assertEquals(now, fullIntervalCacheEntry.getB().getA()); @@ -822,13 +822,13 @@ public class TrackTest { assertEquals(1, invalidationCalls.size()); TimePoint timePointOfLastFixBeforeOutlier = track.getLastFixBefore(timePointForOutlier).getTimePoint(); assertTrue(invalidationCalls.iterator().next().after(timePointOfLastFixBeforeOutlier)); // outlier doesn't turn its preceding element into an outlier - assertNull(distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(now, start)); // no more entry for "to"-value start in cache + assertNull(distanceCache.getEarliestFromAndResultAtOrAfterFrom(now, start)); // no more entry for "to"-value start in cache invalidationCalls.clear(); final TimePoint timePointOfLastOriginalFix = track.getLastRawFix().getTimePoint(); assertEquals(speed.getMetersPerSecond() * (steps - 1), track.getDistanceTraveled(now, timePointOfLastOriginalFix).getMeters(), 0.02); final com.sap.sse.common.Util.Pair> newFullIntervalCacheEntry = distanceCache - .getEarliestFromAndDistanceAtOrAfterFrom(now, timePointOfLastOriginalFix); + .getEarliestFromAndResultAtOrAfterFrom(now, timePointOfLastOriginalFix); assertNotNull(newFullIntervalCacheEntry); // no more entry for "to"-value start in cache assertEquals(timePointOfLastOriginalFix, newFullIntervalCacheEntry.getA()); assertEquals(now, newFullIntervalCacheEntry.getB().getA()); @@ -845,7 +845,7 @@ public class TrackTest { invalidationCalls.clear(); // expect the invalidation to have started after the single cache entry, so the cache entry still has to be there: final com.sap.sse.common.Util.Pair> stillPresentFullIntervalCacheEntry = distanceCache - .getEarliestFromAndDistanceAtOrAfterFrom(now, timePointOfLastOriginalFix); + .getEarliestFromAndResultAtOrAfterFrom(now, timePointOfLastOriginalFix); assertNull(stillPresentFullIntervalCacheEntry); // no more entry for "to"-value start in cache because new temporary outlier lies in previously cached interval GPSFix polishedLastFix = track.getLastFixBefore(new MillisecondsTimePoint(Long.MAX_VALUE)); // get the last smoothened fix... // ...which now is expected to be two fixes before lateOutlier because lateOutlier has previous fixes within the time range @@ -870,7 +870,7 @@ public class TrackTest { assertTrue(timePointForLateOutlier.compareTo(fix.getTimePoint()) < 0); // expect the invalidation to have started at the fix before the outlier, leaving the previous result ending at the fix right before the outlier intact final com.sap.sse.common.Util.Pair> stillStillPresentFullIntervalCacheEntry = distanceCache - .getEarliestFromAndDistanceAtOrAfterFrom(now, timePointOfLastOriginalFix); + .getEarliestFromAndResultAtOrAfterFrom(now, timePointOfLastOriginalFix); assertNull(stillStillPresentFullIntervalCacheEntry); // still nothing in the cache track.lockForRead(); try { @@ -955,11 +955,11 @@ public class TrackTest { final Duration raceDuration = Duration.ONE_HOUR.times(48); final Speed speed = new KnotSpeedImpl(12); final Distance distancePerSample = speed.travel(fixTime, fixTime.plus(samplingInterval)); - final DistanceCache distanceCache = new DistanceCache("test-DistanceCache"); + final TimeRangeCache distanceCache = new TimeRangeCache<>("test-DistanceCache"); DynamicGPSFixTrack track = new DynamicGPSFixTrackImpl(new Object(), /* millisecondsOverWhichToAverage */ 30000l) { private static final long serialVersionUID = -7277196393160609503L; @Override - protected DistanceCache getDistanceCache() { + protected TimeRangeCache getDistanceCache() { return distanceCache; } }; @@ -980,13 +980,13 @@ public class TrackTest { queryTime = queryTime.plus(queryInterval); } } - assertTrue(distanceCache.size() <= DistanceCache.MAX_SIZE); + assertTrue(distanceCache.size() <= TimeRangeCache.MAX_SIZE); } @Test public void testDistanceCacheAccessForPartialStrip() { final Set invalidationCalls = new HashSet(); - final DistanceCache distanceCache = new DistanceCache("test-DistanceCache") { + final TimeRangeCache distanceCache = new TimeRangeCache("test-DistanceCache") { @Override public void invalidateAllAtOrLaterThan(TimePoint timePoint) { super.invalidateAllAtOrLaterThan(timePoint); @@ -996,7 +996,7 @@ public class TrackTest { DynamicGPSFixTrack track = new DynamicGPSFixTrackImpl(new Object(), /* millisecondsOverWhichToAverage */ 30000l) { private static final long serialVersionUID = -7277196393160609503L; @Override - protected DistanceCache getDistanceCache() { + protected TimeRangeCache getDistanceCache() { return distanceCache; } }; @@ -1024,11 +1024,11 @@ public class TrackTest { assertEquals(speed.getMetersPerSecond()*(steps-1), track.getDistanceTraveled(now, start).getMeters(), 0.02); assertTrue(invalidationCalls.isEmpty()); // expect a cache entry exactly for the strip's boundaries - com.sap.sse.common.Util.Pair> stripCacheEntry = distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(stripFrom, stripTo); + com.sap.sse.common.Util.Pair> stripCacheEntry = distanceCache.getEarliestFromAndResultAtOrAfterFrom(stripFrom, stripTo); assertEquals(stripTo, stripCacheEntry.getA()); assertEquals(stripFrom, stripCacheEntry.getB().getA()); // expect a cache entry exactly for the full boundaries - com.sap.sse.common.Util.Pair> fullCacheEntry = distanceCache.getEarliestFromAndDistanceAtOrAfterFrom(now, start); + com.sap.sse.common.Util.Pair> fullCacheEntry = distanceCache.getEarliestFromAndResultAtOrAfterFrom(now, start); assertEquals(start, fullCacheEntry.getA()); assertEquals(now, fullCacheEntry.getB().getA()); } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingCalculatorPerformanceTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingCalculatorPerformanceTest.java index b2982f23952..9752ba115be 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingCalculatorPerformanceTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingCalculatorPerformanceTest.java @@ -67,7 +67,7 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark time = System.currentTimeMillis() - time; result.put("FinderPerformance", time); System.out.println(time); - Assert.assertTrue("Time expected to be less than 5000ms but was "+time+"ms", time < 5000); + Assert.assertTrue("Time expected to be less than 7000ms but was "+time+"ms", time < 7000); } @Test @@ -75,7 +75,7 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark final long time = timeToAddCandidatesToChooser(500, 1, 25); System.out.println(time); result.put("ChooserPerformance", time); - assertTrue(time < 15000); + assertTrue("time needs to be less than 15s but was "+time+"ms", time < 15000); } private long timeToAddCandidatesToChooser(int numberOfTimesAdding, int numberToAddEachTime, int numberOfRepititions) { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index 7b9b907c983..958fe6b4c7d 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -14,7 +14,6 @@ import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; import com.sap.sailing.domain.base.CourseBase; -import com.sap.sailing.domain.base.EventFetcher; import com.sap.sailing.domain.base.Leg; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceColumn; @@ -476,10 +475,6 @@ public class MockedTrackedRace implements DynamicTrackedRace { public void removeListener(RegattaLikeListener listener) { } - @Override - public void adjustEventToRegattaAssociation(EventFetcher eventFetcher) { - } - @Override public RaceExecutionOrderProvider getRaceExecutionOrderProvider() { return null; @@ -679,6 +674,16 @@ public class MockedTrackedRace implements DynamicTrackedRace { return null; } + @Override + public Distance getDistanceFoiled(Competitor competitor, TimePoint timePoint) { + return null; + } + + @Override + public Duration getDurationFoiled(Competitor competitor, TimePoint timePoint) { + return null; + } + @Override public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint, WindPositionMode windPositionMode) { @@ -1149,4 +1154,21 @@ public class MockedTrackedRace implements DynamicTrackedRace { public Distance getEstimatedDistanceToComplete(TimePoint now) { return null; } + + @Override + public > Iterable getSensorTracks( + String trackName) { + return Collections.emptySet(); + } + + @Override + public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) { + return null; + } + + @Override + public SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint, + WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) { + return null; + } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java index 5dbba24f91d..3cb4725cfbf 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java @@ -60,6 +60,7 @@ import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.WindWithConfidence; import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; +import com.sap.sse.common.Duration; import com.sap.sse.common.IsManagedByCache; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; @@ -360,6 +361,16 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { return null; } + @Override + public Distance getDistanceFoiled(Competitor competitor, TimePoint timePoint) { + return null; + } + + @Override + public Duration getDurationFoiled(Competitor competitor, TimePoint timePoint) { + return null; + } + @Override public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint, WindPositionMode windPositionMode) { return null; @@ -696,4 +707,21 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { public Distance getEstimatedDistanceToComplete(TimePoint now) { return null; } + + @Override + public > Iterable getSensorTracks( + String trackName) { + return Collections.emptySet(); + } + + @Override + public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) { + return null; + } + + @Override + public SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint, + WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) { + return null; + } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/RaceTrackerListenersTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/RaceTrackerListenersTest.java index f5f6798bfd5..54839dc2b01 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/RaceTrackerListenersTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/RaceTrackerListenersTest.java @@ -17,12 +17,12 @@ public class RaceTrackerListenersTest { listeners.addListener(new RaceTracker.Listener() { @Override - public void onTrackerWillStop(boolean preemptive) { + public void onTrackerWillStop(boolean preemptive, boolean willBeRemoved) { listeners.removeListener(this); } }); } - listeners.onTrackerWillStop(false); + listeners.onTrackerWillStop(/* preemptive */ false, /* willBeRemoved */ false); } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackSerializationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackSerializationTest.java index 54d044a632d..772b6d59bc6 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackSerializationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackSerializationTest.java @@ -31,7 +31,7 @@ public class BravoFixTrackSerializationTest { @Before public void setUp() { Competitor competitor = TrackBasedTest.createCompetitor("SAP Extreme Sailing Team"); - track = new BravoFixTrackImpl(competitor, BravoFixTrack.TRACK_NAME); + track = new BravoFixTrackImpl(competitor, BravoFixTrack.TRACK_NAME, false); } @Test @@ -116,7 +116,7 @@ public class BravoFixTrackSerializationTest { } private void addOrReplaceBravoFixToTrack(boolean replace) { - double[] fixData = new double[BravoSensorDataMetadata.getTrackColumnCount()]; + Double[] fixData = new Double[BravoSensorDataMetadata.getTrackColumnCount()]; // fill the port/starboard columns as well because their minimum defines the true ride height fixData[BravoSensorDataMetadata.RIDE_HEIGHT_PORT_HULL.getColumnIndex()] = rideHeight; fixData[BravoSensorDataMetadata.RIDE_HEIGHT_STBD_HULL.getColumnIndex()] = rideHeight; diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/AbstractReceiverWithQueue.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/AbstractReceiverWithQueue.java index a753335639a..1894f85c08d 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/AbstractReceiverWithQueue.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/AbstractReceiverWithQueue.java @@ -17,11 +17,13 @@ import com.sap.sailing.domain.tracking.TrackedRegatta; import com.sap.sailing.domain.tractracadapter.DomainFactory; import com.sap.sailing.domain.tractracadapter.LoadingQueueDoneCallBack; import com.sap.sailing.domain.tractracadapter.Receiver; +import com.sap.sailing.domain.tractracadapter.TracTracControlPoint; import com.sap.sse.common.Duration; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Triple; import com.tractrac.model.lib.api.event.IEvent; import com.tractrac.model.lib.api.event.IRace; +import com.tractrac.model.lib.api.route.IControl; import com.tractrac.subscription.lib.api.IEventSubscriber; import com.tractrac.subscription.lib.api.IRaceSubscriber; @@ -250,4 +252,14 @@ public abstract class AbstractReceiverWithQueue implements Runnable, Re } } } + + protected void ensureAllSingleMarksOfCourseAreaAreCreated(final IRace tractracRace) { + for (final IControl tractracControlPoint : getDomainFactory().getControlsForCourseArea(getTracTracEvent(), + tractracRace.getCourseArea())) { + if (!tractracControlPoint.isMultiple()) { + final TracTracControlPoint ttcp = new ControlPointAdapter(tractracControlPoint); + getDomainFactory().getOrCreateControlPoint(ttcp); + } + } + } } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MarkPositionReceiver.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MarkPositionReceiver.java index d49eb1651f0..2b6ed9a2e68 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MarkPositionReceiver.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MarkPositionReceiver.java @@ -43,6 +43,15 @@ public class MarkPositionReceiver extends AbstractReceiverWithQueue event) { System.out.print("R"); + ensureAllSingleMarksOfCourseAreaAreCreated(tractracRace); // this way, single marks will be known by their original name, even if used as virtual marks in gates/lines final IRoute route = event.getA(); final String routeMetadataString = route.getMetadata() != null ? route.getMetadata().getText() : null; final LinkedHashMap ttControlPointsForAllOriginalEventControlPoints = new LinkedHashMap<>(); diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceStartedAndFinishedReceiver.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceStartedAndFinishedReceiver.java index 98bbded5fc6..fabd24351cf 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceStartedAndFinishedReceiver.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceStartedAndFinishedReceiver.java @@ -71,10 +71,18 @@ public class RaceStartedAndFinishedReceiver extends AbstractReceiverWithQueue{ + c.setRequestMethod(HttpGetRequestMethod); + c.setDoOutput(false); + c.setUseCaches(false); + }); + } + + protected HttpURLConnection setConnectionPropertiesAndSendWithPayload(HttpURLConnection connection, String payload) throws IOException { + return followRedirects(connection, c-> { + c.setRequestMethod(HttpPostRequestMethod); + c.setDoOutput(true); + c.setUseCaches(false); + c.setRequestProperty(ContentType, ContentTypeApplicationJson); + c.addRequestProperty(ContentLength, String.valueOf(payload.getBytes().length)); + DataOutputStream writer = new DataOutputStream(c.getOutputStream()); + writer.writeBytes(payload); + writer.flush(); + writer.close(); + }); + } + + @FunctionalInterface + private static interface ConnectionConsumer { + void consume(HttpURLConnection connection) throws IOException; + } + + private HttpURLConnection followRedirects(HttpURLConnection connection, ConnectionConsumer connectionConsumer) throws IOException { + // Initial timeout needs to be big enough to allow the first parts of the response to reach this server + final int HTTP_MAX_REDIRECTS = 5; + int redirects = 0; + int responseCode; + do { + connectionConsumer.consume(connection); + connection.setReadTimeout((int) Duration.ONE_MINUTE.times(10).asMillis()); + responseCode = connection.getResponseCode(); + if ((connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_PERM + || connection.getResponseCode() == HttpURLConnection.HTTP_MOVED_TEMP) && redirects < HTTP_MAX_REDIRECTS) { + String location = connection.getHeaderField("Location"); + final URL nextUrl = new URL(location); + connection = (HttpURLConnection) nextUrl.openConnection(); + redirects++; + } + } while ((responseCode == HttpURLConnection.HTTP_MOVED_PERM + || responseCode == HttpURLConnection.HTTP_MOVED_TEMP) && redirects < HTTP_MAX_REDIRECTS); + return connection; } - protected void setConnectionProperties(HttpURLConnection connection) throws ProtocolException { - connection.setRequestMethod(HttpGetRequestMethod); - connection.setDoOutput(false); - connection.setUseCaches(false); - } - - protected void setConnectionProperties(HttpURLConnection connection, String payload) throws ProtocolException { - connection.setRequestMethod(HttpPostRequestMethod); - connection.setDoOutput(true); - connection.setUseCaches(false); - connection.setRequestProperty(ContentType, ContentTypeApplicationJson); - connection.addRequestProperty(ContentLength, String.valueOf(payload.getBytes().length)); - } - protected boolean isActive() { return this.active; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java index aef4e907422..071b0bce01a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Event.java @@ -13,11 +13,6 @@ import com.sap.sailing.domain.leaderboard.LeaderboardGroup; * */ public interface Event extends EventBase { - Iterable getRegattas(); - - void addRegatta(Regatta regatta); - - void removeRegatta(Regatta regatta); /** * For events, the ID is always a UUID. diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/EventFetcher.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/EventFetcher.java deleted file mode 100755 index 1ef00c35ccf..00000000000 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/EventFetcher.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.sap.sailing.domain.base; - -public interface EventFetcher { - Iterable getAllEvents(); -} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java index 279d0527a20..e03ad7197e0 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/Regatta.java @@ -208,14 +208,6 @@ public interface Regatta extends NamedWithID, IsRegattaLike, HasRaceColumnsAndRe */ void setUseStartTimeInference(boolean useStartTimeInference); - /** - * {@link Event} manages an association to its {@link Event#getRegattas() regattas}. When something on the - * regatta changes (in particular, the implicit link to an event through an event's {@link CourseArea} that - * is assigned as this regatta's {@link #setDefaultCourseArea(CourseArea) default course area}), the - * opposite end of the association needs to be maintained on the event's side. - */ - void adjustEventToRegattaAssociation(EventFetcher eventFetcher); - RaceExecutionOrderProvider getRaceExecutionOrderProvider(); default RankingMetrics getRankingMetricType() { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java index c87dcfbd4d4..0a11c676f89 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/DomainFactoryImpl.java @@ -55,6 +55,7 @@ import com.sap.sailing.domain.leaderboard.impl.LowPoint; import com.sap.sailing.domain.leaderboard.impl.LowPointForLeagueOverallLeaderboard; import com.sap.sailing.domain.leaderboard.impl.LowPointTieBreakBasedOnLastSeriesOnly; import com.sap.sailing.domain.leaderboard.impl.LowPointWinnerGetsZero; +import com.sap.sailing.domain.leaderboard.impl.LowPointWithAutomaticRDG; import com.sap.sailing.domain.leaderboard.impl.LowPointWithEliminationsAndRoundsWinnerGets07; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.MarkPassing; @@ -133,6 +134,8 @@ public class DomainFactoryImpl extends SharedDomainFactoryImpl implements Domain return new LowPointForLeagueOverallLeaderboard(); case LOW_POINT_TIE_BREAK_BASED_ON_LAST_SERIES_ONLY: return new LowPointTieBreakBasedOnLastSeriesOnly(); + case LOW_POINT_WITH_AUTOMATIC_RDG: + return new LowPointWithAutomaticRDG(); } throw new RuntimeException("Unknown scoring scheme type "+scoringSchemeType.name()); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java index 723872c740f..cf7c0044f97 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/EventImpl.java @@ -3,13 +3,10 @@ package com.sap.sailing.domain.base.impl; import java.io.IOException; import java.io.ObjectInputStream; import java.util.Collections; -import java.util.HashSet; -import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; import com.sap.sailing.domain.base.Event; -import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Venue; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sse.common.TimePoint; @@ -17,24 +14,21 @@ import com.sap.sse.common.Util; public class EventImpl extends EventBaseImpl implements Event { private static final long serialVersionUID = 855135446595485715L; - - private final Set regattas; - + private ConcurrentLinkedQueue leaderboardGroups; public EventImpl(String name, TimePoint startDate, TimePoint endDate, String venueName, boolean isPublic, UUID id) { this(name, startDate, endDate, new VenueImpl(venueName), isPublic, id); } - + /** * @param venue must not be null */ public EventImpl(String name, TimePoint startDate, TimePoint endDate, Venue venue, boolean isPublic, UUID id) { super(name, startDate, endDate, venue, isPublic, id); - this.regattas = new HashSet(); this.leaderboardGroups = new ConcurrentLinkedQueue<>(); } - + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); if (leaderboardGroups == null) { @@ -42,21 +36,6 @@ public class EventImpl extends EventBaseImpl implements Event { } } - @Override - public Iterable getRegattas() { - return Collections.unmodifiableSet(regattas); - } - - @Override - public void addRegatta(Regatta regatta) { - regattas.add(regatta); - } - - @Override - public void removeRegatta(Regatta regatta) { - regattas.remove(regatta); - } - public String toString() { return getId() + " " + getName() + " " + getVenue().getName() + " " + isPublic(); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java index 578d94770fe..2a6a8c4ba84 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RegattaImpl.java @@ -31,8 +31,6 @@ import com.sap.sailing.domain.abstractlog.shared.analyzing.CompetitorsInLogAnaly import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseArea; -import com.sap.sailing.domain.base.Event; -import com.sap.sailing.domain.base.EventFetcher; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumnInSeries; @@ -740,19 +738,6 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene return regattaLikeHelper.getTimeOnDistanceAllowancePerNauticalMile(competitor); } - public void adjustEventToRegattaAssociation(EventFetcher eventFetcher) { - CourseArea defaultCourseArea = getDefaultCourseArea(); - for (Event event : eventFetcher.getAllEvents()) { - event.removeRegatta(this); - for (CourseArea courseArea : event.getVenue().getCourseAreas()) { - if (defaultCourseArea != null && courseArea.getId().equals(defaultCourseArea.getId())) { - event.addRegatta(this); - } - } - } - - } - @Override public RaceExecutionOrderProvider getRaceExecutionOrderProvider() { return raceExecutionOrderCache; @@ -870,4 +855,5 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene CompetitorDeregistrator deregisterer = new CompetitorDeregistrator<>(regattaLog, competitors, regattaLogEventAuthorForRegatta); deregisterer.deregister(deregisterer.analyze()); } + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java index edd99bd654b..abd48732d49 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/Leaderboard.java @@ -269,7 +269,7 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { * competitor doesn't appear in any of the race column's attached tracked races. A 0.0 score is returned * if the competitor's result for race is discarded. */ - Double getNetPoints(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException; + Double getNetPoints(Competitor competitor, RaceColumn race, TimePoint timePoint); /** * Tells whether the contribution of raceColumn is discarded in the current leaderboard's standings for @@ -448,10 +448,10 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { Util.Pair getMaximumSpeedOverGround(Competitor competitor, TimePoint timePoint); /** - * @return null if no tracked race is available in this leaderboard, the competitor hasn't started - * a single race at timePoint or if the competitor has not finished one of the races. This method - * computes the average speed starting with the first mark passing until timePoint or if earlier then - * the timePoint of the last mark passing. + * @return the {@link #getTotalDistanceTraveled(Competitor, TimePoint) total distance} the competitor has traveled + * up to {@code timePoint}, divided by the {@link #getTotalTimeSailed(Competitor, TimePoint) total time + * sailed} up to this {@code timePoint}; {@code null} in case a zero or {@code null} time has been spent so + * far, or a {@code null} distance is returned by {@link #getTotalDistanceTraveled(Competitor, TimePoint)}. */ Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint); @@ -481,6 +481,26 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { * leaderboard */ Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint); + + /** + * Computes the distance the competitor has been foiling in the tracked races in this leaderboard, starting + * to count in each race when the competitor passes the start line, aggregating up to timePoint or the + * end of the last race, whichever is first. + * + * @return null if the competitor hasn't foiled any distance in any tracked race in this + * leaderboard + */ + Distance getTotalDistanceFoiled(Competitor competitor, TimePoint timePoint); + + /** + * Computes the duration the competitor has foiled in the tracked races in this leaderboard, starting + * to count in each race when the competitor passes the start line, aggregating up to timePoint or the + * end of the last race, whichever is first. + * + * @return null if the competitor hasn't foiled at any time in any tracked race in this + * leaderboard + */ + Duration getTotalDurationFoiled(Competitor competitor, TimePoint timePoint); /** * Same as {@link #getNetPoints(Competitor, RaceColumn, TimePoint)}, only that for determining the discarded @@ -568,4 +588,10 @@ public interface Leaderboard extends LeaderboardBase, HasRaceColumns { BoatClass getBoatClass(); LeaderboardType getLeaderboardType(); + + /** + * Tells if there is at least one non-{@code null} score for the given {@code competitor} in the + * leaderboard. + */ + boolean hasScores(Competitor competitor, TimePoint timePoint); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoreCorrection.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoreCorrection.java index 790a163f484..6923acb7035 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoreCorrection.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoreCorrection.java @@ -57,16 +57,16 @@ public interface ScoreCorrection extends Serializable { * @param trackedRankProvider * can provide the tracked rank if needed; will only be called if there is no score correction for the * timePoint specified. + * @param leaderboard TODO * @param timePoint * the time point for which to get the corrected score; score corrections have a validity time interval. * Only the last score correction valid at timePoint is considered. - * * @param numberOfCompetitorsFetcher * can determine the number of competitors to use as the basis for penalty score calculation * ("max points") if needed */ Result getCorrectedScore(Callable trackedRankProvider, Competitor competitor, RaceColumn raceColumn, - TimePoint timePoint, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsFetcher, ScoringScheme scoringScheme); + Leaderboard leaderboard, TimePoint timePoint, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsFetcher, ScoringScheme scoringScheme); /** * @param timePoint diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java index b4899a04275..ec65a4d43be 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/ScoringScheme.java @@ -50,7 +50,6 @@ public interface ScoringScheme extends Serializable { * If the competitor has no {@link RaceColumn#getTrackedRace(Competitor) tracked race} in the column in which * the competitor participated, null is returned, meaning the competitor has no score assigned for that * race. - * @param timePoint TODO */ Double getScoreForRank(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, int rank, Callable numberOfCompetitorsInRaceFetcher, @@ -62,23 +61,29 @@ public interface ScoringScheme extends Serializable { * or regatta. * * @param numberOfCompetitorsInLeaderboardFetcher - * if it returns null, the caller cannot determine the number of competitors in the single race; - * otherwise, this parameter tells the number of competitors in the same race as competitor, - * not in the entire raceColumn (those may be more in case of split fleets). The scoring scheme - * may use this number, if available, to infer a penalty score. + * if it returns null, the caller cannot determine the number of competitors in the single + * race; otherwise, this parameter tells the number of competitors in the same race as + * competitor, not in the entire raceColumn (those may be more in case of split + * fleets). The scoring scheme may use this number, if available, to infer a penalty score. * @param timePoint - * an optional timePoint that may help the scheme to determine the penalty related to a certain point in time only. + * an optional timePoint that may help the scheme to determine the penalty related to a certain point in + * time only. + * @param leaderboard + * may be required in case a "penalty" such as a redress needs to inspect the scores of other race + * columns as well; implementations need to take great care not to cause endless recursions by + * naively asking the leaderboard for scores which would recurse into this method */ Double getPenaltyScore(RaceColumn raceColumn, Competitor competitor, MaxPointsReason maxPointsReason, Integer numberOfCompetitorsInRace, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, - TimePoint timePoint); + TimePoint timePoint, Leaderboard leaderboard); /** * @param competitor1Scores scores of the first competitor, in the order of race columns in the leaderboard * @param competitor2Scores scores of the second competitor, in the order of race columns in the leaderboard + * @param leaderboard TODO */ int compareByBetterScore(Competitor o1, - List> competitor1Scores, Competitor o2, List> competitor2Scores, boolean nullScoresAreBetter, TimePoint timePoint); + List> competitor1Scores, Competitor o2, List> competitor2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard); /** * In case two competitors scored in different numbers of races, this scoring scheme decides whether this @@ -91,8 +96,10 @@ public interface ScoringScheme extends Serializable { /** * Usually, when all other sorting criteria end up in a tie, the last race sailed is used to decide. + * @param o1 TODO + * @param o2 TODO */ - int compareByLastRace(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter); + int compareByLastRace(List> o1Scores, List> o2Scores, boolean nullScoresAreBetter, Competitor o1, Competitor o2); /** * Under certain circumstances, a scoring scheme may decide that the scores of a column are not (yet) to be used @@ -111,5 +118,5 @@ public interface ScoringScheme extends Serializable { * ordering of the list containing the total points matches the order in the group. * @throws NoWindException */ - int compareByLatestRegattaInMetaLeaderboard(Leaderboard leaderboard, Competitor o1, Competitor o2, TimePoint timePoint) throws NoWindException; + int compareByLatestRegattaInMetaLeaderboard(Leaderboard leaderboard, Competitor o1, Competitor o2, TimePoint timePoint); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointExtremeSailingSeriesOverall.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointExtremeSailingSeriesOverall.java index 9039195547c..20e4cf2abc0 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointExtremeSailingSeriesOverall.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointExtremeSailingSeriesOverall.java @@ -55,7 +55,7 @@ public abstract class AbstractHighPointExtremeSailingSeriesOverall extends HighP * Implements rule 13.5 of the Extreme Sailing Series notice of race as of August 2012. */ @Override - public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint) { + public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard) { assert o1Scores.size() == o2Scores.size(); int o1Wins = getWins(o1Scores); int o2Wins = getWins(o2Scores); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointFirstGetsFixedOr8AndLastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointFirstGetsFixedOr8AndLastBreaksTie.java index 828a35f39e9..ff804e487f6 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointFirstGetsFixedOr8AndLastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractHighPointFirstGetsFixedOr8AndLastBreaksTie.java @@ -37,7 +37,7 @@ public abstract class AbstractHighPointFirstGetsFixedOr8AndLastBreaksTie extends } @Override - public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint) { + public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard) { Double o1Highest = getHighestScore(o1Scores); Double o2Highest = getHighestScore(o2Scores); return o2Highest.compareTo(o1Highest); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardWithCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardWithCache.java index 490ba407f71..9ec0daca615 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardWithCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractLeaderboardWithCache.java @@ -19,6 +19,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RunnableFuture; +import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; @@ -642,8 +643,12 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard { row.totalTimeSailedReachingInSeconds = totalTimeSailedReaching==null?null:totalTimeSailedReaching.asSeconds(); final Duration totalTimeSailed = this.getTotalTimeSailed(competitor, timePoint); row.totalTimeSailedInSeconds = totalTimeSailed==null?null:totalTimeSailed.asSeconds(); - final Distance totalDistanceTraveledInMeters = this.getTotalDistanceTraveled(competitor, timePoint); - row.totalDistanceTraveledInMeters = totalDistanceTraveledInMeters==null?null:totalDistanceTraveledInMeters.getMeters(); + final Distance totalDistanceTraveled = this.getTotalDistanceTraveled(competitor, timePoint); + row.totalDistanceTraveledInMeters = totalDistanceTraveled==null?null:totalDistanceTraveled.getMeters(); + final Distance totalDistanceFoiled = this.getTotalDistanceFoiled(competitor, timePoint); + row.totalDistanceFoiledInMeters = totalDistanceFoiled==null?null:totalDistanceFoiled.getMeters(); + final Duration totalDurationFoiled = this.getTotalDurationFoiled(competitor, timePoint); + row.totalDurationFoiledInSeconds = totalDurationFoiled==null?null:totalDurationFoiled.asSeconds(); } private LeaderboardDTOCache getLeaderboardDTOCache() { @@ -821,6 +826,12 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard { result.currentHeelInDegrees = heel == null ? null : heel.getDegrees(); Bearing pitch = trackedLeg.getPitch(timePoint); result.currentPitchInDegrees = pitch == null ? null : pitch.getDegrees(); + + Distance distanceFoiled = trackedLeg.getDistanceFoiled(timePoint); + result.currentDistanceFoiledInMeters = distanceFoiled == null ? null : distanceFoiled.getMeters(); + Duration durationFoiled = trackedLeg.getDurationFoiled(timePoint); + result.currentDurationFoiledInSeconds = durationFoiled == null ? null : durationFoiled.asSeconds(); + result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots; Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint); result.distanceTraveledInMeters = distanceTraveled == null ? null : distanceTraveled.getMeters(); @@ -858,7 +869,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard { if (trackedLeg.hasFinishedLeg(timePoint)) { velocityMadeGood = trackedLeg.getAverageVelocityMadeGood(timePoint); } else { - velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT); + velocityMadeGood = trackedLeg.getVelocityMadeGood(timePoint, WindPositionMode.EXACT, cache); } result.velocityMadeGoodInKnots = velocityMadeGood == null ? null : velocityMadeGood.getKnots(); Distance windwardDistanceToGo = trackedLeg.getWindwardDistanceToGo(timePoint, WindPositionMode.LEG_MIDDLE); @@ -1082,18 +1093,37 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard { @Override public Distance getTotalDistanceTraveled(Competitor competitor, TimePoint timePoint) { - Distance result = null; + return getTotals(competitor, timePoint, Distance::add, TrackedRace::getDistanceTraveled); + } + + @Override + public Distance getTotalDistanceFoiled(Competitor competitor, TimePoint timePoint) { + return getTotals(competitor, timePoint, Distance::add, TrackedRace::getDistanceFoiled); + } + + @Override + public Duration getTotalDurationFoiled(Competitor competitor, TimePoint timePoint) { + return getTotals(competitor, timePoint, Duration::plus, TrackedRace::getDurationFoiled); + } + + @FunctionalInterface + private static interface ValueFromRaceGetter { + T get(TrackedRace trackedRace, Competitor competitor, TimePoint timePoint); + } + + private T getTotals(Competitor competitor, TimePoint timePoint, BiFunction adder, ValueFromRaceGetter valueGetter) { + T result = null; for (TrackedRace trackedRace : getTrackedRaces()) { TimePoint startOfRace; if (Util.contains(trackedRace.getRace().getCompetitors(), competitor) && (startOfRace=trackedRace.getStartOfRace()) != null && !startOfRace.after(timePoint)) { - Distance distanceSailedInRace = trackedRace.getDistanceTraveled(competitor, timePoint); + T distanceSailedInRace = valueGetter.get(trackedRace, competitor, timePoint); if (distanceSailedInRace != null) { if (result == null) { result = distanceSailedInRace; } else { - result = result.add(distanceSailedInRace); + result = adder.apply(result, distanceSailedInRace); } } else { // if competitor has not finished one single race in the whole @@ -1118,4 +1148,13 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard { } } + @Override + public boolean hasScores(Competitor competitor, TimePoint timePoint) { + for (final RaceColumn raceColumn : getRaceColumns()) { + if (getTotalPoints(competitor, raceColumn, timePoint) != null) { + return true; + } + } + return false; + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java index e9f0e0edd1a..cc53f6ebf81 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractScoringSchemeImpl.java @@ -124,7 +124,7 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme { * Assuming both competitors scored in the same number of races, compares the sorted scores. */ @Override - public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint) { + public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard) { final Comparator pureScoreComparator = getScoreComparator(nullScoresAreBetter); // needs to compare net points; therefore, divide the total points by the column factor for comparison: List o1NetScores = new ArrayList<>(); @@ -161,13 +161,12 @@ public abstract class AbstractScoringSchemeImpl implements ScoringScheme { } /** - * @param o1ScoresIncludingDiscarded scores that include the points for those races that have been discarded (net points) - * @param o2ScoresIncludingDiscarded scores that include the points for those races that have been discarded (net points) + * @param o1ScoresIncludingDiscarded scores that include the points for those races that have been discarded (total points) + * @param o2ScoresIncludingDiscarded scores that include the points for those races that have been discarded (total points) */ @Override public int compareByLastRace(List> o1ScoresIncludingDiscarded, - List> o2ScoresIncludingDiscarded, - boolean nullScoresAreBetter) { + List> o2ScoresIncludingDiscarded, boolean nullScoresAreBetter, Competitor o1, Competitor o2) { int result = 0; final Comparator pureScoreComparator = getScoreComparator(nullScoresAreBetter); ListIterator> o1Iter = o1ScoresIncludingDiscarded.listIterator(o1ScoresIncludingDiscarded.size()); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java index 30250f6c8fd..63649e7db1b 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/AbstractSimpleLeaderboardImpl.java @@ -40,6 +40,7 @@ import com.sap.sailing.domain.racelog.RaceLogIdentifier; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.util.impl.RaceColumnListeners; +import com.sap.sse.common.Duration; import com.sap.sse.common.ObscuringIterable; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; @@ -385,7 +386,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW public Double getTotalPoints(final Competitor competitor, final RaceColumn raceColumn, final TimePoint timePoint) { return getScoreCorrection().getCorrectedScore( ()->getTrackedRank(competitor, raceColumn, timePoint), competitor, - raceColumn, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore(); + raceColumn, this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()).getCorrectedScore(); } @Override @@ -424,13 +425,13 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW } @Override - public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) throws NoWindException { + public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, TimePoint timePoint) { return getNetPoints(competitor, raceColumn, getRaceColumns(), timePoint); } @Override public Double getNetPoints(Competitor competitor, RaceColumn raceColumn, - Iterable raceColumnsToConsider, TimePoint timePoint) throws NoWindException { + Iterable raceColumnsToConsider, TimePoint timePoint) { final Set discardedRaceColumns = getResultDiscardingRule() .getDiscardedRaceColumns(competitor, this, raceColumnsToConsider, timePoint); return getNetPoints(competitor, raceColumn, timePoint, discardedRaceColumns); @@ -627,7 +628,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW Set discardedRaceColumns) throws NoWindException { Callable trackedRankProvider = ()->getTrackedRank(competitor, race, timePoint); final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, race, - timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); + this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); boolean discarded = isDiscarded(competitor, race, timePoint, discardedRaceColumns); final Double correctedScore = correctedResults.getCorrectedScore(); return new EntryImpl(trackedRankProvider, correctedScore, ()->correctedResults.getUncorrectedScore(), @@ -691,7 +692,7 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW for (final Competitor competitor : getCompetitors()) { Callable trackedRankProvider = ()->getTrackedRank(competitor, raceColumn, timePoint); final Result correctedResults = getScoreCorrection().getCorrectedScore(trackedRankProvider, competitor, raceColumn, - timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); + this, timePoint, new NumberOfCompetitorsFetcherImpl(), getScoringScheme()); Set discardedRacesForCompetitor = discardedRaces.get(competitor); if (discardedRacesForCompetitor == null) { discardedRacesForCompetitor = getResultDiscardingRule().getDiscardedRaceColumns(competitor, this, getRaceColumns(), timePoint); @@ -790,30 +791,10 @@ public abstract class AbstractSimpleLeaderboardImpl extends AbstractLeaderboardW @Override public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) { Speed result = null; - for (TrackedRace trackedRace : getTrackedRaces()) { - if (Util.contains(trackedRace.getRace().getCompetitors(), competitor)) { - NavigableSet markPassings = trackedRace.getMarkPassings(competitor); - if (!markPassings.isEmpty()) { - TimePoint from = markPassings.first().getTimePoint(); - TimePoint to; - if (timePoint.after(markPassings.last().getTimePoint()) && - markPassings.last().getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint()) { - // stop counting when competitor finished the race - to = markPassings.last().getTimePoint(); - } else { - if (markPassings.last().getWaypoint() != trackedRace.getRace().getCourse().getLastWaypoint() && - timePoint.after(markPassings.last().getTimePoint())) { - result = null; - break; - } - to = timePoint; - } - Distance distanceTraveled = trackedRace.getDistanceTraveled(competitor, timePoint); - if (distanceTraveled != null) { - result = distanceTraveled.inTime(to.asMillis()-from.asMillis()); - } - } - } + final Duration totalTimeSailed = this.getTotalTimeSailed(competitor, timePoint); + final Distance totalDistanceSailed = this.getTotalDistanceTraveled(competitor, timePoint); + if (totalDistanceSailed != null && totalTimeSailed != null && !totalTimeSailed.equals(Distance.NULL)) { + result = totalDistanceSailed.inTime(totalTimeSailed); } return result; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/CompetitorProviderFromRaceColumnsAndRegattaLike.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/CompetitorProviderFromRaceColumnsAndRegattaLike.java index 9684c992b6e..fdbfd864643 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/CompetitorProviderFromRaceColumnsAndRegattaLike.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/CompetitorProviderFromRaceColumnsAndRegattaLike.java @@ -181,9 +181,7 @@ public class CompetitorProviderFromRaceColumnsAndRegattaLike { if (allCompetitorsCache == null) { final Set result = new HashSet<>(); final Set raceDefinitions = new HashSet<>(); - boolean hasRaceColumns = false; for (RaceColumn rc : provider.getRaceColumns()) { - hasRaceColumns = true; final Pair, Iterable> allCompetitorsInRaceColumnWithRaceDefinitionsConsidered = rc.getAllCompetitorsWithRaceDefinitionsConsidered(); Util.addAll(allCompetitorsInRaceColumnWithRaceDefinitionsConsidered.getB(), result); Util.addAll(allCompetitorsInRaceColumnWithRaceDefinitionsConsidered.getA(), raceDefinitions); @@ -192,12 +190,10 @@ public class CompetitorProviderFromRaceColumnsAndRegattaLike { } } final RegattaLog regattaLog = provider.getRegattaLike().getRegattaLog(); - if (!hasRaceColumns) { - // If no race exists, the regatta log-provided competitor registrations will not have - // been considered yet; add them: - final Set regattaLogProvidedCompetitors = new CompetitorsInLogAnalyzer<>(regattaLog).analyze(); - result.addAll(regattaLogProvidedCompetitors); - } + // If no race exists, the regatta log-provided competitor registrations will not have + // been considered yet; add them: + final Set regattaLogProvidedCompetitors = new CompetitorsInLogAnalyzer<>(regattaLog).analyze(); + result.addAll(regattaLogProvidedCompetitors); // else, don't add regatta log competitors because they have been added in each column already. // The competitors are collected from the races. Those, however, will be the regatta log // competitors if the race does not define its own. diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DelegatingRegattaLeaderboardWithCompetitorElimination.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DelegatingRegattaLeaderboardWithCompetitorElimination.java index da4d65a6790..f4da263926c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DelegatingRegattaLeaderboardWithCompetitorElimination.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/DelegatingRegattaLeaderboardWithCompetitorElimination.java @@ -280,7 +280,7 @@ public class DelegatingRegattaLeaderboardWithCompetitorElimination extends Abstr return getFullLeaderboard().getMaxPointsReason(competitor, race, timePoint); } - public Double getNetPoints(Competitor competitor, RaceColumn race, TimePoint timePoint) throws NoWindException { + public Double getNetPoints(Competitor competitor, RaceColumn race, TimePoint timePoint) { return getFullLeaderboard().getNetPoints(competitor, race, timePoint); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPoint.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPoint.java index 26ad84bdc50..3c194fadd22 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPoint.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPoint.java @@ -64,7 +64,7 @@ public class HighPoint extends AbstractScoringSchemeImpl { @Override public Double getPenaltyScore(RaceColumn raceColumn, Competitor competitor, MaxPointsReason maxPointsReason, Integer numberOfCompetitorsInRace, - NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) { + NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint, Leaderboard leaderboard) { return 0.0; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10LastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10LastBreaksTie.java index c6316f28971..81bff24d097 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10LastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets10LastBreaksTie.java @@ -5,6 +5,7 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; @@ -21,7 +22,7 @@ public class HighPointFirstGets10LastBreaksTie extends HighPointFirstGetsFixedSc } @Override - public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint) { + public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard) { return 0; } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets12Or8AndLastBreaksTie2017.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets12Or8AndLastBreaksTie2017.java index 9a841c738c3..dd5b0fb8665 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets12Or8AndLastBreaksTie2017.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets12Or8AndLastBreaksTie2017.java @@ -5,6 +5,7 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; import com.sap.sse.common.TimePoint; @@ -36,7 +37,7 @@ public class HighPointFirstGets12Or8AndLastBreaksTie2017 extends AbstractHighPoi @Override public Double getPenaltyScore(RaceColumn raceColumn, Competitor competitor, MaxPointsReason maxPointsReason, - Integer numberOfCompetitorsInRace, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) { + Integer numberOfCompetitorsInRace, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint, Leaderboard leaderboard) { Double result; /** * 1. Get all competitors without a max point reason. diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets1LastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets1LastBreaksTie.java index fb7b38a22d8..ae12da23843 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets1LastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointFirstGets1LastBreaksTie.java @@ -1,20 +1,45 @@ package com.sap.sailing.domain.leaderboard.impl; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Set; import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.util.graph.DirectedEdge; +import com.sap.sse.util.graph.DirectedGraph; +import com.sap.sse.util.topologicalordering.TopologicalComparator; +/** + * The special thing about this scoring scheme is its tie-breaking rule. If two competitors have equal score, + * instead of doing what sometimes is referred to as a "count-back" (sorting races from best to worst score, + * then comparing to same sort order of other competitor and looking for first difference) this scheme + * uses a graph whose edges consist of the "won over in same race" relation where competitors matched + * directly. A topological order will be tried to compute, but cycles may exist (A wins against B, B wins against + * C, C wins against A). In this case, competitors on "strongly-connected components" (node sets in the graph + * in which each node is reachable from each other node in that set) will be ordered by their maximum distance + * from a root node. + * + * @author Axel Uhl (d043530) + * + */ public class HighPointFirstGets1LastBreaksTie extends HighPointFirstGetsFixedScore { private static final long serialVersionUID = 1L; public HighPointFirstGets1LastBreaksTie() { - super(1.0); + super(/* score for race winner */ 1.0, /* minimum score from rank */ 0.0); } @Override @@ -24,12 +49,103 @@ public class HighPointFirstGets1LastBreaksTie extends HighPointFirstGetsFixedSco @Override public Double getPenaltyScore(RaceColumn raceColumn, Competitor competitor, MaxPointsReason maxPointsReason, Integer numberOfCompetitorsInRace, - NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) { + NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint, Leaderboard leaderboard) { return -1.0; } @Override - public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint) { + public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, + List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard) { + // Construct a graph in which all competitors with equal points are represented as nodes; + // edges represent a RaceColumn/Fleet where the "from" node in the edge has won over the "to" + // node in the edge. This graph will be sorted such that competitors precede those over which + // they won, except if there is a cycle in the graph in which case competitors on the cycle + // will be ranked equal by this method, leaving the decision to other criteria + final Double o1NetPoints = leaderboard.getNetPoints(o1, timePoint); + final Double o2NetPoints = leaderboard.getNetPoints(o2, timePoint); + assert Math.abs(o1NetPoints-o2NetPoints) < 0.00001; + final Set nodesInGraph = new HashSet<>(); + nodesInGraph.add(o1); + nodesInGraph.add(o2); + for (final Competitor c : leaderboard.getCompetitors()) { + if (leaderboard.getNetPoints(c, timePoint).equals(o1NetPoints)) { + nodesInGraph.add(c); + } + } + final Set> edges = constructEdges(nodesInGraph, leaderboard, timePoint, nullScoresAreBetter); + final DirectedGraph graph = DirectedGraph.create(nodesInGraph, edges); + final TopologicalComparator comparator = new TopologicalComparator<>(graph); + final int resultBasedOnGraphSort = comparator.compare(o1, o2); + // if o1 and o2 competed more than once in one race with contradicting outcomes (o1 better than o2 in at + // least one race, and o2 better than o1 in at least one race) and are not part of a bigger cycle, + // decide based on direct match score, breaking tie by last direct match between them: + final int result; + if (resultBasedOnGraphSort == 0 && graph.areOnSameCycleCluster(o1, o2) && graph.getCycleCluster(o1).getClusterNodes().size() == 2) { + result = compareByDirectComparison(o1Scores, o1, o2Scores, o2, nullScoresAreBetter, timePoint); + } else { + result = resultBasedOnGraphSort; + } + return result; + } + + private int compareByDirectComparison(List> o1Scores, Competitor o1, + List> o2Scores, Competitor o2, boolean nullScoresAreBetter, TimePoint timePoint) { + int lastDirectComparison = 0; + final Comparator pureScoreComparator = getScoreComparator(nullScoresAreBetter); + Iterator> o1Iter = o1Scores.iterator(); + double o1DirectComparisonScore = 0.0; + double o2DirectComparisonScore = 0.0; + while (o1Iter.hasNext()) { + final Pair o1Result = o1Iter.next(); + Iterator> o2Iter = o2Scores.iterator(); + while (o2Iter.hasNext()) { + final Pair o2Result = o2Iter.next(); + if (o2Result.getA() == o1Result.getA() && + o2Result.getA().getFleetOfCompetitor(o2) == o1Result.getA().getFleetOfCompetitor(o1)) { + o1DirectComparisonScore += o1Result.getB(); + o2DirectComparisonScore += o2Result.getB(); + lastDirectComparison = pureScoreComparator.compare(o1Result.getB(), o2Result.getB()); + break; + } + } + } + return o1DirectComparisonScore == o2DirectComparisonScore ? lastDirectComparison : pureScoreComparator.compare(o1DirectComparisonScore, o2DirectComparisonScore); + } + + /** + * Finds all races ({@link RaceColumn}/{@link Fleet} combinations) where more than one competitor from + * {@code nodesInGraph} competed, and adds an edge to the result for each pair of competitors in that same + * race, leading from the better to the worse competitor. + */ + private Set> constructEdges(Set nodesInGraph, Leaderboard leaderboard, TimePoint timePoint, + boolean nullScoresAreBetter) { + final Comparator pureScoreComparator = getScoreComparator(nullScoresAreBetter); + final Set> edges = new HashSet<>(); + for (final RaceColumn raceColumn : leaderboard.getRaceColumns()) { + for (final Fleet fleet : raceColumn.getFleets()) { + final Iterable competitorsInRace = leaderboard.getCompetitors(raceColumn, fleet); + final Set equalRankedCompetitorsInRace = new HashSet<>(); + Util.addAll(competitorsInRace, equalRankedCompetitorsInRace); + equalRankedCompetitorsInRace.retainAll(nodesInGraph); + final List competitorsInRaceOrderedByScoreInRace = new ArrayList<>(equalRankedCompetitorsInRace); + Collections.sort(competitorsInRaceOrderedByScoreInRace, (c1, c2) -> { + return pureScoreComparator.compare(leaderboard.getTotalPoints(c1, raceColumn, timePoint), + leaderboard.getTotalPoints(c2, raceColumn, timePoint)); + }); + for (int i=0; i> o1ScoresIncludingDiscarded, + List> o2ScoresIncludingDiscarded, boolean nullScoresAreBetter, Competitor o1, + Competitor o2) { return 0; } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointLastBreaksTie.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointLastBreaksTie.java index 89e409b1045..59d5ea4d725 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointLastBreaksTie.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/HighPointLastBreaksTie.java @@ -5,6 +5,7 @@ import java.util.List; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; @@ -20,7 +21,7 @@ public class HighPointLastBreaksTie extends HighPoint { private static final long serialVersionUID = -5338636946886101669L; @Override - public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint) { + public int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard) { return 0; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java index 634991e5c62..b6e1e2af35c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LeaderboardTotalRankComparator.java @@ -12,8 +12,8 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.RaceColumnInSeries; +import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Series; -import com.sap.sailing.domain.common.NoWindError; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.ScoringScheme; @@ -212,15 +212,11 @@ public class LeaderboardTotalRankComparator implements Comparator { result = compareByBetterScore(o1, Collections.unmodifiableList(o1Scores), o2, Collections.unmodifiableList(o2Scores), timePoint); if (result == 0) { // compare by last race: - result = scoringScheme.compareByLastRace(o1TotalPoints, o2TotalPoints, nullScoresAreBetter); + result = scoringScheme.compareByLastRace(o1TotalPoints, o2TotalPoints, nullScoresAreBetter, o1, o2); if (result == 0) { - try { - result = scoringScheme.compareByLatestRegattaInMetaLeaderboard(getLeaderboard(), o1, o2, timePoint); - if (result == 0) { - result = compareByArbitraryButStableCriteria(o1, o2); - } - } catch (NoWindException e) { - throw new NoWindError(e); + result = scoringScheme.compareByLatestRegattaInMetaLeaderboard(getLeaderboard(), o1, o2, timePoint); + if (result == 0) { + result = compareByArbitraryButStableCriteria(o1, o2); } } } @@ -412,11 +408,11 @@ public class LeaderboardTotalRankComparator implements Comparator { /** * Assuming both competitors scored in the same number of races, and assuming they scored the same net score, * break the tie according to the {@link #scoringScheme scoring scheme} set for this comparator. - * @see ScoringScheme#compareByBetterScore(Competitor, List, Competitor, List, boolean, TimePoint) + * @see ScoringScheme#compareByBetterScore(Competitor, List, Competitor, List, boolean, TimePoint, Leaderboard) */ protected int compareByBetterScore(Competitor o1, List> o1Scores, Competitor o2, List> o2Scores, TimePoint timePoint) { - return scoringScheme.compareByBetterScore(o1, o1Scores, o2, o2Scores, nullScoresAreBetter, timePoint); + return scoringScheme.compareByBetterScore(o1, o1Scores, o2, o2Scores, nullScoresAreBetter, timePoint, leaderboard); } /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPoint.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPoint.java index 5d662917b64..8896e1dd99a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPoint.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPoint.java @@ -57,7 +57,7 @@ public class LowPoint extends AbstractScoringSchemeImpl { @Override public Double getPenaltyScore(RaceColumn raceColumn, Competitor competitor, MaxPointsReason maxPointsReason, - Integer numberOfCompetitorsInRace, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint) { + Integer numberOfCompetitorsInRace, NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint, Leaderboard leaderboard) { Double result; if (numberOfCompetitorsInRace == null || raceColumn.hasSplitFleetContiguousScoring()) { result = (double) (numberOfCompetitorsInLeaderboardFetcher.getNumberOfCompetitorsInLeaderboard()+1); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointForLeagueOverallLeaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointForLeagueOverallLeaderboard.java index 5b7638a3e75..1f99e5b47f0 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointForLeagueOverallLeaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointForLeagueOverallLeaderboard.java @@ -10,6 +10,7 @@ import java.util.stream.Stream; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.meta.MetaLeaderboardColumn; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util.Pair; @@ -40,7 +41,7 @@ public class LowPointForLeagueOverallLeaderboard extends LowPoint { */ @Override public int compareByBetterScore(Competitor o1, List> o1Scores, - Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint) { + Competitor o2, List> o2Scores, boolean nullScoresAreBetter, TimePoint timePoint, Leaderboard leaderboard) { final Double o1ScoreSum = o1Scores.stream().collect(Collectors.summingDouble(rcAndScore->rcAndScore.getB())); final Double o2ScoreSum = o2Scores.stream().collect(Collectors.summingDouble(rcAndScore->rcAndScore.getB())); assert Math.abs(o1ScoreSum - o2ScoreSum) < 0.00001; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointWithAutomaticRDG.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointWithAutomaticRDG.java new file mode 100755 index 00000000000..0884eb6dbb9 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/LowPointWithAutomaticRDG.java @@ -0,0 +1,56 @@ +package com.sap.sailing.domain.leaderboard.impl; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.RaceColumn; +import com.sap.sailing.domain.common.MaxPointsReason; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.leaderboard.NumberOfCompetitorsInLeaderboardFetcher; +import com.sap.sse.common.TimePoint; + +public class LowPointWithAutomaticRDG extends LowPoint { + private static final long serialVersionUID = -6609749761795712461L; + + /** + * The penalty score will be that calculated by the default in {@link LowPoint}, except for + * {@link MaxPointsReason#RDG RDG} where all columns where the {@code competitor} has a + * non-{@link MaxPointsReason#RDG RDG} score are averaged (including those discarded) to figure out the score to be + * assigned to the {@link MaxPointsReason#RDG RDG} redress. + */ + @Override + public Double getPenaltyScore(RaceColumn raceColumn, Competitor competitor, MaxPointsReason maxPointsReason, + Integer numberOfCompetitorsInRace, + NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, TimePoint timePoint, + Leaderboard leaderboard) { + final Double result; + if (maxPointsReason == MaxPointsReason.RDG) { + double scoreSum = 0; + int numberOfScores = 0; + for (final RaceColumn rc : leaderboard.getRaceColumns()) { + if (rc != raceColumn && leaderboard.getMaxPointsReason(competitor, rc, timePoint) != MaxPointsReason.RDG) { + final Double totalPointsInRC = leaderboard.getTotalPoints(competitor, rc, timePoint); + if (totalPointsInRC != null) { + scoreSum += totalPointsInRC; + numberOfScores++; + } + } + } + if (numberOfScores == 0) { + // if only RDG races exist, leave null + result = null; + } else { + result = scoreSum / (double) numberOfScores; + } + } else { + result = super.getPenaltyScore(raceColumn, competitor, maxPointsReason, numberOfCompetitorsInRace, + numberOfCompetitorsInLeaderboardFetcher, timePoint, leaderboard); + } + return result; + } + + @Override + public ScoringSchemeType getType() { + return ScoringSchemeType.LOW_POINT_WITH_AUTOMATIC_RDG; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java index 420a48ba0da..3046d3a5d9e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/impl/ScoreCorrectionImpl.java @@ -370,7 +370,7 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { */ @Override public Result getCorrectedScore(final Callable trackedRankProvider, final Competitor competitor, - final RaceColumn raceColumn, final TimePoint timePoint, + final RaceColumn raceColumn, Leaderboard leaderboard, final TimePoint timePoint, final NumberOfCompetitorsInLeaderboardFetcher numberOfCompetitorsInLeaderboardFetcher, final ScoringScheme scoringScheme) { Double result; @@ -390,7 +390,7 @@ public class ScoreCorrectionImpl implements SettableScoreCorrection { if (correctedNonMaxedScore == null) { result = scoringScheme.getPenaltyScore(raceColumn, competitor, maxPointsReason.getMaxPointsReason(), getNumberOfCompetitorsInRace(raceColumn, competitor, numberOfCompetitorsInLeaderboardFetcher), - numberOfCompetitorsInLeaderboardFetcher, timePoint); + numberOfCompetitorsInLeaderboardFetcher, timePoint, leaderboard); } else { result = correctedNonMaxedScore; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/AbstractMetaLeaderboard.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/AbstractMetaLeaderboard.java index 293176a8147..1609478961c 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/AbstractMetaLeaderboard.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/leaderboard/meta/AbstractMetaLeaderboard.java @@ -171,9 +171,16 @@ public abstract class AbstractMetaLeaderboard extends AbstractSimpleLeaderboardI @Override public int getTrackedRank(Competitor competitor, RaceColumn race, TimePoint timePoint) { - final List competitorsFromBestToWorst = ((MetaLeaderboardColumn) race).getLeaderboard().getCompetitorsFromBestToWorst(timePoint); - Util.removeAll(getSuppressedCompetitors(), competitorsFromBestToWorst); - return competitorsFromBestToWorst.indexOf(competitor)+1; + final Leaderboard leaderboard = ((MetaLeaderboardColumn) race).getLeaderboard(); + final int result; + if (leaderboard.hasScores(competitor, timePoint)) { + final List competitorsFromBestToWorst = leaderboard.getCompetitorsFromBestToWorst(timePoint); + Util.removeAll(getSuppressedCompetitors(), competitorsFromBestToWorst); + result = competitorsFromBestToWorst.indexOf(competitor)+1; + } else { + result = 0; + } + return result; } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/TopLevelMasterData.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/TopLevelMasterData.java index 1a16c789ebc..b921d9e345c 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/TopLevelMasterData.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/masterdataimport/TopLevelMasterData.java @@ -23,6 +23,7 @@ import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.configuration.DeviceConfiguration; import com.sap.sailing.domain.base.configuration.DeviceConfigurationMatcher; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaIdentifier; @@ -33,7 +34,6 @@ import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.leaderboard.LeaderboardGroup; import com.sap.sailing.domain.leaderboard.RegattaLeaderboard; import com.sap.sailing.domain.racelog.tracking.SensorFixStore; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.Timed; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/impl/GPSFixStoreImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/impl/GPSFixStoreImpl.java index a6c443554be..c16c669fe70 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/impl/GPSFixStoreImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/impl/GPSFixStoreImpl.java @@ -9,12 +9,12 @@ import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.Regatt import com.sap.sailing.domain.abstractlog.regatta.tracking.analyzing.impl.RegattaLogDeviceMarkMappingFinder; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.racelog.tracking.SensorFixStore; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceMapping; import com.sap.sailing.domain.racelogtracking.DeviceMappingWithRegattaLogEvent; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptyGPSFixStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptyGPSFixStore.java index c91cf28897b..58a500bbcb1 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptyGPSFixStore.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptyGPSFixStore.java @@ -6,10 +6,10 @@ import java.util.function.Consumer; import com.sap.sailing.domain.abstractlog.regatta.RegattaLog; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceMapping; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptySensorFixStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptySensorFixStore.java index cfe56fca9bd..9601bc0022c 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptySensorFixStore.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/EmptySensorFixStore.java @@ -4,8 +4,8 @@ import java.util.Map; import java.util.function.BooleanSupplier; import java.util.function.Consumer; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimeRange; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/FixReceivedListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/FixReceivedListener.java index bebc25035ca..fdf017df4ef 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/FixReceivedListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/FixReceivedListener.java @@ -1,6 +1,6 @@ package com.sap.sailing.domain.racelog.tracking; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sse.common.Timed; /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixStore.java index 61c887340ab..b83eabef87f 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixStore.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/GPSFixStore.java @@ -8,10 +8,10 @@ import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompeti import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceMarkMappingEvent; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.racelogtracking.DeviceMapping; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/SensorFixStore.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/SensorFixStore.java index c66ed7b0a4e..e3e348c7b1c 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/SensorFixStore.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/SensorFixStore.java @@ -4,9 +4,9 @@ import java.util.Map; import java.util.function.BooleanSupplier; import java.util.function.Consumer; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.common.tracking.GPSFix; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimeRange; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/SensorFixStoreSupplier.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/SensorFixStoreSupplier.java new file mode 100755 index 00000000000..30c26c0e91d --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/racelog/tracking/SensorFixStoreSupplier.java @@ -0,0 +1,13 @@ +package com.sap.sailing.domain.racelog.tracking; + +/** + * Some object that can supply a {@link SensorFixStore} can be registered with the OSGi + * registry using this interface, so it can be discovered by others, and the sensor fix + * store can then be obtained. + * + * @author Axel Uhl (d043530) + * + */ +public interface SensorFixStoreSupplier { + SensorFixStore getSensorFixStore(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackfiles/TrackFileImportDeviceIdentifier.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackfiles/TrackFileImportDeviceIdentifier.java index 9df153ea47f..c8dac9b7bfe 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackfiles/TrackFileImportDeviceIdentifier.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackfiles/TrackFileImportDeviceIdentifier.java @@ -2,7 +2,7 @@ package com.sap.sailing.domain.trackfiles; import java.util.UUID; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sse.common.TimePoint; /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackfiles/TrackFileImportDeviceIdentifierImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackfiles/TrackFileImportDeviceIdentifierImpl.java index befb425a903..b925ab5c7a5 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackfiles/TrackFileImportDeviceIdentifierImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackfiles/TrackFileImportDeviceIdentifierImpl.java @@ -5,8 +5,8 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sse.common.TimePoint; import com.sap.sse.common.impl.MillisecondsTimePoint; diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/BaseDoubleVectorFixImporter.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/BaseDoubleVectorFixImporter.java new file mode 100644 index 00000000000..d7f46670a47 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/BaseDoubleVectorFixImporter.java @@ -0,0 +1,46 @@ +package com.sap.sailing.domain.trackimport; + +import java.io.IOException; +import java.io.InputStream; + +import com.sap.sailing.domain.common.tracking.DoubleVectorFix; +import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; + +/** + * Definition of importers used by SensorDataImportServlet to do the import of a specific file type. + */ +public interface BaseDoubleVectorFixImporter { + public interface Callback { + void addFixes(Iterable fixes, TrackFileImportDeviceIdentifier device); + } + + /** + * Parses the uploaded file and calls the callback for every fix that's contained in the input. + * + * @param inputStream + * the uploaded file as {@link InputStream} + * @param callback + * the callback to call for every new fix + * @param sourceName + * the uploaded file's name. This can be used to identify the file type if the importer can import + * different formats. + * @param downsample + * whether or not the importer shall reduce the sampling frequency during import; if {@code false}, all + * fixes read from the {@code inputStream} will be forwarded to the {@code callback}; otherwise, multiple + * fixes may be averaged, smoothened and then forwarded as only one combined fix to the {@code callback}. + * @throws FormatNotSupportedException + * if the uploaded file can't be parsed by the importer + * @throws IOException + * if there is a problem while reading the file + */ + void importFixes(InputStream inputStream, Callback callback, String filename, String sourceName, boolean downsample) + throws FormatNotSupportedException, IOException; + + /** + * The type is a String that's presented to the user for selection of the type when doing an import. + * The type is used on import to identify the importer to use depending on the user's selection. + * + * @return name of the file type to be imported by the importer + */ + String getType(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/DoubleVectorFixImporter.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/DoubleVectorFixImporter.java old mode 100644 new mode 100755 index 0e1e75cdf6f..a4327cdb51b --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/DoubleVectorFixImporter.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/DoubleVectorFixImporter.java @@ -1,58 +1,22 @@ package com.sap.sailing.domain.trackimport; -import java.io.IOException; -import java.io.InputStream; import java.io.Serializable; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; import com.sap.sailing.domain.abstractlog.regatta.RegattaLogEvent; import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompetitorSensorDataMappingEvent; import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.common.tracking.DoubleVectorFix; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; -import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sse.common.TimePoint; /** * Definition of importers used by SensorDataImportServlet to do the import of a specific file type. */ -public interface DoubleVectorFixImporter { - - interface Callback { - void addFixes(Iterable fix, TrackFileImportDeviceIdentifier device); - } - - /** - * Parses the uploaded file and calls the callback for every fix that's contained in the input. - * - * @param inputStream - * the uploaded file as {@link InputStream} - * @param callback - * the callback to call for every new fix - * @param sourceName - * the uploaded file's name. This can be used to identify the file type if the importer can import - * different formats. - * @throws FormatNotSupportedException - * if the uploaded file can't be parsed by the importer - * @throws IOException - * if there is a problem while reading the file - */ - void importFixes(InputStream inputStream, Callback callback, String filename, String sourceName) - throws FormatNotSupportedException, IOException; - - /** - * The type is a String that's presented to the user for selection of the type when doing an import. - * The type is used on import to identify the importer to use depending on the user's selection. - * - * @return name of the file type to be imported by the importer - */ - String getType(); - +public interface DoubleVectorFixImporter extends BaseDoubleVectorFixImporter { /** * Creates the {@link RegattaLogEvent} for the DeviceMapping. */ RegattaLogDeviceCompetitorSensorDataMappingEvent createEvent(TimePoint createdAt, TimePoint logicalTimePoint, AbstractLogEventAuthor author, Serializable id, Competitor mappedTo, DeviceIdentifier device, TimePoint from, TimePoint to); - } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/GPSFixImporter.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/GPSFixImporter.java index d014f682a50..b8174ec0729 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/GPSFixImporter.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/trackimport/GPSFixImporter.java @@ -4,8 +4,8 @@ import java.io.IOException; import java.io.InputStream; import java.util.Map; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.tracking.GPSFix; -import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier; /** diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/AbstractRaceTrackerBaseImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/AbstractRaceTrackerBaseImpl.java index fd00251cf59..675d032a34c 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/AbstractRaceTrackerBaseImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/AbstractRaceTrackerBaseImpl.java @@ -29,17 +29,32 @@ public abstract class AbstractRaceTrackerBaseImpl implements RaceTracker { */ @Override public final void stop(boolean preemptive) throws MalformedURLException, IOException, InterruptedException { + this.stop(preemptive, /* willBeRemoved */ false); + } + + /** + * Ensure stop method does notify all listeners after tracker stopped. If the race will be removed afterwards + * by the caller, the caller should pass {@code true} for the {@code willBeRemoved} parameter. This will, in + * particular, avoid that unnecessary re-calculations will be triggered. + */ + @Override + public final void stop(boolean preemptive, boolean willBeRemoved) throws MalformedURLException, IOException, InterruptedException { try { - listeners.onTrackerWillStop(preemptive); + listeners.onTrackerWillStop(preemptive, willBeRemoved); } finally { - onStop(preemptive); + onStop(preemptive, willBeRemoved); } } /** * Template stop method for subclasses. + * + * @param willBeRemoved + * If the race will be removed afterwards by the caller, the caller should pass {@code true} for the + * {@code willBeRemoved} parameter. This will, in particular, avoid that unnecessary re-calculations will + * be triggered. */ - protected void onStop(boolean preemptive) throws MalformedURLException, IOException, InterruptedException { + protected void onStop(boolean preemptive, boolean willBeRemoved) throws MalformedURLException, IOException, InterruptedException { } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/AbstractTrackedRegattaAndRaceObserver.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/AbstractTrackedRegattaAndRaceObserver.java index b4a6ba5f529..cc4701ef657 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/AbstractTrackedRegattaAndRaceObserver.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/AbstractTrackedRegattaAndRaceObserver.java @@ -97,7 +97,7 @@ public abstract class AbstractTrackedRegattaAndRaceObserver implements TrackedRe trackedRegatta.addRaceListener(raceListener); } - public void raceRemoved(TrackedRace trackedRace) { + public synchronized void raceRemoved(TrackedRace trackedRace) { remove(trackedRace.getRaceIdentifier()); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/BravoFixTrack.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/BravoFixTrack.java index ccf1b1c446f..78551932bfd 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/BravoFixTrack.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/BravoFixTrack.java @@ -2,11 +2,10 @@ package com.sap.sailing.domain.tracking; import java.io.Serializable; -import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.tracking.BravoExtendedFix; import com.sap.sailing.domain.common.tracking.BravoFix; -import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.WithID; @@ -72,5 +71,32 @@ public interface BravoFixTrack extends S /** * The distance sailed foiling during the time range provided */ - Distance getDistanceSpentFoiling(GPSFixTrack gpsFixTrack, TimePoint from, TimePoint to); + Distance getDistanceSpentFoiling(TimePoint from, TimePoint to); + + /** + * Returns {@code true}, if the track contains {@link BravoExtendedFix} instances instead of simple {@link BravoFix BravoFixes}. + * It is not guaranteed that the track exclusively contains {@link BravoExtendedFix BravoExtendedFixes} if this is {@code true}. + * It is up to the operator to ensure, that either normal Bravo data or the extended data is imported, but it isn't enforced. + * If both types of fixes are contained, data may seem to have "gaps" because single fixes returned do not provide the extended data. + */ + boolean hasExtendedFixes(); + + Double getPortDaggerboardRakeIfAvailable(TimePoint timePoint); + Double getStbdDaggerboardRakeStbdIfAvailable(TimePoint timePoint); + Double getPortRudderRakeIfAvailable(TimePoint timePoint); + Double getStbdRudderRakeIfAvailable(TimePoint timePoint); + Bearing getMastRotationIfAvailable(TimePoint timePoint); + Bearing getLeewayIfAvailable(TimePoint timePoint); + Double getSetIfAvailable(TimePoint timePoint); + Bearing getDriftIfAvailable(TimePoint timePoint); + Distance getDepthIfAvailable(TimePoint timePoint); + Bearing getRudderIfAvailable(TimePoint timePoint); + Double getForestayLoadIfAvailable(TimePoint timePoint); + Double getForestayPressureIfAvailable(TimePoint timePoint); + Bearing getTackAngleIfAvailable(TimePoint timePoint); + Bearing getRakeIfAvailable(TimePoint timePoint); + Double getDeflectorPercentageIfAvailable(TimePoint timePoint); + Bearing getTargetHeelIfAvailable(TimePoint timePoint); + Distance getDeflectorIfAvailable(TimePoint timePoint); + Double getTargetBoatspeedPIfAvailable(TimePoint timePoint); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java index b955bcfa34d..bbb0d07f7be 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java @@ -6,6 +6,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogFinishPositioningConfirmed import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseBase; import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.common.TrackedRaceStatusEnum; import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.racelog.Flags; @@ -198,11 +199,15 @@ public interface DynamicTrackedRace extends TrackedRace { * Updates the status of one {@link TrackingDataLoader}. This influences the overall status of the TrackedRace with the following rules: *
      *
    • The {@link TrackedRace} is initially in the state PREPARED
    • + *
    • If the {@code status} of the {@code source} loader is set to {@link TrackedRaceStatusEnum#REMOVED}, the {@link TrackedRace} + * will to to status {@link TrackedRaceStatusEnum#REMOVED}.
    • + *
    • Otherwise, if there are no loaders left, the {@link TrackedRace}'s status goes to {@link TrackedRaceStatusEnum#FINISHED}.
    • *
    • If any loader's state is ERROR, this will also be the case for the {@link TrackedRace}
    • *
    • Otherwise: If any loader's state is LOADING, the {@link TrackedRace} will also be in loading state with the progress being the average progress of all loaders (including those not being in loading state)
    • - *
    • Otherwise: If all laoders are in PREPARED state, this will also be the case for the {@link TrackedRace}
    • + *
    • Otherwise: If all loaders are in PREPARED state, this will also be the case for the {@link TrackedRace}
    • *
    • Otherwise: The {@link TrackedRace} is in TRACKING state
    • *
    + * Loaders in status {@link TrackedRaceStatusEnum#FINISHED} or {@link TrackedRaceStatusEnum#REMOVED} will be removed. * * @see TrackedRace#getStatus() */ diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/EmptyTrackedRegattaListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/EmptyTrackedRegattaListener.java deleted file mode 100644 index d1bc1d36783..00000000000 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/EmptyTrackedRegattaListener.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.sap.sailing.domain.tracking; - -/** - * Empty implementation of {@link TrackedRegattaListener} e.g. used by tests. - */ -public enum EmptyTrackedRegattaListener implements TrackedRegattaListener { - INSTANCE; - - @Override - public void regattaAdded(TrackedRegatta trackedRegatta) { - } - - @Override - public void regattaRemoved(TrackedRegatta trackedRegatta) { - } -} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java index ea7c0131755..a7e6a57fc45 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceChangeListener.java @@ -25,6 +25,8 @@ public interface RaceChangeListener extends CourseListener { void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack); + void firstGPSFixReceived(); + /** * Invoked after the mark passings have been updated in the {@link TrackedRace}. * diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTracker.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTracker.java index cc41a41de4e..218225eb7be 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTracker.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTracker.java @@ -52,6 +52,15 @@ public interface RaceTracker { */ void stop(boolean preemptive) throws MalformedURLException, IOException, InterruptedException; + + /** + * Like {@link #stop(boolean)}, only that with this method the caller can assert by setting {@code willBeRemoved} to + * {@code true} that the race will be removed and no longer be accessible to clients after stopping this tracker. + * This helps save computational efforts because calculations that would otherwise be triggered when loading a race + * completes no longer need to be triggered. + */ + void stop(boolean preemptive, boolean willBeRemoved) throws MalformedURLException, IOException, InterruptedException; + com.sap.sailing.domain.base.Regatta getRegatta(); /** @@ -86,8 +95,12 @@ public interface RaceTracker { * Tracker has stopped event, see {@link RaceTracker#stop(boolean)} method * * @param preemptive + * whether to stop ongoing loading jobs + * @param willBeRemoved + * if {@code true}, the race is about to be removed; hence, no need in resuming any caches or other + * (re-)calculation jobs */ - void onTrackerWillStop(boolean preemptive); + void onTrackerWillStop(boolean preemptive, boolean willBeRemoved); } @FunctionalInterface diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTrackerListeners.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTrackerListeners.java index ff9a95a6d30..ef9cc4c4c00 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTrackerListeners.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/RaceTrackerListeners.java @@ -6,7 +6,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /** * Utility class that handles listener registration and notification for {@link RaceTracker}. If the underlying * {@link RaceTracker} is already stopped, the listener will immediately be notified by - * {@link RaceTracker.Listener#onTrackerWillStop(boolean preemptive)} + * {@link RaceTracker.Listener#onTrackerWillStop(boolean preemptive, boolean willBeRemoved)} */ public class RaceTrackerListeners implements RaceTracker.Listener { private final HashSet registeredListeners = new HashSet<>(); @@ -27,13 +27,13 @@ public class RaceTrackerListeners implements RaceTracker.Listener { } @Override - public void onTrackerWillStop(boolean preemptive) { + public void onTrackerWillStop(boolean preemptive, boolean willBeRemoved) { Iterable listenersToInform; synchronized (this) { isStopped.set(true); isStoppedPreemptive.set(preemptive); listenersToInform = new HashSet<>(registeredListeners); } - listenersToInform.forEach(l -> l.onTrackerWillStop(preemptive)); + listenersToInform.forEach(l -> l.onTrackerWillStop(preemptive, willBeRemoved)); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLegOfCompetitor.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLegOfCompetitor.java index 8dea47da6b0..d2827bc0de0 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLegOfCompetitor.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLegOfCompetitor.java @@ -211,7 +211,7 @@ public interface TrackedLegOfCompetitor extends Serializable { * Same as {@link #getVelocityMadeGood(TimePoint, WindPositionMode)}, only that a cache for wind data and leg type and bearing * is passed. */ - Speed getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache); + SpeedWithBearing getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache); /** @@ -241,6 +241,11 @@ public interface TrackedLegOfCompetitor extends Serializable { Bearing getHeel(TimePoint at); Bearing getPitch(TimePoint at); + + Distance getDistanceFoiled(TimePoint at); + + Duration getDurationFoiled(TimePoint at); + /** * Computes the distance along the wind track to the wind-projected position of the race's overall leader. If leader * and competitor are in the same leg, this is simply the windward distance. If the leader is already one or more @@ -261,7 +266,6 @@ public interface TrackedLegOfCompetitor extends Serializable { /** * Same as {@link #getWindwardDistanceToCompetitorFarthestAhead(TimePoint, WindPositionMode, RankingInfo)}, only that a cache for leg type * calculation is passed. - * @param rankingInfo TODO */ Distance getWindwardDistanceToCompetitorFarthestAhead(TimePoint timePoint, WindPositionMode windPositionMode, RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java index 1b80394310b..cd2cddedfcc 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java @@ -46,6 +46,7 @@ import com.sap.sailing.domain.common.racelog.RacingProcedureType; import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.common.tracking.SensorFix; +import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache; import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator; import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException; import com.sap.sailing.domain.polars.PolarDataService; @@ -233,6 +234,11 @@ public interface TrackedRace extends Serializable, IsManagedByCachenull if there is none. */ > TrackT getSensorTrack(Competitor competitor, String trackName); + + /** + * Returns all contained {@link SensorFixTrack SensorFixTracks} for the given trackName and associated to any competitor. + */ + > Iterable getSensorTracks(String trackName); /** * Tells the leg on which the competitor was at time at. If the competitor hasn't passed @@ -606,7 +612,7 @@ public interface TrackedRace extends Serializable, IsManagedByCachenull if there are no mark passings for the competitor in this race * or if the competitor has not finished one of the legs in the race. @@ -618,6 +624,18 @@ public interface TrackedRace extends Serializable, IsManagedByCachenull if there are no mark passings for the competitor in this race + * or if the competitor has not finished one of the legs in the race. + */ + Distance getDistanceFoiled(Competitor competitor, TimePoint timePoint); + + /** + * @return null if there are no mark passings for the competitor in this race + * or if the competitor has not finished one of the legs in the race. + */ + Duration getDurationFoiled(Competitor competitor, TimePoint timePoint); + /** * See {@link TrackedLegOfCompetitor#getWindwardDistanceToCompetitorFarthestAhead(TimePoint, WindPositionMode, RankingInfo)} */ @@ -1007,4 +1025,51 @@ public interface TrackedRace extends Serializable, IsManagedByCache getAttachedRaceLogs(); + /** + * Computes the average speed over ground for a {@link Competitor} based on times and distances for all + * {@link TrackedLegOfCompetitor legs} the competitor {@link TrackedLegOfCompetitor#hasStartedLeg(TimePoint) has + * started} already at {@code timePoint}. + * + * @param timePoint + * time point up and until to compute the speed + */ + Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint); + + /** + * Computes the competitor's speed projected onto the wind (if wind data is available and the competitor is not + * between start and finish (not racing) or not on a {@link LegType#REACHING reaching} leg; if outside a race and no + * wind information is available, {@code null} is returned. Otherwise, the speed at {@code timePoint} is projected + * onto the course. The wind direction at the {@link WindPositionMode#EXACT exact} competitor position at + * {@code timePoint} is used for the calculation. + */ + default SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint) { + return getVelocityMadeGood(competitor, timePoint, new LeaderboardDTOCalculationReuseCache(timePoint)); + } + + /** + * Like {@link #getVelocityMadeGood(Competitor, TimePoint)}, but allowing callers to specify a {@link WindPositionMode} + * other than the default {@link WindPositionMode#EXACT}. If {@link WindPositionMode#LEG_MIDDLE} is used and the + * competitor is not currently sailing on a leg (hasn't started or has already finished), {@code null} is returned. + */ + default SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint, WindPositionMode windPositionMode) { + return getVelocityMadeGood(competitor, timePoint, windPositionMode, new LeaderboardDTOCalculationReuseCache(timePoint)); + } + + /** + * 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. + */ + default SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint, WindLegTypeAndLegBearingCache cache) { + return getVelocityMadeGood(competitor, timePoint, WindPositionMode.EXACT, cache); + } + + /** + * Like {@link #getVelocityMadeGood(Competitor, TimePoint)}, but allowing callers to specify a + * {@link WindPositionMode} other than the default {@link WindPositionMode#EXACT} as well as a cache that can + * accelerate requests for wind directions, the leg type and the competitor's current leg's bearing. If + * {@link WindPositionMode#LEG_MIDDLE} is used and the competitor is not currently sailing on a leg (hasn't started + * or has already finished), {@code null} is returned. + */ + SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint, WindPositionMode windPositionMode, + WindLegTypeAndLegBearingCache cache); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java index e180b4ae036..31dc80d27c6 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/AbstractRaceChangeListener.java @@ -147,4 +147,9 @@ public abstract class AbstractRaceChangeListener implements RaceChangeListener { public void raceLogDetached(RaceLog raceLog) { defaultAction(); } + + @Override + public void firstGPSFixReceived() { + defaultAction(); + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackImpl.java index dea2dfec800..bb6e27ca4c1 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/BravoFixTrackImpl.java @@ -1,19 +1,28 @@ package com.sap.sailing.domain.tracking.impl; +import java.io.IOException; +import java.io.ObjectInputStream; import java.io.Serializable; +import java.util.function.Function; -import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.confidence.impl.ScalableDouble; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.scalablevalue.impl.ScalableDistance; +import com.sap.sailing.domain.common.tracking.BravoExtendedFix; import com.sap.sailing.domain.common.tracking.BravoFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.BravoFixTrack; import com.sap.sailing.domain.tracking.DynamicBravoFixTrack; import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.GPSTrackListener; +import com.sap.sailing.domain.tracking.Track; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.WithID; +import com.sap.sse.common.scalablevalue.ScalableValue; /** * Specific {@link SensorFixTrackImpl} used for {@link BravoFix}es. @@ -22,7 +31,40 @@ import com.sap.sse.common.WithID; */ public class BravoFixTrackImpl extends SensorFixTrackImpl implements DynamicBravoFixTrack { - private static final long serialVersionUID = 5517848726456424386L; + private static final long serialVersionUID = 460944392510182976L; + + private final boolean hasExtendedFixes; + + private transient TimeRangeCache foilingTimeCache; + + private transient TimeRangeCache foilingDistanceCache; + + private transient TimeRangeCache averageRideHeightCache; + + /** + * If a GPS track was provided at construction time, remember it non-transiently. It is needed when restoring + * the object after de-serialization, so the cache invalidation listener can be re-established. + */ + private GPSFixTrack gpsTrack; + + private class CacheInvalidationGpsTrackListener implements GPSTrackListener { + private static final long serialVersionUID = 6395529765232404414L; + @Override + public boolean isTransient() { + return true; + } + + @Override + public void gpsFixReceived(GPSFixMoving fix, ItemType item, boolean firstFixInTrack) { + assert item == getTrackedItem(); + foilingDistanceCache.invalidateAllAtOrLaterThan(fix.getTimePoint()); + } + + @Override + public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, + long newMillisecondsOverWhichToAverage) { + } + } /** * @param trackedItem @@ -30,68 +72,74 @@ public class BravoFixTrackImpl extends S * @param trackName * the name of the track by which it can be obtained from the {@link TrackedRace}. */ - public BravoFixTrackImpl(ItemType trackedItem, String trackName) { + public BravoFixTrackImpl(ItemType trackedItem, String trackName, boolean hasExtendedFixes) { + this(trackedItem, trackName, hasExtendedFixes, /* listen to GPS track for distance cache invalidation */ null); + } + + /** + * @param gpsTrack + * if not {@code null}, this track will listen on that track for changes in order to invalidate this + * track's caches; for example, if a GPS fix is added, this track may need to invalidate its + * {@link #foilingDistanceCache} accordingly because the distance traveled between the fixes may have + * changed. + */ + public BravoFixTrackImpl(ItemType trackedItem, String trackName, boolean hasExtendedFixes, GPSFixTrack gpsTrack) { super(trackedItem, trackName, BravoFixTrack.TRACK_NAME + " for " + trackedItem); + this.hasExtendedFixes = hasExtendedFixes; + this.foilingTimeCache = createFoilingTimeCache(trackedItem); + this.foilingDistanceCache = createFoilingDistanceCache(trackedItem); + this.averageRideHeightCache = createAverageRideHeightCache(trackedItem); + this.gpsTrack = gpsTrack; + if (gpsTrack != null) { + gpsTrack.addListener(new CacheInvalidationGpsTrackListener()); + } + } + + private TimeRangeCache createAverageRideHeightCache(ItemType trackedItem) { + return createTimeRangeCache(trackedItem, "averageRideHeightCache"); + } + + private TimeRangeCache createTimeRangeCache(ItemType trackedItem, final String cacheName) { + return new TimeRangeCache<>(cacheName+" for "+trackedItem); + } + + private TimeRangeCache createFoilingDistanceCache(ItemType trackedItem) { + return createTimeRangeCache(trackedItem, "foilingDistanceCache"); + } + + private TimeRangeCache createFoilingTimeCache(ItemType trackedItem) { + return createTimeRangeCache(trackedItem, "foilingTimeCache"); + } + + /** + * After reading this object from an {@link ObjectInputStream}, initialize the caches properly. + */ + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + this.foilingTimeCache = createFoilingTimeCache(getTrackedItem()); + this.foilingDistanceCache = createFoilingDistanceCache(getTrackedItem()); + this.averageRideHeightCache = createAverageRideHeightCache(getTrackedItem()); + if (gpsTrack != null) { + gpsTrack.addListener(new CacheInvalidationGpsTrackListener()); + } } @Override public Distance getRideHeight(TimePoint timePoint) { - BravoFix fixAfter = getFirstFixAtOrAfter(timePoint); - if (fixAfter != null && fixAfter.getTimePoint().compareTo(timePoint) == 0) { - // exact match of timepoint -> no interpolation necessary - return fixAfter.getRideHeight(); - } - BravoFix fixBefore = getLastFixAtOrBefore(timePoint); - if (fixBefore != null && fixBefore.getTimePoint().compareTo(timePoint) == 0) { - // exact match of timepoint -> no interpolation necessary - return fixBefore.getRideHeight(); - } - if (fixAfter == null || fixBefore == null) { - // the fix is out of the TimeRange where we have fixes - return null; - } - // TODO interpolate if necessary - return fixBefore.getRideHeight(); + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoFix::getRideHeight, + ScalableDistance::new); } @Override public Bearing getHeel(TimePoint timePoint) { - BravoFix fixAfter = getFirstFixAtOrAfter(timePoint); - if (fixAfter != null && fixAfter.getTimePoint().compareTo(timePoint) == 0) { - // exact match of timepoint -> no interpolation necessary - return fixAfter.getHeel(); - } - BravoFix fixBefore = getLastFixAtOrBefore(timePoint); - if (fixBefore != null && fixBefore.getTimePoint().compareTo(timePoint) == 0) { - // exact match of timepoint -> no interpolation necessary - return fixBefore.getHeel(); - } - if (fixAfter == null || fixBefore == null) { - // the fix is out of the TimeRange where we have fixes - return null; - } - // TODO interpolate if necessary - return fixBefore.getHeel(); + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoFix::getHeel, + NaivelyScalableBearing::new); } @Override public Bearing getPitch(TimePoint timePoint) { - BravoFix fixAfter = getFirstFixAtOrAfter(timePoint); - if (fixAfter != null && fixAfter.getTimePoint().compareTo(timePoint) == 0) { - // exact match of timepoint -> no interpolation necessary - return fixAfter.getPitch(); - } - BravoFix fixBefore = getLastFixAtOrBefore(timePoint); - if (fixBefore != null && fixBefore.getTimePoint().compareTo(timePoint) == 0) { - // exact match of timepoint -> no interpolation necessary - return fixBefore.getPitch(); - } - if (fixAfter == null || fixBefore == null) { - // the fix is out of the TimeRange where we have fixes - return null; - } - // TODO interpolate if necessary - return fixBefore.getPitch(); + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoFix::getPitch, + NaivelyScalableBearing::new); } @Override @@ -100,26 +148,43 @@ public class BravoFixTrackImpl extends S return rideHeight != null && rideHeight.compareTo(BravoFix.MIN_FOILING_HEIGHT_THRESHOLD) >= 0; } + + @Override + public boolean add(BravoFix fix, boolean replace) { + final boolean added = super.add(fix, replace); + if (added) { + final TimePoint fixTimePoint = fix.getTimePoint(); + averageRideHeightCache.invalidateAllAtOrLaterThan(fixTimePoint); + foilingDistanceCache.invalidateAllAtOrLaterThan(fixTimePoint); + foilingTimeCache.invalidateAllAtOrLaterThan(fixTimePoint); + } + return added; + } + @Override public Distance getAverageRideHeight(TimePoint from, TimePoint to) { - final Distance result; - lockForRead(); - try { - Distance sum = Distance.NULL; - int count = 0; - for (final BravoFix fix : getFixes(from, true, to, true)) { - sum = sum.add(fix.getRideHeight()); - count++; + return getValueSum(from, to, /* nullElement */ Distance.NULL, Distance::add, averageRideHeightCache, + /* valueCalculator */ new Track.TimeRangeValueCalculator() { + @Override + public Distance calculate(TimePoint from, TimePoint to) { + Distance result; + Distance sum = Distance.NULL; + int count = 0; + for (final BravoFix fix : getFixes(from, true, to, true)) { + final Distance rideHeight = fix.getRideHeight(); + if (rideHeight != null) { + sum = sum.add(rideHeight); + count++; + } + } + if (count > 0) { + result = sum.scale(1./count); + } else { + result = null; + } + return result; } - if (count > 0) { - result = sum.scale(1./count); - } else { - result = null; - } - } finally { - unlockAfterRead(); - } - return result; + }); } private boolean isFoiling(BravoFix fix) { @@ -128,43 +193,232 @@ public class BravoFixTrackImpl extends S @Override public Duration getTimeSpentFoiling(TimePoint from, TimePoint to) { - Duration result = Duration.NULL; - lockForRead(); - try { - TimePoint last = from; - boolean isFoiling = false; - for (final BravoFix fix : getFixes(from, true, to, true)) { - final boolean fixFoils = isFoiling(fix); - if (isFoiling && fixFoils) { - result = result.plus(last.until(fix.getTimePoint())); + return getValueSum(from, to, /* nullElement */ Duration.NULL, Duration::plus, foilingTimeCache, + /* valueCalculator */ new Track.TimeRangeValueCalculator() { + @Override + public Duration calculate(TimePoint from, TimePoint to) { + Duration result = Duration.NULL; + TimePoint last = from; + boolean isFoiling = false; + for (final BravoFix fix : getFixes(from, true, to, true)) { + final boolean fixFoils = isFoiling(fix); + if (isFoiling && fixFoils) { + result = result.plus(last.until(fix.getTimePoint())); + } + last = fix.getTimePoint(); + isFoiling = fixFoils; } - last = fix.getTimePoint(); - isFoiling = fixFoils; + return result; } - } finally { - unlockAfterRead(); - } - return result; + }); } @Override - public Distance getDistanceSpentFoiling(GPSFixTrack gpsFixTrack, TimePoint from, TimePoint to) { - Distance result = Distance.NULL; - lockForRead(); - try { - TimePoint last = from; - boolean isFoiling = false; - for (final BravoFix fix : getFixes(from, true, to, true)) { - final boolean fixFoils = isFoiling(fix); - if (isFoiling && fixFoils) { - result = result.add(gpsFixTrack.getDistanceTraveled(last, fix.getTimePoint())); + public Distance getDistanceSpentFoiling(TimePoint from, TimePoint to) { + assert gpsTrack != null; + return getValueSum(from, to, /* nullElement */ Distance.NULL, Distance::add, foilingDistanceCache, + /* valueCalculator */ new Track.TimeRangeValueCalculator() { + @Override + public Distance calculate(TimePoint from, TimePoint to) { + Distance result = Distance.NULL; + TimePoint last = from; + boolean isFoiling = false; + for (final BravoFix fix : getFixes(from, true, to, true)) { + final boolean fixFoils = isFoiling(fix); + if (isFoiling && fixFoils) { + result = result.add(gpsTrack.getDistanceTraveled(last, fix.getTimePoint())); + } + last = fix.getTimePoint(); + isFoiling = fixFoils; } - last = fix.getTimePoint(); - isFoiling = fixFoils; + return result; } - } finally { - unlockAfterRead(); + }); + } + + @Override + public boolean hasExtendedFixes() { + return hasExtendedFixes; + } + + /** + * Generic implementation to get values from extended fixes. The implementation ensured that in case of simple + * {@link BravoFix} instances, just {@code null} is returned. If valid {@link BravoExtendedFix BravoExtendedFixes} + * are found, the provided getter is used to extract the value from the identified fix. + *

    + * + * In case of a mix of {@link BravoFix} and {@link BravoExtendedFix} instances, this method may return null values + * for specific {@link TimePoint TimePoints}. + *

    + * + * If the value extracted by the {@code getter} is {@code null}, the next fix will be probed, until no more fix + * exists in that direction or a fix is found that delivers a non-{@code null} value for the {@code getter} result. + * This way it is possible to skip fixes that don't make a statement with regard to the attribute extracted by the + * {@code getter}. + */ + private T getValueFromExtendedFixSkippingNullValues( + final TimePoint timePoint, final Function getter, + Function> converterToScalableValue) { + final com.sap.sse.common.Util.Function> converter = + fix -> { + @SuppressWarnings("unchecked") + final BravoFixType castFix = (BravoFixType) fix; + return converterToScalableValue.apply(getter.apply(castFix)); + }; + return getInterpolatedValue(timePoint, converter, fix->{ + @SuppressWarnings("unchecked") + final BravoFixType castFix = (BravoFixType) fix; + return getter.apply(castFix) != null; + }); + } + + public BravoExtendedFix getFirstFixAtOrAfterIfExtended(TimePoint timePoint) { + BravoFix fix = getFirstFixAtOrAfter(timePoint); + return fix instanceof BravoExtendedFix ? (BravoExtendedFix) fix : null; + } + + public BravoExtendedFix getLastFixAtOrBeforeIfExtended(TimePoint timePoint) { + BravoFix fix = getLastFixAtOrBefore(timePoint); + return fix instanceof BravoExtendedFix ? (BravoExtendedFix) fix : null; + } + + @Override + public Double getPortDaggerboardRakeIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getPortDaggerboardRake, + ScalableDouble::new); + } + + @Override + public Double getStbdDaggerboardRakeStbdIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getStbdDaggerboardRake, + ScalableDouble::new); + } + + @Override + public Double getPortRudderRakeIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getPortRudderRake, + ScalableDouble::new); + } + + @Override + public Double getStbdRudderRakeIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getStbdRudderRake, + ScalableDouble::new); + } + + @Override + public Bearing getMastRotationIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getMastRotation, + NaivelyScalableBearing::new); + } + + @Override + public Bearing getLeewayIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getLeeway, + NaivelyScalableBearing::new); + } + + @Override + public Double getSetIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getSet, + ScalableDouble::new); + } + + @Override + public Bearing getDriftIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getDrift, + NaivelyScalableBearing::new); + } + + @Override + public Distance getDepthIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getDepth, + ScalableDistance::new); + } + + @Override + public Bearing getRudderIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getRudder, + NaivelyScalableBearing::new); + } + + @Override + public Double getForestayLoadIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getForestayLoad, + ScalableDouble::new); + } + + @Override + public Double getForestayPressureIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getForestayPressure, + ScalableDouble::new); + } + + @Override + public Bearing getTackAngleIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getTackAngle, + NaivelyScalableBearing::new); + } + + @Override + public Bearing getRakeIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getRake, + NaivelyScalableBearing::new); + } + + @Override + public Double getDeflectorPercentageIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getDeflectorPercentage, + ScalableDouble::new); + } + + @Override + public Bearing getTargetHeelIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getTargetHeel, + NaivelyScalableBearing::new); + } + + @Override + public Distance getDeflectorIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getDeflector, + ScalableDistance::new); + } + + @Override + public Double getTargetBoatspeedPIfAvailable(TimePoint timePoint) { + return getValueFromExtendedFixSkippingNullValues(timePoint, BravoExtendedFix::getTargetBoatspeedP, + ScalableDouble::new); + } + + private static class NaivelyScalableBearing implements ScalableValue { + private final double deg; + + public NaivelyScalableBearing(Bearing b) { + deg = b.getDegrees(); + } + + private NaivelyScalableBearing(double deg) { + this.deg = deg; + } + + @Override + public ScalableValue multiply(double factor) { + return new NaivelyScalableBearing(deg*factor); + } + + @Override + public ScalableValue add(ScalableValue t) { + return new NaivelyScalableBearing(deg+t.getValue()); + } + + @Override + public Bearing divide(double divisor) { + return new DegreeBearingImpl(deg/divisor); + } + + @Override + public Double getValue() { + return deg; } - return result; } } \ No newline at end of file diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DistanceCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DistanceCache.java deleted file mode 100755 index e6e7d847632..00000000000 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DistanceCache.java +++ /dev/null @@ -1,237 +0,0 @@ -package com.sap.sailing.domain.tracking.impl; - -import java.util.Comparator; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map.Entry; -import java.util.NavigableSet; - -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.tracking.GPSFix; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; -import com.sap.sse.common.Util.Pair; -import com.sap.sse.concurrent.LockUtil; -import com.sap.sse.concurrent.NamedReentrantReadWriteLock; -import com.sap.sse.util.impl.ArrayListNavigableSet; - -/** - * Computing {@link #getDistanceTraveled(TimePoint, TimePoint)} is more expensive the longer the track is and the - * higher the tracking frequency. It is called regularly, usually for the same start time. Keeping to re-evaluate - * all distances between all GPS fixes, even if they remain constant, is a waste of resources, particularly when we - * assume that usually GPS fixes are appended to a track and not usually randomly inserted, even though this can - * happen. - *

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

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

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

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

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

    - * - * Once the {@link #getDistanceTraveled(TimePoint, TimePoint)} has computed its value, it adds the result to the - * cache. - * - * @author Axel Uhl (D043530) - */ -public class DistanceCache { - public static final int MAX_SIZE = 100; - - private final NavigableSet>>> distanceCache; - - /** - * The cache is to have limited size. Eviction shall happen based on a least-recently-used strategy. Usage is - * defined as having been returned by {@link #getEarliestFromAndDistanceAtOrAfterFrom(TimePoint, TimePoint)} or - * having been added by {@link #cache(TimePoint, TimePoint, Distance)}. - *

    - * - * When an eldest entry is asked to be expunged from this map and the map has more than {@link #MAX_SIZE} elements, - * the expunging will be admitted, and the entry is removed from the {@link #distanceCache} core structure. Reading - * and writing this structure must happen under the {@link #lock write lock} because also reading the linked hash - * map that counts access as "use" has a modifying effect on its internal structures. - *

    - * - * The key pairs are from/to pairs. Note that this is in some sense "the opposite direction" compared to the - * alignment of the {@link #distanceCache} structure which has as its outer keys the "to" time point.

    - * - * Read access is to be synchronized using this field's mutex; write access only happens under the - * {@link #lock write lock} and therefore will have no contenders. - */ - private final LinkedHashMap, Void> lruCache; - - private final NamedReentrantReadWriteLock lock; - - private static final Comparator> timePointInPairComparator = new Comparator>() { - @Override - public int compare(Util.Pair o1, Util.Pair o2) { - return o1.getA().compareTo(o2.getA()); - } - }; - - public DistanceCache(String nameForLockLogging) { - lock = new NamedReentrantReadWriteLock("lock for DistanceCache for "+nameForLockLogging, /* fair */ true); - this.distanceCache = new ArrayListNavigableSet>>>(timePointInPairComparator); - this.lruCache = new LinkedHashMap, Void>(/* initial capacity */ 10, /* load factor */ 0.75f, - /* access-based ordering */ true) { - private static final long serialVersionUID = -6568235517111733193L; - - @Override - protected boolean removeEldestEntry(Entry, Void> eldest) { - final boolean expunge = size() > MAX_SIZE; - if (expunge) { - removeCacheEntry(eldest.getKey().getA(), eldest.getKey().getB()); - } - return expunge; - } - }; - } - - public int size() { - return lruCache.size(); - } - - private void removeCacheEntry(TimePoint from, TimePoint to) { - assert lock.getWriteHoldCount() == 1; // we can be sure we are alone here; this only happens when adding a new entry, holding the write lock - Util.Pair>> entryForTo = distanceCache.floor(createDummy(to)); - if (entryForTo.getA().equals(to)) { - Pair entryForFrom = entryForTo.getB().ceiling(new Util.Pair(from, null)); - if (entryForFrom.getA().equals(from)) { - entryForTo.getB().remove(entryForFrom); - if (entryForTo.getB().isEmpty()) { - distanceCache.remove(entryForTo); - } - } - } - } - - /** - * Looks up the entry closest to but no later than to. If not found, null is returned. If - * found, the earliest pair of from/distance that is at or after from will be returned, together with - * the to value of the entry. If there is no entry that is at or after from, - * null is returned. - */ - public Util.Pair> getEarliestFromAndDistanceAtOrAfterFrom(TimePoint from, TimePoint to) { - LockUtil.lockForRead(lock); - try { - Util.Pair> result = null; - Util.Pair>> entryForTo = distanceCache.floor(createDummy(to)); - if (entryForTo != null) { - final Util.Pair fromCeiling = entryForTo.getB().ceiling(new Util.Pair(from, null)); - if (fromCeiling != null) { - result = new Util.Pair>(entryForTo.getA(), fromCeiling); - } - } - // no writer can be active because we're holding the read lock; read access on the lruCache is synchronized using - // the lruCache's mutex; this is necessary because we're using access-based LRU pinging where even getting an entry - // modifies the internal parts of the data structure which is not thread safe. - synchronized (lruCache) { - lruCache.get(new Util.Pair(from, to)); - } - return result; - } finally { - LockUtil.unlockAfterRead(lock); - } - } - - /** - * Removes all cache entries that have a to time point that is at or after timePoint. - */ - public void invalidateAllAtOrLaterThan(TimePoint timePoint) { - LockUtil.lockForWrite(lock); - try { - Util.Pair>> dummy = createDummy(timePoint); - NavigableSet>>> toRemove = distanceCache.tailSet(dummy, /* inclusive */ true); - for (Iterator>>> i=toRemove.iterator(); i.hasNext(); ) { - Util.Pair>> entryToRemove = i.next(); - assert entryToRemove.getA().compareTo(timePoint) >= 0; - for (Pair fromAndDistance : entryToRemove.getB()) { - lruCache.remove(new Util.Pair<>(fromAndDistance.getA(), entryToRemove.getA())); - } - i.remove(); - } - } finally { - LockUtil.unlockAfterWrite(lock); - } - } - - private NavigableSet> getEntryForTo(TimePoint to) { - NavigableSet> result = null; - Util.Pair>> dummyForTo = createDummy(to); - Util.Pair>> entryForTo = distanceCache.floor(dummyForTo); - if (entryForTo != null && entryForTo.getA().equals(to)) { - result = entryForTo.getB(); - } - return result; - } - - public void cache(TimePoint from, TimePoint to, Distance distance) { - LockUtil.lockForWrite(lock); - try { - NavigableSet> entryForTo = getEntryForTo(to); - if (entryForTo == null) { - entryForTo = new ArrayListNavigableSet>(timePointInPairComparator); - Util.Pair>> pairForTo = new Util.Pair>>( - to, entryForTo); - distanceCache.add(pairForTo); - } - entryForTo.add(new Util.Pair(from, distance)); - lruCache.put(new Util.Pair(from, to), null); - } finally { - LockUtil.unlockAfterWrite(lock); - } - } - - private Util.Pair>> createDummy(TimePoint to) { - return new Util.Pair>>(to, null); - } - - /** - * Removes all contents from this cache - */ - public void clear() { - LockUtil.lockForWrite(lock); - try { - distanceCache.clear(); - lruCache.clear(); - } finally { - LockUtil.unlockAfterWrite(lock); - } - - } -} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java old mode 100755 new mode 100644 index b77bfe4e49a..66312ec0b68 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java @@ -14,6 +14,7 @@ import java.util.List; import java.util.Map; import java.util.NavigableSet; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -105,6 +106,12 @@ DynamicTrackedRace, GPSTrackListener { * to be restored during at the time of de-serialization. */ private transient RaceStateChangedListener raceStateBasedStartTimeChangedListener; + + /** + * Flag that indicates if a {@link GPSFix} is already tracked in any {@link GPSFixTrack} for a competitor of this + * race. + */ + private final AtomicBoolean gpsFixReceived; public DynamicTrackedRaceImpl(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable sidelines, WindStore windStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, @@ -123,6 +130,8 @@ DynamicTrackedRace, GPSTrackListener { this.courseDesignChangedListeners = new HashSet<>(); this.startTimeChangedListeners = new HashSet<>(); this.raceAbortedListeners = new HashSet<>(); + + gpsFixReceived = new AtomicBoolean(false); this.raceIsKnownToStartUpwind = race.getBoatClass().typicallyStartsUpwind(); if (!raceIsKnownToStartUpwind) { Set windSourcesToExclude = new HashSet(); @@ -132,6 +141,7 @@ DynamicTrackedRace, GPSTrackListener { windSourcesToExclude.add(new WindSourceImpl(WindSourceType.COURSE_BASED)); setWindSourcesToExclude(windSourcesToExclude); } + for (Competitor competitor : getRace().getCompetitors()) { DynamicGPSFixTrack track = getTrack(competitor); track.addListener(this); @@ -139,6 +149,11 @@ DynamicTrackedRace, GPSTrackListener { // default wind tracks are observed because they are created by the superclass constructor using // createWindTrack which adds this object as a listener } + + @Override + public boolean hasGPSData() { + return gpsFixReceived.get(); + } /** * After de-serialization sets a valid {@link #listeners} collection which is transient and therefore @@ -220,29 +235,37 @@ DynamicTrackedRace, GPSTrackListener { @Override public void onStatusChanged(TrackingDataLoader source, TrackedRaceStatus newStatus) { - TrackedRaceStatusEnum raceStatus = TrackedRaceStatusEnum.FINISHED; + TrackedRaceStatusEnum raceStatus; double totalProgress = 1.0; - synchronized (loaderStatus) { - this.updateLoaderStatus(source, newStatus); - if (!loaderStatus.isEmpty()) { - double sumOfLoaderProgresses = 0.0; - boolean anyError = false; - boolean anyLoading = false; - boolean allPrepared = true; - raceStatus = TrackedRaceStatusEnum.TRACKING; - for (TrackedRaceStatus status : loaderStatus.values()) { - anyError |= (status.getStatus() == TrackedRaceStatusEnum.ERROR); - anyLoading |= (status.getStatus() == TrackedRaceStatusEnum.LOADING); - allPrepared &= (status.getStatus() == TrackedRaceStatusEnum.PREPARED); - sumOfLoaderProgresses += status.getLoadingProgress(); - } - if (anyError) { - raceStatus = TrackedRaceStatusEnum.ERROR; - } else if (anyLoading) { - raceStatus = TrackedRaceStatusEnum.LOADING; - totalProgress = sumOfLoaderProgresses / loaderStatus.size(); - } else { - raceStatus = allPrepared ? TrackedRaceStatusEnum.PREPARED : TrackedRaceStatusEnum.TRACKING; + if (newStatus.getStatus() == TrackedRaceStatusEnum.REMOVED) { + synchronized (loaderStatus) { + this.updateLoaderStatus(source, newStatus); + } + raceStatus = newStatus.getStatus(); + } else { + raceStatus = TrackedRaceStatusEnum.FINISHED; + synchronized (loaderStatus) { + this.updateLoaderStatus(source, newStatus); + if (!loaderStatus.isEmpty()) { + double sumOfLoaderProgresses = 0.0; + boolean anyError = false; + boolean anyLoading = false; + boolean allPrepared = true; + raceStatus = TrackedRaceStatusEnum.TRACKING; + for (TrackedRaceStatus status : loaderStatus.values()) { + anyError |= (status.getStatus() == TrackedRaceStatusEnum.ERROR); + anyLoading |= (status.getStatus() == TrackedRaceStatusEnum.LOADING); + allPrepared &= (status.getStatus() == TrackedRaceStatusEnum.PREPARED); + sumOfLoaderProgresses += status.getLoadingProgress(); + } + if (anyError) { + raceStatus = TrackedRaceStatusEnum.ERROR; + } else if (anyLoading) { + raceStatus = TrackedRaceStatusEnum.LOADING; + totalProgress = sumOfLoaderProgresses / loaderStatus.size(); + } else { + raceStatus = allPrepared ? TrackedRaceStatusEnum.PREPARED : TrackedRaceStatusEnum.TRACKING; + } } } } @@ -250,7 +273,7 @@ DynamicTrackedRace, GPSTrackListener { } private void updateLoaderStatus(TrackingDataLoader loader, TrackedRaceStatus status) { - if (status.getStatus() == TrackedRaceStatusEnum.FINISHED) { + if (status.getStatus() == TrackedRaceStatusEnum.FINISHED || status.getStatus() == TrackedRaceStatusEnum.REMOVED) { loaderStatus.remove(loader); } else { loaderStatus.put(loader, status); @@ -554,6 +577,10 @@ DynamicTrackedRace, GPSTrackListener { notifyListeners(listener -> listener.competitorPositionChanged(fix, competitor)); } + private void notifyListenersAboutFirstGPSFixReceived() { + notifyListeners(RaceChangeListener::firstGPSFixReceived); + } + private void notifyListeners(TrackedRaceStatus status, TrackedRaceStatus oldStatus) { notifyListeners(listener -> listener.statusChanged(status, oldStatus)); } @@ -996,6 +1023,12 @@ DynamicTrackedRace, GPSTrackListener { updated(fix.getTimePoint()); triggerManeuverCacheRecalculation(competitor); notifyListeners(fix, competitor); + + // getAndSet call is atomic which means, that it can be ensured that the listeners are notified only once + final boolean oldGPSFixReceived = gpsFixReceived.getAndSet(true); + if (!oldGPSFixReceived) { + notifyListenersAboutFirstGPSFixReceived(); + } } @Override diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java index 815eccfdbc4..b1b9077ef62 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java @@ -38,6 +38,7 @@ import com.sap.sailing.domain.common.tracking.WithValidityCache; import com.sap.sailing.domain.common.tracking.impl.CompactPositionHelper; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.GPSTrackListener; +import com.sap.sailing.domain.tracking.Track; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimeRange; @@ -116,7 +117,7 @@ public abstract class GPSFixTrackImpl extends * Once the {@link #getDistanceTraveled(TimePoint, TimePoint)} has computed its value, it adds the result to the * cache. */ - private transient DistanceCache distanceCache; + private transient TimeRangeCache distanceCache; private transient MaxSpeedCache maxSpeedCache; @@ -155,7 +156,7 @@ public abstract class GPSFixTrackImpl extends this.millisecondsOverWhichToAverage = millisecondsOverWhichToAverage; this.maxSpeedForSmoothing = maxSpeedForSmoothening; this.listeners = new TrackListenerCollection>(); - this.distanceCache = new DistanceCache(trackedItem==null?"null":trackedItem.toString()); + this.distanceCache = new TimeRangeCache<>(trackedItem==null?"null":trackedItem.toString()); this.maxSpeedCache = createMaxSpeedCache(); this.validityCachingSuspended = false; this.losslessCompaction = losslessCompaction; @@ -190,7 +191,7 @@ public abstract class GPSFixTrackImpl extends private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); - distanceCache = new DistanceCache(getTrackedItem().toString()); + distanceCache = new TimeRangeCache<>(getTrackedItem().toString()); maxSpeedCache = createMaxSpeedCache(); } @@ -506,76 +507,33 @@ public abstract class GPSFixTrackImpl extends @Override public Distance getDistanceTraveled(TimePoint from, TimePoint to) { - return getDistanceTraveledRecursively(from, to, 0); - } - - private Distance getDistanceTraveledRecursively(TimePoint from, TimePoint to, int recursionDepth) { - Distance result; - if (!from.before(to)) { - result = Distance.NULL; - } else { - boolean perfectCacheHit = false; - lockForRead(); - try { - Util.Pair> bestCacheEntry = getDistanceCache() - .getEarliestFromAndDistanceAtOrAfterFrom(from, to); - if (bestCacheEntry != null) { - perfectCacheHit = true; // potentially a cache hit; but if it doesn't span the full interval, it's not perfect; see below - // compute the missing stretches between best cache entry's "from" and our "from" and the cache - // entry's "to" and our "to" - Distance distanceFromFromToBeginningOfCacheEntry = Distance.NULL; - Distance distanceFromEndOfCacheEntryToTo = Distance.NULL; - if (!bestCacheEntry.getB().getA().equals(from)) { - assert bestCacheEntry.getB().getA().after(from); - perfectCacheHit = false; - distanceFromFromToBeginningOfCacheEntry = getDistanceTraveledRecursively(from, bestCacheEntry - .getB().getA(), recursionDepth + 1); - } - if (!bestCacheEntry.getA().equals(to)) { - assert bestCacheEntry.getA().before(to); - perfectCacheHit = false; - distanceFromEndOfCacheEntryToTo = getDistanceTraveledRecursively(bestCacheEntry.getA(), to, - recursionDepth + 1); - } - result = distanceFromFromToBeginningOfCacheEntry.add(bestCacheEntry.getB().getB()).add( - distanceFromEndOfCacheEntryToTo); - } else { - double distanceInNauticalMiles = 0; - if (from.compareTo(to) < 0) { + return getValueSum(from, to, /* nullElement */ Distance.NULL, Distance::add, getDistanceCache(), + /* valueCalculator */ new Track.TimeRangeValueCalculator() { + @Override + public Distance calculate(TimePoint from, TimePoint to) { // getEstimatedPosition's current implementation returns a position equal to that of a fix at // "from" if there is one with exactly that time stamp + Distance distance = Distance.NULL; Position fromPos = getEstimatedPosition(from, /* extrapolate */false); - if (fromPos == null) { - result = Distance.NULL; - } else { + if (fromPos != null) { // TODO bug 4122: idea: use the subset to determine fromPos and toPos in one go, eliminating two expensive getEstimatedPosition calls NavigableSet subset = getGPSFixes().subSet(new DummyGPSFix(from), /* fromInclusive */false, new DummyGPSFix(to), /* toInclusive */false); for (GPSFix fix : subset) { - double distanceBetweenAdjacentFixesInNauticalMiles = fromPos.getDistance( - fix.getPosition()).getNauticalMiles(); - distanceInNauticalMiles += distanceBetweenAdjacentFixesInNauticalMiles; + Distance distanceBetweenAdjacentFixes = fromPos.getDistance( + fix.getPosition()); + distance = distance.add(distanceBetweenAdjacentFixes); fromPos = fix.getPosition(); } Position toPos = getEstimatedPosition(to, false); - distanceInNauticalMiles += fromPos.getDistance(toPos).getNauticalMiles(); - result = new NauticalMileDistance(distanceInNauticalMiles); + distance = distance.add(fromPos.getDistance(toPos)); } - } else { - result = Distance.NULL; + return distance; } - } - } finally { - unlockAfterRead(); - } - if (!perfectCacheHit && recursionDepth == 0) { - getDistanceCache().cache(from, to, result); - } - } - return result; + }); } - + @Override public Distance getRawDistanceTraveled(TimePoint from, TimePoint to) { lockForRead(); @@ -1065,7 +1023,7 @@ public abstract class GPSFixTrackImpl extends } } - protected DistanceCache getDistanceCache() { + protected TimeRangeCache getDistanceCache() { return distanceCache; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java index e557e0cc223..bbfb7df3a95 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackBasedEstimationWindTrackImpl.java @@ -689,7 +689,7 @@ public class TrackBasedEstimationWindTrackImpl extends VirtualWindTrackImpl { @Override public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) { if (oldStatus.getStatus() == TrackedRaceStatusEnum.LOADING) { - if (newStatus.getStatus() != TrackedRaceStatusEnum.LOADING) { + if (newStatus.getStatus() != TrackedRaceStatusEnum.LOADING && newStatus.getStatus() != TrackedRaceStatusEnum.REMOVED) { suspended = false; } } else if (newStatus.getStatus() == TrackedRaceStatusEnum.LOADING) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java index b5172980ecb..6bcbaf8e926 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java @@ -44,7 +44,6 @@ import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache; import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; -import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache; import com.sap.sailing.domain.tracking.WindPositionMode; @@ -98,7 +97,7 @@ public class TrackedLegImpl implements TrackedLeg { } @Override - public TrackedRace getTrackedRace() { + public TrackedRaceImpl getTrackedRace() { return trackedRace; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java index c55005f065d..f2e94464680 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegOfCompetitorImpl.java @@ -29,7 +29,6 @@ import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.Maneuver; import com.sap.sailing.domain.tracking.MarkPassing; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; -import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.WindLegTypeAndLegBearingCache; import com.sap.sailing.domain.tracking.WindPositionMode; import com.sap.sse.common.Duration; @@ -68,7 +67,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { return trackedLeg.getLeg(); } - private TrackedRace getTrackedRace() { + private TrackedRaceImpl getTrackedRace() { return getTrackedLeg().getTrackedRace(); } @@ -268,29 +267,28 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { /** * Projects speed onto the wind direction for upwind/downwind legs to see how fast a boat travels - * "along the wind's direction." For reaching legs (neither upwind nor downwind), the speed is projected onto - * the leg's direction. - * @param windPositionMode see {@link #getWind(Position, TimePoint, Set)} + * "along the wind's direction." For reaching legs (neither upwind nor downwind), the speed is projected onto the + * leg's direction. * - * @throws NoWindException in case the wind direction is not known + * @param speed + * if {@code null} then {@code null} will be returned + * @param windPositionMode + * see {@link #getWind(Position, TimePoint, Set)} + * + * @throws NoWindException + * in case the wind direction is not known */ private SpeedWithBearing getWindwardSpeed(SpeedWithBearing speed, final TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) { - SpeedWithBearing result = null; + final SpeedWithBearing result; if (speed != null) { Bearing projectToBearing; try { if (cache.getLegType(getTrackedLeg(), at) != LegType.REACHING) { - final Wind wind; - if (windPositionMode == WindPositionMode.EXACT) { - wind = cache.getWind(getTrackedRace(), getCompetitor(), at); - } else { - wind = getTrackedRace().getWind( - getTrackedLeg().getEffectiveWindPosition( - () -> getTrackedRace().getTrack(getCompetitor()) - .getEstimatedPosition(at, false), at, windPositionMode), at); - } + final Wind wind = getTrackedRace().getWind(windPositionMode, getTrackedLeg(), getCompetitor(), at, cache); if (wind == null) { + // This is not really likely to happen because wind==null would have let the call + // to cache.getLegType(...) fail with a NoWindException throw new NoWindException("Need at least wind direction to determine windward speed"); } projectToBearing = wind.getBearing(); @@ -307,7 +305,11 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { projectToBearing = projectToBearing.reverse(); } result = new KnotSpeedWithBearingImpl(Math.abs(speed.getKnots() * cos), projectToBearing); + } else { + result = null; } + } else { + result = null; } return result; } @@ -645,7 +647,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { } @Override - public Speed getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) { + public SpeedWithBearing getVelocityMadeGood(TimePoint at, WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) { if (hasStartedLeg(at)) { TimePoint timePoint; if (hasFinishedLeg(at)) { @@ -709,7 +711,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { public Distance getRideHeight(TimePoint at) { final Distance result; if (hasStartedLeg(at)) { - TimePoint timePoint =hasFinishedLeg(at) ? getMarkPassingForLegEnd().getTimePoint() : at; + TimePoint timePoint = hasFinishedLeg(at) ? getMarkPassingForLegEnd().getTimePoint() : at; BravoFixTrack track = getTrackedRace() .> getSensorTrack(competitor, BravoFixTrack.TRACK_NAME); result = track == null ? null : track.getRideHeight(timePoint); @@ -719,6 +721,37 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { return result; } + @Override + public Distance getDistanceFoiled(TimePoint at) { + final Distance result; + if (hasStartedLeg(at)) { + TimePoint timePoint = hasFinishedLeg(at) ? getMarkPassingForLegEnd().getTimePoint() : at; + BravoFixTrack track = getTrackedRace() + .> getSensorTrack(competitor, BravoFixTrack.TRACK_NAME); + result = track == null ? null + : track.getDistanceSpentFoiling(getMarkPassingForLegStart().getTimePoint(), + timePoint); + } else { + result = null; + } + return result; + } + + @Override + public Duration getDurationFoiled(TimePoint at) { + final Duration result; + if (hasStartedLeg(at)) { + TimePoint timePoint = hasFinishedLeg(at) ? getMarkPassingForLegEnd().getTimePoint() : at; + BravoFixTrack track = getTrackedRace() + .> getSensorTrack(competitor, BravoFixTrack.TRACK_NAME); + result = track == null ? null + : track.getTimeSpentFoiling(getMarkPassingForLegStart().getTimePoint(), timePoint); + } else { + result = null; + } + return result; + } + @Override public Duration getEstimatedTimeToNextMark(TimePoint timePoint, WindPositionMode windPositionMode) { return getEstimatedTimeToNextMark(timePoint, windPositionMode, new LeaderboardDTOCalculationReuseCache(timePoint)); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index d51e3b9d2e7..3767441eff5 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -1212,7 +1212,50 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } private Distance getDistanceTraveled(Competitor competitor, TimePoint timePoint, boolean considerGateStart) { - final Distance result; + return getValueFromStartToTimePointOrEnd(competitor, timePoint, + (from, to)->{ + final Distance result; + final Distance preResult = getTrack(competitor).getDistanceTraveled(from, to); + if (considerGateStart && preResult != null) { + result = preResult.add(getAdditionalGateStartDistance(competitor, timePoint)); + } else { + result = preResult; + } + return result; + }); + } + + @Override + public Distance getDistanceFoiled(Competitor competitor, TimePoint timePoint) { + return getBravoValue(competitor, timePoint, BravoFixTrack::getDistanceSpentFoiling); + } + + @Override + public Duration getDurationFoiled(Competitor competitor, TimePoint timePoint) { + return getBravoValue(competitor, timePoint, BravoFixTrack::getTimeSpentFoiling); + } + + @FunctionalInterface + private static interface BravoFromToValueCalculator { + T getValue(BravoFixTrack bravoFixTrack, TimePoint from, TimePoint to); + } + + private T getBravoValue(Competitor competitor, TimePoint timePoint, BravoFromToValueCalculator bravoValueCalculator) { + return getValueFromStartToTimePointOrEnd(competitor, timePoint, + (from, to)->{ + final T result; + final BravoFixTrack bravoFixTrack = getSensorTrack(competitor, BravoFixTrack.TRACK_NAME); + if (bravoFixTrack != null) { + result = bravoValueCalculator.getValue(bravoFixTrack, from, to); + } else { + result = null; + } + return result; + }); + } + + private T getValueFromStartToTimePointOrEnd(Competitor competitor, TimePoint timePoint, Track.TimeRangeValueCalculator valueCalculator) { + final T result; NavigableSet markPassings = getMarkPassings(competitor); try { lockForRead(markPassings); @@ -1239,12 +1282,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl if (end == null) { result = null; } else { - final Distance preResult = getTrack(competitor).getDistanceTraveled(markPassings.first().getTimePoint(), end); - if (considerGateStart && preResult != null) { - result = preResult.add(getAdditionalGateStartDistance(competitor, timePoint)); - } else { - result = preResult; - } + result = valueCalculator.calculate(markPassings.first().getTimePoint(), end); } } return result; @@ -1738,20 +1776,6 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } return result; } - - @Override - public boolean hasGPSData() { - boolean result = false; - if (!tracks.values().isEmpty()) { - for (GPSFixTrack gpsTrack : tracks.values()) { - if (!gpsTrack.isEmpty()) { - result = true; - break; - } - } - } - return result; - } /** * Checks whether the {@link Wind#getTimePoint()} is in range of start and end {@link TimePoint}s plus extra time @@ -3373,7 +3397,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } if (newStatus.getStatus() == TrackedRaceStatusEnum.LOADING && oldStatus != TrackedRaceStatusEnum.LOADING) { suspendAllCachesNotUpdatingWhileLoading(); - } else if (oldStatus == TrackedRaceStatusEnum.LOADING && newStatus.getStatus() != TrackedRaceStatusEnum.LOADING) { + } else if (oldStatus == TrackedRaceStatusEnum.LOADING && newStatus.getStatus() != TrackedRaceStatusEnum.LOADING && newStatus.getStatus() != TrackedRaceStatusEnum.REMOVED) { resumeAllCachesNotUpdatingWhileLoading(); } } @@ -3584,12 +3608,14 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl @Override public Speed getSpeed(Competitor competitor, long millisecondsBeforeRaceStart) { + final Speed result; if (getStartOfRace() == null) { - return null; + result = null; + } else { + TimePoint beforeStart = new MillisecondsTimePoint(getStartOfRace().asMillis() - millisecondsBeforeRaceStart); + result = getTrack(competitor).getEstimatedSpeed(beforeStart); } - - TimePoint beforeStart = new MillisecondsTimePoint(getStartOfRace().asMillis() - millisecondsBeforeRaceStart); - return getTrack(competitor).getEstimatedSpeed(beforeStart); + return result; } @Override @@ -4101,6 +4127,22 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl LockUtil.unlockAfterRead(sensorTracksLock); } } + + @Override + public > Iterable getSensorTracks( + String trackName) { + return LockUtil.>executeWithReadLockAndResult(sensorTracksLock, () -> { + final Set result = new HashSet<>(); + for (Competitor competitor : tracks.keySet()) { + final Pair key = new Pair<>(competitor, trackName); + final TrackT track = getTrackInternal(key); + if (track != null) { + result.add(track); + } + } + return result; + }); + } protected > TrackT getOrCreateSensorTrack( Competitor competitor, String trackName, TrackFactory newTrackFactory) { @@ -4187,4 +4229,76 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl public Iterable getAttachedRaceLogs() { return new HashSet<>(attachedRaceLogs.values()); } + + @Override + public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) { + Speed result = null; + Duration totalTimeSailedInRace = Duration.NULL; + Distance totalDistanceSailedInRace = Distance.NULL; + for (TrackedLeg legGeneral : getTrackedLegs()) { + TrackedLegOfCompetitor leg = legGeneral.getTrackedLeg(competitor); + if (leg != null && leg.hasStartedLeg(timePoint)) { + totalDistanceSailedInRace = totalDistanceSailedInRace.add(leg.getDistanceTraveled(timePoint)); + totalTimeSailedInRace = totalTimeSailedInRace.plus(leg.getTime(timePoint)); + } + } + if (!totalTimeSailedInRace.equals(Duration.NULL) && !totalDistanceSailedInRace.equals(Distance.NULL)) { + result = totalDistanceSailedInRace.inTime(totalTimeSailedInRace); + } + return result; + } + + @Override + public SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint, + WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) { + TrackedLegOfCompetitor trackedLeg = getTrackedLeg(competitor, timePoint); + final SpeedWithBearing result; + if (trackedLeg != null) { + result = trackedLeg.getVelocityMadeGood(timePoint, windPositionMode, cache); + } else { + // check if wind information is available; if so, compute a VMG only based on wind data: + if (windPositionMode == WindPositionMode.LEG_MIDDLE) { + result = null; + } else { + final Wind wind = getWind(windPositionMode, /* trackedLeg */ null, competitor, timePoint, cache); + result = projectOnto(getTrack(competitor).getEstimatedSpeed(timePoint), wind.getBearing()); + } + } + return result; + } + + SpeedWithBearing projectOnto(SpeedWithBearing speed, Bearing projectToBearing) { + final SpeedWithBearing result; + if (speed != null && speed.getBearing() != null && projectToBearing != null) { + double cos = Math.cos(speed.getBearing().getRadians() - projectToBearing.getRadians()); + if (cos < 0) { + projectToBearing = projectToBearing.reverse(); + } + result = new KnotSpeedWithBearingImpl(Math.abs(speed.getKnots() * cos), projectToBearing); + } else { + result = null; + } + return result; + } + + /** + * @param trackedLeg + * The caller is expected to obtain the {@link #getTrackedLeg(Competitor, TimePoint) tracked leg} the + * {@code competitor} is sailing in at time point {@code at}. If {@code null}, any + * non-{@link WindPositionMode#EXACT exact} wind position mode will use {@code null} for the wind + * position, defaulting to the "COMBINED" wind source at the middle of the course. In particular, it then + * obviously makes no real sense to request {@link WindPositionMode#LEG_MIDDLE} because no leg is known. + */ + Wind getWind(WindPositionMode windPositionMode, TrackedLegImpl trackedLeg, Competitor competitor, TimePoint at, WindLegTypeAndLegBearingCache cache) { + final Wind wind; + if (windPositionMode == WindPositionMode.EXACT) { + wind = cache.getWind(this, competitor, at); + } else { + wind = getWind(trackedLeg == null ? null : + trackedLeg.getEffectiveWindPosition( + () -> getTrack(competitor) + .getEstimatedPosition(at, false), at, windPositionMode), at); + } + return wind; + } } diff --git a/java/com.sap.sailing.expeditionconnector.common/.classpath b/java/com.sap.sailing.expeditionconnector.common/.classpath new file mode 100755 index 00000000000..afb7df28d9e --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.common/.classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/java/com.sap.sailing.expeditionconnector.common/.project b/java/com.sap.sailing.expeditionconnector.common/.project new file mode 100755 index 00000000000..96ef73399d7 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.common/.project @@ -0,0 +1,39 @@ + + + com.sap.sailing.expeditionconnector.common + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + com.google.gdt.eclipse.core.webAppProjectValidator + + + + + com.google.gwt.eclipse.core.gwtProjectValidator + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + com.google.gwt.eclipse.core.gwtNature + + diff --git a/java/com.sap.sailing.expeditionconnector.common/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.expeditionconnector.common/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..d17b6724d17 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.common/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.7 diff --git a/java/com.sap.sailing.expeditionconnector.common/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.expeditionconnector.common/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..f29e940a005 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.common/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sailing.expeditionconnector.common/META-INF/MANIFEST.MF b/java/com.sap.sailing.expeditionconnector.common/META-INF/MANIFEST.MF new file mode 100755 index 00000000000..d41efa2965a --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.common/META-INF/MANIFEST.MF @@ -0,0 +1,11 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Common +Bundle-SymbolicName: com.sap.sailing.expeditionconnector.common +Bundle-Version: 1.0.0.qualifier +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-ActivationPolicy: lazy +Export-Package: com.sap.sailing.expeditionconnector +Require-Bundle: com.sap.sse.gwt, + com.sap.sailing.domain.common diff --git a/java/com.sap.sailing.expeditionconnector.common/build.properties b/java/com.sap.sailing.expeditionconnector.common/build.properties new file mode 100755 index 00000000000..34d2e4d2dad --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.common/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sailing.expeditionconnector.common/pom.xml b/java/com.sap.sailing.expeditionconnector.common/pom.xml new file mode 100755 index 00000000000..c641b232220 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.common/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sailing.expeditionconnector.common + eclipse-plugin + + + + org.eclipse.tycho + tycho-compiler-plugin + ${tycho-version} + + 1.7 + 1.7 + + + + org.eclipse.tycho + tycho-source-plugin + ${tycho-version} + + + plugin-source + generate-sources + + plugin-source + + + + + + + diff --git a/java/com.sap.sailing.expeditionconnector.common/src/com/sap/sailing/ExpeditionConnectorCommon.gwt.xml b/java/com.sap.sailing.expeditionconnector.common/src/com/sap/sailing/ExpeditionConnectorCommon.gwt.xml new file mode 100755 index 00000000000..78b5c462b1b --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.common/src/com/sap/sailing/ExpeditionConnectorCommon.gwt.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/java/com.sap.sailing.expeditionconnector.common/src/com/sap/sailing/expeditionconnector/ExpeditionDeviceConfiguration.java b/java/com.sap.sailing.expeditionconnector.common/src/com/sap/sailing/expeditionconnector/ExpeditionDeviceConfiguration.java new file mode 100755 index 00000000000..730784d7ccf --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.common/src/com/sap/sailing/expeditionconnector/ExpeditionDeviceConfiguration.java @@ -0,0 +1,75 @@ +package com.sap.sailing.expeditionconnector; + +import java.io.Serializable; +import java.util.UUID; + +import com.sap.sse.common.impl.NamedImpl; + +public class ExpeditionDeviceConfiguration extends NamedImpl implements Serializable { + private static final long serialVersionUID = -7819154195403387909L; + + private final UUID deviceUuid; + + /** + * The ID listed as first element in a UDP message coming from Expedition, prefixed by a '#' + * character. If {@code null}, no mapping currently exists for the device. + */ + private final Integer expeditionBoatId; + + public ExpeditionDeviceConfiguration(String name, UUID deviceUuid, Integer expeditionBoatId) { + super(name); + this.deviceUuid = deviceUuid; + this.expeditionBoatId = expeditionBoatId; + } + + public UUID getDeviceUuid() { + return deviceUuid; + } + + public Integer getExpeditionBoatId() { + return expeditionBoatId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); + result = prime * result + ((deviceUuid == null) ? 0 : deviceUuid.hashCode()); + result = prime * result + ((expeditionBoatId == null) ? 0 : expeditionBoatId.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; + ExpeditionDeviceConfiguration other = (ExpeditionDeviceConfiguration) obj; + if (getName() == null) { + if (other.getName() != null) + return false; + } else if (!getName().equals(other.getName())) + return false; + if (deviceUuid == null) { + if (other.deviceUuid != null) + return false; + } else if (!deviceUuid.equals(other.deviceUuid)) + return false; + if (expeditionBoatId == null) { + if (other.expeditionBoatId != null) + return false; + } else if (!expeditionBoatId.equals(other.expeditionBoatId)) + return false; + return true; + } + + @Override + public String toString() { + return "ExpeditionDeviceConfiguration [deviceUuid=" + deviceUuid + ", expeditionBoatId=" + expeditionBoatId + + ", getName()=" + getName() + "]"; + } +} diff --git a/java/com.sap.sailing.expeditionconnector.common/src/com/sap/sailing/expeditionconnector/ExpeditionDeviceIdentifier.java b/java/com.sap.sailing.expeditionconnector.common/src/com/sap/sailing/expeditionconnector/ExpeditionDeviceIdentifier.java new file mode 100755 index 00000000000..dd44b6d6f39 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.common/src/com/sap/sailing/expeditionconnector/ExpeditionDeviceIdentifier.java @@ -0,0 +1,12 @@ +package com.sap.sailing.expeditionconnector; + +import java.util.UUID; + +import com.sap.sailing.domain.common.DeviceIdentifier; + +public interface ExpeditionDeviceIdentifier extends DeviceIdentifier { + /** + * Derived from an {@link ExpeditionDeviceConfiguration#getDeviceUuid()}. + */ + UUID getId(); +} diff --git a/java/com.sap.sailing.expeditionconnector.common/src/com/sap/sailing/expeditionconnector/ExpeditionSensorDeviceIdentifier.java b/java/com.sap.sailing.expeditionconnector.common/src/com/sap/sailing/expeditionconnector/ExpeditionSensorDeviceIdentifier.java new file mode 100755 index 00000000000..ee56e5f788f --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.common/src/com/sap/sailing/expeditionconnector/ExpeditionSensorDeviceIdentifier.java @@ -0,0 +1,12 @@ +package com.sap.sailing.expeditionconnector; + +/** + * A device identifier used to identify an installation of the regatta tool "Expedition" + * regarding its sensor data beyond the basic GPS and wind information. + * + * @author Axel Uhl (d043530) + * + */ +public interface ExpeditionSensorDeviceIdentifier extends ExpeditionDeviceIdentifier { + public static final String TYPE = "EXPEDITION_SENSOR"; +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/.classpath b/java/com.sap.sailing.expeditionconnector.persistence/.classpath new file mode 100755 index 00000000000..eca7bdba8f0 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/java/com.sap.sailing.expeditionconnector.persistence/.project b/java/com.sap.sailing.expeditionconnector.persistence/.project new file mode 100755 index 00000000000..c6034e7c533 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/.project @@ -0,0 +1,28 @@ + + + com.sap.sailing.expeditionconnector.persistence + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/java/com.sap.sailing.expeditionconnector.persistence/.settings/org.eclipse.jdt.core.prefs b/java/com.sap.sailing.expeditionconnector.persistence/.settings/org.eclipse.jdt.core.prefs new file mode 100755 index 00000000000..0c68a61dca8 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/java/com.sap.sailing.expeditionconnector.persistence/.settings/org.eclipse.pde.core.prefs b/java/com.sap.sailing.expeditionconnector.persistence/.settings/org.eclipse.pde.core.prefs new file mode 100755 index 00000000000..f29e940a005 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/java/com.sap.sailing.expeditionconnector.persistence/META-INF/MANIFEST.MF b/java/com.sap.sailing.expeditionconnector.persistence/META-INF/MANIFEST.MF new file mode 100755 index 00000000000..2d660db1059 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/META-INF/MANIFEST.MF @@ -0,0 +1,18 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Persistence +Bundle-SymbolicName: com.sap.sailing.expeditionconnector.persistence +Bundle-Version: 1.0.0.qualifier +Bundle-Activator: com.sap.sailing.expeditionconnector.persistence.impl.Activator +Bundle-Vendor: SAP +Bundle-RequiredExecutionEnvironment: JavaSE-1.8 +Import-Package: org.osgi.framework;version="1.3.0" +Require-Bundle: com.sap.sailing.domain.persistence, + com.sap.sailing.domain.shared.android, + com.sap.sailing.domain.common, + com.sap.sse.common, + com.sap.sse.mongodb, + org.mongodb.mongo-java-driver;bundle-version="2.13.0", + com.sap.sailing.expeditionconnector.common, + com.sap.sailing.server.gateway.serialization.shared.android +Export-Package: com.sap.sailing.expeditionconnector.persistence diff --git a/java/com.sap.sailing.expeditionconnector.persistence/build.properties b/java/com.sap.sailing.expeditionconnector.persistence/build.properties new file mode 100755 index 00000000000..34d2e4d2dad --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/java/com.sap.sailing.expeditionconnector.persistence/pom.xml b/java/com.sap.sailing.expeditionconnector.persistence/pom.xml new file mode 100755 index 00000000000..d8d0564b320 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/pom.xml @@ -0,0 +1,12 @@ + + + 4.0.0 + + root + com.sap.sailing + 1.0.0-SNAPSHOT + + com.sap.sailing.expeditionconnector.persistence + eclipse-plugin + diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/DomainObjectFactory.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/DomainObjectFactory.java new file mode 100755 index 00000000000..3e03a1249b1 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/DomainObjectFactory.java @@ -0,0 +1,7 @@ +package com.sap.sailing.expeditionconnector.persistence; + +import com.sap.sailing.expeditionconnector.ExpeditionDeviceConfiguration; + +public interface DomainObjectFactory { + Iterable getExpeditionDeviceConfigurations(); +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionGpsDeviceIdentifier.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionGpsDeviceIdentifier.java new file mode 100755 index 00000000000..7c59f1aeae0 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionGpsDeviceIdentifier.java @@ -0,0 +1,14 @@ +package com.sap.sailing.expeditionconnector.persistence; + +import com.sap.sailing.expeditionconnector.ExpeditionDeviceIdentifier; + +/** + * A device identifier used to identify an installation of the regatta tool "Expedition" + * regarding its basic GPS information. + * + * @author Axel Uhl (d043530) + * + */ +public interface ExpeditionGpsDeviceIdentifier extends ExpeditionDeviceIdentifier { + public static final String TYPE = "EXPEDITION_GPS"; +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionGpsDeviceIdentifierImpl.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionGpsDeviceIdentifierImpl.java new file mode 100755 index 00000000000..3f9712f87aa --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionGpsDeviceIdentifierImpl.java @@ -0,0 +1,18 @@ +package com.sap.sailing.expeditionconnector.persistence; + +import java.util.UUID; + +import com.sap.sailing.expeditionconnector.persistence.impl.AbstractExpeditionDeviceIdentifierImpl; + +public class ExpeditionGpsDeviceIdentifierImpl extends AbstractExpeditionDeviceIdentifierImpl implements ExpeditionGpsDeviceIdentifier { + private static final long serialVersionUID = -4049961972156611640L; + + public ExpeditionGpsDeviceIdentifierImpl(UUID id) { + super(id); + } + + @Override + public String getIdentifierType() { + return ExpeditionGpsDeviceIdentifier.TYPE; + } +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionGpsDeviceIdentifierJsonHandler.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionGpsDeviceIdentifierJsonHandler.java new file mode 100755 index 00000000000..7139cac90c9 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionGpsDeviceIdentifierJsonHandler.java @@ -0,0 +1,16 @@ +package com.sap.sailing.expeditionconnector.persistence; + +import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdentifierJsonHandler; + +public class ExpeditionGpsDeviceIdentifierJsonHandler extends ExpeditionGpsDeviceIdentifierSerializationHandler +implements DeviceIdentifierJsonHandler { + + @Override + public DeviceIdentifier deserialize(Object serialized, String type, String stringRepresentation) + throws TransformationException { + return deserialize((String) serialized, type, stringRepresentation); + } + +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionGpsDeviceIdentifierSerializationHandler.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionGpsDeviceIdentifierSerializationHandler.java new file mode 100755 index 00000000000..5965eb87869 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionGpsDeviceIdentifierSerializationHandler.java @@ -0,0 +1,27 @@ +package com.sap.sailing.expeditionconnector.persistence; + +import java.util.UUID; + +import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sse.common.Util; + +public class ExpeditionGpsDeviceIdentifierSerializationHandler { + private ExpeditionGpsDeviceIdentifier castIdentifier(DeviceIdentifier identifier) throws TransformationException { + if (!(identifier instanceof ExpeditionGpsDeviceIdentifier)) + throw new TransformationException("Expected a ExpeditionGpsDeviceIdentifier, got instead: " + identifier); + return (ExpeditionGpsDeviceIdentifier) identifier; + } + + public Util.Pair serialize(DeviceIdentifier deviceIdentifier) throws TransformationException { + return new Util.Pair(ExpeditionGpsDeviceIdentifier.TYPE, castIdentifier(deviceIdentifier).getId().toString()); + } + + public DeviceIdentifier deserialize(String input, String type, String stringRep) throws TransformationException { + try { + return new ExpeditionGpsDeviceIdentifierImpl(UUID.fromString(input)); + } catch (IllegalArgumentException e) { + throw new TransformationException("Invalid string representation of smartphone UUID", e); + } + } +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionSensorDeviceIdentifierImpl.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionSensorDeviceIdentifierImpl.java new file mode 100755 index 00000000000..4ad233a9d82 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionSensorDeviceIdentifierImpl.java @@ -0,0 +1,19 @@ +package com.sap.sailing.expeditionconnector.persistence; + +import java.util.UUID; + +import com.sap.sailing.expeditionconnector.ExpeditionSensorDeviceIdentifier; +import com.sap.sailing.expeditionconnector.persistence.impl.AbstractExpeditionDeviceIdentifierImpl; + +public class ExpeditionSensorDeviceIdentifierImpl extends AbstractExpeditionDeviceIdentifierImpl implements ExpeditionSensorDeviceIdentifier { + private static final long serialVersionUID = -4049961972156611640L; + + public ExpeditionSensorDeviceIdentifierImpl(UUID id) { + super(id); + } + + @Override + public String getIdentifierType() { + return ExpeditionSensorDeviceIdentifier.TYPE; + } +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionSensorDeviceIdentifierSerializationHandler.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionSensorDeviceIdentifierSerializationHandler.java new file mode 100755 index 00000000000..3872e9255f9 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/ExpeditionSensorDeviceIdentifierSerializationHandler.java @@ -0,0 +1,28 @@ +package com.sap.sailing.expeditionconnector.persistence; + +import java.util.UUID; + +import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.expeditionconnector.ExpeditionSensorDeviceIdentifier; +import com.sap.sse.common.Util; + +public class ExpeditionSensorDeviceIdentifierSerializationHandler { + private ExpeditionSensorDeviceIdentifier castIdentifier(DeviceIdentifier identifier) throws TransformationException { + if (!(identifier instanceof ExpeditionSensorDeviceIdentifier)) + throw new TransformationException("Expected a ExpeditionSensorDeviceIdentifier, got instead: " + identifier); + return (ExpeditionSensorDeviceIdentifier) identifier; + } + + public Util.Pair serialize(DeviceIdentifier deviceIdentifier) throws TransformationException { + return new Util.Pair(ExpeditionSensorDeviceIdentifier.TYPE, castIdentifier(deviceIdentifier).getId().toString()); + } + + public DeviceIdentifier deserialize(String input, String type, String stringRep) throws TransformationException { + try { + return new ExpeditionSensorDeviceIdentifierImpl(UUID.fromString(input)); + } catch (IllegalArgumentException e) { + throw new TransformationException("Invalid string representation of smartphone UUID", e); + } + } +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/MongoObjectFactory.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/MongoObjectFactory.java new file mode 100755 index 00000000000..e4cfecac400 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/MongoObjectFactory.java @@ -0,0 +1,9 @@ +package com.sap.sailing.expeditionconnector.persistence; + +import com.sap.sailing.expeditionconnector.ExpeditionDeviceConfiguration; + +public interface MongoObjectFactory { + void storeExpeditionDeviceConfiguration(ExpeditionDeviceConfiguration expeditionDeviceConfiguration); + + void removeExpeditionDeviceConfiguration(ExpeditionDeviceConfiguration expeditionDeviceConfiguration); +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/PersistenceFactory.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/PersistenceFactory.java new file mode 100755 index 00000000000..d955241fbae --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/PersistenceFactory.java @@ -0,0 +1,23 @@ +package com.sap.sailing.expeditionconnector.persistence; + +import com.sap.sailing.expeditionconnector.persistence.impl.PersistenceFactoryImpl; +import com.sap.sse.mongodb.MongoDBConfiguration; +import com.sap.sse.mongodb.MongoDBService; + +public interface PersistenceFactory { + PersistenceFactory INSTANCE = new PersistenceFactoryImpl(); + + /** + * Obtains the domain object factory using the default persistence settings from {@link MongoDBConfiguration#getDefaultConfiguration()}. + */ + DomainObjectFactory getDefaultDomainObjectFactory(); + + DomainObjectFactory getDomainObjectFactory(MongoDBService mongoDbService); + + /** + * Obtains the Mongo object factory using the default persistence settings from {@link MongoDBConfiguration#getDefaultConfiguration()}. + */ + MongoObjectFactory getDefaultMongoObjectFactory(); + + MongoObjectFactory getMongoObjectFactory(MongoDBService mongoDbService); +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/AbstractExpeditionDeviceIdentifierImpl.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/AbstractExpeditionDeviceIdentifierImpl.java new file mode 100755 index 00000000000..21ff23cd0a6 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/AbstractExpeditionDeviceIdentifierImpl.java @@ -0,0 +1,37 @@ +package com.sap.sailing.expeditionconnector.persistence.impl; + +import java.util.UUID; + +import com.sap.sailing.expeditionconnector.ExpeditionDeviceIdentifier; + +public abstract class AbstractExpeditionDeviceIdentifierImpl implements ExpeditionDeviceIdentifier { + private static final long serialVersionUID = -6605059302775505785L; + private final UUID id; + + public AbstractExpeditionDeviceIdentifierImpl(UUID id) { + super(); + this.id = id; + } + + @Override + public String getStringRepresentation() { + return id.toString(); + } + + @Override + public UUID getId() { + return id; + } + + @Override + public boolean equals(Object obj) { + return (obj instanceof ExpeditionDeviceIdentifier) && + ((ExpeditionDeviceIdentifier) obj).getIdentifierType().equals(getIdentifierType()) && + ((ExpeditionDeviceIdentifier) obj).getId().equals(getId()); + } + + @Override + public int hashCode() { + return 9478 ^ getIdentifierType().hashCode() ^ getId().hashCode(); + } +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/Activator.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/Activator.java new file mode 100755 index 00000000000..4e988439924 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/Activator.java @@ -0,0 +1,63 @@ +package com.sap.sailing.expeditionconnector.persistence.impl; + +import java.util.Dictionary; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Set; + +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; + +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; +import com.sap.sailing.expeditionconnector.ExpeditionSensorDeviceIdentifier; +import com.sap.sailing.expeditionconnector.persistence.ExpeditionGpsDeviceIdentifier; +import com.sap.sse.common.TypeBasedServiceFinder; +import com.sap.sse.mongodb.MongoDBService; + +public class Activator implements BundleActivator { + + private static BundleContext context; + + /** + * Registrations of OSGi services to be de-registered when the bundle shuts down + */ + private Set> registrations = new HashSet<>(); + + static BundleContext getContext() { + return context; + } + + /* + * (non-Javadoc) + * + * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) + */ + public void start(BundleContext bundleContext) throws Exception { + Activator.context = bundleContext; + registrations.add(context.registerService(DeviceIdentifierMongoHandler.class, new ExpeditionGpsDeviceIdentifierMongoHandler(), getDict(ExpeditionGpsDeviceIdentifier.TYPE))); + registrations.add(context.registerService(DeviceIdentifierMongoHandler.class, new ExpeditionSensorDeviceIdentifierMongoHandler(), getDict(ExpeditionSensorDeviceIdentifier.TYPE))); + for (CollectionNames name : CollectionNames.values()) { + MongoDBService.INSTANCE.registerExclusively(CollectionNames.class, name.name()); + } + } + + /* + * (non-Javadoc) + * + * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext) + */ + public void stop(BundleContext bundleContext) throws Exception { + Activator.context = null; + for (ServiceRegistration reg : registrations) { + reg.unregister(); + } + registrations.clear(); + } + + private Dictionary getDict(String type) { + Dictionary properties = new Hashtable(); + properties.put(TypeBasedServiceFinder.TYPE, type); + return properties; + } +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/CollectionNames.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/CollectionNames.java new file mode 100755 index 00000000000..d3abbcdfcda --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/CollectionNames.java @@ -0,0 +1,5 @@ +package com.sap.sailing.expeditionconnector.persistence.impl; + +public enum CollectionNames { + EXPEDITION_DEVICE_CONFIGURATIONS; +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/DomainObjectFactoryImpl.java new file mode 100755 index 00000000000..d02b63d2639 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/DomainObjectFactoryImpl.java @@ -0,0 +1,35 @@ +package com.sap.sailing.expeditionconnector.persistence.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import com.mongodb.DB; +import com.mongodb.DBCollection; +import com.mongodb.DBObject; +import com.sap.sailing.expeditionconnector.ExpeditionDeviceConfiguration; +import com.sap.sailing.expeditionconnector.persistence.DomainObjectFactory; + +public class DomainObjectFactoryImpl implements DomainObjectFactory { + private final DB db; + + public DomainObjectFactoryImpl(DB db) { + this.db = db; + } + + @Override + public Iterable getExpeditionDeviceConfigurations() { + final List result = new ArrayList<>(); + final DBCollection expeditionDeviceConfigurationsCollection = db.getCollection(CollectionNames.EXPEDITION_DEVICE_CONFIGURATIONS.name()); + for (final Object o : expeditionDeviceConfigurationsCollection.find()) { + final DBObject dbo = (DBObject) o; + final UUID uuid = (UUID) dbo.get(FieldNames.EXPEDITION_DEVICE_CONFIGURATION_UUID.name()); + final String name = (String) dbo.get(FieldNames.EXPEDITION_DEVICE_CONFIGURATION_NAME.name()); + final Number boatIdAsNumber = (Number) dbo.get(FieldNames.EXPEDITION_DEVICE_CONFIGURATION_BOAT_ID.name()); + final Integer boatId = boatIdAsNumber == null ? null : boatIdAsNumber.intValue(); + result.add(new ExpeditionDeviceConfiguration(name, uuid, boatId)); + } + return result; + } + +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/ExpeditionGpsDeviceIdentifierMongoHandler.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/ExpeditionGpsDeviceIdentifierMongoHandler.java new file mode 100755 index 00000000000..16387b809d9 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/ExpeditionGpsDeviceIdentifierMongoHandler.java @@ -0,0 +1,15 @@ +package com.sap.sailing.expeditionconnector.persistence.impl; + +import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; +import com.sap.sailing.expeditionconnector.persistence.ExpeditionGpsDeviceIdentifierSerializationHandler; + +public class ExpeditionGpsDeviceIdentifierMongoHandler extends ExpeditionGpsDeviceIdentifierSerializationHandler +implements DeviceIdentifierMongoHandler { + @Override + public DeviceIdentifier deserialize(Object serialized, String type, String stringRepresentation) + throws TransformationException { + return deserialize((String) serialized, type, stringRepresentation); + } +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/ExpeditionSensorDeviceIdentifierMongoHandler.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/ExpeditionSensorDeviceIdentifierMongoHandler.java new file mode 100755 index 00000000000..a549dab7d6c --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/ExpeditionSensorDeviceIdentifierMongoHandler.java @@ -0,0 +1,15 @@ +package com.sap.sailing.expeditionconnector.persistence.impl; + +import com.sap.sailing.domain.common.DeviceIdentifier; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.persistence.racelog.tracking.DeviceIdentifierMongoHandler; +import com.sap.sailing.expeditionconnector.persistence.ExpeditionSensorDeviceIdentifierSerializationHandler; + +public class ExpeditionSensorDeviceIdentifierMongoHandler extends ExpeditionSensorDeviceIdentifierSerializationHandler +implements DeviceIdentifierMongoHandler { + @Override + public DeviceIdentifier deserialize(Object serialized, String type, String stringRepresentation) + throws TransformationException { + return deserialize((String) serialized, type, stringRepresentation); + } +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/FieldNames.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/FieldNames.java new file mode 100755 index 00000000000..26e383ab254 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/FieldNames.java @@ -0,0 +1,5 @@ +package com.sap.sailing.expeditionconnector.persistence.impl; + +public enum FieldNames { + EXPEDITION_DEVICE_CONFIGURATION_NAME, EXPEDITION_DEVICE_CONFIGURATION_UUID, EXPEDITION_DEVICE_CONFIGURATION_BOAT_ID; +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/MongoObjectFactoryImpl.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/MongoObjectFactoryImpl.java new file mode 100755 index 00000000000..b7a67de00f0 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/MongoObjectFactoryImpl.java @@ -0,0 +1,59 @@ +package com.sap.sailing.expeditionconnector.persistence.impl; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.mongodb.BasicDBObject; +import com.mongodb.DB; +import com.mongodb.DBCollection; +import com.mongodb.DBObject; +import com.mongodb.WriteConcern; +import com.sap.sailing.expeditionconnector.ExpeditionDeviceConfiguration; +import com.sap.sailing.expeditionconnector.persistence.MongoObjectFactory; + +public class MongoObjectFactoryImpl implements MongoObjectFactory { + private static final Logger logger = Logger.getLogger(MongoObjectFactoryImpl.class.getName()); + private final DBCollection expeditionDeviceConfigurationsCollection; + + public MongoObjectFactoryImpl(DB db) { + this.expeditionDeviceConfigurationsCollection = db.getCollection(CollectionNames.EXPEDITION_DEVICE_CONFIGURATIONS.name()); + DBObject index = new BasicDBObject(); + index.put(FieldNames.EXPEDITION_DEVICE_CONFIGURATION_UUID.name(), 1); + expeditionDeviceConfigurationsCollection.createIndex(index, new BasicDBObject("unique", true)); + } + + private BasicDBObject getExpeditionDeviceConfigurationDBKey(ExpeditionDeviceConfiguration expeditionDeviceConfiguration) { + final BasicDBObject basicDBObject = new BasicDBObject(FieldNames.EXPEDITION_DEVICE_CONFIGURATION_UUID.name(), expeditionDeviceConfiguration.getDeviceUuid()); + return basicDBObject; + } + + @Override + public void storeExpeditionDeviceConfiguration(ExpeditionDeviceConfiguration expeditionDeviceConfiguration) { + final BasicDBObject key = getExpeditionDeviceConfigurationDBKey(expeditionDeviceConfiguration); + final DBObject expeditionDeviceConfigurationDBObject = new BasicDBObject(); + expeditionDeviceConfigurationDBObject.put(FieldNames.EXPEDITION_DEVICE_CONFIGURATION_UUID.name(), expeditionDeviceConfiguration.getDeviceUuid()); + expeditionDeviceConfigurationDBObject.put(FieldNames.EXPEDITION_DEVICE_CONFIGURATION_NAME.name(), expeditionDeviceConfiguration.getName()); + expeditionDeviceConfigurationDBObject.put(FieldNames.EXPEDITION_DEVICE_CONFIGURATION_BOAT_ID.name(), expeditionDeviceConfiguration.getExpeditionBoatId()); + boolean success = false; + int attempt = 0; + Exception lastException = null; + while (attempt < 5 && !success) { + try { + expeditionDeviceConfigurationsCollection.update(key, expeditionDeviceConfigurationDBObject, /* upsert */ true, /* multi */ false, WriteConcern.SAFE); + success = true; + attempt++; + } catch (Exception e) { + lastException = e; + logger.log(Level.WARNING, "Exception trying to write Expedition device configuration. Trying again", e); + } + } + if (!success) { + throw new RuntimeException("Couldn't store Expedition device configuration "+expeditionDeviceConfiguration, lastException); + } + } + + @Override + public void removeExpeditionDeviceConfiguration(ExpeditionDeviceConfiguration expeditionDeviceConfiguration) { + expeditionDeviceConfigurationsCollection.remove(getExpeditionDeviceConfigurationDBKey(expeditionDeviceConfiguration), WriteConcern.SAFE); + } +} diff --git a/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/PersistenceFactoryImpl.java b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/PersistenceFactoryImpl.java new file mode 100755 index 00000000000..3a70e37b2b2 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.persistence/src/com/sap/sailing/expeditionconnector/persistence/impl/PersistenceFactoryImpl.java @@ -0,0 +1,31 @@ +package com.sap.sailing.expeditionconnector.persistence.impl; + +import com.sap.sailing.expeditionconnector.persistence.DomainObjectFactory; +import com.sap.sailing.expeditionconnector.persistence.MongoObjectFactory; +import com.sap.sailing.expeditionconnector.persistence.PersistenceFactory; +import com.sap.sse.mongodb.MongoDBConfiguration; +import com.sap.sse.mongodb.MongoDBService; + +public class PersistenceFactoryImpl implements PersistenceFactory { + + @Override + public DomainObjectFactory getDomainObjectFactory(MongoDBService mongoDbService) { + return new DomainObjectFactoryImpl(mongoDbService.getDB()); + } + + @Override + public MongoObjectFactory getMongoObjectFactory(MongoDBService mongoDbService) { + return new MongoObjectFactoryImpl(mongoDbService.getDB()); + } + + @Override + public DomainObjectFactory getDefaultDomainObjectFactory() { + return getDomainObjectFactory(MongoDBConfiguration.getDefaultConfiguration().getService()); + } + + @Override + public MongoObjectFactory getDefaultMongoObjectFactory() { + return getMongoObjectFactory(MongoDBConfiguration.getDefaultConfiguration().getService()); + } + +} diff --git a/java/com.sap.sailing.expeditionconnector.test/.classpath b/java/com.sap.sailing.expeditionconnector.test/.classpath index 4f83b2397ec..03c4ce9fdd8 100644 --- a/java/com.sap.sailing.expeditionconnector.test/.classpath +++ b/java/com.sap.sailing.expeditionconnector.test/.classpath @@ -2,6 +2,7 @@ + diff --git a/java/com.sap.sailing.expeditionconnector.test/build.properties b/java/com.sap.sailing.expeditionconnector.test/build.properties index 34d2e4d2dad..f11210d3f2c 100755 --- a/java/com.sap.sailing.expeditionconnector.test/build.properties +++ b/java/com.sap.sailing.expeditionconnector.test/build.properties @@ -1,4 +1,5 @@ -source.. = src/ +source.. = src/,\ + resources/ output.. = bin/ bin.includes = META-INF/,\ . diff --git a/java/com.sap.sailing.expeditionconnector.test/resources/2017Nov04VeryShort.csv b/java/com.sap.sailing.expeditionconnector.test/resources/2017Nov04VeryShort.csv new file mode 100644 index 00000000000..87bbb0618b3 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.test/resources/2017Nov04VeryShort.csv @@ -0,0 +1,64 @@ +Boat,Utc,Bsp,Awa,Aws,Twa,Tws,Twd,RudderFwd,Leeway,Set,Drift,Hdg,AirTemp,SeaTemp,Baro,Depth,Heel,Trim,Rudder,Tab,Forestay,Downhaul,MastAng,FStayLen,MastButt,Load S,Load P,Rake,Volts,ROT,GpQual,PDOP,GpsNum,GpsAge,Altitude,GeoSep,GpsMode,Lat,Lon,Cog,Sog,DiffStn,Error,RunnerS,RunnerP,Vang,Trav,Main,KeelAng,KeelHt,Board,Oil P,RPM 1,RPM 2,Board P,Board S,DistToLn,RchTmToLn,RchDtToLn,GPS time,Twd+90,Twd-90,Downhaul2,Mk Lat,Mk Lon,Port lat,Port lon,Stbd lat,Stbd lon,HPE,RH,Lead P,Lead S,BackStay,RakeDeg,DflectrPP,TG Heell,ForestayPres,DflectrMM,User 5,User 6,User 7,User 8,User 9,User 10,User 11,User 12,User 13,User 14,User 15,User 16,User 17,User 18,User 19,User 20,User 21,User 22,User 23,User 24,User 25,User 26,User 27,User 28,User 29,User 30,User 31,TmToGun,TmToLn,TmToBurn,BelowLn,GunBlwLn,WvSigHt,WvSigPd,WvMaxHt,WvMaxPd,Slam,Motion,Mwa,Mws,Boom,Twist,TackLossT,TackLossD,TrimRate,HeelRate,DeflectorP,RudderP,RudderS,RudderToe,BspTr,FStayInner,DeflectorS,Bobstay,Outhaul,D0 P,D0 S,D1 P,D1 S,V0 P,V0 S,V1 P,V1 S +0,43043.622986,0,105.2,9.21,106.1,8.95,259.9,,-0,152.9,0.06,151.6,,,,2.8,0.92,0.01,-7.19,,1.036,,,,,,,67,,,0,,0,0,30.92,0,,-33.9085207,18.4197313,152.9,0.063,,,,,,,,,,,,,,,,,,,0.622989,180,0,,-33.8723241,18.4068248,-33.8998667,18.44635,-33.9048333,18.4433333,,,,,,1.255319,78.54628,17.899,0.964532,484.2771,-0.000027,,,,,,,,,,,,,,,,,,,,,,,,,,,0.005822,,,0.941392,,,,,,,,,,,0 +1,43043.622986,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 +2,43043.622986,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 +3,43043.622986,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 +4,43043.622986,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 +5,43043.622986,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 +6,43043.622986,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 +7,43043.622986,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0 +.52,155.6,5.4,333.2,,0,58.4,0.06,179,,,,1.4,0.7,-0.05,7.17,,0.543,,,,,,,104,,,0,,0,0,35.33,0,,-33.908531,18.4197168,58.4,0.064,,,,,,,,,,,,,,,,4494.746494,,,0.305674,63.2,243.2,,,,,,,,,,,,,2.367484,98.865089,9.160931,0.494149,579.191406,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,36.141281,,4369.841626 +0,43043.305710,0,153.2,5.33,154.2,5.36,332.9,,0,58.4,0.1,179.1,,,,1.4,0.68,0.01,7.16,,0.545,,,,,,,104,,,0,,0,0,35.3,0,,-33.9085312,18.4197166,58.4,0.103,,,,,,,,,,,,,,,,4494.745257,,,0.305686,62.9,242.9,,39.5759514,2.5748167,,,,,,,,,,2.367534,98.865723,9.031358,0.493427,579.195068,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,36.384381,,4369.841628 +0,43043.305723,0,151.7,5.12,152.9,5.31,332.6,,0,58.4,0.11,178.9,,,,1.2,0.65,-0.07,7.18,,0.538,,,,,,,104,,,0,,0,0,35.32,0,,-33.9085314,18.4197169,58.4,0.11,,,,,,,,,,,,,,,,4494.744879,,,0.3057,62.6,242.6,,,,,,,,,,,,,2.367484,98.866142,8.846865,0.493583,579.195679,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,36.692521,,4369.841645 +0,43043.305734,0,150.5,4.97,151.9,5.24,332.3,,0,58.4,0.09,178.8,,,,1.2,0.63,-0.04,7.17,,0.545,,,,,,,104,,,0,,0,0,35.29,0,,-33.9085313,18.419717,58.4,0.089,,,,,,,,,,,,,,,,4494.745473,,,0.305712,62.3,242.3,,,,,,,,,,,,,2.367501,98.86599,8.596081,0.494632,579.193542,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.160375,,4369.841654 +0,43043.305745,0,150.1,4.89,151.4,5.18,332,,0,58.4,0.05,178.8,,,,1.2,0.68,-0.05,7.17,,0.542,,,,,,,104,,,0,,0,0,35.24,0,,-33.9085312,18.4197168,58.4,0.047,,,,,,,,,,,,,,,,4494.746847,,,0.305723,62.1,242.1,,,,,,,,,,,,,2.367514,98.865654,8.345397,0.494219,579.191162,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.48573,,4369.841648 +0,43043.305758,0,149.1,4.85,150.4,5.14,331.7,,0,58.4,0.05,178.9,,,,1.4,0.79,-0,7.16,,0.546,,,,,,,104,,,0,,0,0,35.17,0,,-33.9085311,18.4197165,58.4,0.046,,,,,,,,,,,,,,,,4494.747044,,,0.305735,61.7,241.7,,,,,,,,,,,,,2.367511,98.865479,8.207174,0.494884,579.190796,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.398528,,4369.841627 +0,43043.305769,0,148.1,4.77,149.6,5.12,331.3,,0,58.4,0.05,179.2,,,,1.4,0.85,-0.03,7.17,,0.545,,,,,,,104,,,0,,0,0,35.09,0,,-33.9085309,18.4197168,58.4,0.051,,,,,,,,,,,,,,,,4494.746833,,,0.305747,61.4,241.4,,,,,,,,,,,,,2.367501,98.865295,8.123696,0.493687,579.192505,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.170342,,4369.841614 +0,43043.305781,0,148.1,4.72,149.1,5.1,331.1,,0,58.4,0.06,179.6,,,,1.4,0.83,-0,7.16,,0.544,,,,,,,104,,,0,,0,0,35.02,0,,-33.9085306,18.4197173,58.4,0.062,,,,,,,,,,,,,,,,4494.746565,,,0.305758,61.1,241.1,,,,,,,,,,,,,2.367521,98.865692,8.040314,0.494814,579.191406,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,36.82654,,4369.841601 +0,43043.305793,0,148.4,4.71,149.3,5.07,331,,0,58.4,0.07,179.6,,,,1.4,0.84,-0.04,7.16,,0.547,,,,,,,104,,,0,,0,0,34.92,0,,-33.9085303,18.4197174,58.4,0.066,,,,,,,,,,,,,,,,4494.746314,,,0.30577,61.1,241.1,,,,,,,,,,,,,2.367481,98.865128,7.936176,0.494846,579.18927,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,36.81312,,4369.841594 +0,43043.305804,0,147.6,4.65,148.5,5.02,330.7,,0,58.4,0.07,179.5,,,,1.4,0.89,0.01,7.17,,0.545,,,,,,,104,,,0,,0,0,34.89,0,,-33.9085302,18.419717,58.4,0.067,,,,,,,,,,,,,,,,4494.746283,,,0.305781,60.7,240.7,,,,,,,,,,,,,2.367517,98.865089,7.759246,0.494822,579.192688,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,36.646684,,4369.84158 +0,43043.305816,0,147.2,4.62,148.1,4.98,330.5,,0,58.4,0.07,179.5,,,,1.4,0.93,0,7.16,,0.547,,,,,,,104,,,0,,0,0,34.95,0,,-33.9085303,18.4197172,58.4,0.065,,,,,,,,,,,,,,,,4494.746274,,,0.305793,60.5,240.5,,,,,,,,,,,,,2.367514,98.865921,7.594308,0.493407,579.193115,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,36.572412,,4369.841589 +0,43043.305827,0,148.9,4.69,148.9,4.98,330.6,,0,58.4,0.05,179.3,,,,1.4,0.91,0.05,7.18,,0.548,,,,,,,104,,,0,,0,0,34.99,0,,-33.9085306,18.4197173,58.4,0.051,,,,,,,,,,,,,,,,4494.746742,,,0.305804,60.5,240.5,,,,,,,,,,,,,2.367514,98.865173,7.596838,0.49495,579.186035,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,36.766499,,4369.841615 +0,43043.305839,0,152.8,4.78,151.5,4.99,331.2,,0,58.4,0.05,179.1,,,,1.4,0.85,0.04,7.2,,0.551,,,,,,,104,,,0,,0,0,34.92,0,,-33.9085307,18.4197173,58.4,0.051,,,,,,,,,,,,,,,,4494.746863,,,0.305816,61.1,241.1,,,,,,,,,,,,,2.367504,98.864769,7.629427,0.494454,579.188171,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.057669,,4369.841627 +0,43043.305851,0,153.5,4.8,152.5,4.98,331.4,,0,58.4,0.05,179.2,,,,1.4,0.84,0.07,7.16,,0.557,,,,,,,104,,,0,,0,0,34.87,0,,-33.9085305,18.4197175,58.4,0.053,,,,,,,,,,,,,,,,4494.746728,,,0.305828,61.4,241.4,,,,,,,,,,,,,2.367504,98.864746,7.608146,0.494083,579.190308,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.21491,,4369.84162 +0,43043.305862,0,154.6,4.75,153.2,4.95,331.6,,0,58.4,0.05,179.3,,,,1.4,0.86,-0.05,7.17,,0.542,,,,,,,104,,,0,,0,0,34.79,0,,-33.9085304,18.4197177,58.4,0.054,,,,,,,,,,,,,,,,4494.746707,,,0.305839,61.6,241.6,,,,,,,,,,,,,2.367477,98.864372,7.457287,0.495428,579.183655,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.620092,,4369.841617 +0,43043.305874,0,152.5,4.68,152.4,4.9,331.4,,0,58.4,0.05,179.2,,,,1.4,0.82,-0.06,7.17,,0.542,,,,,,,104,,,0,,0,0,34.74,0,,-33.9085302,18.4197176,58.4,0.049,,,,,,,,,,,,,,,,4494.746823,,,0.305851,61.4,241.4,,,,,,,,,,,,,2.367517,98.864601,7.258937,0.49545,579.187744,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,38.152083,,4369.841609 +0,43043.305886,0,151.2,4.7,151.4,4.89,331.1,,0,58.4,0.04,179.3,,,,1.4,0.71,-0.05,7.17,,0.543,,,,,,,104,,,0,,0,0,34.72,0,,-33.90853,18.4197174,58.4,0.041,,,,,,,,,,,,,,,,4494.747111,,,0.305862,61.2,241.2,,,,,,,,,,,,,2.367527,98.864799,7.251778,0.493887,579.18689,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,38.082603,,4369.841593 +0,43043.305897,0,149.2,4.78,150,4.91,330.7,,0,58.4,0.04,179.3,,,,1.4,0.58,-0.05,7.17,,0.543,,,,,,,104,,,0,,0,0,34.75,0,,-33.90853,18.4197174,58.4,0.044,,,,,,,,,,,,,,,,4494.747004,,,0.305874,60.8,240.8,,,,,,,,,,,,,2.367501,98.864571,7.301067,0.494079,579.18927,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.699507,,4369.841591 +0,43043.305909,0,148.2,4.8,149.2,4.89,330.4,,0,58.4,0.04,179.4,,,,1.4,0.51,-0.05,7.17,,0.542,,,,,,,104,,,0,,0,0,34.87,0,,-33.9085299,18.4197176,58.4,0.043,,,,,,,,,,,,,,,,4494.747033,,,0.305885,60.5,240.5,,,,,,,,,,,,,2.367507,98.864716,7.254144,0.493431,579.189026,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.528905,,4369.841588 +0,43043.305921,0,145.2,4.74,147.1,4.86,329.7,,0,58.4,0.05,179.6,,,,1.4,0.51,-0.04,7.17,,0.543,,,,,,,104,,,0,,0,0,34.93,0,,-33.9085297,18.4197177,58.4,0.049,,,,,,,,,,,,,,,,4494.74687,,,0.305897,59.9,239.9,,,,,,,,,,,,,2.367527,98.864639,7.141332,0.494522,579.183044,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.369035,,4369.841577 +0,43043.305933,0,142.7,4.65,144.8,4.82,329,,0,58.4,0.05,179.6,,,,1.4,0.53,-0.04,7.17,,0.544,,,,,,,104,,,0,,0,0,34.96,0,,-33.9085296,18.4197175,58.4,0.05,,,,,,,,,,,,,,,,4494.746803,,,0.305909,59.1,239.1,,,,,,,,,,,,,2.367504,98.864037,6.976142,0.494604,579.186462,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.286149,,4369.841565 +0,43043.305944,0,142.3,4.56,144.3,4.77,328.7,,0,58.4,0.05,179.6,,,,1.4,0.53,-0.04,7.17,,0.545,,,,,,,104,,,0,,0,0,34.89,0,,-33.9085292,18.4197173,58.4,0.048,,,,,,,,,,,,,,,,4494.746825,,,0.30592,58.7,238.7,,,,,,,,,,,,,2.367541,98.864273,6.768263,0.495198,579.184937,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.566006,,4369.84154 +0,43043.305957,0,141.1,4.46,143,4.68,328.1,,0,58.4,0.04,179.6,,,,1.4,0.49,-0.06,7.18,,0.547,,,,,,,104,,,0,,0,0,34.87,0,,-33.9085289,18.419717,58.4,0.039,,,,,,,,,,,,,,,,4494.747067,,,0.305932,58.2,238.2,,,,,,,,,,,,,2.367497,98.864983,6.428645,0.495544,579.194458,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.945739,,4369.841514 +0,43043.305968,0,141.2,4.39,142.7,4.61,327.7,,0,58.4,0.06,179.6,,,,1.4,0.44,-0.05,7.17,,0.544,,,,,,,104,,,0,,0,0,34.86,0,,-33.9085286,18.4197169,58.4,0.061,,,,,,,,,,,,,,,,4494.746729,,,0.305943,57.8,237.8,,,,,,,,,,,,,2.367521,98.865654,6.165024,0.494762,579.188416,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,38.349829,,4369.841498 +0,43043.305979,0,141.1,4.29,142.4,4.57,327.4,,0,58.4,0.08,179.4,,,,1.4,0.41,-0.03,7.16,,0.544,,,,,,,104,,,0,,0,0,34.91,0,,-33.9085285,18.4197166,58.4,0.082,,,,,,,,,,,,,,,,4494.745714,,,0.305955,57.5,237.5,,,,,,,,,,,,,2.367497,98.8647,6.012874,0.495066,579.189026,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,38.652994,,4369.84149 +0,43043.305991,0,139.7,4.16,141.2,4.53,326.8,,0,58.4,0.09,179.4,,,,1.4,0.43,-0.04,7.17,,0.544,,,,,,,104,,,0,,0,0,34.94,0,,-33.9085286,18.4197167,58.4,0.095,,,,,,,,,,,,,,,,4494.74531,,,0.305966,56.8,236.8,,,,,,,,,,,,,2.367514,98.864342,5.854272,0.494269,579.185791,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,38.640193,,4369.8415 +0,43043.306002,0,139.5,4.07,140.9,4.49,326.4,,0,58.4,0.07,179.4,,,,1.4,0.47,-0,7.17,,0.546,,,,,,,104,,,0,,0,0,34.94,0,,-33.9085288,18.4197167,58.4,0.073,,,,,,,,,,,,,,,,4494.745619,,,0.305978,56.5,236.5,,,,,,,,,,,,,2.367487,98.864784,5.71282,0.495704,579.18927,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,38.73358,,4369.841509 +0,43043.306015,0,139.6,4.04,140.5,4.48,326,,0,58.4,0.05,179.4,,,,1.4,0.5,-0.01,7.17,,0.548,,,,,,,104,,,0,,0,0,34.9,0,,-33.908529,18.4197167,58.4,0.051,,,,,,,,,,,,,,,,4494.746677,,,0.305992,56,236,,,,,,,,,,,,,2.367487,98.865372,5.657006,0.494508,579.193787,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,38.586456,,4369.841519 +0,43043.306026,0,139.8,4.08,140.6,4.49,325.7,,0,58.4,0.04,179.4,,,,1.4,0.51,0.13,7.17,,0.553,,,,,,,104,,,0,,0,0,34.83,0,,-33.9085289,18.4197168,58.4,0.04,,,,,,,,,,,,,,,,4494.747024,,,0.306001,55.7,235.7,,,,,,,,,,,,,2.367544,98.865784,5.693069,0.494978,579.190979,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,38.369375,,4369.841518 +0,43043.306038,0,140.1,4.18,140.4,4.51,325.4,,0,58.4,0.04,179.5,,,,1.4,0.58,0.04,7.17,,0.549,,,,,,,104,,,0,,0,0,34.85,0,,-33.9085289,18.4197173,58.4,0.043,,,,,,,,,,,,,,,,4494.747023,,,0.306013,55.4,235.4,,,,,,,,,,,,,2.367471,98.864349,5.787839,0.495452,579.183228,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.834913,,4369.84153 +0,43043.306049,0,143,4.26,142.4,4.5,325.7,,0,58.4,0.06,179.5,,,,1.4,0.72,0.06,7.17,,0.55,,,,,,,104,,,0,,0,0,34.93,0,,-33.9085285,18.4197174,58.4,0.06,,,,,,,,,,,,,,,,4494.746438,,,0.306024,55.7,235.7,,,,,,,,,,,,,2.367527,98.864777,5.744619,0.495404,579.191406,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,38.092266,,4369.841516 +0,43043.306061,0,144.1,4.31,143.2,4.48,325.7,,0,58.4,0.06,179.3,,,,1.4,0.85,0.06,7.17,,0.551,,,,,,,104,,,0,,0,0,34.99,0,,-33.9085286,18.4197174,58.4,0.061,,,,,,,,,,,,,,,,4494.746376,,,0.306039,55.7,235.7,,39.5759514,2.5748167,,,,,,,,,,2.367501,98.863998,5.652483,0.494522,579.182373,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,38.450637,,4369.841528 +0,43043.306073,0,146.5,4.37,145,4.49,326.1,,0,58.4,0.06,179.3,,,,1.4,0.89,0.05,7.17,,0.542,,,,,,,104,,,0,,0,0,35.04,0,,-33.9085288,18.4197175,58.4,0.063,,,,,,,,,,,,,,,,4494.746299,,,0.30605,56,236,,,,,,,,,,,,,2.367501,98.863808,5.684719,0.494808,579.183899,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,38.620995,,4369.84154 +0,43043.306085,0,151.8,4.45,148.6,4.5,327,,0,58.4,0.06,179.3,,,,1.3,0.84,0.07,7.17,,0.553,,,,,,,104,,,0,,0,0,34.99,0,,-33.908529,18.4197178,58.4,0.059,,,,,,,,,,,,,,,,4494.746646,,,0.306062,56.9,236.9,,,,,,,,,,,,,2.367507,98.863884,5.737547,0.496847,579.185791,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,39.067871,,4369.841558 +0,43043.306096,0,154.6,4.56,151.3,4.54,327.7,,0,58.4,0.1,179,,,,1.4,0.8,0.07,7.18,,0.549,,,,,,,104,,,0,,0,0,34.97,0,,-33.9085291,18.419718,58.4,0.1,,,,,,,,,,,,,,,,4494.74513,,,0.306071,57.6,237.6,,,,,,,,,,,,,2.367514,98.864517,5.866133,0.495148,579.187744,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,39.49511,,4369.841581 +0,43043.306108,0,154.9,4.75,152.4,4.63,328.1,,0,58.4,0.1,179.4,,,,1.4,0.79,0.07,7.19,,0.553,,,,,,,104,,,0,,0,0,34.98,0,,-33.9085292,18.4197179,58.4,0.099,,,,,,,,,,,,,,,,4494.745193,,,0.306085,58.1,238.1,,,,,,,,,,,,,2.367507,98.864769,6.238055,0.495054,579.182373,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,38.827474,,4369.841569 +0,43043.306120,0,151.6,4.95,150.9,4.76,327.8,,0,58.4,0.11,179.3,,,,1.4,0.81,0.02,7.18,,0.542,,,,,,,104,,,0,,0,0,34.92,0,,-33.9085294,18.4197178,58.4,0.115,,,,,,,,,,,,,,,,4494.74457,,,0.306097,57.9,237.9,,,,,,,,,,,,,2.367464,98.863533,6.728293,0.495422,579.184082,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,37.529799,,4369.841579 +0,43043.306131,0,145.5,5.09,146.9,4.85,326.8,,0,58.4,0.1,179.3,,,,1.4,0.8,-0.03,7.18,,0.568,,,,,,,104,,,0,,0,0,34.87,0,,-33.9085293,18.4197182,58.4,0.104,,,,,,,,,,,,,,,,4494.74531,,,0.306108,56.9,236.9,,,,,,,,,,,,,2.367484,98.863914,7.083571,0.495694,579.183655,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,36.000165,,4369.841585 +0,43043.306143,0,138.5,5.2,141.9,4.88,325.5,,0,58.4,0.14,179.6,,,,1.4,0.69,0.03,7.18,,0.54,,,,,,,104,,,0,,0,0,34.78,0,,-33.9085293,18.4197192,58.4,0.14,,,,,,,,,,,,,,,,4494.743829,,,0.30612,55.6,235.6,,,,,,,,,,,,,2.367554,98.863335,7.188333,0.49929,579.176147,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,34.716986,,4369.841608 +0,43043.306155,0,135.4,5.26,139.1,4.85,324.5,,0,58.4,0.13,180.1,,,,1.4,0.56,0.01,7.18,,0.538,,,,,,,104,,,0,,0,0,34.7,0,,-33.9085294,18.4197202,58.4,0.128,,,,,,,,,,,,,,,,4494.744186,,,0.306131,54.8,234.8,,,,,,,,,,,,,2.367584,98.860855,7.099799,0.496363,579.170105,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,34.056772,,4369.84163 +0,43043.306166,0,130.5,5.22,134.8,4.82,323.2,,0,57.5,0.13,180.1,,,,1.4,0.55,0.01,7.18,,0.545,,,,,,,104,,,0,,0,0,34.63,0,,-33.908529,18.4197211,57.3,0.121,,,,,,,,,,,,,,,,4494.744047,,,0.306143,53.5,233.5,,,,,,,,,,,,,2.367578,98.860954,6.947227,0.497447,579.166443,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,33.579793,,4369.841636 +0,43043.306178,0,130.6,5.17,134.2,4.77,322.6,,0,56.2,0.11,179.7,,,,1.3,0.58,0.01,7.18,,0.538,,,,,,,104,,,0,,0,0,34.67,0,,-33.9085293,18.4197207,56.2,0.109,,,,,,,,,,,,,,,,4494.744776,,,0.306155,52.7,232.7,,,,,,,,,,,,,2.367564,98.860756,6.770802,0.496485,579.175049,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,33.788713,,4369.841651 +0,43043.306190,0,129.7,5.08,132.6,4.74,321.8,,0,55.8,0.12,179.8,,,,1.4,0.5,0,7.17,,0.535,,,,,,,104,,,0,,0,0,34.74,0,,-33.9085298,18.41972,55.8,0.117,,,,,,,,,,,,,,,,4494.744618,,,0.306166,51.9,231.9,,,,,,,,,,,,,2.367571,99.007469,6.664012,0.497367,581.100708,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,33.518448,,4369.841646 +0,43043.306202,0,130.1,4.96,132.6,4.75,321.3,,0,55.8,0.1,179.9,,,,1.4,0.34,-0.03,7.17,,0.481,,,,,,,104,,,0,,0,0,34.66,0,,-33.9085299,18.4197202,55.8,0.1,,,,,,,,,,,,,,,,4494.744771,,,0.306178,51.4,231.4,,,,,,,,,,,,,2.365773,99.772903,6.686917,0.471604,585.662476,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,33.219946,,4369.841653 +0,43043.306213,0,132.3,4.74,133.2,4.73,321.1,,0,55.8,0.08,179.8,,,,1.6,0.21,-0.01,7.18,,0.465,,,,,,,104,,,0,,0,0,34.57,0,,-33.9085301,18.4197204,55.8,0.077,,,,,,,,,,,,,,,,4494.745837,,,0.306189,51,231,,,,,,,,,,,,,2.364245,100.821457,6.619851,0.424921,591.220581,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,33.117383,,4369.841672 +0,43043.306225,0,138.2,4.57,137.1,4.71,321.8,,0,55.8,0.06,179.5,,,,1.4,0.19,-0.04,7.17,,0.446,,,,,,,104,,,0,,0,0,34.52,0,,-33.9085308,18.4197203,55.8,0.061,,,,,,,,,,,,,,,,4494.7464,,,0.306201,51.7,231.7,,,,,,,,,,,,,2.363921,101.887444,6.54773,0.417969,595.222595,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,33.840911,,4369.841714 +0,43043.306237,0,141,4.6,139.4,4.77,322.1,,0,55.8,0.07,179.2,,,,1.4,0.29,-0,7.19,,0.439,,,,,,,104,,,0,,0,0,34.5,0,,-33.9085314,18.4197198,55.8,0.068,,,,,,,,,,,,,,,,4494.74639,,,0.306215,52,232,,,,,,,,,,,,,2.362825,102.492744,6.754865,0.394745,597.877625,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,33.863526,,4369.841735 +0,43043.306249,0,143.7,4.78,141.4,4.86,322.5,,0,55.8,0.09,179.3,,,,1.4,0.35,-0,7.18,,0.441,,,,,,,104,,,0,,0,0,34.5,0,,-33.9085316,18.41972,55.8,0.092,,,,,,,,,,,,,,,,4494.745476,,,0.306227,52.4,232.4,,,,,,,,,,,,,2.362845,102.847641,7.134893,0.394569,598.614563,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,33.25136,,4369.84175 +0,43043.306260,0,146.5,5.02,143.8,4.97,323,,0,55.8,0.09,179.3,,,,1.4,0.3,-0.02,7.17,,0.441,,,,,,,104,,,0,,0,0,34.51,0,,-33.9085316,18.4197203,55.8,0.087,,,,,,,,,,,,,,,,4494.745482,,,0.306238,52.9,232.9,,,,,,,,,,,,,2.362792,102.896881,7.545109,0.398081,598.711548,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,32.74152,,4369.841757 +0,43043.306272,0,147.8,5.33,145.2,5.1,323.4,,0,55.8,0.07,179.2,,,,1.4,0.22,-0.01,7.18,,0.445,,,,,,,104,,,0,,0,0,34.51,0,,-33.9085316,18.4197205,55.8,0.069,,,,,,,,,,,,,,,,4494.746137,,,0.306247,53.4,233.4,,,,,,,,,,,,,2.362818,102.911133,8.060515,0.397374,598.774536,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,32.456471,,4369.841769 +0,43043.306284,0,148,5.79,146,5.33,323.6,,0,55.8,0.05,179.2,,,,1.4,0.22,-0.02,7.18,,0.458,,,,,,,104,,,0,,0,0,34.48,0,,-33.9085317,18.4197209,55.8,0.056,,,,,,,,,,,,,,,,4494.746909,,,0.306262,53.6,233.6,,,,,,,,,,,,,2.362845,102.932083,8.930959,0.396582,598.878845,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,31.621008,,4369.841779 +0,43043.306295,0,146.6,6.18,145.5,5.54,323.5,,0,55.8,0.08,179.3,,,,1.4,0.31,-0.01,7.16,,0.599,,,,,,,104,,,0,,0,0,34.43,0,,-33.9085315,18.4197211,55.8,0.084,,,,,,,,,,,,,,,,4494.745723,,,0.306273,53.6,233.6,,,,,,,,,,,,,2.366752,102.944511,9.646354,0.446602,598.913452,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,29.906062,,4369.841785 +0,43043.306307,0,145.3,6.61,144.7,5.72,323.4,,0,55.8,0.08,179.5,,,,1.4,0.38,-0.02,7.19,,0.657,,,,,,,104,,,0,,0,0,34.44,0,,-33.9085314,18.4197215,55.8,0.082,,,,,,,,,,,,,,,,4494.745765,,,0.306285,53.4,233.4,,,,,,,,,,,,,2.370409,102.947891,10.227253,0.558491,598.919434,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,28.078976,,4369.841781 +0,43043.306319,0,144.3,7.06,144.2,5.91,323.3,,0,55.8,0.07,179.5,,,,1.4,0.37,-0.02,7.19,,0.672,,,,,,,104,,,0,,0,0,34.41,0,,-33.9085312,18.4197216,55.8,0.062,,,,,,,,,,,,,,,,4494.745845,,,0.306293,53.3,233.3,,,,,,,,,,,,,2.371047,102.947685,10.765502,0.580573,598.915161,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,26.559608,,4369.841778 +0,43043.306331,0,142.9,7.36,143.2,6.06,323.1,,0,55.8,0.04,179.5,,,,1.3,0.34,-0.02,7.17,,0.84,,,,,,,105,,,0,,0,0,34.44,0,,-33.9085311,18.419722,55.8,0.042,,,,,,,,,,,,,,,,4494.747071,,,0.306308,53.1,233.1,,,,,,,,,,,,,2.377057,102.942726,11.129307,0.648761,598.823792,0,,,,,,,,,,,,,,,,,,,,,,,,,,,,25.354303,,4369.841785 diff --git a/java/com.sap.sailing.expeditionconnector.test/resources/Expedition_28Oct17_0820.txt b/java/com.sap.sailing.expeditionconnector.test/resources/Expedition_28Oct17_0820.txt new file mode 100755 index 00000000000..38a4c37b4d2 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.test/resources/Expedition_28Oct17_0820.txt @@ -0,0 +1,159 @@ + #0,1,0.000,2,-129.8,3,2.64,4,-125.4,5,2.54,6,42.8,9,153.4,10,0.00,11,0.0,12,0.12,13,153.4,17,-3.00,18,0.50,19,0.28,20,18.41,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.126,146,0.347763,152,109.8,172,1.872,173,2.0,175,1.185,176,294.957,238,3.256*0E + #0,1,0.000,2,-130.2,3,2.65,4,-125.7,5,2.54,6,42.7,9,153.4,10,0.00,11,0.0,12,0.13,13,153.4,17,-3.00,18,0.50,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.136,146,0.347766,152,109.2,172,1.872,173,2.0,175,1.185,176,294.959,238,3.256*03 + #0,1,0.000,2,-130.7,3,2.65,4,-126.0,5,2.54,6,42.5,9,153.4,10,0.00,11,0.0,12,0.14,13,153.4,17,-3.00,18,0.50,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.146,146,0.347766,152,108.6,172,1.872,173,2.0,175,1.185,176,294.960,238,3.256*0F + #0,9,153.4,11,0.0,12,0.15,152,107.9,238,3.256*32 + #0,1,0.000,2,-131.2,3,2.65,4,-126.4,5,2.54,6,42.3,9,153.4,10,0.00,11,0.0,12,0.16,13,153.4,17,-3.00,18,0.50,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419951,50,0.0,51,0.155,146,0.347769,152,107.1,172,1.872,173,2.0,175,1.184,176,294.959,238,3.256*04 + #0,1,0.000,2,-132.4,3,2.65,4,-127.4,5,2.54,6,41.9,9,153.4,10,0.00,11,0.0,12,0.18,13,153.4,17,-3.00,18,0.50,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419951,50,0.0,51,0.176,146,0.347769,152,105.3,172,1.872,173,2.0,175,1.184,176,294.954,238,3.255*08 + #0,1,0.000,2,-133.1,3,2.65,4,-127.9,5,2.54,6,41.7,9,153.4,10,0.00,11,0.0,12,0.18,13,153.4,17,-3.00,18,0.50,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.176,146,0.347772,152,104.3,172,1.872,173,2.0,175,1.183,176,294.956,238,3.254*01 + #0,1,0.000,2,-133.8,3,2.66,4,-128.4,5,2.54,6,41.5,9,153.4,10,0.00,11,0.0,12,0.17,13,153.4,17,-3.00,18,0.50,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.173,146,0.347772,152,103.2,172,1.872,173,2.0,175,1.184,176,294.956,238,3.254*00 + #0,1,0.000,2,-134.5,3,2.66,4,-128.9,5,2.54,6,41.3,9,153.4,10,0.00,11,0.0,12,0.17,13,153.4,17,-3.00,18,0.50,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419951,50,0.0,51,0.167,146,0.347775,172,1.872,173,2.0,175,1.184,176,294.954*0F + #0,1,0.000,2,-135.1,3,2.66,4,-129.5,5,2.54,6,41.1,9,153.4,10,0.00,11,0.0,12,0.16,13,153.4,17,-3.00,18,0.50,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.159,146,0.347775,152,102.1,172,1.872,173,2.0,175,1.184,176,294.956,238,3.253*00 + #0,1,0.000,2,-135.8,3,2.66,4,-130.0,5,2.54,6,40.9,9,153.4,10,0.00,11,0.0,12,0.15,13,153.4,17,-3.00,18,0.50,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.152,146,0.347775,152,101.0,172,1.872,173,2.0,175,1.184,176,294.957,238,3.252*07 + #0,1,0.000,2,-136.3,3,2.66,4,-130.6,5,2.54,6,40.6,9,153.4,10,0.00,11,0.0,12,0.15,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.145,146,0.347778,152,99.9,172,1.872,173,2.0,175,1.184,176,294.958,238,3.252*3A + #0,1,0.000,2,-136.8,3,2.66,4,-131.0,5,2.54,6,40.4,9,153.4,10,0.00,11,0.0,12,0.14,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.138,146,0.347778,152,98.9,172,1.872,173,2.0,175,1.184,176,294.956,238,3.251*33 + #0,1,0.000,2,-137.2,3,2.66,4,-131.5,5,2.54,6,40.3,9,153.4,10,0.00,11,0.0,12,0.14,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.131,146,0.347781,152,97.9,172,1.872,173,2.0,175,1.183,176,294.957,238,3.251*3C + #0,9,153.4,11,0.0,12,0.13,152,97.0,238,3.250*03 + #0,1,0.000,2,-137.8,3,2.66,4,-132.2,5,2.54,6,39.9,9,153.4,10,0.00,11,0.0,12,0.16,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419951,50,0.0,51,0.160,146,0.347781,152,95.6,172,1.872,173,2.0,175,1.184,176,294.956,238,3.250*38 + #0,1,0.000,2,-137.8,3,2.66,4,-132.2,5,2.54,6,39.9,9,153.4,10,0.00,11,0.0,12,0.16,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419951,50,0.0,51,0.160,146,0.347781,152,95.6,172,1.872,173,2.0,175,1.184,176,294.956,238,3.249*30 + #0,1,0.000,2,-138.0,3,2.67,4,-132.6,5,2.54,6,39.6,9,153.4,10,0.00,11,0.0,12,0.19,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419951,50,0.0,51,0.193,146,0.347784,152,94.7,172,1.872,173,2.0,175,1.184,176,294.954,238,3.249*39 + #0,1,0.000,2,-138.0,3,2.67,4,-132.8,5,2.54,6,39.5,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.209,146,0.347786,152,94.4,172,1.872,173,2.0,175,1.184,176,294.954,238,3.250*36 + #0,1,0.000,2,-138.0,3,2.67,4,-132.9,5,2.54,6,39.4,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.223,146,0.347786,152,94.2,172,1.872,173,2.0,175,1.184,176,294.955,238,3.251*3B + #0,1,0.000,2,-138.0,3,2.67,4,-133.0,5,2.54,6,39.3,9,153.4,10,0.00,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.192,48,-33.907350,49,18.419951,50,0.0,51,0.233,146,0.347786,152,94.2,172,1.872,173,2.0,175,1.184,176,294.954,238,3.252*05 + #0,1,0.000,2,-138.0,3,2.67,4,-133.1,5,2.54,6,39.1,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.192,48,-33.907350,49,18.419951,50,0.0,51,0.233,146,0.347789,152,94.0,172,1.872,173,2.0,175,1.184,176,294.951,238,3.254*3A + #0,1,0.000,2,-138.0,3,2.67,4,-133.2,5,2.54,6,39.0,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.230,146,0.347789,152,93.8,172,1.872,173,2.0,175,1.184,176,294.951,238,3.254*35 + #0,1,0.000,2,-138.1,3,2.67,4,-133.3,5,2.55,6,38.9,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.223,146,0.347792,152,93.6,172,1.872,173,2.0,175,1.185,176,294.951,238,3.255*3B + #0,1,0.000,2,-138.2,3,2.68,4,-133.5,5,2.55,6,38.7,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.192,48,-33.907350,49,18.419951,50,0.0,51,0.217,146,0.347792,152,93.3,172,1.872,173,2.0,175,1.184,176,294.952,238,3.257*3C + #0,1,0.000,2,-138.4,3,2.68,4,-133.7,5,2.55,6,38.6,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.210,146,0.347792,152,93.0,172,1.872,173,2.0,175,1.184,176,294.954,238,3.259*34 + #0,1,0.000,2,-138.9,3,2.68,4,-134.1,5,2.55,6,38.2,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419951,50,0.0,51,0.210,146,0.347795,152,92.7,172,1.872,173,2.0,175,1.184,176,294.954,238,3.261*35 + #0,9,153.4,11,0.0,12,0.21,152,91.7,238,3.264*04 + #0,1,0.000,2,-139.1,3,2.69,4,-134.4,5,2.56,6,38.1,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.210,146,0.347798,152,91.2,172,1.872,173,2.0,175,1.185,176,294.958,238,3.270*3F + #0,1,0.000,2,-139.4,3,2.69,4,-134.7,5,2.56,6,37.9,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419951,50,0.0,51,0.207,146,0.347798,152,90.7,172,1.872,173,2.0,175,1.185,176,294.958,238,3.274*3B + #0,1,0.000,2,-139.6,3,2.69,4,-134.9,5,2.56,6,37.8,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419951,50,0.0,51,0.207,146,0.347798,152,90.2,172,1.872,173,2.0,175,1.185,176,294.960,238,3.278*35 + #0,1,0.000,2,-139.8,3,2.70,4,-135.1,5,2.57,6,37.6,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419951,50,0.0,51,0.207,146,0.347801,152,89.7,172,1.872,173,2.0,175,1.185,176,294.961,238,3.282*30 + #0,1,0.000,2,-139.9,3,2.70,4,-135.4,5,2.57,6,37.5,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419951,50,0.0,51,0.207,146,0.347801,152,89.3,172,1.872,173,2.0,175,1.185,176,294.960,238,3.287*34 + #0,1,0.000,2,-140.0,3,2.70,4,-135.5,5,2.58,6,37.3,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.207,146,0.347804,152,89.0,172,1.872,173,2.0,175,1.185,176,294.962,238,3.292*39 + #0,1,0.000,2,-140.0,3,2.71,4,-135.7,5,2.58,6,37.2,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907350,49,18.419950,50,0.0,51,0.207,146,0.347804,152,88.7,172,1.872,173,2.0,175,1.185,176,294.958,238,3.297*30 + #0,1,0.000,2,-140.0,3,2.71,4,-135.8,5,2.58,6,37.1,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419950,50,0.0,51,0.207,146,0.347804,152,88.4,172,1.872,173,2.0,175,1.185,176,294.956,238,3.302*3F + #0,1,0.000,2,-140.0,3,2.72,4,-135.9,5,2.59,6,36.9,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.207,146,0.347807,152,88.3,172,1.872,173,2.0,175,1.185,176,294.956,238,3.308*3A + #0,1,0.000,2,-140.0,3,2.72,4,-136.0,5,2.59,6,36.8,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.207,146,0.347807,152,88.1,172,1.872,173,2.0,175,1.185,176,294.956,238,3.313*39 + #0,1,0.000,2,-140.2,3,2.73,4,-136.2,5,2.60,6,36.5,9,153.4,10,0.00,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.207,146,0.347810,172,1.872,173,2.0,175,1.184,176,294.951*33 + #0,9,153.4,11,0.0,12,0.21,152,87.6,238,3.318*08 + #0,1,0.000,2,-140.4,3,2.73,4,-136.3,5,2.60,6,36.4,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.207,146,0.347810,152,87.3,172,1.872,173,2.0,175,1.184,176,294.949,238,3.328*35 + #0,1,0.000,2,-140.6,3,2.73,4,-136.5,5,2.61,6,36.2,9,153.4,10,0.00,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.214,146,0.347813,152,87.3,172,1.872,173,2.0,175,1.184,176,294.950,238,3.333*05 + #0,1,0.000,2,-141.0,3,2.74,4,-136.8,5,2.61,6,36.0,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.222,146,0.347813,152,86.9,172,1.872,173,2.0,175,1.184,176,294.951,238,3.337*31 + #0,1,0.000,2,-141.4,3,2.74,4,-137.1,5,2.61,6,35.9,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907350,49,18.419950,50,0.0,51,0.229,146,0.347815,152,86.4,172,1.872,173,2.0,175,1.184,176,294.951,238,3.337*36 + #0,1,0.000,2,-141.8,3,2.74,4,-137.4,5,2.62,6,35.7,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.212,146,0.347815,152,85.8,172,1.872,173,2.0,175,1.184,176,294.953,238,3.341*35 + #0,1,0.000,2,-142.3,3,2.75,4,-137.8,5,2.62,6,35.5,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907350,49,18.419950,50,0.0,51,0.195,146,0.347815,152,85.1,172,1.872,173,2.0,175,1.185,176,294.954,238,3.345*35 + #0,1,0.000,2,-142.8,3,2.75,4,-138.2,5,2.62,6,35.3,9,153.4,10,0.00,11,0.0,12,0.20,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.178,146,0.347818,152,84.4,172,1.872,173,2.0,175,1.185,176,294.954,238,3.349*3B + #0,1,0.000,2,-143.3,3,2.75,4,-138.6,5,2.63,6,35.0,9,153.4,10,0.00,11,0.0,12,0.18,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907350,49,18.419950,50,0.0,51,0.161,146,0.347818,152,83.6,172,1.872,173,2.0,175,1.185,176,294.954,238,3.353*3B + #0,1,0.000,2,-143.8,3,2.75,4,-139.1,5,2.63,6,34.8,9,153.4,10,0.00,11,0.0,12,0.16,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.144,146,0.347821,152,82.7,172,1.872,173,2.0,175,1.186,176,294.955,238,3.357*3B + #0,1,0.000,2,-144.6,3,2.76,4,-139.9,5,2.63,6,34.4,9,153.4,10,0.00,11,0.0,12,0.14,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.193,48,-33.907350,49,18.419950,50,0.0,51,0.158,146,0.347821,152,81.9,172,1.872,173,2.0,175,1.186,176,294.962,238,3.360*37 + #0,1,0.000,2,-144.6,3,2.76,4,-139.9,5,2.63,6,34.4,9,153.4,10,0.00,11,0.0,12,0.16,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.193,48,-33.907350,49,18.419950,50,0.0,51,0.158,146,0.347821,152,80.3,172,1.872,173,2.0,175,1.186,176,294.962,238,3.364*3A + #0,9,153.4,11,0.0,12,0.16,152,80.3,238,3.370*00 + #0,1,0.000,2,-145.3,3,2.76,4,-140.5,5,2.64,6,34.1,9,153.4,10,0.00,11,0.0,12,0.15,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907350,49,18.419950,50,0.0,51,0.146,146,0.347824,152,78.9,172,1.872,173,2.0,175,1.185,176,294.964,238,3.370*3B + #0,1,0.000,2,-145.5,3,2.76,4,-140.8,5,2.64,6,33.9,9,153.4,10,0.00,11,0.0,12,0.15,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907350,49,18.419950,50,0.0,51,0.146,146,0.347827,152,78.4,172,1.872,173,2.0,175,1.185,176,294.962,238,3.375*32 + #0,1,0.000,2,-145.6,3,2.76,4,-141.0,5,2.64,6,33.7,9,153.4,10,0.00,11,0.0,12,0.15,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.145,146,0.347827,152,77.9,172,1.872,173,2.0,175,1.185,176,294.965,238,3.377*30 + #0,1,0.000,2,-145.7,3,2.76,4,-141.2,5,2.64,6,33.6,9,153.4,10,0.00,11,0.0,12,0.14,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.144,146,0.347827,152,77.5,172,1.872,173,2.0,175,1.184,176,294.964,238,3.379*33 + #0,1,0.000,2,-145.7,3,2.76,4,-141.4,5,2.64,6,33.5,9,153.4,10,0.00,11,0.0,12,0.17,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.168,146,0.347830,152,77.3,172,1.872,173,2.0,175,1.184,176,294.962,238,3.381*3A + #0,1,0.000,2,-145.7,3,2.76,4,-141.5,5,2.64,6,33.3,9,153.4,10,0.00,11,0.0,12,0.19,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.188,146,0.347830,152,77.0,172,1.872,173,2.0,175,1.183,176,294.963,238,3.383*3A + #0,1,0.000,2,-145.7,3,2.76,4,-141.6,5,2.65,6,33.2,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.205,146,0.347833,152,76.9,172,1.872,173,2.0,175,1.183,176,294.963,238,3.385*3A + #0,1,0.000,2,-145.6,3,2.76,4,-141.6,5,2.65,6,33.1,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.212,146,0.347833,152,76.8,172,1.872,173,2.0,175,1.183,176,294.963,238,3.387*3D + #0,1,0.000,2,-145.6,3,2.76,4,-141.7,5,2.65,6,32.9,9,153.4,10,0.00,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.218,146,0.347833,152,76.8,172,1.872,173,2.0,175,1.183,176,294.959,238,3.388*09 + #0,1,0.000,2,-145.5,3,2.76,4,-141.7,5,2.65,6,32.8,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.222,146,0.347836,152,76.7,172,1.872,173,2.0,175,1.184,176,294.958,238,3.390*37 + #0,1,0.000,2,-145.4,3,2.77,4,-141.8,5,2.65,6,32.6,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.218,146,0.347839,152,76.6,172,1.872,173,2.0,175,1.184,176,294.961,238,3.390*3B + #0,9,153.4,11,0.0,12,0.22,152,76.4,238,3.392*05 + #0,1,0.000,2,-145.3,3,2.77,4,-141.8,5,2.65,6,32.4,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.208,146,0.347839,152,76.3,172,1.872,173,2.0,175,1.184,176,294.959,238,3.395*34 + #0,1,0.000,2,-145.3,3,2.77,4,-141.9,5,2.65,6,32.3,9,153.4,10,0.00,11,0.0,12,0.20,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.197,146,0.347839,152,76.2,172,1.872,173,2.0,175,1.184,176,294.960,238,3.396*3E + #0,1,0.000,2,-145.2,3,2.77,4,-141.9,5,2.66,6,32.2,9,153.4,10,0.00,11,0.0,12,0.20,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.43,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.197,146,0.347841,152,76.2,172,1.872,173,2.0,175,1.184,176,294.959,238,3.398*37 + #0,1,0.000,2,-145.1,3,2.77,4,-141.9,5,2.66,6,32.1,9,153.4,10,0.00,11,0.0,12,0.20,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.43,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.201,146,0.347841,152,76.2,172,1.872,173,2.0,175,1.184,176,294.959,238,3.400*3D + #0,1,0.000,2,-144.9,3,2.77,4,-141.9,5,2.66,6,32.0,9,153.4,10,0.00,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.43,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.207,146,0.347844,172,1.872,173,2.0,175,1.184,176,294.958*3D + #0,1,0.000,2,-144.8,3,2.78,4,-141.9,5,2.66,6,31.9,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.43,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.207,146,0.347844,152,76.2,172,1.872,173,2.0,175,1.184,176,294.958,238,3.402*30 + #0,1,0.000,2,-144.6,3,2.78,4,-141.8,5,2.66,6,31.8,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.207,146,0.347844,152,76.3,172,1.872,173,2.0,175,1.184,176,294.958,238,3.404*38 + #0,1,0.000,2,-144.4,3,2.78,4,-141.8,5,2.66,6,31.7,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.214,146,0.347847,152,76.4,172,1.872,173,2.0,175,1.184,176,294.957,238,3.406*3E + #0,1,0.000,2,-144.2,3,2.78,4,-141.7,5,2.67,6,31.6,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.220,146,0.347847,152,76.5,172,1.872,173,2.0,175,1.184,176,294.958,238,3.408*33 + #0,1,0.000,2,-144.0,3,2.79,4,-141.6,5,2.67,6,31.5,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.226,146,0.347850,152,76.6,172,1.872,173,2.0,175,1.185,176,294.958,238,3.410*3A + #0,1,0.000,2,-143.8,3,2.79,4,-141.5,5,2.67,6,31.4,9,153.4,10,0.00,11,0.0,12,0.24,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419950,50,0.0,51,0.244,146,0.347850,152,76.8,172,1.872,173,2.0,175,1.184,176,294.957,238,3.413*36 + #0,1,0.000,2,-143.7,3,2.79,4,-141.5,5,2.67,6,31.3,9,153.4,10,0.00,11,0.0,12,0.24,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.244,146,0.347853,152,77.0,172,1.872,173,2.0,175,1.185,176,294.959,238,3.413*33 + #0,9,153.4,11,0.0,12,0.24,152,77.1,238,3.416*0C + #0,1,0.000,2,-143.6,3,2.79,4,-141.5,5,2.67,6,31.1,9,153.4,10,0.00,11,0.0,12,0.24,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.244,146,0.347853,152,77.1,172,1.872,173,2.0,175,1.185,176,294.960,238,3.417*3E + #0,1,0.000,2,-143.6,3,2.80,4,-141.5,5,2.67,6,31.0,9,153.4,10,0.00,11,0.0,12,0.24,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.193,48,-33.907350,49,18.419949,50,0.0,51,0.244,146,0.347856,152,77.0,172,1.872,173,2.0,175,1.184,176,294.959,238,3.418*38 + #0,1,0.000,2,-143.7,3,2.80,4,-141.5,5,2.67,6,30.9,9,153.4,10,0.00,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.193,48,-33.907350,49,18.419949,50,0.0,51,0.237,146,0.347856,152,77.0,172,1.872,173,2.0,175,1.184,176,294.959,238,3.419*01 + #0,1,0.000,2,-143.8,3,2.80,4,-141.6,5,2.67,6,30.8,9,153.4,10,0.00,11,0.0,12,0.24,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.231,146,0.347856,152,77.0,172,1.872,173,2.0,175,1.184,176,294.959,238,3.420*34 + #0,1,0.000,2,-143.9,3,2.80,4,-141.7,5,2.67,6,30.7,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.231,146,0.347859,152,76.8,172,1.872,173,2.0,175,1.185,176,294.961,238,3.420*30 + #0,1,0.000,2,-144.0,3,2.80,4,-141.8,5,2.67,6,30.5,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.231,146,0.347859,152,76.6,172,1.872,173,2.0,175,1.185,176,294.961,238,3.421*3C + #0,1,0.000,2,-144.1,3,2.81,4,-141.9,5,2.67,6,30.4,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.231,146,0.347862,152,76.4,172,1.872,173,2.0,175,1.184,176,294.959,238,3.421*3C + #0,1,0.000,2,-144.3,3,2.81,4,-142.1,5,2.67,6,30.2,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.251,146,0.347862,152,76.1,172,1.872,173,2.0,175,1.185,176,294.962,238,3.422*3A + #0,1,0.000,2,-144.4,3,2.81,4,-142.2,5,2.67,6,30.1,9,153.4,10,0.00,11,0.0,12,0.25,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.271,146,0.347862,152,75.9,172,1.872,173,2.0,175,1.185,176,294.963,238,3.422*33 + #0,1,0.000,2,-144.7,3,2.82,4,-142.5,5,2.67,6,29.8,9,153.4,10,0.00,11,0.0,12,0.27,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.258,146,0.347865,152,75.6,172,1.872,173,2.0,175,1.185,176,294.961,238,3.422*37 + #0,1,0.000,2,-144.8,3,2.82,4,-142.6,5,2.67,6,29.7,9,153.4,10,0.00,11,0.0,12,0.26,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907350,49,18.419949,50,0.0,51,0.252,146,0.347867,152,75.1,172,1.872,173,2.0,175,1.185,176,294.962,238,3.423*39 + #0,9,153.4,11,0.0,12,0.25,152,74.8,238,3.423*01 + #0,1,0.000,2,-144.8,3,2.82,4,-142.7,5,2.67,6,29.6,9,153.4,10,0.00,11,0.0,12,0.25,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907350,49,18.419949,50,0.0,51,0.245,146,0.347867,152,74.6,172,1.872,173,2.0,175,1.185,176,294.962,238,3.422*3B + #0,1,0.000,2,-144.9,3,2.82,4,-142.8,5,2.67,6,29.5,9,153.4,10,0.00,11,0.0,12,0.24,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.238,146,0.347867,152,74.4,172,1.872,173,2.0,175,1.185,176,294.964,238,3.422*39 + #0,1,0.000,2,-145.0,3,2.83,4,-142.9,5,2.67,6,29.3,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.232,146,0.347870,152,74.2,172,1.872,173,2.0,175,1.184,176,294.963,238,3.422*3D + #0,1,0.000,2,-145.0,3,2.83,4,-143.0,5,2.67,6,29.2,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.225,146,0.347870,152,74.1,172,1.872,173,2.0,175,1.185,176,294.962,238,3.421*32 + #0,1,0.000,2,-145.0,3,2.83,4,-143.0,5,2.67,6,29.1,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.219,146,0.347873,152,73.9,172,1.872,173,2.0,175,1.185,176,294.964,238,3.421*35 + #0,1,0.000,2,-145.0,3,2.83,4,-143.1,5,2.67,6,29.0,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419949,50,0.0,51,0.219,146,0.347873,152,73.8,172,1.872,173,2.0,175,1.185,176,294.961,238,3.420*32 + #0,1,0.000,2,-145.0,3,2.83,4,-143.1,5,2.67,6,28.9,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.219,146,0.347873,152,73.8,172,1.872,173,2.0,175,1.185,176,294.962,238,3.418*30 + #0,1,0.000,2,-145.0,3,2.83,4,-143.1,5,2.67,6,28.8,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.219,146,0.347876,152,73.7,172,1.872,173,2.0,175,1.185,176,294.961,238,3.417*37 + #0,1,0.000,2,-144.9,3,2.83,4,-143.2,5,2.67,6,28.7,9,153.4,10,0.00,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.219,146,0.347876,152,73.7,172,1.872,173,2.0,175,1.184,176,294.961,238,3.416*03 + #0,1,0.000,2,-144.8,3,2.82,4,-143.2,5,2.66,6,28.5,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.226,146,0.347879,172,1.872,173,2.0,175,1.185,176,294.962*07 + #0,1,0.000,2,-144.7,3,2.82,4,-143.2,5,2.66,6,28.4,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.233,146,0.347879,152,73.6,172,1.872,173,2.0,175,1.185,176,294.960,238,3.414*30 + #0,9,153.4,11,0.0,12,0.23,152,73.7,238,3.411*0E + #0,1,0.000,2,-144.7,3,2.82,4,-143.1,5,2.66,6,28.3,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.227,146,0.347882,152,73.7,172,1.872,173,2.0,175,1.185,176,294.960,238,3.409*38 + #0,1,0.000,2,-144.6,3,2.81,4,-143.1,5,2.66,6,28.2,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.220,146,0.347882,152,73.7,172,1.872,173,2.0,175,1.185,176,294.958,238,3.406*39 + #0,1,0.000,2,-144.5,3,2.81,4,-143.1,5,2.66,6,28.1,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.214,146,0.347885,152,73.7,172,1.872,173,2.0,175,1.185,176,294.955,238,3.406*34 + #0,1,0.000,2,-144.4,3,2.81,4,-143.1,5,2.66,6,28.0,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.207,146,0.347885,152,73.9,172,1.872,173,2.0,175,1.185,176,294.955,238,3.402*3C + #0,1,0.000,2,-144.3,3,2.80,4,-143.0,5,2.65,6,28.0,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.201,146,0.347885,152,73.9,172,1.872,173,2.0,175,1.185,176,294.955,238,3.402*3E + #0,1,0.000,2,-144.2,3,2.80,4,-143.0,5,2.65,6,27.9,9,153.4,10,0.00,11,0.0,12,0.20,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.201,146,0.347888,152,73.9,172,1.872,173,2.0,175,1.185,176,294.955,238,3.399*30 + #0,1,0.000,2,-144.0,3,2.79,4,-142.9,5,2.65,6,27.8,9,153.4,10,0.00,11,0.0,12,0.20,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.202,146,0.347888,152,74.0,172,1.872,173,2.0,175,1.185,176,294.957,238,3.397*3C + #0,1,0.000,2,-143.9,3,2.78,4,-142.9,5,2.65,6,27.7,9,153.4,10,0.00,11,0.0,12,0.20,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.202,146,0.347891,152,74.1,172,1.872,173,2.0,175,1.185,176,294.955,238,3.394*34 + #0,1,0.000,2,-143.5,3,2.77,4,-142.7,5,2.64,6,27.6,9,153.4,10,0.00,11,0.0,12,0.20,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907349,49,18.419949,50,0.0,51,0.178,146,0.347891,152,74.2,172,1.872,173,2.0,175,1.185,176,294.957,238,3.391*32 + #0,1,0.000,2,-143.3,3,2.76,4,-142.6,5,2.64,6,27.5,9,153.4,10,0.00,11,0.0,12,0.18,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.185,146,0.347894,152,74.6,172,1.872,173,2.0,175,1.185,176,294.958,238,3.388*39 + #0,1,0.000,2,-143.0,3,2.75,4,-142.5,5,2.64,6,27.5,9,153.4,10,0.00,11,0.0,12,0.19,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907349,49,18.419949,50,0.0,51,0.193,146,0.347894,152,74.8,172,1.872,173,2.0,175,1.185,176,294.960,238,3.382*32 + #0,9,153.4,11,0.0,12,0.19,152,75.1,238,3.379*0E + #0,1,0.000,2,-142.8,3,2.74,4,-142.3,5,2.63,6,27.4,9,153.4,10,0.00,11,0.0,12,0.20,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.200,146,0.347896,152,75.4,172,1.872,173,2.0,175,1.185,176,294.961,238,3.375*3F + #0,1,0.000,2,-142.4,3,2.73,4,-142.1,5,2.63,6,27.4,9,153.4,10,0.00,11,0.0,12,0.18,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.180,146,0.347896,152,75.7,172,1.872,173,2.0,175,1.185,176,294.960,238,3.372*32 + #0,1,0.000,2,-142.1,3,2.72,4,-141.9,5,2.63,6,27.4,9,153.4,10,0.00,11,0.0,12,0.16,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.159,146,0.347896,152,76.1,172,1.872,173,2.0,175,1.185,176,294.961,238,3.368*38 + #0,1,0.000,2,-141.7,3,2.71,4,-141.7,5,2.63,6,27.4,9,153.4,10,0.00,11,0.0,12,0.16,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.156,146,0.347899,152,76.6,172,1.872,173,2.0,175,1.184,176,294.962,238,3.365*38 + #0,1,0.000,2,-141.3,3,2.70,4,-141.5,5,2.62,6,27.4,9,153.4,10,0.00,11,0.0,12,0.16,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907349,49,18.419949,50,0.0,51,0.156,146,0.347899,152,77.1,172,1.872,173,2.0,175,1.184,176,294.960,238,3.361*3E + #0,1,0.000,2,-141.0,3,2.69,4,-141.2,5,2.62,6,27.3,9,153.4,10,0.00,11,0.0,12,0.16,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.159,146,0.347902,152,77.6,172,1.872,173,2.0,175,1.184,176,294.960,238,3.358*3C + #0,1,0.000,2,-140.6,3,2.68,4,-141.0,5,2.62,6,27.3,9,153.4,10,0.00,11,0.0,12,0.15,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.149,146,0.347902,152,78.1,172,1.872,173,2.0,175,1.185,176,294.961,238,3.355*3F + #0,1,0.000,2,-140.2,3,2.67,4,-140.7,5,2.62,6,27.3,9,153.4,10,0.00,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907350,49,18.419949,50,0.0,51,0.138,146,0.347902,152,78.1,172,1.872,173,2.0,175,1.184,176,294.959,238,3.351*0C + #0,1,0.000,2,-139.4,3,2.65,4,-140.2,5,2.61,6,27.3,9,153.4,10,0.00,11,0.0,12,0.15,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419949,50,0.0,51,0.155,146,0.347905,172,1.872,173,2.0,175,1.184,176,294.957*01 + #0,1,0.000,2,-139.1,3,2.64,4,-139.9,5,2.61,6,27.3,9,153.4,10,0.00,11,0.0,12,0.17,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419948,50,0.0,51,0.168,146,0.347908,152,79.7,172,1.872,173,2.0,175,1.185,176,294.959,238,3.341*3E + #0,9,153.4,11,0.0,12,0.17,152,80.2,238,3.341*02 + #0,1,0.000,2,-138.7,3,2.63,4,-139.7,5,2.61,6,27.3,9,153.4,10,0.00,11,0.0,12,0.17,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.168,146,0.347908,152,80.2,172,1.872,173,2.0,175,1.184,176,294.959,238,3.338*3D + #0,1,0.000,2,-138.4,3,2.62,4,-139.4,5,2.60,6,27.3,9,153.4,10,0.00,11,0.0,12,0.17,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.168,146,0.347908,152,80.7,172,1.872,173,2.0,175,1.185,176,294.959,238,3.335*34 + #0,1,0.000,2,-138.1,3,2.62,4,-139.2,5,2.60,6,27.3,9,153.4,10,0.00,11,0.0,12,0.16,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.161,146,0.347911,152,81.6,172,1.872,173,2.0,175,1.185,176,294.957,238,3.335*39 + #0,1,0.000,2,-137.9,3,2.61,4,-139.0,5,2.60,6,27.2,9,153.4,10,0.00,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.153,146,0.347911,152,81.6,172,1.872,173,2.0,175,1.185,176,294.956,238,3.328*05 + #0,1,0.000,2,-137.6,3,2.60,4,-138.8,5,2.60,6,27.2,9,153.4,10,0.00,11,0.0,12,0.15,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.146,146,0.347914,152,82.0,172,1.872,173,2.0,175,1.185,176,294.956,238,3.325*3F + #0,1,0.000,2,-137.4,3,2.59,4,-138.6,5,2.59,6,27.2,9,153.4,10,0.00,11,0.0,12,0.15,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.159,146,0.347914,152,82.4,172,1.872,173,2.0,175,1.185,176,294.955,238,3.325*3A + #0,1,0.000,2,-137.2,3,2.58,4,-138.5,5,2.59,6,27.2,9,153.4,10,0.00,11,0.0,12,0.16,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.169,146,0.347914,152,82.7,172,1.872,173,2.0,175,1.185,176,294.955,238,3.322*3A + #0,1,0.000,2,-137.0,3,2.58,4,-138.3,5,2.59,6,27.1,9,153.4,10,0.00,11,0.0,12,0.17,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419948,50,0.0,51,0.179,146,0.347917,152,83.1,172,1.872,173,2.0,175,1.185,176,294.957,238,3.319*32 + #0,1,0.000,2,-136.8,3,2.56,4,-138.1,5,2.58,6,27.1,9,153.4,10,0.00,11,0.0,12,0.18,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.43,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.199,146,0.347920,152,83.4,172,1.872,173,2.0,175,1.184,176,294.953,238,3.316*3C + #0,1,0.000,2,-136.7,3,2.55,4,-138.0,5,2.58,6,27.0,9,153.4,10,0.00,11,0.0,12,0.20,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.43,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.208,146,0.347920,152,83.9,172,1.872,173,2.0,175,1.184,176,294.953,238,3.312*39 + #0,1,0.000,2,-136.7,3,2.55,4,-137.9,5,2.58,6,27.0,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.43,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.218,146,0.347920,152,84.1,172,1.872,173,2.0,175,1.184,176,294.953,238,3.306*35 + #0,1,0.000,2,-136.7,3,2.54,4,-137.9,5,2.57,6,26.9,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.225,146,0.347922,152,84.2,172,1.872,173,2.0,175,1.185,176,294.956,238,3.303*3F + #0,9,153.4,11,0.0,12,0.22,152,84.3,238,3.299*05 + #0,1,0.000,2,-136.8,3,2.53,4,-137.9,5,2.57,6,26.9,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.193,48,-33.907350,49,18.419948,50,0.0,51,0.228,146,0.347922,152,84.3,172,1.872,173,2.0,175,1.184,176,294.958,238,3.296*39 + #0,1,0.000,2,-137.0,3,2.52,4,-137.9,5,2.57,6,26.8,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.228,146,0.347925,152,84.2,172,1.872,173,2.0,175,1.185,176,294.957,238,3.292*3E + #0,1,0.000,2,-137.1,3,2.52,4,-138.0,5,2.57,6,26.7,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.229,146,0.347925,152,84.1,172,1.872,173,2.0,175,1.185,176,294.957,238,3.288*3E + #0,1,0.000,2,-137.4,3,2.51,4,-138.1,5,2.56,6,26.6,9,153.4,10,0.00,11,0.0,12,0.23,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.229,146,0.347925,152,83.9,172,1.872,173,2.0,175,1.184,176,294.957,238,3.284*3A + #0,1,0.000,2,-137.7,3,2.50,4,-138.2,5,2.56,6,26.5,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.223,146,0.347928,152,83.6,172,1.872,173,2.0,175,1.185,176,294.958,238,3.280*3B + #0,1,0.000,2,-138.0,3,2.49,4,-138.4,5,2.56,6,26.4,9,153.4,10,0.00,11,0.0,12,0.22,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.216,146,0.347928,152,83.3,172,1.872,173,2.0,175,1.185,176,294.958,238,3.276*36 + #0,1,0.000,2,-138.3,3,2.48,4,-138.6,5,2.55,6,26.3,9,153.4,10,0.00,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.210,146,0.347931,152,83.3,172,1.872,173,2.0,175,1.185,176,294.959,238,3.272*0A + #0,1,0.000,2,-138.6,3,2.47,4,-138.8,5,2.55,6,26.1,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.210,146,0.347931,152,82.5,172,1.872,173,2.0,175,1.184,176,294.959,238,3.267*3E + #0,1,0.000,2,-138.9,3,2.46,4,-139.0,5,2.55,6,26.0,9,153.4,10,0.00,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.211,146,0.347931,152,82.5,172,1.872,173,2.0,175,1.184,176,294.957,238,3.263*03 + #0,1,0.000,2,-139.6,3,2.45,4,-139.4,5,2.54,6,25.8,9,153.4,10,0.00,11,0.0,12,0.21,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907350,49,18.419948,50,0.0,51,0.185,146,0.347934,152,82.1,172,1.872,173,2.0,175,1.184,176,294.955,238,3.258*34 + #0,1,0.000,2,-139.8,3,2.44,4,-139.6,5,2.53,6,25.7,9,153.4,10,0.00,11,0.0,12,0.18,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.193,48,-33.907350,49,18.419948,50,0.0,51,0.172,146,0.347937,152,81.2,172,1.872,173,2.0,175,1.184,176,294.955,238,3.258*30 + #0,9,153.4,11,0.0,12,0.17,152,80.8,238,3.249*01 + #0,1,0.000,2,-140.1,3,2.43,4,-139.8,5,2.53,6,25.6,9,153.4,10,0.00,11,0.0,12,0.17,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.193,48,-33.907350,49,18.419948,50,0.0,51,0.172,146,0.347937,152,80.5,172,1.872,173,2.0,175,1.184,176,294.956,238,3.244*39 + #0,1,0.000,2,-140.3,3,2.42,4,-139.9,5,2.53,6,25.5,9,153.4,10,0.00,11,0.0,12,0.17,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.40,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.173,146,0.347937,152,80.1,172,1.872,173,2.0,175,1.184,176,294.955,238,3.240*3B + #0,1,0.000,2,-140.5,3,2.41,4,-140.1,5,2.52,6,25.4,9,153.4,10,0.00,11,0.0,12,0.16,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.160,146,0.347940,152,79.8,172,1.872,173,2.0,175,1.184,176,294.956,238,3.235*34 + #0,1,0.000,2,-140.7,3,2.40,4,-140.3,5,2.52,6,25.3,9,153.4,10,0.00,11,0.0,12,0.15,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.147,146,0.347940,152,79.5,172,1.872,173,2.0,175,1.184,176,294.959,238,3.231*32 + #0,1,0.000,2,-140.9,3,2.39,4,-140.4,5,2.52,6,25.2,9,153.4,10,0.00,11,0.0,12,0.13,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.134,146,0.347943,152,79.2,172,1.872,173,2.0,175,1.185,176,294.960,238,3.227*3E + #0,1,0.000,2,-141.1,3,2.38,4,-140.5,5,2.51,6,25.1,9,153.4,10,0.00,11,0.0,12,0.13,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.135,146,0.347943,152,78.9,172,1.872,173,2.0,175,1.185,176,294.962,238,3.222*3B + #0,1,0.000,2,-141.3,3,2.37,4,-140.7,5,2.51,6,25.0,9,153.4,10,0.00,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.41,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.135,146,0.347943,172,1.872,173,2.0,175,1.185,176,294.964*3A + #0,1,0.000,2,-141.5,3,2.36,4,-140.8,5,2.51,6,24.9,9,153.4,10,0.00,11,0.0,12,0.14,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.136,146,0.347946,152,78.7,172,1.872,173,2.0,175,1.185,176,294.963,238,3.218*31 + #0,1,0.000,2,-141.8,3,2.35,4,-141.0,5,2.50,6,24.7,9,153.4,10,0.00,11,0.0,12,0.14,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.137,146,0.347948,152,78.4,172,1.872,173,2.0,175,1.185,176,294.969,238,3.214*33 + #0,1,0.000,2,-141.9,3,2.34,4,-141.2,5,2.50,6,24.6,9,153.4,10,0.00,11,0.0,12,0.14,13,153.4,17,-3.00,18,0.51,19,0.28,20,18.42,22,1.192,48,-33.907350,49,18.419948,50,0.0,51,0.117,146,0.347948,152,77.9,172,1.872,173,2.0,175,1.185,176,294.969,238,3.209*3C diff --git a/java/com.sap.sailing.expeditionconnector.test/resources/UsrChannels.h b/java/com.sap.sailing.expeditionconnector.test/resources/UsrChannels.h new file mode 100755 index 00000000000..8d3a133bb5e --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector.test/resources/UsrChannels.h @@ -0,0 +1,393 @@ +#pragma once + +// All directions in magnetic + +// multiboat channels + +enum ExChannels +{ + ExUtc, // Microsoft DATE type,utc system time + ExBsp, + ExAwa, + ExAws, + ExTwa, // #4 + ExTws, // #5 + ExTwd, // #6 + + ExRudderFwd, + ExDeltaTargBsp, + ExCourse, + + ExLwy, // #10 + ExSet, + ExDrift, + ExHdg, // #13 + ExAirTemp, + ExSeaTemp, + ExBaro, + ExDepth, // metres + ExRoll, + ExPitch, + ExRudder, // #20 + ExTab, + ExForestayLoad, + ExDownhaulLoad, + ExMastAngle, + ExForestayLen, + ExMast, + ExStbdLoadCell, + ExPortLoadCell, + ExRake, + ExVolts, // #30 + ExVmg, + ExROT, + ExLayDistOnStrb, + ExLayTimeOnStrb, + ExLayPortBear, + ExLayDistOnPort, + ExLayTimeOnPort, + ExLayStrbBear, + ExGpsQuality, // 0 Bad, 1 Autonomous, 2 Differential, 3 p-code, 4,5 Rtk, 6 dr, if change this, need to change CPort::NmeaAPB() + ExGpsHDOP, // #40 + ExGpsPDOP, + ExGpsVDOP, + ExGpsNumber, // Number of satellites in active constellation + ExGpsAge, // Age of differential data + ExGpsAltitude, // antenna height + ExGpsGeoidSeparation, + ExGpsMode, // 0 = 1D, 1 = 2D, 2 = 3D, 3 = Auto, 6 = error + + ExLat, // #48 // if add GPS vars, extend CCore::IsGPSvar() + ExLon, // #49 + ExCog, // #50 + ExSog, // #51 + + ExDiffRefStn, + + ExTargTwaN, + ExTargBspN, + ExTargVmg, + ExTargRoll, + ExPolarBsp, + ExPolarBspPercent, + ExPolarRoll, + + ExErrorCode, // #60 + + ExStrbRunner, + ExPortRunner, + + ExPolarBspN, + ExPolarBspPercentN, + + ExTargTwaLwy, // target twa without leeway + + ExVmgPercent, + + ExVang, + ExTraveller, + ExMainSheet, + + ExPolVmcToMark, // #70 // vmc if headed at mark + + ExKeelAngle, + ExKeelHeight, + ExBoard, + ExOilPressure, + ExRPM1, + ExRPM2, + + ExBoardP, + ExBoardS, + + ExOppTrack, + ExDistFinish, // #80 + + ExStartTimeToPort, + ExStartTimeToStrb, + ExLineSquareWind, + ExStartDistToLine, + ExStartRchTimeToLine, // time to reach into line + ExStartRchDistToLine, + ExStartRchBspToLine, + + ExMarkTime, + ExNextMarkTimeOnPort, + ExNextMarkTimeOnStrb, // #90 + + ExXte, + ExVmc, + + ExMagVar, + + ExGwd, // #94 + ExGws, // #95 + + ExLayDist, // distance to layline we are heading to + ExLayTime, // time to layline + ExLayBear, // bearing of that layline + + ExVmcPercent, + ExPolVmc, // #100 + ExOptVmc, + ExOptVmcHdg, + ExOptVmcTwa, + ExDeltaTargTwa, + + ExMarkRng, + ExMarkBrg, + ExMarkGpsTime, + ExMarkTwa, + + ExPredSet, + ExPredDrift, // #110 + + ExNextMarkRng, + ExNextMarkBrg, + ExNextMarkTwa, + + ExRadarRng, + ExRadarBrg, + ExStartDistBelowLineStern, + + ExAlt0, // alternating number channels must be consecutive + ExAlt1, + ExAlt2, + ExAlt3, // #120 + ExAlt4, + ExAlt5, + ExAlt6, + ExAlt7, + ExAlt8, + ExAlt9, +// ExAltNum = ExAlt9 - ExAlt0 + 1 // defined in CoreMem.h + ExAltMaxId = ExAlt9, // alternating channels must be consecutive + + ExNextMarkPolTime, + + ExStartLineBiasDeg, + ExStartLineBiasLen, + + ExStartLayPortBear, // #130 // laylines for start line + ExStartLayStrbBear, + + ExNextMarkAwa, + ExNextMarkAws, + + ExStartRSTime, // turning to right, ending up on starboard + ExStartRPTime, // turning to right, ending up on port + ExStartLSTime, // turning to left, ending up on starboard + ExStartLPTime, // turning to left, ending up on port + + ExGpsDistToRaceNote, + ExGpsTimeToRaceNote, + + ExLogBsp, // #140 + ExLogSog, + ExStartGpsTimeToLine, + ExStartGpsTimeToBurn, + ExTargTwaS, // Start + ExTargBspS, // Start + + ExGpsTime, // #146 + + ExTwdPlus90, // Twd + 90 + ExTwdLess90, // Twd - 90 + + ExShadow, + ExShadowOppTack, // #150 + + ExDownhaulLoad2, + + ExTackAngle, + ExTackAnglePolar, + + ExTargAwa, + + ExStartTimeBurnStrbX, // time to burn when tack onto starboard end starboard layline and sail to 20s from line + ExStartTimeBurnPortX, // offset for the start stbd layline to the pin + ExStartLayTimeP, + ExStartLayTimeS, + + ExMarkSet, + ExMarkDrift, // #160 + + ExMarkLat, + ExMarkLon, + ExStartPortEndLat, // ends of line + ExStartPortEndLon, + ExStartStrbEndLat, + ExStartStrbEndLon, + + ExGpsHPE, + ExHumidity, + ExLeadPort, + ExLeadStbd, // #170 + ExBackstay, + + ExUser0, // #172: RakeDeg (Rake in degrees) // user channels must be consecutive + ExUser1, // #173: DflectrPP (Deflector percentage) + ExUser2, // #174: TG Heel (Target Heel) + ExUser3, // #175: ForestayPres (Forestay Pressure) + ExUser4, // #176: DflectrMM (Deflector in mm) + ExUser5, + ExUser6, + ExUser7, + ExUser8, // #180 + ExUser9, + ExUser10, + ExUser11, + ExUser12, + ExUser13, + ExUser14, + ExUser15, + ExUser16, + ExUser17, + ExUser18, // #190 + ExUser19, + ExUser20, + ExUser21, + ExUser22, + ExUser23, + ExUser24, + ExUser25, + ExUser26, + ExUser27, + ExUser28, // #200 + ExUser29, + ExUser30, + ExUser31, + ExUserMax = ExUser31, // user channels must be consecutive + + ExStartTimeToGun, + ExStartTimeToLine, + ExStartTimeToBurn, + ExStartDistBelowLine, + ExStartDistBelowLineGun, + + ExGateTimeOnPort, // this is to the gate mark + ExGateDistOnStrb, // #210 + ExGateTimeOnStrb, + ExGateDistOnPort, + + ExGateSpotTimeOnStrb, + ExGateSpotTimeOnPort, + + ExLayPortBearUp, + ExLayStrbBearUp, + ExLayPortBearDn, + ExLayStrbBearDn, + + ExTideLayPortTimeOnPort, + ExTideLayPortTimeOnStbd, // #220 + ExTideLayStbdTimeOnPort, + ExTideLayStbdTimeOnStbd, + ExTideLayPortTime, + ExTideLayStbdTime, + + ExMaxLayPortBear, + ExMinLayPortBear, + ExMaxLayStrbBear, + ExMinLayStrbBear, + + ExTwdLayMark, + ExTwdLayMarkOpp, // #230 // lay on other board + + ExDeltaBspSog, + ExDeltaHdgCog, + + ExLayPortRatio, + ExLayStrbRatio, + + ExFourierTwd, + ExFourierTws, + + ExTargTwaP, + ExTargBspP, + + ExTwdTarg, + + ExPolCustom1, // #240 // these need to be in this order : see CCore::DerivedPolarNumbers() + ExPolCustom2, + ExPolCustom3, + ExPolCustom4, + ExPolCustom1PC, + ExPolCustom2PC, + ExPolCustom3PC, + ExPolCustom4PC, + ExPolCustom1Targ, + ExPolCustom2Targ, + ExPolCustom3Targ, // #250 + ExPolCustom4Targ, + + ExWaveSigHeight, // XDR from Volvo wave sensor + ExWaveSigPeriod, + ExWaveMaxHeight, + ExWaveMaxPeriod, + + ExSlam, + ExMotion, + + ExMwa, + ExMws, + ExBoom, // #260 + + ExTargBspPercent, + ExHeadingToSteer, + ExHeadingToSteerPol, + + ExStartBspToPort, + ExStartBspToStrb, + ExStartBspOnPort, + ExStartBspOnStrb, + + ExTwdTwist, + + ExSailNow, + ExSailMark, // #270 + ExSailNext, + + ExTackLossVMGSec, + ExTackLossVMGMetres, + + ExNearestTide, + + ExTripLog, + ExTurnToMark, // delta of cog and bearing to mark + + ExPitchRate, + ExRollRate, + + ExDeltaPolBsp, + ExDeltaTargRoll, // #280 + + ExDeflectorP, + ExRudderP, + ExRudderS, + ExRudderToe, + ExBspTransverse, + ExForestayInner, + ExGateTime, // this is to the gate mark + + ExZeroAhead, + ExBrgFromBoat0, + ExRngFromBoat0, // #290 + + ExDeflectorS, + ExBobstay, + ExOuthaul, + + ExD0port, + ExD0starboard, + ExD1port, + ExD1starbboard, + ExV0port, + ExV0starbboard, + ExV1port, // #300 + ExV1starbboard, + + ExStartTimeToPortSimple, + ExStartTimeToStrbSimple, + + ExNumChannels +}; diff --git a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java index 19bda80b374..709e5294686 100755 --- a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java +++ b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java @@ -5,7 +5,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStreamReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; @@ -15,12 +17,19 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; import java.util.Date; import java.util.GregorianCalendar; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.TimeZone; +import java.util.UUID; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; import org.junit.After; import org.junit.Before; @@ -30,18 +39,34 @@ import org.junit.rules.Timeout; import com.sap.sailing.declination.Declination; import com.sap.sailing.declination.DeclinationService; +import com.sap.sailing.domain.common.DeviceIdentifier; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Wind; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.racelog.tracking.TransformationException; +import com.sap.sailing.domain.common.tracking.BravoExtendedFix; +import com.sap.sailing.domain.common.tracking.DoubleVectorFix; import com.sap.sailing.domain.common.tracking.GPSFix; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.common.tracking.impl.BravoExtendedFixImpl; +import com.sap.sailing.domain.racelog.tracking.FixReceivedListener; +import com.sap.sailing.domain.racelog.tracking.SensorFixStore; import com.sap.sailing.domain.test.mock.MockedTrackedRace; +import com.sap.sailing.expeditionconnector.DeviceRegistry; import com.sap.sailing.expeditionconnector.ExpeditionListener; import com.sap.sailing.expeditionconnector.ExpeditionMessage; +import com.sap.sailing.expeditionconnector.ExpeditionSensorDeviceIdentifier; +import com.sap.sailing.expeditionconnector.ExpeditionTrackerFactory; import com.sap.sailing.expeditionconnector.ExpeditionWindTracker; -import com.sap.sailing.expeditionconnector.ExpeditionWindTrackerFactory; import com.sap.sailing.expeditionconnector.UDPExpeditionReceiver; +import com.sap.sailing.expeditionconnector.persistence.ExpeditionGpsDeviceIdentifier; +import com.sap.sailing.expeditionconnector.persistence.ExpeditionGpsDeviceIdentifierImpl; +import com.sap.sailing.expeditionconnector.persistence.ExpeditionSensorDeviceIdentifierImpl; +import com.sap.sse.common.NoCorrespondingServiceRegisteredException; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.TimeRange; +import com.sap.sse.common.Timed; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; @@ -58,9 +83,132 @@ public class UDPExpeditionReceiverTest { private UDPExpeditionReceiver receiver; private ExpeditionListener listener; private Thread receiverThread; + private DynamicDeviceRegistry deviceRegistry; + private TestSensorFixStore sensorFixStore; + private class DynamicDeviceRegistry implements DeviceRegistry { + private final Map gpsDeviceIdentifiers; + private final Map sensorDeviceIdentifiers; + + public DynamicDeviceRegistry() { + gpsDeviceIdentifiers = new HashMap<>(); + sensorDeviceIdentifiers = new HashMap<>(); + } + + @Override + public ExpeditionGpsDeviceIdentifier getGpsDeviceIdentifier(int boatId) { + ExpeditionGpsDeviceIdentifier result = gpsDeviceIdentifiers.get(boatId); + if (result == null) { + result = new ExpeditionGpsDeviceIdentifierImpl(UUID.randomUUID()); + gpsDeviceIdentifiers.put(boatId, result); + } + return result; + } + + @Override + public ExpeditionSensorDeviceIdentifier getSensorDeviceIdentifier(int boatId) { + ExpeditionSensorDeviceIdentifier result = sensorDeviceIdentifiers.get(boatId); + if (result == null) { + result = new ExpeditionSensorDeviceIdentifierImpl(UUID.randomUUID()); + sensorDeviceIdentifiers.put(boatId, result); + } + return result; + } + + @Override + public SensorFixStore getSensorFixStore() { + return sensorFixStore; + } + } + + private class TestSensorFixStore implements SensorFixStore { + private Map> fixesReceived; + + public TestSensorFixStore() { + fixesReceived = new HashMap<>(); + } + + @Override + public void loadFixes(Consumer consumer, DeviceIdentifier deviceIdentifier, + TimePoint start, TimePoint end, boolean toIsInclusive) + throws NoCorrespondingServiceRegisteredException, TransformationException { + } + + @Override + public void loadFixes(Consumer consumer, DeviceIdentifier deviceIdentifier, + TimePoint start, TimePoint end, boolean inclusive, BooleanSupplier isPreemptiveStopped, + Consumer progressReporter) + throws NoCorrespondingServiceRegisteredException, TransformationException { + } + + @Override + public void storeFix(DeviceIdentifier device, FixT fix) { + List fixesReceivedFromDevice = fixesReceived.get(device); + if (fixesReceivedFromDevice == null) { + fixesReceivedFromDevice = new ArrayList<>(); + fixesReceived.put(device, fixesReceivedFromDevice); + } + fixesReceivedFromDevice.add(fix); + } + + @Override + public void storeFixes(DeviceIdentifier device, Iterable fixes) { + for (final FixT fix : fixes) { + storeFix(device, fix); + } + } + + @Override + public void addListener(FixReceivedListener listener, DeviceIdentifier device) { + } + + @Override + public void removeListener(FixReceivedListener listener) { + } + + @Override + public void removeListener(FixReceivedListener listener, DeviceIdentifier device) { + } + + @Override + public TimeRange getTimeRangeCoveredByFixes(DeviceIdentifier device) + throws TransformationException, NoCorrespondingServiceRegisteredException { + return null; + } + + @Override + public long getNumberOfFixes(DeviceIdentifier device) + throws TransformationException, NoCorrespondingServiceRegisteredException { + return fixesReceived.get(device) == null ? 0 : fixesReceived.get(device).size(); + } + + @Override + public Map getLastFix(Iterable forDevices) + throws TransformationException, NoCorrespondingServiceRegisteredException { + final Map result = new HashMap<>(); + for (final Entry> fixes : fixesReceived.entrySet()) { + @SuppressWarnings("unchecked") + final List fixList = (List) fixes.getValue(); + result.put(fixes.getKey(), fixList.get(fixList.size()-1)); + } + return result; + } + + @Override + public boolean loadOldestFix(Consumer consumer, DeviceIdentifier device, + TimeRange timeRangetoLoad) throws NoCorrespondingServiceRegisteredException, TransformationException { + return false; + } + + @Override + public boolean loadYoungestFix(Consumer consumer, DeviceIdentifier device, + TimeRange timeRangetoLoad) throws NoCorrespondingServiceRegisteredException, TransformationException { + return false; + } + } + @Before - public void setUp() throws UnknownHostException, SocketException { + public void setUp() throws UnknownHostException, SocketException, InterruptedException { validLines = new String[] { "#0,1,7.700,2,-39.0,3,23.00,9,319.0,12,1.17,146,40348.390035*37", "#0,4,-54.9,5,17.69,6,263.1,9,318.0*0D", @@ -114,9 +262,12 @@ public class UDPExpeditionReceiverTest { buf = new byte[512]; packet = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), PORT); socket = new DatagramSocket(); - receiver = new UDPExpeditionReceiver(PORT); + sensorFixStore = new TestSensorFixStore(); + deviceRegistry = new DynamicDeviceRegistry(); + receiver = new UDPExpeditionReceiver(PORT, deviceRegistry); receiverThread = new Thread(receiver, "Expedition Receiver"); receiverThread.start(); + Thread.sleep(10); // to give receiver enough time to start listening on UDP socket listener = new ExpeditionListener() { @Override public void received(ExpeditionMessage message) { @@ -175,6 +326,32 @@ public class UDPExpeditionReceiverTest { assertEquals(windFixes.get(0).getPosition(), windFixes.get(2).getPosition()); } + @Test + public void testBasicPhoenixUDPProperties() throws IOException, InterruptedException { + receiver.addListener(listener, /* validMessagesOnly */ true); + final InputStreamReader reader = new InputStreamReader(getClass().getResourceAsStream("/Expedition_28Oct17_0820.txt")); + final BufferedReader br = new BufferedReader(reader); + final List lines = new ArrayList<>(); + String line; + while ((line=br.readLine()) != null) { + if (!line.trim().isEmpty()) { + lines.add(line.trim()); + } + } + br.close(); + sendAndWaitABit(lines.toArray(new String[0])); + assertEquals(159, lines.size()); + assertEquals(159, messages.size()); + final ExpeditionGpsDeviceIdentifier gpsDevice = deviceRegistry.getGpsDeviceIdentifier(0); + final ExpeditionSensorDeviceIdentifier sensorDevice = deviceRegistry.getSensorDeviceIdentifier(0); + assertTrue(sensorFixStore.getNumberOfFixes(gpsDevice) > 0); + assertEquals(-33.907350, ((GPSFixMoving) sensorFixStore.getLastFix(Collections.singleton(gpsDevice)).get(gpsDevice)).getPosition().getLatDeg(), 0.0001); + assertEquals(18.419951, ((GPSFixMoving) sensorFixStore.getLastFix(Collections.singleton(gpsDevice)).get(gpsDevice)).getPosition().getLngDeg(), 0.0001); + assertTrue(sensorFixStore.getNumberOfFixes(sensorDevice) > 0); + final BravoExtendedFix sensorFix = new BravoExtendedFixImpl((DoubleVectorFix) sensorFixStore.getLastFix(Collections.singleton(sensorDevice)).get(sensorDevice)); + assertEquals(1.872, sensorFix.getRake().getDegrees(), 0.000001); + assertEquals(18.4, sensorFix.getRudder().getDegrees(), 0.05); + } @Test public void testTimeStampConversion() throws IOException, InterruptedException { @@ -261,7 +438,7 @@ public class UDPExpeditionReceiverTest { MockedTrackedRace race = new MockedTrackedRace(); DeclinationService declinationService = DeclinationService.INSTANCE; ExpeditionWindTracker windTracker = new ExpeditionWindTracker(race, declinationService, receiver, - (ExpeditionWindTrackerFactory) ExpeditionWindTrackerFactory.getInstance()); + (ExpeditionTrackerFactory) ExpeditionTrackerFactory.getInstance()); receiver.addListener(listener, /* validMessagesOnly */ true); receiver.addListener(windTracker, /* validMessagesOnly */ true); String[] lines = new String[validLines.length+1]; diff --git a/java/com.sap.sailing.expeditionconnector/META-INF/MANIFEST.MF b/java/com.sap.sailing.expeditionconnector/META-INF/MANIFEST.MF index 07ea47da229..137a3878953 100644 --- a/java/com.sap.sailing.expeditionconnector/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.expeditionconnector/META-INF/MANIFEST.MF @@ -6,13 +6,18 @@ Bundle-Version: 1.0.0.qualifier Bundle-Vendor: SAP Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Export-Package: com.sap.sailing.expeditionconnector, - com.sap.sailing.expeditionconnector.impl;x-friends:="com.sap.sailing.server.test" + com.sap.sailing.expeditionconnector.impl;x-friends:="com.sap.sailing.server.test,com.sap.sailing.expeditionconnector.persistence" Require-Bundle: com.sap.sailing.domain, com.sap.sailing.declination, com.sap.sailing.udpconnector, com.sap.sailing.domain.common, com.sap.sse.common, - com.sap.sse + com.sap.sse, + com.sap.sailing.domain.shared.android, + com.sap.sailing.server.gateway.serialization.shared.android, + com.sap.sailing.expeditionconnector.common, + com.sap.sailing.expeditionconnector.persistence Bundle-ActivationPolicy: lazy -Import-Package: org.osgi.framework +Import-Package: org.osgi.framework, + org.osgi.util.tracker;version="1.5.1" Bundle-Activator: com.sap.sailing.expeditionconnector.impl.Activator diff --git a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/DeviceRegistry.java b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/DeviceRegistry.java new file mode 100755 index 00000000000..9f918719a8c --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/DeviceRegistry.java @@ -0,0 +1,27 @@ +package com.sap.sailing.expeditionconnector; + +import com.sap.sailing.domain.racelog.tracking.SensorFixStore; +import com.sap.sailing.expeditionconnector.persistence.ExpeditionGpsDeviceIdentifier; + +/** + * Makes accessible a mapping from "Expedition" boat IDs to device UUIDs. + * + * @author Axel Uhl (D043530) + * + */ +public interface DeviceRegistry { + /** + * If a device is registered for the boat ID {@code boatId}, a non-{@code null} device identifier is returned that + * represents a virtual device for the GPS data received from that boat ID. Otherwise, {@code null} is returned. + */ + ExpeditionGpsDeviceIdentifier getGpsDeviceIdentifier(int boatId); + + /** + * If a device is registered for the boat ID {@code boatId}, a non-{@code null} device identifier is returned that + * represents a virtual device for the additional non-GPS and non-wind sensor data received from that boat ID. + * Otherwise, {@code null} is returned. + */ + ExpeditionSensorDeviceIdentifier getSensorDeviceIdentifier(int boatId); + + SensorFixStore getSensorFixStore(); +} diff --git a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionMessage.java b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionMessage.java index 4793bd5de57..5d024f16490 100755 --- a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionMessage.java +++ b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionMessage.java @@ -49,6 +49,89 @@ public interface ExpeditionMessage extends UDPMessage { #0,6,344.2,40,2.10,94,350.8,95,2.35*28 #0,4,135.0,5,2.06,6,345.5,94,350.8,95,2.35*05 */ + /** + * variable ID for boat speed + */ + final int ID_BSP = 1; + + /** + * variable ID for apparent wind angle (probably in decimal degrees) + */ + final int ID_AWA = 2; + + /** + * variable ID for apparent wind speed (probably in decimal knots) + */ + final int ID_AWS = 3; + + /** + * variable ID for true wind angle, relative to keel, in decimal degrees + */ + final int ID_TWA = 4; + + /** + * variable ID for true wind speed in decimal knots + */ + final int ID_TWS = 5; + + /** + * Variable ID for what Expedition thinks is the true wind direction ("from"), in decimal degrees. Some + * users prefer to not enter the current declination for a time / place into their Expedition + * client. In this case, the values presented for this key are the magnetic wind direction instead. + * They should then be corrected by adding the current declination. + */ + final int ID_TWD = 6; + + /** + * variable ID for course + */ + final int ID_COURSE = 9; + + /** + * variable ID for leeway + */ + final int ID_LWY = 10; + + /** + * variable ID for "set" (?) + */ + final int ID_SET = 11; + + /** + * variable ID for drift + */ + final int ID_DRIFT = 12; + + /** + * variable ID for magnetic heading, meaning the keel's direction, in decimal degrees + */ + final int ID_HEADING = 13; + + /** + * variable ID for depth in meters + */ + final int ID_DEPTH = 17; + + /** + * variable ID for roll (heel), probably in degrees + */ + final int ID_ROLL = 18; + + /** + * variable ID for pitch, probably in degrees + */ + final int ID_PITCH = 19; + + /** + * variable ID for rudder, probably in degrees + */ + final int ID_RUDDER = 20; + + /** + * variable ID for forestay load (unit unknown yet) + */ + final int ID_FORESTAY_LOAD = 22; + /** * variable ID for the GPS-measured latitude, in decimal degrees */ @@ -67,35 +150,7 @@ public interface ExpeditionMessage extends UDPMessage { /** * variable ID for the GPS-measured speed over ground (SoG) */ - final int ID_GPS_SOG = 50; - - /** - * variable ID for the GPS-measured time as days since 31.12.1899 UTC, meaning 1.0 is 1.1.1900 0:00:00 UTC - */ - final int ID_GPS_TIME = 146; - - /** - * variable ID for magnetic heading, meaning the keel's direction, in decimal degrees - */ - final int ID_HEADING = 13; - - /** - * variable ID for true wind angle, relative to keel, in decimal degrees - */ - final int ID_TWA = 4; - - /** - * Variable ID for what Expedition thinks is the true wind direction ("from"), in decimal degrees. Some - * users prefer to not enter the current declination for a time / place into their Expedition - * client. In this case, the values presented for this key are the magnetic wind direction instead. - * They should then be corrected by adding the current declination. - */ - final int ID_TWD = 6; - - /** - * variable ID for true wind speed in decimal knots - */ - final int ID_TWS = 5; + final int ID_GPS_SOG = 51; /** * True wind direction over ground ("from") in decimal degrees, cleansed using the GPS device @@ -106,7 +161,47 @@ public interface ExpeditionMessage extends UDPMessage { * True wind speed over ground, cleansed using the GPS device */ final int ID_GWS = 95; - + + /** + * variable ID for the GPS-measured time as days since 31.12.1899 UTC, meaning 1.0 is 1.1.1900 0:00:00 UTC + */ + final int ID_GPS_TIME = 146; + + /** + * variable ID for tack angle, probably in degrees + */ + final int ID_TACK_ANGLE = 152; + + /** + * variable ID for rake in degrees + */ + final int ID_RAKE_DEG = 172; + + /** + * variable ID for deflector percentage + */ + final int ID_DFLCTR_PP = 173; + + /** + * variable ID for target heel angle + */ + final int ID_TG_HEEL = 174; + + /** + * variable ID for forestay pressure + */ + final int ID_FORESTAY_PRES = 175; + + /** + * variable ID for deflector in millimeters + */ + final int ID_DFLECTR_MM = 176; + + /** + * variable ID for target boat speed (P?), presumably in knots + */ + final int ID_TARG_BSP_P = 238; + /** * A message's checksum determines whether the package is to be considered valid. */ diff --git a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionTrackerFactory.java b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionTrackerFactory.java new file mode 100755 index 00000000000..630d19832ef --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionTrackerFactory.java @@ -0,0 +1,264 @@ +package com.sap.sailing.expeditionconnector; + +import java.net.SocketException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; +import org.osgi.util.tracker.ServiceTrackerCustomizer; + +import com.sap.sailing.declination.DeclinationService; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.common.tracking.DoubleVectorFix; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.racelog.tracking.SensorFixStore; +import com.sap.sailing.domain.racelog.tracking.SensorFixStoreSupplier; +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.tracking.WindTrackerFactory; +import com.sap.sailing.expeditionconnector.impl.Activator; +import com.sap.sailing.expeditionconnector.persistence.DomainObjectFactory; +import com.sap.sailing.expeditionconnector.persistence.ExpeditionGpsDeviceIdentifier; +import com.sap.sailing.expeditionconnector.persistence.ExpeditionGpsDeviceIdentifierImpl; +import com.sap.sailing.expeditionconnector.persistence.ExpeditionSensorDeviceIdentifierImpl; +import com.sap.sailing.expeditionconnector.persistence.MongoObjectFactory; +import com.sap.sailing.expeditionconnector.persistence.PersistenceFactory; + +public class ExpeditionTrackerFactory implements WindTrackerFactory, DeviceRegistry { + private static Logger logger = Logger.getLogger(ExpeditionTrackerFactory.class.getName()); + + private static ExpeditionTrackerFactory defaultInstance; + + /** + * Remembers the wind tracker and the port on which the UDP receiver with which the wind tracker is + * registers is listening for incoming Expedition messages. + */ + private final Map windTrackers; + + private final Map windReceivers; + + /** + * When one or more device configurations exist then each {@link UDPExpeditionReceiver} created by this factory will + * also produce {@link GPSFixMoving} and {@link DoubleVectorFix} fixes and send them to the {@link SensorFixStore}, + * using device identifiers of type {@link ExpeditionGpsDeviceIdentifier} and + * {@link ExpeditionSensorDeviceIdentifier}, respectively, whose inner ID is the + * {@link ExpeditionDeviceConfiguration#getDeviceUuid() UUID} of the device configuration. + */ + private final ConcurrentHashMap deviceConfigurations; + + /** + * Holds the mappings from Expedition boat IDs (default being 0, counting upwards) to the + * {@link ExpeditionDeviceConfiguration} that {@link ExpeditionDeviceConfiguration#getExpeditionBoatId() has this + * boat ID}. + */ + private final ConcurrentHashMap devicesPerBoatId; + + private final int defaultPort; + + /** + * Cached result from calling {@link SensorFixStoreSupplier#getSensorFixStore()} on any supplier discovered + * in the OSGi registry. Refreshed when the sensor fix store supplier changes in the OSGi registry. + */ + private SensorFixStore sensorFixStore; + + private ServiceTracker sensorFixServiceTracker; + + private final MongoObjectFactory mongoObjectFactory; + + public ExpeditionTrackerFactory(SensorFixStore sensorFixStore, DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory) { + this.windTrackers = new HashMap(); + this.sensorFixStore = sensorFixStore; + windReceivers = new HashMap(); + final Activator activator = Activator.getInstance(); + defaultPort = activator.getExpeditionUDPPort(); + deviceConfigurations = new ConcurrentHashMap<>(); + devicesPerBoatId = new ConcurrentHashMap<>(); + final BundleContext context = activator.getContext(); + this.mongoObjectFactory = mongoObjectFactory; + for (final ExpeditionDeviceConfiguration expeditionDeviceConfigurationLoadedFromDB : domainObjectFactory.getExpeditionDeviceConfigurations()) { + addOrReplaceDeviceConfigurationNoPersistence(expeditionDeviceConfigurationLoadedFromDB); + } + if (context != null) { + sensorFixServiceTracker = startListeningForSensorFixStoreSuppliers(context); + } else { + sensorFixServiceTracker = null; + } + logger.info("Created "+getClass().getName()+" with default UDP port "+defaultPort); + } + + private ServiceTracker startListeningForSensorFixStoreSuppliers(BundleContext context) { + final ServiceTracker result = new ServiceTracker<>(context, SensorFixStoreSupplier.class, + new ServiceTrackerCustomizer() { + @Override + public SensorFixStoreSupplier addingService(ServiceReference reference) { + final SensorFixStoreSupplier service = Activator.getInstance().getContext().getService(reference); + sensorFixStore = service.getSensorFixStore(); + return service; + } + + @Override + public void modifiedService(ServiceReference reference, + SensorFixStoreSupplier service) { + } + + @Override + public void removedService(ServiceReference reference, + SensorFixStoreSupplier service) { + // check if there is still a supplier left: + final SensorFixStoreSupplier nextService = sensorFixServiceTracker.getService(); + if (nextService != null) { + sensorFixStore = nextService.getSensorFixStore(); + } + } + }); + result.open(); + return result; + } + + public synchronized static ExpeditionTrackerFactory getInstance() { + return getInstance(/* sensorFixStore */ null, PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory(), + PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory()); + } + + /** + * Use this constructor in non-OSGi scenarios such as plain JUnit test environments where the + * {@link SensorFixStoreSupplier} cannot be discovered through the registry. + */ + public static ExpeditionTrackerFactory getInstance(SensorFixStore sensorFixStore, DomainObjectFactory domainObjectFactory, MongoObjectFactory mongoObjectFactory) { + if (defaultInstance == null) { + defaultInstance = new ExpeditionTrackerFactory(sensorFixStore, domainObjectFactory, mongoObjectFactory); + } + return defaultInstance; + } + + @Override + public WindTracker createWindTracker(DynamicTrackedRegatta trackedRegatta, RaceDefinition race, + boolean correctByDeclination) throws SocketException { + WindTracker result = getExistingWindTracker(race); + if (result == null) { + DynamicTrackedRace trackedRace = trackedRegatta.getTrackedRace(race); + UDPExpeditionReceiver receiver = getOrCreateWindReceiverOnDefaultPort(); + result = new ExpeditionWindTracker(trackedRace, + correctByDeclination ? DeclinationService.INSTANCE : null, receiver, this); + windTrackers.put(race, result); + } + return result; + } + + @Override + public WindTracker getExistingWindTracker(RaceDefinition race) { + return windTrackers.get(race); + } + + public UDPExpeditionReceiver getOrCreateWindReceiverOnDefaultPort() throws SocketException { + return getOrCreateWindReceiverForPort(defaultPort); + } + + private synchronized UDPExpeditionReceiver getOrCreateWindReceiverForPort(int port) throws SocketException { + UDPExpeditionReceiver receiver = windReceivers.get(port); + if (receiver == null) { + receiver = new UDPExpeditionReceiver(port, this); + windReceivers.put(port, receiver); + Thread t = new Thread(receiver, "Expedition Wind Receiver on port "+port); + t.setDaemon(true); + t.start(); + } + return receiver; + } + + /** + * Notifies the factory that the wind tracker has stopped tracking wind for race. This + * will remove the tracker from the respective caches. + */ + synchronized void trackerStopped(RaceDefinition race, ExpeditionWindTracker windTracker) { + if (windTrackers.get(race) != windTracker) { + throw new IllegalArgumentException("Intenral error: expected to remove wind tracker "+windTracker+ + ", but another wind tracker "+windTrackers.get(race)+" was registered."); + } + windTrackers.remove(race); + if (windTracker.getReceiver().isStopped()) { + UDPExpeditionReceiver receiver = windReceivers.get(windTracker.getReceiver().getPort()); + if (receiver != windTracker.getReceiver()) { + throw new IllegalArgumentException("Internal error: expected to remove wind receiver "+ + windTracker.getReceiver()+" but found receiver "+receiver); + } + windReceivers.remove(windTracker.getReceiver().getPort()); + } + } + + @Override + public String toString() { + return "ExpeditionWindTrackerFactory [defaultPort=" + defaultPort + "]"; + } + + public Iterable getDeviceConfigurations() { + return Collections.unmodifiableCollection(deviceConfigurations.values()); + } + + public void addOrReplaceDeviceConfiguration(ExpeditionDeviceConfiguration deviceConfiguration) { + addOrReplaceDeviceConfigurationNoPersistence(deviceConfiguration); + mongoObjectFactory.storeExpeditionDeviceConfiguration(deviceConfiguration); + } + + private void addOrReplaceDeviceConfigurationNoPersistence(ExpeditionDeviceConfiguration deviceConfiguration) { + if (deviceConfiguration.getExpeditionBoatId() != null && + devicesPerBoatId.containsKey(deviceConfiguration.getExpeditionBoatId()) && + !devicesPerBoatId.get(deviceConfiguration.getExpeditionBoatId()).equals(deviceConfiguration)) { + throw new IllegalStateException("Trying to create an ambiguous Expedition Boat ID mapping: established is "+ + devicesPerBoatId.get(deviceConfiguration.getExpeditionBoatId())+ + " and boat ID #"+deviceConfiguration.getExpeditionBoatId()+" therefore cannot be mapped to "+deviceConfiguration+ + " at the same time."); + } + final ExpeditionDeviceConfiguration old = deviceConfigurations.put(deviceConfiguration.getDeviceUuid(), deviceConfiguration); + if (old != null && old.getExpeditionBoatId() != null) { + devicesPerBoatId.remove(old.getExpeditionBoatId()); + } + if (deviceConfiguration.getExpeditionBoatId() != null) { + devicesPerBoatId.put(deviceConfiguration.getExpeditionBoatId(), deviceConfiguration); + } + } + + public void removeDeviceConfiguration(ExpeditionDeviceConfiguration deviceConfiguration) { + deviceConfigurations.remove(deviceConfiguration.getDeviceUuid()); + if (deviceConfiguration.getExpeditionBoatId() != null) { + devicesPerBoatId.remove(deviceConfiguration.getExpeditionBoatId()); + } + mongoObjectFactory.removeExpeditionDeviceConfiguration(deviceConfiguration); + } + + @Override + public ExpeditionGpsDeviceIdentifier getGpsDeviceIdentifier(int boatId) { + final ExpeditionDeviceConfiguration deviceConfig = devicesPerBoatId.get(boatId); + final ExpeditionGpsDeviceIdentifier result; + if (deviceConfig == null) { + result = null; + } else { + result = new ExpeditionGpsDeviceIdentifierImpl(deviceConfig.getDeviceUuid()); + } + return result; + } + + @Override + public ExpeditionSensorDeviceIdentifier getSensorDeviceIdentifier(int boatId) { + final ExpeditionDeviceConfiguration deviceConfig = devicesPerBoatId.get(boatId); + final ExpeditionSensorDeviceIdentifier result; + if (deviceConfig == null) { + result = null; + } else { + result = new ExpeditionSensorDeviceIdentifierImpl(deviceConfig.getDeviceUuid()); + } + return result; + } + + @Override + public SensorFixStore getSensorFixStore() { + return sensorFixStore; + } +} diff --git a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionWindTracker.java b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionWindTracker.java index e4b9c2b898d..843a9e4c798 100755 --- a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionWindTracker.java +++ b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionWindTracker.java @@ -20,7 +20,7 @@ import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.WindTracker; /** - * Can be subscribed to a {@link UDPExpeditionReceiver} and forwards the wind information + * Can be subscribed to a {@link UDPExpeditionReceiver} and forwards wind information * received to the {@link DynamicTrackedRace} passed to the constructor. * * @author Axel Uhl (d043530) @@ -35,7 +35,7 @@ public class ExpeditionWindTracker extends AbstractWindTracker implements Expedi private final UDPExpeditionReceiver receiver; - private final ExpeditionWindTrackerFactory factory; + private final ExpeditionTrackerFactory factory; /** * @param declinationService @@ -47,7 +47,7 @@ public class ExpeditionWindTracker extends AbstractWindTracker implements Expedi * calling {@link #stop}, this subscription will be removed again. */ public ExpeditionWindTracker(DynamicTrackedRace race, DeclinationService declinationService, - UDPExpeditionReceiver receiver, ExpeditionWindTrackerFactory factory) { + UDPExpeditionReceiver receiver, ExpeditionTrackerFactory factory) { super(race); this.lastKnownPositionPerBoatID = new HashMap(); this.declinationService = declinationService; @@ -60,7 +60,7 @@ public class ExpeditionWindTracker extends AbstractWindTracker implements Expedi public void stop() { synchronized (factory) { receiver.removeListener(this); - factory.windTrackerStopped(getTrackedRace().getRace(), this); + factory.trackerStopped(getTrackedRace().getRace(), this); } } @@ -87,7 +87,7 @@ public class ExpeditionWindTracker extends AbstractWindTracker implements Expedi try { Declination declination = declinationService.getDeclination(message.getTimePoint(), lastKnownPositionPerBoatID.get(message.getBoatID()), - /* timeoutForOnlineFetchInMilliseconds */5000); + /* timeoutForOnlineFetchInMilliseconds */500); if (declination != null) { windSpeed = new KnotSpeedWithBearingImpl(windSpeed.getKnots(), new DegreeBearingImpl( windSpeed.getBearing().getDegrees()).add(declination.getBearingCorrectedTo(message.getTimePoint()))); diff --git a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionWindTrackerFactory.java b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionWindTrackerFactory.java deleted file mode 100755 index b5bc9246921..00000000000 --- a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/ExpeditionWindTrackerFactory.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.sap.sailing.expeditionconnector; - -import java.net.SocketException; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Logger; - -import com.sap.sailing.declination.DeclinationService; -import com.sap.sailing.domain.base.RaceDefinition; -import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; -import com.sap.sailing.domain.tracking.DynamicTrackedRace; -import com.sap.sailing.domain.tracking.WindTracker; -import com.sap.sailing.domain.tracking.WindTrackerFactory; -import com.sap.sailing.expeditionconnector.impl.Activator; - -public class ExpeditionWindTrackerFactory implements WindTrackerFactory { - private static Logger logger = Logger.getLogger(ExpeditionWindTrackerFactory.class.getName()); - - private static ExpeditionWindTrackerFactory defaultInstance; - - /** - * Remembers the wind tracker and the port on which the UDP receiver with which the wind tracker is - * registers is listening for incoming Expedition messages. - */ - private final Map windTrackers; - - private final Map windReceivers; - - private final int defaultPort; - - public ExpeditionWindTrackerFactory() { - this.windTrackers = new HashMap(); - windReceivers = new HashMap(); - defaultPort = Activator.getInstance().getExpeditionUDPPort(); - logger.info("Created "+getClass().getName()+" with default UDP port "+defaultPort); - } - - public synchronized static ExpeditionWindTrackerFactory getInstance() { - if (defaultInstance == null) { - defaultInstance = new ExpeditionWindTrackerFactory(); - } - return defaultInstance; - } - - @Override - public WindTracker createWindTracker(DynamicTrackedRegatta trackedRegatta, RaceDefinition race, - boolean correctByDeclination) throws SocketException { - WindTracker result = getExistingWindTracker(race); - if (result == null) { - DynamicTrackedRace trackedRace = trackedRegatta.getTrackedRace(race); - UDPExpeditionReceiver receiver = getOrCreateWindReceiverOnDefaultPort(); - result = new ExpeditionWindTracker(trackedRace, - correctByDeclination ? DeclinationService.INSTANCE : null, receiver, this); - windTrackers.put(race, result); - } - return result; - } - - @Override - public WindTracker getExistingWindTracker(RaceDefinition race) { - return windTrackers.get(race); - } - - public UDPExpeditionReceiver getOrCreateWindReceiverOnDefaultPort() throws SocketException { - return getOrCreateWindReceiverForPort(defaultPort); - } - - private synchronized UDPExpeditionReceiver getOrCreateWindReceiverForPort(int port) throws SocketException { - UDPExpeditionReceiver receiver = windReceivers.get(port); - if (receiver == null) { - receiver = new UDPExpeditionReceiver(port); - windReceivers.put(port, receiver); - Thread t = new Thread(receiver, "Expedition Wind Receiver on port "+port); - t.setDaemon(true); - t.start(); - } - return receiver; - } - - /** - * Notifies the factory that the wind tracker has stopped tracking wind for race. This - * will remove the tracker from the respective caches. - */ - synchronized void windTrackerStopped(RaceDefinition race, ExpeditionWindTracker windTracker) { - if (windTrackers.get(race) != windTracker) { - throw new IllegalArgumentException("Intenral error: expected to remove wind tracker "+windTracker+ - ", but another wind tracker "+windTrackers.get(race)+" was registered."); - } - windTrackers.remove(race); - if (windTracker.getReceiver().isStopped()) { - UDPExpeditionReceiver receiver = windReceivers.get(windTracker.getReceiver().getPort()); - if (receiver != windTracker.getReceiver()) { - throw new IllegalArgumentException("Internal error: expected to remove wind receiver "+ - windTracker.getReceiver()+" but found receiver "+receiver); - } - windReceivers.remove(windTracker.getReceiver().getPort()); - } - } - - @Override - public String toString() { - return "ExpeditionWindTrackerFactory [defaultPort=" + defaultPort + "]"; - } -} diff --git a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/UDPExpeditionReceiver.java b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/UDPExpeditionReceiver.java index 8edf05e4cd8..1b6be99d92a 100755 --- a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/UDPExpeditionReceiver.java +++ b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/UDPExpeditionReceiver.java @@ -4,8 +4,15 @@ import java.io.IOException; import java.net.SocketException; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; +import com.sap.sailing.domain.common.sensordata.ExpeditionExtendedSensorDataMetadata; +import com.sap.sailing.domain.common.tracking.DoubleVectorFix; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl; +import com.sap.sailing.domain.racelog.tracking.SensorFixStore; import com.sap.sailing.expeditionconnector.impl.ExpeditionMessageParser; +import com.sap.sailing.expeditionconnector.persistence.ExpeditionGpsDeviceIdentifier; import com.sap.sailing.udpconnector.UDPReceiver; /** @@ -20,10 +27,18 @@ public class UDPExpeditionReceiver extends UDPReceiver timeStampOfLastMessageReceived; + private final Map delayBetweenMessageTimestampAndTimepointReceived; private final ExpeditionMessageParser parser; + /** + * An optional lookup facility for device identifiers for a {@link ExpeditionMessage#getBoatID() boat ID} as + * received in an Expedition UDP stream; if device identifiers are returned by a non-{@code null} registry, GPS / + * sensor fixes will be assembled upon receiving them, and they will be submitted to the {@link SensorFixStore}. + * Otherwise, only wind data will be forwarded. + */ + private final DeviceRegistry deviceRegistry; + /** * Launches a listener and dumps messages received to the console * @param args 0: port to listen on @@ -46,13 +61,93 @@ public class UDPExpeditionReceiver extends UDPReceiver(); - parser = new ExpeditionMessageParser(this); + this(listeningOnPort, /* DeviceRegistry */ null); } - public Map getTimeStampOfLastMessageReceived() { - return timeStampOfLastMessageReceived; + /** + * You need call {@link #run} to actually start receiving events. To do this asynchronously, start this object in a + * new thread. + * + * @param deviceRegistry + * if not {@code null}, a look-up for each message's {@link ExpeditionMessage#getBoatID() boat ID} will + * be performed; if valid device identifiers for GPS and sensor tracks are returned, this receiver will + * produce GPS and sensor fixes, respectively, with the device identifiers produced by the + * {@link DeviceRegistry} and submit them to the {@link SensorFixStore}. + */ + public UDPExpeditionReceiver(int listeningOnPort, DeviceRegistry deviceRegistry) throws SocketException { + super(listeningOnPort); + this.deviceRegistry = deviceRegistry; + this.delayBetweenMessageTimestampAndTimepointReceived = new HashMap(); + parser = new ExpeditionMessageParser(this); + addListener(msg->produceAndStoreOptionalFixes(msg), /* validMessagesOnly */ true); + } + + private void produceAndStoreOptionalFixes(ExpeditionMessage msg) { + if (deviceRegistry != null) { + final ExpeditionGpsDeviceIdentifier gpsDeviceIdentifier = deviceRegistry.getGpsDeviceIdentifier(msg.getBoatID()); + if (gpsDeviceIdentifier != null) { + tryToProduceAndStoreGpsFix(msg, gpsDeviceIdentifier); + } + final ExpeditionSensorDeviceIdentifier sensorDeviceIdentifier = deviceRegistry.getSensorDeviceIdentifier(msg.getBoatID()); + if (sensorDeviceIdentifier != null) { + tryToProduceAndStoreSensorFix(msg, sensorDeviceIdentifier); + } + } + } + + /** + * If this message completes the set of data required to produce a sensor fix, do so and + * store in the {@link DeviceRegistry#getSensorFixStore() sensor fix store}. + */ + private void tryToProduceAndStoreSensorFix(ExpeditionMessage msg, ExpeditionSensorDeviceIdentifier sensorDeviceIdentifier) { + final Map mapExpeditionMessageFieldIdToMetadata = new HashMap<>(); + mapExpeditionMessageFieldIdToMetadata.put(ExpeditionMessage.ID_ROLL, ExpeditionExtendedSensorDataMetadata.HEEL); + mapExpeditionMessageFieldIdToMetadata.put(ExpeditionMessage.ID_PITCH, ExpeditionExtendedSensorDataMetadata.TRIM); + mapExpeditionMessageFieldIdToMetadata.put(ExpeditionMessage.ID_RUDDER, ExpeditionExtendedSensorDataMetadata.RUDDER); + mapExpeditionMessageFieldIdToMetadata.put(ExpeditionMessage.ID_TACK_ANGLE, ExpeditionExtendedSensorDataMetadata.TACK_ANGLE); + mapExpeditionMessageFieldIdToMetadata.put(ExpeditionMessage.ID_RAKE_DEG, ExpeditionExtendedSensorDataMetadata.RAKE_DEG); + mapExpeditionMessageFieldIdToMetadata.put(ExpeditionMessage.ID_DFLCTR_PP, ExpeditionExtendedSensorDataMetadata.DEFLECTOR_PERCENTAGE); + mapExpeditionMessageFieldIdToMetadata.put(ExpeditionMessage.ID_TG_HEEL, ExpeditionExtendedSensorDataMetadata.TARGET_HEEL); + mapExpeditionMessageFieldIdToMetadata.put(ExpeditionMessage.ID_FORESTAY_LOAD, ExpeditionExtendedSensorDataMetadata.FORESTAY_LOAD); + mapExpeditionMessageFieldIdToMetadata.put(ExpeditionMessage.ID_FORESTAY_PRES, ExpeditionExtendedSensorDataMetadata.FORESTAY_PRESSURE); + mapExpeditionMessageFieldIdToMetadata.put(ExpeditionMessage.ID_DFLECTR_MM, ExpeditionExtendedSensorDataMetadata.DEFLECTOR_MILLIMETERS); + mapExpeditionMessageFieldIdToMetadata.put(ExpeditionMessage.ID_TARG_BSP_P, ExpeditionExtendedSensorDataMetadata.TARGET_BOATSPEED_P); + + int maxColumnIndex = -1; + final Map valuesPerDoubleVectorFixColumnIndices = new HashMap<>(); + for (final Entry mapEntry : mapExpeditionMessageFieldIdToMetadata.entrySet()) { + if (msg.hasValue(mapEntry.getKey())) { + valuesPerDoubleVectorFixColumnIndices.put(mapEntry.getValue().getColumnIndex(), msg.getValue(mapEntry.getKey())); + maxColumnIndex = Math.max(maxColumnIndex, mapEntry.getValue().getColumnIndex()); + } + } + final Double[] vector = new Double[maxColumnIndex+1]; + for (final Entry valuesPerDoubleVectorFixColumnIndex : valuesPerDoubleVectorFixColumnIndices.entrySet()) { + vector[valuesPerDoubleVectorFixColumnIndex.getKey()] = valuesPerDoubleVectorFixColumnIndex.getValue(); + } + final DoubleVectorFix fix = new DoubleVectorFixImpl(msg.getTimePoint(), vector); + if (fix != null && fix.hasValidData()) { + deviceRegistry.getSensorFixStore().storeFix(sensorDeviceIdentifier, fix); + } + } + + /** + * If this message completes the set of data required to produce a GPS fix, do so and + * store in the {@link DeviceRegistry#getSensorFixStore() sensor fix store}. + */ + private void tryToProduceAndStoreGpsFix(ExpeditionMessage msg, ExpeditionGpsDeviceIdentifier gpsDeviceIdentifier) { + final GPSFixMoving fix = msg.getGPSFixMoving(); + if (fix != null) { + deviceRegistry.getSensorFixStore().storeFix(gpsDeviceIdentifier, fix); + } + } + + public Long getLastKnownMessageDelayInMillis(int boatID) { + return delayBetweenMessageTimestampAndTimepointReceived.get(boatID); + } + + public void updateLastKnownMessageDelay(int boatID, long timestampAsMillis) { + delayBetweenMessageTimestampAndTimepointReceived.put(boatID, timestampAsMillis); } protected ExpeditionMessageParser getParser() { diff --git a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/Activator.java b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/Activator.java index 79e02938b9a..31074365dd0 100755 --- a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/Activator.java +++ b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/Activator.java @@ -1,13 +1,30 @@ package com.sap.sailing.expeditionconnector.impl; +import java.util.Dictionary; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; import org.osgi.framework.BundleActivator; import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceRegistration; +import com.sap.sailing.domain.racelogtracking.DeviceIdentifierStringSerializationHandler; import com.sap.sailing.domain.tracking.WindTrackerFactory; -import com.sap.sailing.expeditionconnector.ExpeditionWindTrackerFactory; +import com.sap.sailing.expeditionconnector.ExpeditionSensorDeviceIdentifier; +import com.sap.sailing.expeditionconnector.ExpeditionTrackerFactory; +import com.sap.sailing.expeditionconnector.persistence.DomainObjectFactory; +import com.sap.sailing.expeditionconnector.persistence.ExpeditionGpsDeviceIdentifier; +import com.sap.sailing.expeditionconnector.persistence.ExpeditionGpsDeviceIdentifierJsonHandler; +import com.sap.sailing.expeditionconnector.persistence.MongoObjectFactory; +import com.sap.sailing.expeditionconnector.persistence.PersistenceFactory; +import com.sap.sailing.server.gateway.serialization.racelog.tracking.DeviceIdentifierJsonHandler; +import com.sap.sse.common.TypeBasedServiceFinder; +import com.sap.sse.util.impl.ThreadFactoryWithPriority; public class Activator implements BundleActivator { private static Logger logger = Logger.getLogger(Activator.class.getName()); @@ -16,17 +33,33 @@ public class Activator implements BundleActivator { private static Activator instance; + /** + * Registrations of OSGi services to be de-registered when the bundle shuts down + */ + private Set> registrations = new HashSet<>(); + private static final int DEFAULT_PORT = 2013; private int port; + + private BundleContext context; + private final ExecutorService executor = Executors.newSingleThreadExecutor(new ThreadFactoryWithPriority(Thread.NORM_PRIORITY, /* daemon */ true)); + public Activator() { port = Integer.valueOf(System.getProperty(EXPEDITION_UDP_PORT_PROPERTY_NAME, ""+DEFAULT_PORT)); logger.log(Level.INFO, "setting default for "+EXPEDITION_UDP_PORT_PROPERTY_NAME+" to "+port); } + private Dictionary getDict(String type) { + Dictionary properties = new Hashtable(); + properties.put(TypeBasedServiceFinder.TYPE, type); + return properties; + } + @Override public void start(BundleContext context) throws Exception { + this.context = context; if (instance == null) { instance = this; } @@ -35,8 +68,20 @@ public class Activator implements BundleActivator { logger.log(Level.INFO, "found "+EXPEDITION_UDP_PORT_PROPERTY_NAME+"="+port+" in OSGi context"); } // register the Expedition wind tracker factory as an OSGi service - context.registerService(ExpeditionWindTrackerFactory.class, ExpeditionWindTrackerFactory.getInstance(), /* properties */null); - context.registerService(WindTrackerFactory.class, ExpeditionWindTrackerFactory.getInstance(), /* properties */null); + final DomainObjectFactory domainObjectFactory = PersistenceFactory.INSTANCE.getDefaultDomainObjectFactory(); + final MongoObjectFactory mongoObjectFactory = PersistenceFactory.INSTANCE.getDefaultMongoObjectFactory(); + executor.execute(()->{ + logger.info("Creating ExpeditionTrackerFactory"); + final ExpeditionTrackerFactory expeditionTrackerFactory = new ExpeditionTrackerFactory( + /* sensorFixStore will be discovered by tracker factory through OSGi */ null, domainObjectFactory, mongoObjectFactory); + registrations.add(context.registerService(ExpeditionTrackerFactory.class, expeditionTrackerFactory, /* properties */null)); + registrations.add(context.registerService(WindTrackerFactory.class, expeditionTrackerFactory, /* properties */null)); + + registrations.add(context.registerService(DeviceIdentifierJsonHandler.class, new ExpeditionGpsDeviceIdentifierJsonHandler(), getDict(ExpeditionGpsDeviceIdentifier.TYPE))); + registrations.add(context.registerService(DeviceIdentifierStringSerializationHandler.class, new ExpeditionGpsStringSerializationHandler(), getDict(ExpeditionGpsDeviceIdentifier.TYPE))); + registrations.add(context.registerService(DeviceIdentifierJsonHandler.class, new ExpeditionSensorDeviceIdentifierJsonHandler(), getDict(ExpeditionSensorDeviceIdentifier.TYPE))); + registrations.add(context.registerService(DeviceIdentifierStringSerializationHandler.class, new ExpeditionSensorStringSerializationHandler(), getDict(ExpeditionSensorDeviceIdentifier.TYPE))); + }); } public static Activator getInstance() { @@ -46,8 +91,21 @@ public class Activator implements BundleActivator { return instance; } + public BundleContext getContext() { + return context; + } + + public void setContext(BundleContext context) { + this.context = context; + } + @Override public void stop(BundleContext context) throws Exception { + for (ServiceRegistration reg : registrations) { + reg.unregister(); + } + registrations.clear(); + this.context = null; } public int getExpeditionUDPPort() { diff --git a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/ExpeditionGpsStringSerializationHandler.java b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/ExpeditionGpsStringSerializationHandler.java new file mode 100755 index 00000000000..3714e70be85 --- /dev/null +++ b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/ExpeditionGpsStringSerializationHandler.java @@ -0,0 +1,9 @@ +package com.sap.sailing.expeditionconnector.impl; + +import com.sap.sailing.domain.racelogtracking.DeviceIdentifierStringSerializationHandler; +import com.sap.sailing.expeditionconnector.persistence.ExpeditionGpsDeviceIdentifierSerializationHandler; + +public class ExpeditionGpsStringSerializationHandler extends ExpeditionGpsDeviceIdentifierSerializationHandler +implements DeviceIdentifierStringSerializationHandler { + +} diff --git a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/ExpeditionMessageImpl.java b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/ExpeditionMessageImpl.java index d9b999ee772..f0508c90773 100755 --- a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/ExpeditionMessageImpl.java +++ b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/ExpeditionMessageImpl.java @@ -1,5 +1,6 @@ package com.sap.sailing.expeditionconnector.impl; +import java.util.Calendar; import java.util.Collections; import java.util.GregorianCalendar; import java.util.HashMap; @@ -42,19 +43,7 @@ public class ExpeditionMessageImpl implements ExpeditionMessage { * {@link #getTimePoint() time point}. */ public ExpeditionMessageImpl(int boatID, Map values, boolean valid, String originalMessage) { - this.boatID = boatID; - // ensure that nobody can manipulate the map used by this message object from outside - this.values = new HashMap(values); - this.valid = valid; - this.originalMessage = originalMessage; - this.createdAtMillis = System.currentTimeMillis(); - if (hasValue(ID_GPS_TIME)) { - timePoint = new MillisecondsTimePoint((long) - (getValue(ID_GPS_TIME)*24*3600*1000) + // this is the milliseconds since 31.12.1899 0:00:00 UTC - cal.getTimeInMillis()); - } else { - timePoint = new MillisecondsTimePoint(createdAtMillis); - } + this(boatID, values, valid, /* defaultTimePoint */ null, originalMessage, /* unused */ true); } /** @@ -63,9 +52,13 @@ public class ExpeditionMessageImpl implements ExpeditionMessage { * stamp */ public ExpeditionMessageImpl(int boatID, Map values, boolean valid, TimePoint defaultTimePoint, String originalMessage) { + this(boatID, values, valid, defaultTimePoint, originalMessage, /* unused */ true); if (defaultTimePoint == null) { - throw new IllegalArgumentException("defaultTimePoint for ExpeditionMessageImpl constructor must not be null"); + throw new IllegalArgumentException("defaultTimePoint for this ExpeditionMessageImpl constructor must not be null"); } + } + + private ExpeditionMessageImpl(int boatID, Map values, boolean valid, TimePoint defaultTimePoint, String originalMessage, boolean unused) { this.boatID = boatID; // ensure that nobody can manipulate the map used by this message object from outside this.values = new HashMap(values); @@ -73,9 +66,25 @@ public class ExpeditionMessageImpl implements ExpeditionMessage { this.originalMessage = originalMessage; this.createdAtMillis = System.currentTimeMillis(); if (hasValue(ID_GPS_TIME)) { + final double gpsTimeValue = getValue(ID_GPS_TIME); + final long referenceTime; + if (gpsTimeValue <= 1.0) { // "Phoenix" law: if between 0 and 1 then it's likely a day-relative time point + // so add the millis of today midnight UTC + Calendar todayMidnightUTC = new GregorianCalendar(); + todayMidnightUTC.setTimeZone(TimeZone.getTimeZone("UTC")); + todayMidnightUTC.set(Calendar.HOUR_OF_DAY, 0); + todayMidnightUTC.set(Calendar.MINUTE, 0); + todayMidnightUTC.set(Calendar.SECOND, 0); + todayMidnightUTC.set(Calendar.MILLISECOND, 0); + referenceTime = todayMidnightUTC.getTimeInMillis(); + } else { + referenceTime = cal.getTimeInMillis(); + } timePoint = new MillisecondsTimePoint((long) - (getValue(ID_GPS_TIME)*24*3600*1000) + // this is the milliseconds since 31.12.1899 0:00:00 UTC - cal.getTimeInMillis()); + (gpsTimeValue*24*3600*1000) + // this is the milliseconds since 31.12.1899 0:00:00 UTC + referenceTime); + } else if (defaultTimePoint == null) { + timePoint = new MillisecondsTimePoint(createdAtMillis); } else { timePoint = defaultTimePoint; } diff --git a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/ExpeditionMessageParser.java b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/ExpeditionMessageParser.java index c21d19f5d48..88a13bbefae 100755 --- a/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/ExpeditionMessageParser.java +++ b/java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/impl/ExpeditionMessageParser.java @@ -36,7 +36,7 @@ public class ExpeditionMessageParser implements UDPMessageParser values = new HashMap(); String[] variablesAndValuesInterleaved = variableValuePairs.split(","); long now = System.currentTimeMillis(); - Long diff = receiver.getTimeStampOfLastMessageReceived().get(boatID); + Long diff = receiver.getLastKnownMessageDelayInMillis(boatID); TimePoint defaultForMessageTimePoint; if (diff != null) { // compute a reasonable default for a time stamp in case message doesn't provide one @@ -60,7 +60,7 @@ public class ExpeditionMessageParser implements UDPMessageParser + + @@ -46,6 +48,7 @@ + @@ -90,4 +93,11 @@ + + + + + + + diff --git a/java/com.sap.sailing.feature/feature.xml b/java/com.sap.sailing.feature/feature.xml index 728e3f5ab9a..4ec77eb88b3 100755 --- a/java/com.sap.sailing.feature/feature.xml +++ b/java/com.sap.sailing.feature/feature.xml @@ -395,4 +395,32 @@ Computes leaderboard information for sailing races and offers RESTful APIs to al version="0.0.0" unpack="false"/> + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF index b6912d31c74..7fc34e485e1 100644 --- a/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui.test/META-INF/MANIFEST.MF @@ -15,7 +15,7 @@ Require-Bundle: com.sap.sailing.server, com.sap.sailing.domain.racelogtrackingadapter.testsupport, com.rabbitmq.client;bundle-version="2.8.4", com.sap.sse.filestorage, - com.google.gwt.user;bundle-version="2.8.1";resolution:=optional, + com.google.gwt.user;bundle-version="2.8.2";resolution:=optional, org.moxieapps.gwt.highcharts;bundle-version="1.5.0" Bundle-ClassPath: . Import-Package: com.sap.sailing.domain.racelogtracking.impl, diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java index 4aa7b76fa15..a6398d346fd 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java @@ -70,6 +70,7 @@ import com.sap.sailing.domain.tracking.WindStore; import com.sap.sailing.domain.tracking.WindTrack; import com.sap.sailing.domain.tracking.WindWithConfidence; import com.sap.sailing.domain.tracking.impl.WindTrackImpl; +import com.sap.sse.common.Duration; import com.sap.sse.common.IsManagedByCache; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; @@ -406,6 +407,16 @@ public class MockedTrackedRace implements DynamicTrackedRace { return null; } + @Override + public Distance getDistanceFoiled(Competitor competitor, TimePoint timePoint) { + return null; + } + + @Override + public Duration getDurationFoiled(Competitor competitor, TimePoint timePoint) { + return null; + } + @Override public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint, WindPositionMode windPositionMode) { return null; @@ -720,7 +731,6 @@ public class MockedTrackedRace implements DynamicTrackedRace { @Override public boolean isUsingMarkPassingCalculator() { - // TODO Auto-generated method stub return false; } @@ -875,4 +885,21 @@ public class MockedTrackedRace implements DynamicTrackedRace { public Distance getEstimatedDistanceToComplete(TimePoint now) { return null; } + + @Override + public > Iterable getSensorTracks( + String trackName) { + return Collections.emptySet(); + } + + @Override + public Speed getAverageSpeedOverGround(Competitor competitor, TimePoint timePoint) { + return null; + } + + @Override + public SpeedWithBearing getVelocityMadeGood(Competitor competitor, TimePoint timePoint, + WindPositionMode windPositionMode, WindLegTypeAndLegBearingCache cache) { + return null; + } } diff --git a/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs b/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs index 872f8cf77fc..176dead2344 100755 --- a/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs +++ b/java/com.sap.sailing.gwt.ui/.settings/org.eclipse.core.resources.prefs @@ -3,6 +3,7 @@ encoding//src/main/java/com/sap/sailing/gwt/home/mobile/partials/sectionHeader/S 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 encoding//src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDetailsComposite.java=utf-8 +encoding//src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExpeditionDeviceConfigurationsPanel.java=utf-8 encoding//src/main/java/com/sap/sailing/gwt/ui/adminconsole/IgtimiAccountsPanel.java=utf-8 encoding//src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java=utf-8 encoding//src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties=UTF-8 diff --git a/java/com.sap.sailing.gwt.ui/AdminConsole.css b/java/com.sap.sailing.gwt.ui/AdminConsole.css index 15c8f269e5a..9e65fc5bcdc 100644 --- a/java/com.sap.sailing.gwt.ui/AdminConsole.css +++ b/java/com.sap.sailing.gwt.ui/AdminConsole.css @@ -80,10 +80,6 @@ fieldset legend { color: #FF0000; } -.linkNoBorder { - border: none; -} - .refreshLink { border: none; position: relative; @@ -95,11 +91,6 @@ fieldset legend { vertical-align: middle; } -.boldLabel { - font-size: 14px; - font-weight: bold; -} - .boldAnchor { font-weight: bold; vertical-align: middle; diff --git a/java/com.sap.sailing.gwt.ui/DataMining.css b/java/com.sap.sailing.gwt.ui/DataMining.css index 54a9607fc06..d6f22a64cb9 100644 --- a/java/com.sap.sailing.gwt.ui/DataMining.css +++ b/java/com.sap.sailing.gwt.ui/DataMining.css @@ -109,23 +109,6 @@ table { margin: 0 12px 18px 12px; } -.sapLogo { - float: left; - margin: 13px 0 0 12px; -} - -.sailingAnalyticsLabelPanel { - margin: 10px 0 0 0; -} - -.sailingAnalyticsLabel { - float: left; - color: #fff; - font-size: 14px; - font-weight: bold; - margin: 15px 0 0 0; -} - .leaderboardLabel { border-bottom: 2px solid #007dc0; font-weight: bold; @@ -257,4 +240,4 @@ input.magnifier { .queryDefinitionViewerContent { background-color: white; padding: 5px; -} \ No newline at end of file +} diff --git a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF index 2f95c07d959..3d652f0ac0f 100644 --- a/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.gwt.ui/META-INF/MANIFEST.MF @@ -24,8 +24,8 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.freg.resultimport, com.sap.sailing.domain.swisstimingreplayadapter, com.sap.sailing.simulator, - com.google.gwt.user;bundle-version="2.8.1";resolution:=optional, - com.google.gwt.servlet;bundle-version="2.8.1", + com.google.gwt.user;bundle-version="2.8.2";resolution:=optional, + com.google.gwt.servlet;bundle-version="2.8.2", com.sap.sailing.domain.igtimiadapter, com.sap.sailing.xrr.resultimport, com.sap.sailing.datamining.shared, @@ -53,7 +53,9 @@ Require-Bundle: com.sap.sailing.domain, com.sap.sailing.polars.datamining, com.sap.sse.datamining.annotations, com.sap.sse.shared.android, - com.sap.sailing.competitorimport + com.sap.sailing.competitorimport, + com.sap.sailing.expeditionconnector, + com.sap.sailing.expeditionconnector.common Bundle-Activator: com.sap.sailing.gwt.ui.server.Activator Bundle-ActivationPolicy: lazy Import-Package: javax.servlet;version="3.1.0", diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch index 1764d7a0376..ba9e454eae9 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch @@ -55,12 +55,13 @@ + - + diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT.launch b/java/com.sap.sailing.gwt.ui/SailingGWT.launch index ac769bad419..f34b219a1a1 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT.launch @@ -53,6 +53,8 @@ + + diff --git a/java/com.sap.sailing.gwt.ui/Simulator.css b/java/com.sap.sailing.gwt.ui/Simulator.css index 0f78295f104..ba0f5ccc33e 100644 --- a/java/com.sap.sailing.gwt.ui/Simulator.css +++ b/java/com.sap.sailing.gwt.ui/Simulator.css @@ -27,22 +27,6 @@ html { font-size: 15px; } -.titleLabelWrapper { - position: relative; - margin-left: auto; - margin-right: auto; - width: 50%; -} - -.titleLabel { - text-align: center; - font-family: 'Open Sans', Arial, Verdana, sans-serif; - font-weight: 600 - font-size: 18px; - color: #fff; - margin: 23px 0 0 0; -} - body { background: #ebebeb; font-family: 'Open Sans', Arial, Verdana, sans-serif; @@ -81,31 +65,11 @@ table { border-collapse: separate; } -/*.RaceBoardHeaderPanel { - margin: 0 12px 14px 12px; -}*/ -.sapLogo { - float: left; - margin: 13px 0 0 12px; -} - .pioLogo { float: right; margin: 11px 20px 0 12px; } -.sailingAnalyticsLabelPanel { - margin: 10px 0 0 0; -} - -.sailingAnalyticsLabel { - float: left; - color: #fff; - font-size: 14px; - font-weight: bold; - margin: 15px 0 0 0; -} - .timePanelSettings { display: none; } diff --git a/java/com.sap.sailing.gwt.ui/Spectator.css b/java/com.sap.sailing.gwt.ui/Spectator.css index 6f55fe4310c..277654681e4 100644 --- a/java/com.sap.sailing.gwt.ui/Spectator.css +++ b/java/com.sap.sailing.gwt.ui/Spectator.css @@ -96,23 +96,6 @@ table { margin: 0 12px 18px 12px; } -.sapLogo { - float: left; - margin: 13px 0 0 12px; -} - -.sailingAnalyticsLabelPanel { - margin: 10px 0 0 0; -} - -.sailingAnalyticsLabel { - float: left; - color: #fff; - font-size: 14px; - font-weight: bold; - margin: 15px 0 0 0; -} - .leaderboardLabel { border-bottom: 2px solid #007dc0; font-weight: bold; diff --git a/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml b/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml index 2dd07f89431..8538c7b0804 100755 --- a/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml +++ b/java/com.sap.sailing.gwt.ui/WEB-INF/web.xml @@ -53,6 +53,15 @@ /* + + LocaleInjectionFilter + com.sap.sse.security.ui.server.LocaleInjectionFilter + + + LocaleInjectionFilter + *.html + + Sailing Service SailingService diff --git a/java/com.sap.sailing.gwt.ui/images/mobile/icon-bottle-white.svg b/java/com.sap.sailing.gwt.ui/images/mobile/icon-bottle-white.svg new file mode 100644 index 00000000000..744ffa64df7 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/images/mobile/icon-bottle-white.svg @@ -0,0 +1 @@ +Anniversary Icon \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/images/mobile/max_speed.svg b/java/com.sap.sailing.gwt.ui/images/mobile/max_speed.svg new file mode 100644 index 00000000000..67fe3bc7481 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/images/mobile/max_speed.svg @@ -0,0 +1,3 @@ + + + diff --git a/java/com.sap.sailing.gwt.ui/images/sap_66_transparent.png b/java/com.sap.sailing.gwt.ui/images/sap_66_transparent.png deleted file mode 100644 index 7e76e7fcf00..00000000000 Binary files a/java/com.sap.sailing.gwt.ui/images/sap_66_transparent.png and /dev/null differ diff --git a/java/com.sap.sailing.gwt.ui/images/titleLabelInner.png b/java/com.sap.sailing.gwt.ui/images/titleLabelInner.png deleted file mode 100644 index 04c78e98c3c..00000000000 Binary files a/java/com.sap.sailing.gwt.ui/images/titleLabelInner.png and /dev/null differ diff --git a/java/com.sap.sailing.gwt.ui/images/titleLabelWrapper.png b/java/com.sap.sailing.gwt.ui/images/titleLabelWrapper.png deleted file mode 100644 index 3881f73df40..00000000000 Binary files a/java/com.sap.sailing.gwt.ui/images/titleLabelWrapper.png and /dev/null differ diff --git a/java/com.sap.sailing.gwt.ui/pom.xml b/java/com.sap.sailing.gwt.ui/pom.xml index 820d5ab5f18..101dc398344 100755 --- a/java/com.sap.sailing.gwt.ui/pom.xml +++ b/java/com.sap.sailing.gwt.ui/pom.xml @@ -50,6 +50,12 @@ 1.6.0-SNAPSHOT sources + + com.sap.sailing + com.sap.sailing.expeditionconnector.common + ${project.version} + sources + com.sap.sailing com.sap.sailing.domain.common diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/datamining/shared/FoilingSegmentsDataMiningSettings_CustomFieldSerializer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/datamining/shared/FoilingSegmentsDataMiningSettings_CustomFieldSerializer.java new file mode 100755 index 00000000000..1b2cda75229 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/datamining/shared/FoilingSegmentsDataMiningSettings_CustomFieldSerializer.java @@ -0,0 +1,57 @@ +package com.google.gwt.user.client.rpc.core.com.sap.sailing.datamining.shared; + +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.datamining.shared.FoilingSegmentsDataMiningSettings; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.Speed; +import com.sap.sse.common.Duration; + +public final class FoilingSegmentsDataMiningSettings_CustomFieldSerializer extends CustomFieldSerializer { + @Override + public boolean hasCustomInstantiateInstance() { + return true; + } + + @Override + public FoilingSegmentsDataMiningSettings instantiateInstance(SerializationStreamReader streamReader) throws SerializationException { + return instantiate(streamReader); + } + + public static FoilingSegmentsDataMiningSettings instantiate(SerializationStreamReader streamReader) throws SerializationException { + final Duration minimumFoilingSegmentDuration = (Duration) streamReader.readObject(); + final Duration minimumDurationBetweenAdjacentFoilingSegments = (Duration) streamReader.readObject(); + final Speed minimumSpeedForFoiling = (Speed) streamReader.readObject(); + final Speed maximumSpeedNotFoiling = (Speed) streamReader.readObject(); + final Distance minimumRideHeight = (Distance) streamReader.readObject(); + return new FoilingSegmentsDataMiningSettings(minimumFoilingSegmentDuration, minimumDurationBetweenAdjacentFoilingSegments, + minimumSpeedForFoiling, maximumSpeedNotFoiling, minimumRideHeight); + } + + @Override + public void deserializeInstance(SerializationStreamReader streamReader, FoilingSegmentsDataMiningSettings instance) + throws SerializationException { + deserialize(streamReader, instance); + } + + public static void deserialize(SerializationStreamReader streamReader, FoilingSegmentsDataMiningSettings instance) { + // handled by instantiate + } + + @Override + public void serializeInstance(SerializationStreamWriter streamWriter, FoilingSegmentsDataMiningSettings instance) + throws SerializationException { + serialize(streamWriter, instance); + } + + public static void serialize(SerializationStreamWriter streamWriter, FoilingSegmentsDataMiningSettings instance) + throws SerializationException { + streamWriter.writeObject(instance.getMinimumFoilingSegmentDuration()); + streamWriter.writeObject(instance.getMinimumDurationBetweenAdjacentFoilingSegments()); + streamWriter.writeObject(instance.getMinimumSpeedForFoiling()); + streamWriter.writeObject(instance.getMaximumSpeedNotFoiling()); + streamWriter.writeObject(instance.getMinimumRideHeight()); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/impl/KnotSpeedImpl_CustomFieldSerializer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/impl/KnotSpeedImpl_CustomFieldSerializer.java new file mode 100755 index 00000000000..a54e3cc1d03 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/domain/common/impl/KnotSpeedImpl_CustomFieldSerializer.java @@ -0,0 +1,48 @@ +package com.google.gwt.user.client.rpc.core.com.sap.sailing.domain.common.impl; + +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.impl.KnotSpeedImpl; + +public class KnotSpeedImpl_CustomFieldSerializer extends CustomFieldSerializer { + + @Override + public void serializeInstance(SerializationStreamWriter streamWriter, KnotSpeedImpl instance) + throws SerializationException { + serialize(streamWriter, instance); + } + + public static void serialize(SerializationStreamWriter streamWriter, KnotSpeedImpl instance) + throws SerializationException { + streamWriter.writeDouble(instance.getKnots()); + } + + @Override + public boolean hasCustomInstantiateInstance() { + return true; + } + + @Override + public KnotSpeedImpl instantiateInstance(SerializationStreamReader streamReader) + throws SerializationException { + return instantiate(streamReader); + } + + public static KnotSpeedImpl instantiate(SerializationStreamReader streamReader) + throws SerializationException { + return new KnotSpeedImpl(streamReader.readDouble()); + } + + @Override + public void deserializeInstance(SerializationStreamReader streamReader, KnotSpeedImpl instance) + throws SerializationException { + deserialize(streamReader, instance); + } + + public static void deserialize(SerializationStreamReader streamReader, KnotSpeedImpl instance) { + // Done by instantiateInstance + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/expeditionconnector/ExpeditionDeviceConfiguration_CustomFieldSerializer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/expeditionconnector/ExpeditionDeviceConfiguration_CustomFieldSerializer.java new file mode 100755 index 00000000000..9a17f3263e8 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sailing/expeditionconnector/ExpeditionDeviceConfiguration_CustomFieldSerializer.java @@ -0,0 +1,52 @@ +package com.google.gwt.user.client.rpc.core.com.sap.sailing.expeditionconnector; + +import java.util.UUID; + +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.expeditionconnector.ExpeditionDeviceConfiguration; + +public class ExpeditionDeviceConfiguration_CustomFieldSerializer extends CustomFieldSerializer { + + @Override + public void serializeInstance(SerializationStreamWriter streamWriter, ExpeditionDeviceConfiguration instance) + throws SerializationException { + serialize(streamWriter, instance); + } + + public static void serialize(SerializationStreamWriter streamWriter, ExpeditionDeviceConfiguration instance) + throws SerializationException { + streamWriter.writeString(instance.getName()); + streamWriter.writeObject(instance.getDeviceUuid()); + streamWriter.writeObject(instance.getExpeditionBoatId()); + } + + @Override + public boolean hasCustomInstantiateInstance() { + return true; + } + + @Override + public ExpeditionDeviceConfiguration instantiateInstance(SerializationStreamReader streamReader) + throws SerializationException { + return instantiate(streamReader); + } + + public static ExpeditionDeviceConfiguration instantiate(SerializationStreamReader streamReader) + throws SerializationException { + return new ExpeditionDeviceConfiguration(streamReader.readString(), (UUID) streamReader.readObject(), (Integer) streamReader.readObject()); + } + + @Override + public void deserializeInstance(SerializationStreamReader streamReader, ExpeditionDeviceConfiguration instance) + throws SerializationException { + deserialize(streamReader, instance); + } + + public static void deserialize(SerializationStreamReader streamReader, ExpeditionDeviceConfiguration instance) { + // Done by instantiateInstance + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sse/common/impl/MillisecondsDurationImpl_CustomFieldSerializer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sse/common/impl/MillisecondsDurationImpl_CustomFieldSerializer.java new file mode 100755 index 00000000000..13f100482f5 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sse/common/impl/MillisecondsDurationImpl_CustomFieldSerializer.java @@ -0,0 +1,48 @@ +package com.google.gwt.user.client.rpc.core.com.sap.sse.common.impl; + +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.sse.common.impl.MillisecondsDurationImpl; + +public class MillisecondsDurationImpl_CustomFieldSerializer extends CustomFieldSerializer { + + @Override + public void serializeInstance(SerializationStreamWriter streamWriter, MillisecondsDurationImpl instance) + throws SerializationException { + serialize(streamWriter, instance); + } + + public static void serialize(SerializationStreamWriter streamWriter, MillisecondsDurationImpl instance) + throws SerializationException { + streamWriter.writeLong(instance.asMillis()); + } + + @Override + public boolean hasCustomInstantiateInstance() { + return true; + } + + @Override + public MillisecondsDurationImpl instantiateInstance(SerializationStreamReader streamReader) + throws SerializationException { + return instantiate(streamReader); + } + + public static MillisecondsDurationImpl instantiate(SerializationStreamReader streamReader) + throws SerializationException { + return new MillisecondsDurationImpl(streamReader.readLong()); + } + + @Override + public void deserializeInstance(SerializationStreamReader streamReader, MillisecondsDurationImpl instance) + throws SerializationException { + deserialize(streamReader, instance); + } + + public static void deserialize(SerializationStreamReader streamReader, MillisecondsDurationImpl instance) { + // Done by instantiateInstance + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sse/datamining/shared/impl/AverageWithStatsImpl_CustomFieldSerializer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sse/datamining/shared/impl/AverageWithStatsImpl_CustomFieldSerializer.java new file mode 100755 index 00000000000..a8f8a8737d6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sse/datamining/shared/impl/AverageWithStatsImpl_CustomFieldSerializer.java @@ -0,0 +1,58 @@ +package com.google.gwt.user.client.rpc.core.com.sap.sse.datamining.shared.impl; + +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.sse.datamining.shared.impl.AverageWithStatsImpl; + +@SuppressWarnings("rawtypes") +public class AverageWithStatsImpl_CustomFieldSerializer extends CustomFieldSerializer { + @Override + public boolean hasCustomInstantiateInstance() { + return true; + } + + @Override + public AverageWithStatsImpl instantiateInstance(SerializationStreamReader streamReader) throws SerializationException { + return instantiate(streamReader); + } + + public static AverageWithStatsImpl instantiate(SerializationStreamReader streamReader) throws SerializationException { + final Object average = streamReader.readObject(); + final Object min = streamReader.readObject(); + final Object max = streamReader.readObject(); + final Object median = streamReader.readObject(); + final Object standardDeviation = streamReader.readObject(); + final String resultType = streamReader.readString(); + final long count = streamReader.readLong(); + return new AverageWithStatsImpl(average, min, max, median, standardDeviation, count, resultType); + } + + @Override + public void deserializeInstance(SerializationStreamReader streamReader, AverageWithStatsImpl instance) + throws SerializationException { + deserialize(streamReader, instance); + } + + public static void deserialize(SerializationStreamReader streamReader, AverageWithStatsImpl instance) { + // handled by instantiate + } + + @Override + public void serializeInstance(SerializationStreamWriter streamWriter, AverageWithStatsImpl instance) + throws SerializationException { + serialize(streamWriter, instance); + } + + public static void serialize(SerializationStreamWriter streamWriter, AverageWithStatsImpl instance) + throws SerializationException { + streamWriter.writeObject(instance.getAverage()); + streamWriter.writeObject(instance.getMin()); + streamWriter.writeObject(instance.getMax()); + streamWriter.writeObject(instance.getMedian()); + streamWriter.writeObject(instance.getStandardDeviation()); + streamWriter.writeString(instance.getResultType()); + streamWriter.writeLong(instance.getCount()); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sse/datamining/shared/impl/dto/ReducedDimensionsDTO_CustomFieldSerializer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sse/datamining/shared/impl/dto/ReducedDimensionsDTO_CustomFieldSerializer.java new file mode 100755 index 00000000000..3e4a09d6713 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/google/gwt/user/client/rpc/core/com/sap/sse/datamining/shared/impl/dto/ReducedDimensionsDTO_CustomFieldSerializer.java @@ -0,0 +1,67 @@ +package com.google.gwt.user.client.rpc.core.com.sap.sse.datamining.shared.impl.dto; + +import java.util.HashMap; +import java.util.HashSet; + +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.sse.datamining.shared.impl.dto.DataRetrieverLevelDTO; +import com.sap.sse.datamining.shared.impl.dto.FunctionDTO; +import com.sap.sse.datamining.shared.impl.dto.ReducedDimensionsDTO; + +/** + * Ensures that the {@link FunctionDTO#getLocalizedName() localizedName} field is serialized by + * really evaluating it eagerly now; this way, an optional lazy evaluation function does not need + * to be serialized. + * + * @author Axel Uhl (d043530) + * + */ +public class ReducedDimensionsDTO_CustomFieldSerializer extends CustomFieldSerializer { + + @Override + public void serializeInstance(SerializationStreamWriter streamWriter, ReducedDimensionsDTO instance) + throws SerializationException { + serialize(streamWriter, instance); + } + + public static void serialize(SerializationStreamWriter streamWriter, ReducedDimensionsDTO instance) + throws SerializationException { + final HashMap> reducedDimensions = instance.getReducedDimensions(); + streamWriter.writeObject(reducedDimensions); + final HashMap fromOriginalToReducedDimension = new HashMap<>(instance.getFromOriginalToReducedDimension()); + streamWriter.writeObject(fromOriginalToReducedDimension); + } + + @Override + public boolean hasCustomInstantiateInstance() { + return true; + } + + @Override + public ReducedDimensionsDTO instantiateInstance(SerializationStreamReader streamReader) + throws SerializationException { + return instantiate(streamReader); + } + + @SuppressWarnings("unchecked") // the cast to List is the problem here + public static ReducedDimensionsDTO instantiate(SerializationStreamReader streamReader) + throws SerializationException { + final HashMap> reducedDimensions = (HashMap>) streamReader.readObject(); + final HashMap fromOriginalToReducedDimension = (HashMap) streamReader.readObject(); + return new ReducedDimensionsDTO(reducedDimensions, fromOriginalToReducedDimension); + } + + @Override + public void deserializeInstance(SerializationStreamReader streamReader, ReducedDimensionsDTO instance) + throws SerializationException { + deserialize(streamReader, instance); + } + + public static void deserialize(SerializationStreamReader streamReader, ReducedDimensionsDTO instance) { + // Done by instantiateInstance + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoPlayActivityMapperImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoPlayActivityMapperImpl.java index ad2ab26f909..9ce399a46eb 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoPlayActivityMapperImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoPlayActivityMapperImpl.java @@ -28,6 +28,9 @@ import com.sap.sailing.gwt.autoplay.client.places.screens.liveraceloop.raceboard import com.sap.sailing.gwt.autoplay.client.places.screens.liveraceloop.racemapwithleaderboard.LiveRaceWithRacemapAndLeaderBoardPlace; import com.sap.sailing.gwt.autoplay.client.places.screens.liveraceloop.racemapwithleaderboard.LiveRaceWithRacemapAndLeaderBoardPresenterImpl; import com.sap.sailing.gwt.autoplay.client.places.screens.liveraceloop.racemapwithleaderboard.LiveRaceWithRacemapAndLeaderBoardViewImpl; +import com.sap.sailing.gwt.autoplay.client.places.screens.preevent.IdlePreEventPlace; +import com.sap.sailing.gwt.autoplay.client.places.screens.preevent.IdlePreEventPresenterImpl; +import com.sap.sailing.gwt.autoplay.client.places.screens.preevent.IdlePreEventViewImpl; import com.sap.sailing.gwt.autoplay.client.places.screens.preliveraceloop.competitors.PreRaceCompetitorsFlagsPlace; import com.sap.sailing.gwt.autoplay.client.places.screens.preliveraceloop.competitors.PreRaceCompetitorsImagePlace; import com.sap.sailing.gwt.autoplay.client.places.screens.preliveraceloop.competitors.PreRaceCompetitorsPresenterImpl; @@ -66,12 +69,15 @@ public class AutoPlayActivityMapperImpl implements ActivityMapper { clientFactory, new PreLiveRaceLeaderBoardWithImageViewImpl(new CompetitorImageProvider())); } if (place instanceof PreRaceLeaderBoardWithFlagPlace) { - return new PreLiveRaceLeaderBoardWithImagePresenterImpl((PreRaceLeaderBoardWithFlagPlace) place, clientFactory, - new PreLiveRaceLeaderBoardWithImageViewImpl(new FlagImageProvider())); + return new PreLiveRaceLeaderBoardWithImagePresenterImpl((PreRaceLeaderBoardWithFlagPlace) place, + clientFactory, new PreLiveRaceLeaderBoardWithImageViewImpl(new FlagImageProvider())); } if (place instanceof IdleUpNextPlace) { return new IdleUpNextPresenterImpl((IdleUpNextPlace) place, clientFactory, new IdleNextUpViewImpl()); } + if (place instanceof IdlePreEventPlace) { + return new IdlePreEventPresenterImpl((IdlePreEventPlace) place, clientFactory, new IdlePreEventViewImpl()); + } if (place instanceof VideoPlace) { return new VideoPresenterImpl((VideoPlace) place, clientFactory, new VideoViewImpl()); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoPlayContextImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoPlayContextImpl.java index f9f9144a9c5..a94e2351a0a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoPlayContextImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoPlayContextImpl.java @@ -56,7 +56,7 @@ public class AutoPlayContextImpl implements AutoPlayContext { RegattaAndRaceIdentifier currentLiveRace) { if (this.liveRace != null) { if (!this.liveRace.equals(currentLiveRace)) { - LOG.info(">> AUTOPLAY >> Context >> Lastrace was " + lastRace + " liferace is now " + liveRace); + LOG.info("Autoplay: Lastrace was " + lastRace + " liferace is now " + liveRace); this.lastRace = this.liveRace; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoplayPerspectiveLifecycle.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoplayPerspectiveLifecycle.java index 5bf0c2991fc..2e7f725bd2b 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoplayPerspectiveLifecycle.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/app/AutoplayPerspectiveLifecycle.java @@ -1,5 +1,6 @@ package com.sap.sailing.gwt.autoplay.client.app; +import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO; import com.sap.sailing.gwt.autoplay.client.shared.leaderboard.LeaderboardWithZoomingPerspectiveLifecycle; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -18,7 +19,8 @@ public class AutoplayPerspectiveLifecycle extends AbstractPerspectiveLifecycle autoplayPerspectiveSettings = autoplayLifecycle .createDefaultSettings(); - LinkWithSettingsGenerator> settingsGenerator = new LinkWithSettingsGenerator<>(Window.Location.getPath(), apcd); + LinkWithSettingsGenerator> settingsGenerator = new LinkWithSettingsGenerator<>(Window.Location.getPath(), autoplayLifecycle::createDefaultSettings, apcd); new SettingsDialogForLinkSharing>(settingsGenerator, autoplayLifecycle, autoplayPerspectiveSettings, StringMessages.INSTANCE, true, callback).show(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdlePreEventNode.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdlePreEventNode.java new file mode 100644 index 00000000000..f22d06736bd --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdlePreEventNode.java @@ -0,0 +1,24 @@ +package com.sap.sailing.gwt.autoplay.client.nodes; + +import com.sap.sailing.gwt.autoplay.client.app.AutoPlayClientFactory; +import com.sap.sailing.gwt.autoplay.client.events.AutoPlayHeaderEvent; +import com.sap.sailing.gwt.autoplay.client.nodes.base.FiresPlaceNode; +import com.sap.sailing.gwt.autoplay.client.places.screens.preevent.IdlePreEventPlace; + +public class IdlePreEventNode extends FiresPlaceNode { + private final AutoPlayClientFactory cf; + + public IdlePreEventNode(AutoPlayClientFactory cf) { + super(IdlePreEventNode.class.getName()); + this.cf = cf; + } + + @Override + public void onStart() { + String eventName = cf.getAutoPlayCtx().getEvent().getName(); + getBus().fireEvent(new AutoPlayHeaderEvent(eventName, "")); + setPlaceToGo(new IdlePreEventPlace()); + firePlaceChangeAndStartTimer(); + + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleRaceLeaderboard.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleRaceLeaderboard.java index 47d6dc45dfd..0ac396ef4b0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleRaceLeaderboard.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleRaceLeaderboard.java @@ -26,12 +26,8 @@ public class IdleRaceLeaderboard extends FiresPlaceNode { public IdleRaceLeaderboard(AutoPlayClientFactory cf) { super(IdleRaceLeaderboard.class.getName()); this.cf = cf; - PerspectiveCompositeSettings settings = cf.getAutoPlayCtx() - .getAutoplaySettings(); leaderboardTimer = new Timer(PlayModes.Live, /* delayBetweenAutoAdvancesInMilliseconds */ LeaderboardEntryPoint.DEFAULT_REFRESH_INTERVAL_MILLIS); - leaderboardTimer - .setLivePlayDelayInMillis(settings.getPerspectiveOwnSettings().getTimeToSwitchBeforeRaceStart()); leaderboardTimer.setRefreshInterval(REFRESH_INTERVAL_IN_MILLIS_LEADERBOARD); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleUpNextNode.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleUpNextNode.java index 7a287abf302..fe3d2dd4904 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleUpNextNode.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/IdleUpNextNode.java @@ -17,8 +17,6 @@ import com.sap.sailing.gwt.autoplay.client.places.screens.idleloop.idleupnext.Id import com.sap.sailing.gwt.autoplay.client.utils.AutoplayHelper; import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener; -import com.sap.sailing.gwt.ui.shared.EventDTO; -import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.common.Util.Pair; @@ -26,9 +24,7 @@ import com.sap.sse.common.Util.Pair; public class IdleUpNextNode extends FiresPlaceNode { private final AutoPlayClientFactory cf; private IdleUpNextPlace place; - private static RaceTimesInfoProvider raceTimesInfoProvider; - // private static Timer raceboardTimer = new Timer(PlayModes.Live, /* delayBetweenAutoAdvancesInMilliseconds - // */1000l); + private RaceTimesInfoProvider raceTimesInfoProvider; public IdleUpNextNode(AutoPlayClientFactory cf) { super(IdleUpNextNode.class.getName()); @@ -42,17 +38,18 @@ public class IdleUpNextNode extends FiresPlaceNode { getBus().fireEvent(new AutoPlayHeaderEvent(eventName, "")); setPlaceToGo(place); firePlaceChangeAndStartTimer(); + createRaceTimeInfoProvider(); + } - if (raceTimesInfoProvider == null) { - createRaceTimeInfoProvider(); - } - + @Override + public void onStop() { + raceTimesInfoProvider.terminate(); + super.onStop(); } private void createRaceTimeInfoProvider() { raceTimesInfoProvider = new RaceTimesInfoProvider(cf.getSailingService(), AutoplayHelper.asyncActionsExecutor, cf.getErrorReporter(), new ArrayList(), 10000l); - raceTimesInfoProvider.reset(); StrippedLeaderboardDTO selectedLeaderboard = AutoplayHelper.getSelectedLeaderboard( cf.getAutoPlayCtx().getEvent(), cf.getAutoPlayCtx().getContextDefinition().getLeaderboardName()); @@ -89,20 +86,6 @@ public class IdleUpNextNode extends FiresPlaceNode { }); }; - /** - * functional sideeffect free method for getting a leaderboard from an event based on the name - */ - public static StrippedLeaderboardDTO getSelectedLeaderboard(EventDTO event, String leaderBoardName) { - for (LeaderboardGroupDTO leaderboardGroup : event.getLeaderboardGroups()) { - for (StrippedLeaderboardDTO leaderboard : leaderboardGroup.getLeaderboards()) { - if (leaderboard.name.equals(leaderBoardName)) { - return leaderboard; - } - } - } - return null; - } - public static void checkForRaceStarts(AbstractLeaderboardDTO currentLeaderboard, Date serverTimeDuringRequest, RaceTimesInfoProvider raceTimesInfoProvider, ArrayList> raceToStartOfRace) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/LiveRaceBoardNode.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/LiveRaceBoardNode.java index 7c57ab9a034..f8581d237a7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/LiveRaceBoardNode.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/LiveRaceBoardNode.java @@ -41,9 +41,7 @@ public class LiveRaceBoardNode extends FiresPlaceNode implements RaceTimesInfoPr this.cf = cf; SailingServiceAsync sailingService = cf.getSailingService(); - PerspectiveCompositeSettings settings = cf.getAutoPlayCtx().getAutoplaySettings(); raceboardTimer = new Timer(PlayModes.Live, TIMER_ADVANCE_STEPSIZE); - raceboardTimer.setLivePlayDelayInMillis(settings.getPerspectiveOwnSettings().getTimeToSwitchBeforeRaceStart()); raceboardTimer.setRefreshInterval(REFRESH_INTERVAL_IN_MILLIS_RACEBOARD); raceboardTimer.play(); raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, AutoplayHelper.asyncActionsExecutor, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/LiveRaceWithRacemapNode.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/LiveRaceWithRacemapNode.java index 1108a2063c2..24262d13580 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/LiveRaceWithRacemapNode.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/LiveRaceWithRacemapNode.java @@ -49,7 +49,7 @@ public class LiveRaceWithRacemapNode extends FiresPlaceNode { @Override public void onSuccess(RVWrapper result) { - place.setRaceMap(result.raceboardPerspective, result.csel); + place.setRaceMap(result.raceboardPerspective, result.csel,result.raceboardTimer,result.creationTimeProvider); setPlaceToGo(place); firePlaceChangeAndStartTimer(); getBus().fireEvent(new AutoPlayHeaderEvent(cf.getAutoPlayCtx().getLiveRace().getRegattaName(), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/PreLiveRaceWithRacemapNode.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/PreLiveRaceWithRacemapNode.java index 71dd4dc5019..2f43c4625f7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/PreLiveRaceWithRacemapNode.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/PreLiveRaceWithRacemapNode.java @@ -37,7 +37,8 @@ public class PreLiveRaceWithRacemapNode extends FiresPlaceNode { @Override public void onSuccess(RVWrapper result) { PreRaceRacemapPlace place = new PreRaceRacemapPlace(); - place.setRaceMap(result.raceboardPerspective, result.csel); + place.setRaceMap(result.raceboardPerspective, result.csel, result.raceboardTimer, + result.creationTimeProvider); // add later with settings here place.setURL(cf.getAutoPlayCtx().getEvent().getOfficialWebsiteURL()); setPlaceToGo(place); @@ -45,7 +46,6 @@ public class PreLiveRaceWithRacemapNode extends FiresPlaceNode { new AutoPlayHeaderEvent(cf.getAutoPlayCtx().getPreLiveRace().getRegattaName(), cf.getAutoPlayCtx().getPreLiveRace().getRaceName())); firePlaceChangeAndStartTimer(); - } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/RootNodeClassic.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/RootNodeClassic.java index 23677554fe8..59c0f948f8f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/RootNodeClassic.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/RootNodeClassic.java @@ -10,19 +10,24 @@ import com.sap.sailing.gwt.autoplay.client.nodes.base.RootNodeState; public class RootNodeClassic extends RootNodeBase { private final AutoPlayNode idle; private final AutoPlayNode live; + private final IdlePreEventNode preEvent; public RootNodeClassic(AutoPlayClientFactory cf) { super(RaceEndWithCompetitorsFlagsNode.class.getName(), cf); this.idle = new IdleRaceLeaderboard(cf); this.live = new LiveRaceBoardNode(cf); + this.preEvent = new IdlePreEventNode(cf); } protected boolean processStateTransition(RegattaAndRaceIdentifier currentPreLiveRace, RegattaAndRaceIdentifier currentLiveRace, RootNodeState goingTo, RootNodeState comingFrom) { getClientFactory().getAutoPlayCtx().updateLiveRace(currentPreLiveRace, currentLiveRace); switch (goingTo) { - case IDLE: + case PRE_EVENT: + transitionTo(preEvent); + break; case AFTER_LIVE: + case IDLE: transitionTo(idle); break; case PRE_RACE: @@ -48,5 +53,4 @@ public class RootNodeClassic extends RootNodeBase { transitionTo(idle); } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/RootNodeSixtyInch.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/RootNodeSixtyInch.java index f9abc327069..22601b434d3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/RootNodeSixtyInch.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/RootNodeSixtyInch.java @@ -16,22 +16,23 @@ public class RootNodeSixtyInch extends RootNodeBase { private final AutoPlayLoopNode liveRaceLoop; private final AutoPlaySequenceNode afterLiveRaceLoop; protected boolean afterRaceFinished = true; + private AutoPlayLoopNode preEvent; public RootNodeSixtyInch(AutoPlayClientFactory cf) { super(RaceEndWithCompetitorsFlagsNode.class.getName(), cf); - this.idleLoop = new AutoPlayLoopNode("IdleLoop", 30, new IdleUpNextNode(cf), - new IdleOverallLeaderBoardNode(cf), new VideoNode(cf)); - this.preLiveRaceLoop = new AutoPlayLoopNode("PreLiveRaceLoop", 90, new PreLiveRaceLeaderBoardWithCompetitorsNode(cf), - new PreLiveRaceWithRacemapNode(cf)); + this.idleLoop = new AutoPlayLoopNode("IdleLoop", 30, new IdleUpNextNode(cf), new IdleOverallLeaderBoardNode(cf), + new VideoNode(cf)); + this.preEvent = new AutoPlayLoopNode("PreEvent", 30, new IdlePreEventNode(cf), new VideoNode(cf)); + this.preLiveRaceLoop = new AutoPlayLoopNode("PreLiveRaceLoop", 90, + new PreLiveRaceLeaderBoardWithCompetitorsNode(cf), new PreLiveRaceWithRacemapNode(cf)); this.liveRaceLoop = new AutoPlayLoopNode("LiveRaceLoop", 30, new LiveRaceWithRacemapNode(cf)); this.afterLiveRaceLoop = new AutoPlaySequenceNode("AfterLiveRaceLoop", 30, - new RaceEndWithCompetitorsBoatsNode(cf), - new RaceEndWithCompetitorsFlagsNode(cf)); + new RaceEndWithCompetitorsBoatsNode(cf), new RaceEndWithCompetitorsFlagsNode(cf)); afterLiveRaceLoop.setOnSequenceEnd(new Command() { @Override public void execute() { - afterRaceFinished = true; + afterRaceFinished = true; } }); } @@ -40,14 +41,17 @@ public class RootNodeSixtyInch extends RootNodeBase { RegattaAndRaceIdentifier currentLiveRace, RootNodeState goingTo, RootNodeState comingFrom) { // block transitions, until the afterLiveRaceLoop is finished if (comingFrom == RootNodeState.AFTER_LIVE) { - if(afterRaceFinished){ + if (afterRaceFinished) { afterRaceFinished = false; - }else{ + } else { return true; } } getClientFactory().getAutoPlayCtx().updateLiveRace(currentPreLiveRace, currentLiveRace); switch (goingTo) { + case PRE_EVENT: + transitionTo(preEvent); + break; case IDLE: transitionTo(idleLoop); break; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/base/RootNodeBase.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/base/RootNodeBase.java index a4d3f7adff0..4d957d8de41 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/base/RootNodeBase.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/base/RootNodeBase.java @@ -66,12 +66,20 @@ public abstract class RootNodeBase extends BaseCompositeNode { public void onSuccess(final EventDTO event) { if (firstTimeEventLoaded) { AutoPlayHeaderEvent hE = new AutoPlayHeaderEvent(event.getName(), ""); - hE.setHeaderLogoUrl(event.getLogoImage().getSourceRef()); + if(event.getLogoImage()!=null){ + hE.setHeaderLogoUrl(event.getLogoImage().getSourceRef()); + } cf.getEventBus().fireEvent(hE); firstTimeEventLoaded = false; } cf.getAutoPlayCtx().updateEvent(event); - _doCheck(); + if (event.isFinished() || event.isRunning()) { + _doCheck(); + } else { + setCurrentState(false, null, RootNodeState.PRE_EVENT, currentState); + // faster update if event not yet started! + checkTimer.schedule(1000); + } } @Override @@ -82,7 +90,6 @@ public abstract class RootNodeBase extends BaseCompositeNode { } private void _doCheck() { - final RegattaAndRaceIdentifier currentPreLiveRace = cf.getAutoPlayCtx().getPreLiveRace(); final RegattaAndRaceIdentifier currentLiveRace = cf.getAutoPlayCtx().getLiveRace(); @@ -101,8 +108,7 @@ public abstract class RootNodeBase extends BaseCompositeNode { log("No live race found, " + (!comingFromLiveRace ? "not " : "") + "coming from live race"); setCurrentState(false, null, - comingFromLiveRace ? RootNodeState.AFTER_LIVE : RootNodeState.IDLE, - currentState); + comingFromLiveRace ? RootNodeState.AFTER_LIVE : RootNodeState.IDLE, currentState); } else { @@ -110,18 +116,18 @@ public abstract class RootNodeBase extends BaseCompositeNode { final RegattaAndRaceIdentifier loadedLiveRace = result.getB(); boolean isPreLiveRace = timeToRaceStartInMs > LIVE_SWITCH_DELAY; - // exit + // exit if (currentLiveRace != null && !loadedLiveRace.equals(currentLiveRace)) { log("Received different live race, hard switching to AFTER_LIVE race"); - setCurrentState(isPreLiveRace, loadedLiveRace, RootNodeState.AFTER_LIVE , currentState); + setCurrentState(isPreLiveRace, loadedLiveRace, RootNodeState.AFTER_LIVE, currentState); } else { - - log("New " + (isPreLiveRace?"live ":"pre live") - + " race found: " + loadedLiveRace + " starting in " + (timeToRaceStartInMs/1000) + "s"); - - setCurrentState(isPreLiveRace, loadedLiveRace, isPreLiveRace? RootNodeState.PRE_RACE: RootNodeState.LIVE, currentState); - + log("New " + (isPreLiveRace ? "live " : "pre live") + " race found: " + loadedLiveRace + + " starting in " + (timeToRaceStartInMs / 1000) + "s"); + + setCurrentState(isPreLiveRace, loadedLiveRace, + isPreLiveRace ? RootNodeState.PRE_RACE : RootNodeState.LIVE, currentState); + } } } @@ -136,8 +142,8 @@ public abstract class RootNodeBase extends BaseCompositeNode { }); } - private final void setCurrentState(boolean isPreLiveRace, RegattaAndRaceIdentifier liveRace, - RootNodeState goingTo, RootNodeState comingFrom) { + private final void setCurrentState(boolean isPreLiveRace, RegattaAndRaceIdentifier liveRace, RootNodeState goingTo, + RootNodeState comingFrom) { if (goingTo != comingFrom) { log("RootNodeBase transition " + comingFrom + " -> " + goingTo); RegattaAndRaceIdentifier candidatePreLiveRace = isPreLiveRace ? liveRace : null; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/base/RootNodeState.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/base/RootNodeState.java index 08ecd1884c5..0319aeefb33 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/base/RootNodeState.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/nodes/base/RootNodeState.java @@ -1,5 +1,5 @@ package com.sap.sailing.gwt.autoplay.client.nodes.base; public enum RootNodeState { - IDLE, PRE_RACE, LIVE, AFTER_LIVE; + IDLE, PRE_RACE, LIVE, AFTER_LIVE, PRE_EVENT; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPlace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPlace.java index 7b843596eb6..5ab8e0acf03 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPlace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPlace.java @@ -4,7 +4,9 @@ import com.google.gwt.place.shared.Place; import com.google.gwt.place.shared.PlaceTokenizer; import com.sap.sailing.gwt.home.communication.event.sixtyinch.GetSixtyInchStatisticDTO; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; +import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap; +import com.sap.sse.gwt.client.player.Timer; public class LiveRaceWithRacemapAndLeaderBoardPlace extends Place { public static class Tokenizer implements PlaceTokenizer { @@ -23,19 +25,31 @@ public class LiveRaceWithRacemapAndLeaderBoardPlace extends Place { private Throwable error; private CompetitorSelectionModel raceMapSelectionProvider; private GetSixtyInchStatisticDTO statistic; + private Timer raceboardTimer; + private RaceTimesInfoProvider timeProvider; - public void setRaceMap(RaceMap result, CompetitorSelectionModel csel) { + public void setRaceMap(RaceMap result, CompetitorSelectionModel csel, Timer raceboardTimer, RaceTimesInfoProvider timeProvider) { this.raceMap = result; this.raceMapSelectionProvider = csel; + this.raceboardTimer = raceboardTimer; + this.timeProvider = timeProvider; } public GetSixtyInchStatisticDTO getStatistic() { return statistic; } + + public Timer getRaceboardTimer() { + return raceboardTimer; + } public RaceMap getRaceMap() { return raceMap; } + + public RaceTimesInfoProvider getTimeProvider() { + return timeProvider; + } public Throwable getError() { return error; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPresenterImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPresenterImpl.java index f6889b2ea21..935573de344 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPresenterImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardPresenterImpl.java @@ -170,6 +170,12 @@ public class LiveRaceWithRacemapAndLeaderBoardPresenterImpl if (timer != null) { timer.pause(); } + if(getPlace().getRaceboardTimer() != null){ + getPlace().getRaceboardTimer().pause(); + } + if(getPlace().getTimeProvider() != null){ + getPlace().getTimeProvider().terminate(); + } selectionTimer.cancel(); view.onStop(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardViewImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardViewImpl.java index 0c3ea95d66b..568ed50aeb9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardViewImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/liveraceloop/racemapwithleaderboard/LiveRaceWithRacemapAndLeaderBoardViewImpl.java @@ -4,7 +4,6 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ResizeComposite; @@ -75,25 +74,9 @@ public class LiveRaceWithRacemapAndLeaderBoardViewImpl extends ResizeComposite i panel.setWidget(this); rawRaceMap = raceMap; racemap.add(raceMap); - resizeMapOnceInitially(raceMap); - leaderBoardHolder.add(leaderboardPanel); } - - private void resizeMapOnceInitially(RaceMap raceMap2) { - new Timer() { - @Override - public void run() { - raceMap2.redraw(); - racemap.onResize(); - if(racemap.isAttached()){ - this.schedule(50); - } - } - }.schedule(50); - } - @Override public void setStatistic(String windinfo, Distance distance, long duration) { statisticProperty1.setText(StringMessages.INSTANCE.windSpeed()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventEntry.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventEntry.java new file mode 100644 index 00000000000..c6a733fc418 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventEntry.java @@ -0,0 +1,23 @@ +package com.sap.sailing.gwt.autoplay.client.places.screens.preevent; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Element; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Widget; + +public class IdlePreEventEntry extends Widget { + private static final NextUpEntryUiBinder uiBinder = GWT.create(NextUpEntryUiBinder.class); + + interface NextUpEntryUiBinder extends UiBinder { + } + + @UiField + DivElement newsTimeUi; + + public IdlePreEventEntry(String time) { + setElement(uiBinder.createAndBindUi(this)); + newsTimeUi.setInnerText(time); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventEntry.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventEntry.ui.xml new file mode 100644 index 00000000000..eb032d14dd4 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventEntry.ui.xml @@ -0,0 +1,15 @@ + + + + .newsTime { + display: inline-block; + font-weight: bold; + color: #008fcc; + padding: 10px; + } + + +
    +
    +
    + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventNextView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventNextView.java new file mode 100644 index 00000000000..634981c28e6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventNextView.java @@ -0,0 +1,15 @@ +package com.sap.sailing.gwt.autoplay.client.places.screens.preevent; + +import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.sap.sailing.gwt.home.shared.utils.Countdown.RemainingTime; + +public interface IdlePreEventNextView { + public interface IdlePreEventNextPresenter { + } + + void startingWith(IdlePreEventNextPresenter p, AcceptsOneWidget panel); + + void setBackgroudImage(String string); + + void setStartIn(RemainingTime invalid); +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventPlace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventPlace.java new file mode 100644 index 00000000000..e71c28c6fa0 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventPlace.java @@ -0,0 +1,18 @@ +package com.sap.sailing.gwt.autoplay.client.places.screens.preevent; + +import com.google.gwt.place.shared.Place; +import com.google.gwt.place.shared.PlaceTokenizer; + +public class IdlePreEventPlace extends Place { + public static class Tokenizer implements PlaceTokenizer { + @Override + public String getToken(IdlePreEventPlace place) { + return ""; + } + + @Override + public IdlePreEventPlace getPlace(String token) { + return new IdlePreEventPlace(); + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventPresenterImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventPresenterImpl.java new file mode 100644 index 00000000000..135c5e91838 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventPresenterImpl.java @@ -0,0 +1,129 @@ +package com.sap.sailing.gwt.autoplay.client.places.screens.preevent; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Random; + +import com.google.gwt.safehtml.shared.UriUtils; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.sap.sailing.gwt.autoplay.client.app.AutoPlayClientFactory; +import com.sap.sailing.gwt.autoplay.client.app.AutoPlayPresenterConfigured; +import com.sap.sailing.gwt.home.shared.utils.Countdown; +import com.sap.sailing.gwt.home.shared.utils.Countdown.CountdownListener; +import com.sap.sailing.gwt.home.shared.utils.Countdown.RemainingTime; +import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.common.media.MediaTagConstants; +import com.sap.sse.gwt.client.media.ImageDTO; + +public class IdlePreEventPresenterImpl extends AutoPlayPresenterConfigured + implements IdlePreEventNextView.IdlePreEventNextPresenter, CountdownListener { + private static final int IMAGE_SWITCH_DELAY = 10000; + private final IdlePreEventNextView view; + private Timer updateImage; + private Timer updateText; + private Countdown countdown; + private Date currentStartTime; + + public IdlePreEventPresenterImpl(IdlePreEventPlace place, AutoPlayClientFactory clientFactory, + IdlePreEventNextView slide2ViewImpl) { + super(place, clientFactory); + this.view = slide2ViewImpl; + } + + @Override + public void startConfigured(AcceptsOneWidget panel) { + updateEventImage(); + view.startingWith(this, panel); + updateImage = new Timer() { + + @Override + public void run() { + updateEventImage(); + } + }; + updateImage.scheduleRepeating(IMAGE_SWITCH_DELAY); + + updateText = new Timer() { + + @Override + public void run() { + updateCountdown(); + } + + }; + updateText.scheduleRepeating(1000); + } + + protected void updateCountdown() { + EventDTO event = getSlideCtx().getEvent(); + if (event.startDate != null) { + if (!event.startDate.equals(currentStartTime)) { + currentStartTime = event.startDate; + if (countdown != null) { + countdown.cancel(); + } + this.countdown = new Countdown(new MillisecondsTimePoint(currentStartTime), this); + } + } else { + view.setStartIn(null); + } + } + + @Override + public void onStop() { + super.onStop(); + updateImage.cancel(); + updateText.cancel(); + countdown.cancel(); + } + + protected void updateEventImage() { + if (getClientFactory().getAutoPlayCtx() == null || getClientFactory().getAutoPlayCtx().getEvent() == null) { + return; + } + List teaserHighlight = new ArrayList<>(); + List bigScreenImages = new ArrayList<>(); + for (ImageDTO imageDTO : getSlideCtx().getEvent().getImages()) { + if (imageDTO.getTags().contains(MediaTagConstants.BIGSCREEN)) { + bigScreenImages.add(imageDTO); + } else if (imageDTO.getTags().contains(MediaTagConstants.TEASER)) { + teaserHighlight.add(imageDTO); + } else if (imageDTO.getTags().contains(MediaTagConstants.HIGHLIGHT)) { + teaserHighlight.add(imageDTO); + } + } + List usedImages; + if (bigScreenImages.isEmpty()) { + if (teaserHighlight.isEmpty()) { + usedImages = getSlideCtx().getEvent().getImages(); + } else { + usedImages = teaserHighlight; + } + } else { + usedImages = bigScreenImages; + } + + if (!usedImages.isEmpty()) { + int selected = 0; + if (usedImages.size() > 1) { + Random r = new Random(); + selected = r.nextInt(usedImages.size()); + } + ImageDTO imageToUseDTO = usedImages.get(selected); + if (imageToUseDTO != null) { + final StringBuilder thumbnailUrlBuilder = new StringBuilder("url('"); + thumbnailUrlBuilder.append(UriUtils.fromString(imageToUseDTO.getSourceRef()).asString()); + thumbnailUrlBuilder.append("')"); + view.setBackgroudImage(thumbnailUrlBuilder.toString()); + } + } + } + + @Override + public void changed(RemainingTime major, RemainingTime minor) { + view.setStartIn(major); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventViewImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventViewImpl.java new file mode 100644 index 00000000000..bed69699afa --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventViewImpl.java @@ -0,0 +1,58 @@ +package com.sap.sailing.gwt.autoplay.client.places.screens.preevent; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.HTMLPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.shared.utils.Countdown.RemainingTime; +import com.sap.sailing.gwt.home.shared.utils.Countdown.Unit; +import com.sap.sailing.gwt.ui.client.StringMessages; + +public class IdlePreEventViewImpl extends Composite implements IdlePreEventNextView { + private static IdlePreEventUpViewImplUiBinder uiBinder = GWT.create(IdlePreEventUpViewImplUiBinder.class); + @UiField + SimplePanel mainPanelUi; + @UiField + HTMLPanel dataPanel; + + interface Dummy extends ClientBundle { + ImageResource dummy(); + } + + interface IdlePreEventUpViewImplUiBinder extends UiBinder { + } + + public IdlePreEventViewImpl() { + initWidget(uiBinder.createAndBindUi(this)); + } + + @Override + public void startingWith(IdlePreEventNextPresenter p, AcceptsOneWidget panel) { + panel.setWidget(this); + } + + @Override + public void setBackgroudImage(String string) { + mainPanelUi.getElement().getStyle().setProperty("backgroundPosition", "center center"); + mainPanelUi.getElement().getStyle().setBackgroundImage(string); + } + + @Override + public void setStartIn(RemainingTime untilStart) { + dataPanel.clear(); + // do not be too specific, as this might expose differenes in event reloading, live delay and other smaller time + // differences + if (untilStart.unit == Unit.SECONDS) { + dataPanel.add(new IdlePreEventEntry(StringMessages.INSTANCE.countDownEnd())); + } else { + dataPanel.add( + new IdlePreEventEntry(StringMessages.INSTANCE.countDown(untilStart.value, untilStart.unitI18n()))); + } + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventViewImpl.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventViewImpl.ui.xml new file mode 100644 index 00000000000..79d307ab142 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/IdlePreEventViewImpl.ui.xml @@ -0,0 +1,39 @@ + + + + + + + + .fullscreenBackground { + position: relative; + background-repeat: no-repeat; + background-size: cover; + + width: 100%; + height: 100%; + } + + + .newsBox { + position: absolute; + width: 700px; + right: 100px; + top: 100px; + background-color: rgba(255,255,255,0.95); + border-radius: 10px; + padding: 40px; + font-size: 40px !important; + font-family: 'Open Sans', sans-serif !important; + font-weight: normal; + + } + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/dummy.png b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/dummy.png new file mode 100644 index 00000000000..149c9fef0b1 Binary files /dev/null and b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preevent/dummy.png differ diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPlace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPlace.java index 13b61749601..43b6894179a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPlace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPlace.java @@ -3,7 +3,9 @@ package com.sap.sailing.gwt.autoplay.client.places.screens.preliveraceloop.racem import com.google.gwt.place.shared.Place; import com.google.gwt.place.shared.PlaceTokenizer; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; +import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap; +import com.sap.sse.gwt.client.player.Timer; public class PreRaceRacemapPlace extends Place { public static class Tokenizer implements PlaceTokenizer { @@ -22,15 +24,23 @@ public class PreRaceRacemapPlace extends Place { private Throwable error; private CompetitorSelectionModel raceMapSelectionProvider; private String url; + private Timer raceboardTimer; + private RaceTimesInfoProvider timeProvider; - public void setRaceMap(RaceMap result, CompetitorSelectionModel csel) { + public void setRaceMap(RaceMap result, CompetitorSelectionModel csel, Timer raceboardTimer, RaceTimesInfoProvider timeProvider) { this.raceMap = result; this.raceMapSelectionProvider = csel; + this.raceboardTimer = raceboardTimer; + this.timeProvider = timeProvider; } public RaceMap getRaceMap() { return raceMap; } + + public Timer getRaceboardTimer() { + return raceboardTimer; + } public Throwable getError() { return error; @@ -51,4 +61,8 @@ public class PreRaceRacemapPlace extends Place { public String getUrl() { return url; } + + public RaceTimesInfoProvider getTimeProvider() { + return timeProvider; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPresenterImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPresenterImpl.java index d58598e7a4a..291657d5e7d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPresenterImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/PreRaceRacemapPresenterImpl.java @@ -76,9 +76,13 @@ public class PreRaceRacemapPresenterImpl extends AutoPlayPresenterConfigured
     	
     	
    -	
    +	
     	
     
     		
      diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistik/PreRaceStatisticsBoxItem.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistic/PreRaceStatisticsBoxItem.java similarity index 97% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistik/PreRaceStatisticsBoxItem.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistic/PreRaceStatisticsBoxItem.java index f9b3f667910..246fd5e7a56 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistik/PreRaceStatisticsBoxItem.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistic/PreRaceStatisticsBoxItem.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.autoplay.client.places.screens.preliveraceloop.racemap.statistik; +package com.sap.sailing.gwt.autoplay.client.places.screens.preliveraceloop.racemap.statistic; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.Element; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistik/PreRaceStatisticsBoxItem.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistic/PreRaceStatisticsBoxItem.ui.xml similarity index 93% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistik/PreRaceStatisticsBoxItem.ui.xml rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistic/PreRaceStatisticsBoxItem.ui.xml index 2ff4552d0e8..9d2448c0016 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistik/PreRaceStatisticsBoxItem.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistic/PreRaceStatisticsBoxItem.ui.xml @@ -3,7 +3,7 @@ xmlns:g="urn:import:com.google.gwt.user.client.ui" xmlns:s="urn:import:com.sap.sailing.gwt.home.client.shared"> - +
    • diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistik/PreRaceStatisticsBoxQR.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistic/PreRaceStatisticsBoxQR.java similarity index 97% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistik/PreRaceStatisticsBoxQR.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistic/PreRaceStatisticsBoxQR.java index e6a1b270f11..6d6229fc645 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistik/PreRaceStatisticsBoxQR.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistic/PreRaceStatisticsBoxQR.java @@ -1,4 +1,4 @@ -package com.sap.sailing.gwt.autoplay.client.places.screens.preliveraceloop.racemap.statistik; +package com.sap.sailing.gwt.autoplay.client.places.screens.preliveraceloop.racemap.statistic; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.DivElement; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistik/PreRaceStatisticsBoxQR.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistic/PreRaceStatisticsBoxQR.ui.xml similarity index 94% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistik/PreRaceStatisticsBoxQR.ui.xml rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistic/PreRaceStatisticsBoxQR.ui.xml index 537be57470c..712a347e6a0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistik/PreRaceStatisticsBoxQR.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/places/screens/preliveraceloop/racemap/statistic/PreRaceStatisticsBoxQR.ui.xml @@ -6,7 +6,7 @@ + type="com.sap.sailing.gwt.autoplay.client.places.screens.preliveraceloop.racemap.statistic.PreRaceStatisticsBoxResources" />
    • raceTimesInfo, long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) { - if (alreadyfired) { - return; - } - alreadyfired = true; raceTimesInfoProvider.removeRaceTimesInfoProviderListener(this); - raceboardTimer.adjustClientServerOffset(clientTimeWhenRequestWasSent, serverTimeDuringRequest, + fastCurrentTimeProvider.adjustClientServerOffset(clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived); Pair timeToStartAndRaceIdentifier = checkForLiveRace( selectedLeaderboard, serverTimeDuringRequest, raceTimesInfoProvider); callback.onSuccess(timeToStartAndRaceIdentifier); + // kill old provider! + raceTimesInfoProvider.terminate(); } }); - + raceTimesInfoProvider.forceTimesInfosUpdate(); } /** @@ -137,12 +131,16 @@ public class AutoplayHelper { RegattaAndRaceIdentifier raceIdentifier = race.getRaceIdentifier(fleet); if (raceIdentifier != null) { RaceTimesInfoDTO raceTimes = raceTimesInfos.get(raceIdentifier); - if (raceTimes != null && raceTimes.startOfTracking != null && raceTimes.getStartOfRace() != null - && (raceTimes.endOfRace == null || serverTimeDuringRequest - .getTime() < raceTimes.endOfRace.getTime() + WAIT_TIME_AFTER_END_OF_RACE_MIILIS)) { + boolean notNullInRequiredValues = raceTimes != null && raceTimes.startOfTracking != null + && raceTimes.getStartOfRace() != null; + boolean raceHasNotEndedOrOnlyRecentlyEnded = raceTimes.endOfRace == null + || serverTimeDuringRequest.getTime() + - raceTimes.delayToLiveInMs < raceTimes.endOfRace.getTime() + + WAIT_TIME_AFTER_END_OF_RACE_MIILIS; + + if (notNullInRequiredValues && raceHasNotEndedOrOnlyRecentlyEnded) { long startTimeInMs = raceTimes.getStartOfRace().getTime(); - long delayToLiveInMs = raceTimes.delayToLiveInMs; - long startIn = startTimeInMs - serverTimeDuringRequest.getTime() - delayToLiveInMs; + long startIn = startTimeInMs - serverTimeDuringRequest.getTime() - raceTimes.delayToLiveInMs; if (startIn <= PRE_RACE_DELAY && startIn > NEGATIVE_SANITY_CHECK) { startOfLifeRace = raceTimes.getStartOfRace(); return new Pair(startIn, raceIdentifier); @@ -155,29 +153,33 @@ public class AutoplayHelper { return null; } + /** + * Struct like Returnvalue class for a racemap. The user is required to pause the timer and terminate the + * RaceTimeProvider after use! + */ public static class RVWrapper { + public final RaceTimesInfoProvider creationTimeProvider; + public final Timer raceboardTimer; + public final RaceMap raceboardPerspective; + public final CompetitorSelectionModel csel; - public RVWrapper(RaceMap raceboardPerspective2, CompetitorSelectionModel competitorSelectionProvider) { - this.raceboardPerspective = raceboardPerspective2; + public RVWrapper(RaceMap raceboardPerspective, CompetitorSelectionModel competitorSelectionProvider, + Timer raceboardTimer, RaceTimesInfoProvider creationTimeProvider) { + this.raceboardPerspective = raceboardPerspective; this.csel = competitorSelectionProvider; + this.raceboardTimer = raceboardTimer; + this.creationTimeProvider = creationTimeProvider; } - - public RaceMap raceboardPerspective; - public CompetitorSelectionModel csel; } public static void create(SailingServiceAsync sailingService, ErrorReporter errorReporter, String leaderBoardName, UUID eventId, EventDTO event, EventBus eventBus, SailingDispatchSystem sailingDispatchSystem, RegattaAndRaceIdentifier regattaAndRaceIdentifier, AsyncCallback callback) { GWT.log("Creating map for " + regattaAndRaceIdentifier); - raceboardTimer.setLivePlayDelayInMillis(1000); - raceboardTimer.setRefreshInterval(1000); + Timer creationTimer = new Timer(PlayModes.Live, /* delayBetweenAutoAdvancesInMilliseconds */1000l); - if (raceTimesInfoProvider == null) { - raceTimesInfoProvider = new RaceTimesInfoProvider(sailingService, AutoplayHelper.asyncActionsExecutor, - errorReporter, new ArrayList(), 10000l); - } - raceTimesInfoProvider.reset(); + creationTimer.setLivePlayDelayInMillis(1000); + creationTimer.setRefreshInterval(1000); StrippedLeaderboardDTO selectedLeaderboard = AutoplayHelper.getSelectedLeaderboard(event, leaderBoardName); @@ -185,42 +187,56 @@ public class AutoplayHelper { @Override public void onSuccess(Iterable competitors) { - loadRaceTimes(selectedLeaderboard, new RaceTimesInfoProviderListener() { - boolean alreadyFired; + RaceTimesInfoProvider creationTimeProvider = new RaceTimesInfoProvider(sailingService, + asyncActionsExecutor, errorReporter, new ArrayList(), 1000l); + + continouslyLoadRaceTimeData(selectedLeaderboard, new RaceTimesInfoProviderListener() { + boolean mapAlreadyCreated; + @Override public void raceTimesInfosReceived(Map raceTimesInfo, long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) { - raceboardTimer.adjustClientServerOffset(clientTimeWhenRequestWasSent, serverTimeDuringRequest, + // keep the creationTime updated upon liveChanges! + creationTimer.adjustClientServerOffset(clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived); - raceboardTimer.play(); + creationTimer + .setLivePlayDelayInMillis(raceTimesInfo.get(regattaAndRaceIdentifier).delayToLiveInMs); + creationTimer.play(); - if (regattaAndRaceIdentifier != null && !alreadyFired) { - alreadyFired = true; - sailingService.getCompetitorBoats(regattaAndRaceIdentifier, - new AsyncCallback>() { - @Override - public void onSuccess(Map result) { - createRaceMapIfNotExist(regattaAndRaceIdentifier, selectedLeaderboard, - result, competitors, sailingService, - AutoplayHelper.asyncActionsExecutor, errorReporter, raceboardTimer, - callback, clientTimeWhenResponseWasReceived, - serverTimeDuringRequest, clientTimeWhenRequestWasSent, - raceTimesInfo, new DefaultQuickRanksDTOProvider()); - } - - @Override - public void onFailure(Throwable caught) { - callback.onFailure( - new IllegalStateException("Error getting Competitor Boats")); - } - }); + if (regattaAndRaceIdentifier != null) { + if(!mapAlreadyCreated){ + mapAlreadyCreated = true; + sailingService.getCompetitorBoats(regattaAndRaceIdentifier, + new AsyncCallback>() { + @Override + public void onSuccess(Map result) { + createRaceMapIfNotExist(regattaAndRaceIdentifier, selectedLeaderboard, + result, competitors, sailingService, + AutoplayHelper.asyncActionsExecutor, errorReporter, creationTimer, + callback, clientTimeWhenResponseWasReceived, + serverTimeDuringRequest, clientTimeWhenRequestWasSent, + raceTimesInfo, creationTimeProvider, + new DefaultQuickRanksDTOProvider()); + } + + @Override + public void onFailure(Throwable caught) { + creationTimeProvider.terminate(); + creationTimer.pause(); + callback.onFailure( + new IllegalStateException("Error getting Competitor Boats")); + } + }); + } } else { + creationTimeProvider.terminate(); + creationTimer.pause(); callback.onFailure(new IllegalStateException("No Live Race Found")); } } - }, raceTimesInfoProvider); + }, creationTimeProvider); } @Override @@ -230,7 +246,7 @@ public class AutoplayHelper { }); } - protected static void loadRaceTimes(AbstractLeaderboardDTO selectedLeaderboard, + protected static void continouslyLoadRaceTimeData(AbstractLeaderboardDTO selectedLeaderboard, RaceTimesInfoProviderListener callback, RaceTimesInfoProvider raceTimesInfoProvider) { for (RaceColumnDTO race : selectedLeaderboard.getRaceList()) { for (FleetDTO fleet : race.getFleets()) { @@ -240,24 +256,15 @@ public class AutoplayHelper { } } } - raceTimesInfoProvider.forceTimesInfosUpdate(); raceTimesInfoProvider.addRaceTimesInfoProviderListener(new RaceTimesInfoProviderListener() { - private boolean alreadyfired; - @Override public void raceTimesInfosReceived(Map raceTimesInfo, long clientTimeWhenRequestWasSent, Date serverTimeDuringRequest, long clientTimeWhenResponseWasReceived) { - if (alreadyfired) { - return; - } - alreadyfired = true; callback.raceTimesInfosReceived(raceTimesInfo, clientTimeWhenRequestWasSent, serverTimeDuringRequest, clientTimeWhenResponseWasReceived); - raceTimesInfoProvider.removeRaceTimesInfoProviderListener(this); } }); - } private static void createRaceMapIfNotExist(RegattaAndRaceIdentifier currentLiveRace, @@ -266,7 +273,7 @@ public class AutoplayHelper { AsyncActionsExecutor asyncActionsExecutor, ErrorReporter errorReporter, Timer raceboardTimer, AsyncCallback callback, long clientTimeWhenResponseWasReceived, Date serverTimeDuringRequest, long clientTimeWhenRequestWasSent, Map raceTimesInfos, - AbstractQuickRanksDTOProvider provider) { + RaceTimesInfoProvider creationTimeProvider, AbstractQuickRanksDTOProvider provider) { ArrayList typesToConsiderOnZoom = new ArrayList<>(); // Other zoom types such as BOATS, TAILS or WINDSENSORS are not currently used as default zoom types. @@ -293,7 +300,8 @@ public class AutoplayHelper { raceboardTimer.setPlayMode(PlayModes.Live); // wait for one update raceboardPerspective.onResize(); - callback.onSuccess(new RVWrapper(raceboardPerspective, competitorSelectionProvider)); + callback.onSuccess( + new RVWrapper(raceboardPerspective, competitorSelectionProvider, raceboardTimer, creationTimeProvider)); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/SailingLocalesAllPermutations.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/SailingLocalesAllPermutations.gwt.xml index b79c9c282d7..970d476cb47 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/SailingLocalesAllPermutations.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/SailingLocalesAllPermutations.gwt.xml @@ -22,4 +22,5 @@ + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/authentication/FixedSailingAuthentication.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/authentication/FixedSailingAuthentication.java index 985972decf4..428c807eba6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/authentication/FixedSailingAuthentication.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/authentication/FixedSailingAuthentication.java @@ -17,6 +17,7 @@ public class FixedSailingAuthentication extends GenericAuthentication { super(userService, userManagementMenuView, new FixedFlyoutAuthenticationView(res), SailingAuthenticationEntryPointLinkFactory.INSTANCE, res); res.mainCss().ensureInjected(); + new FixedSailingLoginHintPopup(getAuthenticationManager()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/authentication/FixedSailingLoginHintPopup.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/authentication/FixedSailingLoginHintPopup.java new file mode 100644 index 00000000000..81dd373cfe6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/authentication/FixedSailingLoginHintPopup.java @@ -0,0 +1,15 @@ +package com.sap.sailing.gwt.common.authentication; + +import com.sap.sse.security.ui.authentication.AuthenticationManager; +import com.sap.sse.security.ui.authentication.generic.sapheader.FixedLoginHintPopup; + +/** + * Sailing specific version of {@link FixedLoginHintPopup}. + */ +public class FixedSailingLoginHintPopup extends FixedLoginHintPopup { + + public FixedSailingLoginHintPopup(AuthenticationManager authenticationManager) { + super(authenticationManager, SailingAuthenticationEntryPointLinkFactory.INSTANCE); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/authentication/SailingAuthenticationEntryPointLinkFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/authentication/SailingAuthenticationEntryPointLinkFactory.java index d67f4d07999..a31031e2768 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/authentication/SailingAuthenticationEntryPointLinkFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/authentication/SailingAuthenticationEntryPointLinkFactory.java @@ -29,6 +29,11 @@ public final class SailingAuthenticationEntryPointLinkFactory extends AbstractEn return createFullQualifiedLink("/gwt/Home.html", "/user/passwordreset/:"); } + @Override + public String createMoreInfoAboutLoginLink() { + return createFullQualifiedLink("/gwt/Home.html", "/about/account/:"); + } + private String createFullQualifiedLink(String path, String hash) { return UrlBuilderUtil.createUrlBuilderFromCurrentLocationWithCleanParametersAndPathAndHash(path, hash).buildString(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java index 1e850f124ce..0fe92e2e587 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResolver.java @@ -34,6 +34,7 @@ public class BoatClassImageResolver { boatClassIconsMap.put(BoatClassMasterdata._49ER.getDisplayName(), imageResources._49erIcon()); boatClassIconsMap.put(BoatClassMasterdata._5O5.getDisplayName(), imageResources._505mIcon()); boatClassIconsMap.put(BoatClassMasterdata._5_5MR.getDisplayName(), imageResources._5_5MRIcon()); + boatClassIconsMap.put(BoatClassMasterdata._6MR.getDisplayName(), imageResources._6MRIcon()); boatClassIconsMap.put(BoatClassMasterdata._8MR.getDisplayName(), imageResources._8MRIcon()); boatClassIconsMap.put(BoatClassMasterdata.A_CAT.getDisplayName(), imageResources.A_CatIcon()); boatClassIconsMap.put(BoatClassMasterdata.ALBIN_EXPRESS.getDisplayName(), imageResources.AlbinExpressIcon()); @@ -122,11 +123,13 @@ public class BoatClassImageResolver { boatClassIconsMap.put(BoatClassMasterdata.TEENY.getDisplayName(), imageResources.TeenyIcon()); boatClassIconsMap.put(BoatClassMasterdata.TORNADO.getDisplayName(), imageResources.TornadoIcon()); boatClassIconsMap.put(BoatClassMasterdata.TOM_28_MAX.getDisplayName(), imageResources.Tom28MaxIcon()); + boatClassIconsMap.put(BoatClassMasterdata.TP52.getDisplayName(), imageResources.TP52Icon()); boatClassIconsMap.put(BoatClassMasterdata.TRIAS.getDisplayName(), imageResources.TriasIcon()); boatClassIconsMap.put(BoatClassMasterdata.VIPER_640.getDisplayName(), imageResources.Viper640Icon()); boatClassIconsMap.put(BoatClassMasterdata.VX_ONE.getDisplayName(), imageResources.VXOneIcon()); boatClassIconsMap.put(BoatClassMasterdata.WETA.getDisplayName(), imageResources.WetaIcon()); boatClassIconsMap.put(BoatClassMasterdata.X_99.getDisplayName(), imageResources.X99Icon()); + boatClassIconsMap.put(BoatClassMasterdata.RUNNING.getDisplayName(), imageResources.RunningIcon()); } public static ImageResource getBoatClassIconResource(String displayName) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java index f2ebe18f2df..ee8b1e880fa 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/BoatClassImageResources.java @@ -40,6 +40,9 @@ public interface BoatClassImageResources extends ClientBundle { @Source("com/sap/sailing/gwt/ui/client/images/boatclass/5.5mR.png") ImageResource _5_5MRIcon(); + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/6MR.png") + ImageResource _6MRIcon(); + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/8MR.png") ImageResource _8MRIcon(); @@ -308,5 +311,11 @@ public interface BoatClassImageResources extends ClientBundle { ImageResource WetaIcon(); @Source("com/sap/sailing/gwt/ui/client/images/boatclass/TOM_28_MAX.png") - ImageResource Tom28MaxIcon(); + ImageResource Tom28MaxIcon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/TP52.png") + ImageResource TP52Icon(); + + @Source("com/sap/sailing/gwt/ui/client/images/boatclass/RUNNING.png") + ImageResource RunningIcon(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/DateUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/DateUtil.java index 2643ce45026..479a7e1ce9d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/DateUtil.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/common/client/DateUtil.java @@ -59,8 +59,14 @@ public class DateUtil { } public static int daysFromNow(Date date) { - long day = date.getTime() / DAY_IN_MILLIS; - long today= new Date().getTime() / DAY_IN_MILLIS; - return (int) (day - today); + return (int) (getDayCount(date) - getDayCount(new Date())); + } + + public static int daysUntilNow(Date date) { + return (int) (getDayCount(new Date()) - getDayCount(date)); + } + + private static long getDayCount(Date date) { + return date.getTime() / DAY_IN_MILLIS; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/HomeBase.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/HomeBase.gwt.xml index 8bdd972094f..63efcd24539 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/HomeBase.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/HomeBase.gwt.xml @@ -42,6 +42,7 @@ + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/anniversary/AnniversariesDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/anniversary/AnniversariesDTO.java new file mode 100644 index 00000000000..d8a1431db0f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/anniversary/AnniversariesDTO.java @@ -0,0 +1,34 @@ +package com.sap.sailing.gwt.home.communication.anniversary; + +import java.util.Collection; + +import com.sap.sse.gwt.dispatch.shared.commands.DTO; +import com.sap.sse.gwt.dispatch.shared.commands.ListResult; + +/** + * This class is used to transmit current anniversary states to the front-end. It is used to highlight anniversaries + * that will happen soon, or that did happen a few days ago prominently on the start page. + */ +public class AnniversariesDTO extends ListResult implements DTO { + + /** + * Creates an empty {@link AnniversariesDTO}. + * + * @see ListResult#ListResult() + */ + public AnniversariesDTO() { + } + + /** + * Creates an {@link AnniversariesDTO} containing the given {@link AnniversaryDTO anniversary entries}. + * + * @param values + * {@link Collection} of a {@link AnniversaryDTO anniversary entries} + * + * @see ListResult#ListResult(Collection) + */ + public AnniversariesDTO(Collection values) { + super(values); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/anniversary/AnniversaryDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/anniversary/AnniversaryDTO.java new file mode 100644 index 00000000000..b1654fa63ed --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/anniversary/AnniversaryDTO.java @@ -0,0 +1,111 @@ +package com.sap.sailing.gwt.home.communication.anniversary; + +import java.util.UUID; + +import com.sap.sailing.domain.common.dto.AnniversaryType; +import com.sap.sailing.domain.common.dto.EventType; +import com.sap.sse.gwt.dispatch.shared.commands.DTO; + +/** + * {@link DTO} object representing an anniversary entry (count-down or announcement) to be shown on the start page. + */ +public class AnniversaryDTO implements DTO { + + private int target; + private int currentRaceCount; + private EventType eventType; + private AnniversaryType type; + private UUID eventID; + private String leaderboardName; + private String remoteUrl; + private String regattaName; + private String raceName; + private String leaderboardDisplayName; + private String eventName; + + /** + * Gwt Serialisation only constructor + */ + protected AnniversaryDTO() { + } + + AnniversaryDTO(int target, int currentRaceCount, AnniversaryType type) { + this.target = target; + this.currentRaceCount = currentRaceCount; + this.type = type; + } + + AnniversaryDTO(int target,int currentRaceCount, AnniversaryType type, UUID eventID, String leaderboardName, String remoteUrl, + String raceName, String regattaName, String eventName, String leaderboardDisplayName, + EventType eventType) { + this(target, currentRaceCount, type); + this.eventID = eventID; + this.leaderboardName = leaderboardName; + this.remoteUrl = remoteUrl; + this.raceName = raceName; + this.regattaName = regattaName; + this.eventName = eventName; + this.leaderboardDisplayName = leaderboardDisplayName; + this.eventType = eventType; + } + + public EventType getEventType() { + return eventType; + } + + public int getTarget() { + return target; + } + + public String getEventName() { + return eventName; + } + + public String getLeaderboardDisplayName() { + return leaderboardDisplayName; + } + + public int getCurrentRaceCount() { + return currentRaceCount; + } + + public int getCountdown() { + return target - currentRaceCount; + } + + public AnniversaryType getType() { + return type; + } + + public UUID getEventID() { + return eventID; + } + + public String getLeaderboardName() { + return leaderboardName; + } + + public String getRemoteUrl() { + return remoteUrl; + } + + public String getRegattaName() { + return regattaName; + } + + public String getRaceName() { + return raceName; + } + + public boolean isAnnouncement() { + return eventID != null && leaderboardName != null && regattaName != null + && raceName != null; + } + + @Override + public String toString() { + return "AnniversaryInformation [target=" + target + ", countdown=" + currentRaceCount + ", type=" + type + ", eventID=" + + eventID + ", leaderboardName=" + leaderboardName + ", remoteUrl=" + remoteUrl + ", regattaName=" + + regattaName + ", raceName=" + raceName + "]"; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/anniversary/GetAnniversariesAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/anniversary/GetAnniversariesAction.java new file mode 100644 index 00000000000..9f5473c30ea --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/anniversary/GetAnniversariesAction.java @@ -0,0 +1,81 @@ +package com.sap.sailing.gwt.home.communication.anniversary; + +import java.net.URL; +import java.util.Date; +import java.util.Map; + +import com.google.gwt.core.shared.GwtIncompatible; +import com.sap.sailing.domain.anniversary.DetailedRaceInfo; +import com.sap.sailing.domain.base.EventBase; +import com.sap.sailing.domain.common.dto.AnniversaryType; +import com.sap.sailing.gwt.home.communication.SailingAction; +import com.sap.sailing.gwt.home.communication.SailingDispatchContext; +import com.sap.sailing.gwt.home.communication.event.EventState; +import com.sap.sailing.gwt.server.HomeServiceUtil; +import com.sap.sailing.gwt.server.HomeServiceUtil.EventVisitor; +import com.sap.sailing.server.RacingEventService; +import com.sap.sse.common.Duration; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.gwt.dispatch.shared.commands.ResultWithTTL; + +public class GetAnniversariesAction implements SailingAction> { + + private static final int SHOW_IF_FEWER_RACES_TILL_NEXT_ANNIVERSARY = 500; + private static final int DAYS_TO_SHOW_PAST_ANNIVERSARY = 14; + + @Override + @GwtIncompatible + public ResultWithTTL execute(SailingDispatchContext context) { + final AnniversariesDTO anniversaries = new AnniversariesDTO(); + final RacingEventService service = context.getRacingEventService(); + final Map> knownAnnivs = service.getKnownAnniversaries(); + final int currentRaceCount = service.getCurrentRaceCount(); + + final Pair next = service.getNextAnniversary(); + + if (next != null && (next.getA() - currentRaceCount) < SHOW_IF_FEWER_RACES_TILL_NEXT_ANNIVERSARY) { + if (!knownAnnivs.containsKey(next.getA())) { + anniversaries.addValue(new AnniversaryDTO(next.getA(), currentRaceCount, next.getB())); + } + } + + knownAnnivs.forEach((target, anniversaryInfo) -> { + final DetailedRaceInfo raceinfo = anniversaryInfo.getA(); + if (daysUntilNow(raceinfo.getStartOfRace().asDate()) < DAYS_TO_SHOW_PAST_ANNIVERSARY) { + anniversaries.addValue(new AnniversaryDTO(target, currentRaceCount, anniversaryInfo.getB(), + raceinfo.getEventID(), raceinfo.getLeaderboardName(), + raceinfo.getRemoteUrl() == null ? null : raceinfo.getRemoteUrl().toExternalForm(), + raceinfo.getIdentifier().getRaceName(), raceinfo.getIdentifier().getRegattaName(), + raceinfo.getEventName(), raceinfo.getLeaderboardDisplayName(), raceinfo.getEventType())); + } + }); + + TimeToLiveCalculator timeToLiveCalculator = new TimeToLiveCalculator(); + HomeServiceUtil.forAllPublicEvents(service, context.getRequest(), timeToLiveCalculator); + return new ResultWithTTL(timeToLiveCalculator.getTimeToLive(), anniversaries); + } + + @GwtIncompatible + private class TimeToLiveCalculator implements EventVisitor { + + private Duration timeToLive = Duration.ONE_MINUTE.times(5); + + @Override + public void visit(EventBase event, boolean onRemoteServer, URL baseURL) { + if (HomeServiceUtil.calculateEventState(event) == EventState.RUNNING) { + timeToLive = Duration.ONE_MINUTE; + } + } + + public Duration getTimeToLive() { + return timeToLive; + } + + } + + // FIXME Remove as soon as DateUtil is refactored to work on server-side too + private static int daysUntilNow(Date date) { + final long dayInMillis = 1000 * 60 * 60 * 24; + return (int) (new Date().getTime() / dayInMillis - date.getTime() / dayInMillis); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetEventViewAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetEventViewAction.java index f5c7030d590..921ce88f966 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetEventViewAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/event/GetEventViewAction.java @@ -7,16 +7,17 @@ import java.util.logging.Logger; 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.home.communication.SailingAction; import com.sap.sailing.gwt.home.communication.SailingDispatchContext; import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO; -import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO.EventType; import com.sap.sailing.gwt.home.communication.eventview.RegattaMetadataDTO; import com.sap.sailing.gwt.home.server.EventActionUtil; import com.sap.sailing.gwt.home.server.EventActionUtil.LeaderboardCallback; import com.sap.sailing.gwt.home.server.LeaderboardContext; import com.sap.sailing.gwt.server.HomeServiceUtil; +import com.sap.sailing.server.util.EventUtil; import com.sap.sse.common.media.MediaTagConstants; import com.sap.sse.gwt.dispatch.shared.caching.IsClientCacheable; import com.sap.sse.shared.media.ImageDescriptor; @@ -66,8 +67,18 @@ public class GetEventViewAction implements SailingAction, IsClient dto.setState(HomeServiceUtil.calculateEventState(event)); // bug2982: always show leaderboard and competitor analytics dto.setHasAnalytics(true); + + String description = event.getDescription(); + if (description == null || description.trim().isEmpty() || event.getName().equalsIgnoreCase(description)) { + // If a description isn't useful, it should not be shown in the UI + description = null; + } + dto.setDescription(description); - final boolean isFakeSeries = HomeServiceUtil.isFakeSeries(event); + final EventType eventType = EventUtil.getEventType(event); + dto.setType(eventType); + + final boolean isFakeSeries = eventType == EventType.SERIES; EventActionUtil.forLeaderboardsOfEvent(context, event, new LeaderboardCallback() { @Override @@ -85,8 +96,6 @@ public class GetEventViewAction implements SailingAction, IsClient }); if (isFakeSeries) { - dto.setType(EventType.SERIES_EVENT); - LeaderboardGroup overallLeaderboardGroup = event.getLeaderboardGroups().iterator().next(); dto.setSeriesName(HomeServiceUtil.getLeaderboardDisplayName(overallLeaderboardGroup)); @@ -99,8 +108,6 @@ public class GetEventViewAction implements SailingAction, IsClient EventState eventState = HomeServiceUtil.calculateEventState(eventInSeries); dto.addEventToSeries(new EventReferenceWithStateDTO(eventInSeries.getId(), displayName, eventState)); } - } else { - dto.setType(dto.getRegattas().size() == 1 ? EventType.SINGLE_REGATTA: EventType.MULTI_REGATTA); } return dto; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventlist/EventListYearDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventlist/EventListYearDTO.java index 5d07eac5178..794f112f4c2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventlist/EventListYearDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventlist/EventListYearDTO.java @@ -48,13 +48,26 @@ public class EventListYearDTO implements IsSerializable { protected void addEvent(EventListEventDTO event) { eventCount += event.getEventSeries() == null ? 1 : event.getEventSeries().getEventsCount(); for (int i = 0; i < events.size(); i++) { - if (events.get(i).getStartDate().compareTo(event.getStartDate()) < 0) { + if (isEventBeforeOtherInList(event, events.get(i))) { events.add(i, event); return; } } events.add(event); } + + private boolean isEventBeforeOtherInList(EventListEventDTO eventTocheck, EventListEventDTO referenceEvent) { + // Currently running events should always be the first in the list + // Big events (e.g. Kieler Woche) can start before smaller events but end after them. + // After the smaller event ended, the big event should stay at the beginning of the list. + if (eventTocheck.isRunning() && !referenceEvent.isRunning()) { + return true; + } + if (!eventTocheck.isRunning() && referenceEvent.isRunning()) { + return false; + } + return referenceEvent.getStartDate().before(eventTocheck.getStartDate()); + } @GwtIncompatible protected void addStatistics(Statistics statistics) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventview/EventViewDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventview/EventViewDTO.java index 7df90631a05..20ee063f1a4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventview/EventViewDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventview/EventViewDTO.java @@ -5,6 +5,7 @@ import java.util.Collection; import java.util.List; import java.util.TreeSet; +import com.sap.sailing.domain.common.dto.EventType; 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; @@ -12,11 +13,6 @@ import com.sap.sse.gwt.client.media.ImageDTO; import com.sap.sse.gwt.dispatch.shared.commands.Result; public class EventViewDTO extends EventMetadataDTO implements Result, HasLogo { - - public enum EventType { - SINGLE_REGATTA, MULTI_REGATTA, SERIES_EVENT - } - private TreeSet regattas = new TreeSet<>(); private ArrayList eventsOfSeries = new ArrayList<>(); @@ -27,6 +23,7 @@ public class EventViewDTO extends EventMetadataDTO implements Result, HasLogo { private ImageDTO logoImage; private String officialWebsiteURL; private String sailorsInfoWebsiteURL; + private String description; public EventType getType() { return type; @@ -120,4 +117,12 @@ public class EventViewDTO extends EventMetadataDTO implements Result, HasLogo { } return venue; } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/fakeseries/GetEventSeriesViewAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/fakeseries/GetEventSeriesViewAction.java index eacfc8674dc..902cacb2e7d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/fakeseries/GetEventSeriesViewAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/fakeseries/GetEventSeriesViewAction.java @@ -10,6 +10,7 @@ import com.sap.sailing.gwt.home.communication.SailingDispatchContext; import com.sap.sailing.gwt.home.communication.event.EventMetadataDTO; import com.sap.sailing.gwt.home.communication.fakeseries.EventSeriesViewDTO.EventSeriesState; import com.sap.sailing.gwt.server.HomeServiceUtil; +import com.sap.sailing.server.util.EventUtil; import com.sap.sse.common.media.MediaTagConstants; import com.sap.sse.gwt.dispatch.shared.caching.IsClientCacheable; import com.sap.sse.gwt.dispatch.shared.exceptions.DispatchException; @@ -58,7 +59,7 @@ public class GetEventSeriesViewAction implements SailingAction execute(SailingDispatchContext ctx) throws DispatchException { - KeywordQuery searchQuery = new KeywordQuery(searchText.split("[ \t]+")); + KeywordQuery searchQuery = new KeywordQuery(Util.splitAlongWhitespaceRespectingDoubleQuotedPhrases(searchText)); RacingEventService service = ctx.getRacingEventService(); if (remoteServerName == null) { return getListResult(service.search(searchQuery), ctx.getRequestBaseURL(), false); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/DesktopActivityMapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/DesktopActivityMapper.java index d6a1150832c..abc7a8f86d2 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/DesktopActivityMapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/DesktopActivityMapper.java @@ -3,7 +3,6 @@ package com.sap.sailing.gwt.home.desktop.app; import com.google.gwt.activity.shared.Activity; import com.google.gwt.activity.shared.ActivityMapper; import com.google.gwt.place.shared.Place; -import com.sap.sailing.gwt.common.client.formfactor.DeviceDetector; import com.sap.sailing.gwt.home.desktop.places.aboutus.AboutUsActivityProxy; import com.sap.sailing.gwt.home.desktop.places.aboutus.AboutUsPlace; import com.sap.sailing.gwt.home.desktop.places.contact.ContactActivityProxy; @@ -11,6 +10,7 @@ import com.sap.sailing.gwt.home.desktop.places.contact.ContactPlace; import com.sap.sailing.gwt.home.desktop.places.error.ErrorActivityProxy; import com.sap.sailing.gwt.home.desktop.places.event.EventActivityProxy; import com.sap.sailing.gwt.home.desktop.places.events.EventsActivityProxy; +import com.sap.sailing.gwt.home.desktop.places.morelogininformation.MoreLoginInformationActivityProxy; import com.sap.sailing.gwt.home.desktop.places.solutions.SolutionsActivityProxy; import com.sap.sailing.gwt.home.desktop.places.sponsoring.SponsoringActivityProxy; import com.sap.sailing.gwt.home.desktop.places.sponsoring.SponsoringPlace; @@ -26,6 +26,7 @@ import com.sap.sailing.gwt.home.shared.places.events.EventsPlace; import com.sap.sailing.gwt.home.shared.places.fakeseries.AbstractSeriesPlace; import com.sap.sailing.gwt.home.shared.places.imprint.ImprintActivityProxy; import com.sap.sailing.gwt.home.shared.places.imprint.ImprintPlace; +import com.sap.sailing.gwt.home.shared.places.morelogininformation.MoreLoginInformationPlace; import com.sap.sailing.gwt.home.shared.places.searchresult.SearchResultActivityProxy; import com.sap.sailing.gwt.home.shared.places.searchresult.SearchResultPlace; import com.sap.sailing.gwt.home.shared.places.solutions.SolutionsPlace; @@ -35,6 +36,7 @@ import com.sap.sailing.gwt.home.shared.places.user.confirmation.ConfirmationPlac import com.sap.sailing.gwt.home.shared.places.user.passwordreset.PasswordResetActivityProxy; import com.sap.sailing.gwt.home.shared.places.user.passwordreset.PasswordResetPlace; import com.sap.sailing.gwt.home.shared.places.user.profile.AbstractUserProfilePlace; +import com.sap.sse.gwt.client.formfactor.DeviceDetector; public class DesktopActivityMapper implements ActivityMapper { private final DesktopClientFactory clientFactory; @@ -82,9 +84,11 @@ public class DesktopActivityMapper implements ActivityMapper { } else if (place instanceof ConfirmationPlace) { return new ConfirmationActivityProxy((ConfirmationPlace) place, clientFactory); } else if (place instanceof PasswordResetPlace) { - return new PasswordResetActivityProxy((PasswordResetPlace) place, clientFactory); + return new PasswordResetActivityProxy((PasswordResetPlace) place, clientFactory); } else if (place instanceof ImprintPlace) { return new ImprintActivityProxy((ImprintPlace) place); + } else if (place instanceof MoreLoginInformationPlace) { + return new MoreLoginInformationActivityProxy((MoreLoginInformationPlace) place, clientFactory); } else { return null; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/DesktopLoginHintPopup.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/DesktopLoginHintPopup.java new file mode 100644 index 00000000000..d4e59670d7e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/DesktopLoginHintPopup.java @@ -0,0 +1,19 @@ +package com.sap.sailing.gwt.home.desktop.app; + +import com.sap.sse.security.ui.authentication.AuthenticationManager; +import com.sap.sse.security.ui.authentication.UserManagementResources; +import com.sap.sse.security.ui.authentication.UserManagementResources.LocalCss; +import com.sap.sse.security.ui.authentication.login.LoginHintPopup; + +/** + * Specific version of {@link LoginHintPopup} that meets the positioning of the page header in Home.html. + */ +public class DesktopLoginHintPopup extends LoginHintPopup { + private static final LocalCss LOCAL_CSS = UserManagementResources.INSTANCE.css(); + + public DesktopLoginHintPopup(AuthenticationManager authenticationManager, DesktopPlacesNavigator placesNavigator) { + super(authenticationManager, () -> placesNavigator.goToPlace(placesNavigator.getMoreLoginInfo()), null); + this.addStyleName(LOCAL_CSS.flyover_small_hidden()); + this.addStyleName(LOCAL_CSS.flyover_position_grid()); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/DesktopPlacesNavigator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/DesktopPlacesNavigator.java index 33051970b7e..ea8ee8cffd1 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/DesktopPlacesNavigator.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/DesktopPlacesNavigator.java @@ -14,5 +14,4 @@ public class DesktopPlacesNavigator extends HomePlacesNavigator { public PlaceNavigation getSponsoringNavigation() { return createGlobalPlaceNavigation(new SponsoringPlace()); } - } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/TabletAndDesktopApplicationClientFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/TabletAndDesktopApplicationClientFactory.java old mode 100755 new mode 100644 index 56c45cb4dd0..40b4f95cf4b --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/TabletAndDesktopApplicationClientFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/app/TabletAndDesktopApplicationClientFactory.java @@ -35,7 +35,6 @@ import com.sap.sse.security.ui.authentication.AuthenticationClientFactoryImpl; import com.sap.sse.security.ui.authentication.AuthenticationManager; import com.sap.sse.security.ui.authentication.AuthenticationManagerImpl; import com.sap.sse.security.ui.authentication.AuthenticationPlaceManagementController; -import com.sap.sse.security.ui.authentication.WrappedPlaceManagementController; import com.sap.sse.security.ui.authentication.info.LoggedInUserInfoPlace; import com.sap.sse.security.ui.authentication.view.FlyoutAuthenticationPresenter; import com.sap.sse.security.ui.client.i18n.StringMessages; @@ -43,7 +42,7 @@ import com.sap.sse.security.ui.client.i18n.StringMessages; public class TabletAndDesktopApplicationClientFactory extends AbstractApplicationClientFactory implements DesktopClientFactory { private final SailingDispatchSystem dispatch = new SailingDispatchSystemImpl(); - private final WrappedPlaceManagementController userManagementWizardController; + private final AuthenticationPlaceManagementController userManagementWizardController; private final AuthenticationManager authenticationManager; public TabletAndDesktopApplicationClientFactory(boolean isStandaloneServer) { @@ -78,6 +77,8 @@ public class TabletAndDesktopApplicationClientFactory extends AbstractApplicatio new FlyoutAuthenticationPresenter(userManagementDisplay, getTopLevelView().getAuthenticationMenuView(), userManagementWizardController, eventBus, authenticationManager.getAuthenticationContext()); + + new DesktopLoginHintPopup(authenticationManager, placesNavigator); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/anniversary/AnniversaryItem.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/anniversary/AnniversaryItem.java new file mode 100644 index 00000000000..f4805454537 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/anniversary/AnniversaryItem.java @@ -0,0 +1,20 @@ +package com.sap.sailing.gwt.home.desktop.partials.anniversary; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; +import com.google.gwt.uibinder.client.UiBinder; +import com.sap.sailing.gwt.home.shared.partials.anniversary.AbstractAnniversaryItem; + +class AnniversaryItem extends AbstractAnniversaryItem { + + interface AnniversaryItemUiBinder extends UiBinder { + } + + private static AnniversaryItemUiBinder uiBinder = GWT.create(AnniversaryItemUiBinder.class); + + AnniversaryItem(boolean isAnnouncement) { + setElement(uiBinder.createAndBindUi(this)); + super.initLayout(isAnnouncement); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/anniversary/AnniversaryItem.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/anniversary/AnniversaryItem.ui.xml new file mode 100644 index 00000000000..108658ab883 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/anniversary/AnniversaryItem.ui.xml @@ -0,0 +1,108 @@ + + + + + + .main { + padding: 2em 0; + background-color: #f8f8f8; + margin-bottom: 1em; + } + .content { + margin: auto; + max-width: 52em; + position: relative; + } + .symbolContainer { + position: absolute; + top: 50%; + transform: translateY(-50%); + width: 11em; + height: 11em; + border: 0.2em solid #fff; + background: #008fcc; + color: #fff; + text-align: center; + padding: 0.666666666666667em 0.333333333333333em; + border-radius: 0.666666666666667em; + box-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); + text-transform: uppercase; + } + .icon { + background-repeat: no-repeat; + height: 5em; + margin: 0 2.733333333333333em; + } + .count { + font-weight: bold; + font-size: 3em; + line-height: 1em; + } + .unit { + line-height: 1.5em; + font-size: 1.5em; + } + .countdown .count { + padding-top: .75em + } + .countdown .unit { + line-height: 2em; + } + .announcement .count { + font-size: 2.5em; + } + .announcement .unit { + font-size: 1.25em; + } + .textContainer { + position: relative; + margin-left: 12.5em; + min-height: 11em; + padding: .5em + } + .teaser { + font-size: 1.5em; + font-weight: bold; + color: #008fcc; + } + .description { + padding-top: 1em; + } + .linkContainer { + text-align: center; + } + .linkContainer > a { + margin-top: 1em; + } + .legalNotice { + font-size: 1.5em; + cursor: pointer; + color: #008fcc; + line-height: .5em; + position: absolute; + padding-top: 0.25em; + } + + + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/anniversary/DesktopAnniversaries.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/anniversary/DesktopAnniversaries.java new file mode 100644 index 00000000000..077dbd1fb77 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/anniversary/DesktopAnniversaries.java @@ -0,0 +1,21 @@ +package com.sap.sailing.gwt.home.desktop.partials.anniversary; + +import com.sap.sailing.gwt.home.shared.partials.anniversary.AbstractAnniversaries; +import com.sap.sailing.gwt.home.shared.partials.anniversary.AnniversariesView; + +/** + * {@link AnniversariesView} implementation for desktop. + */ +public class DesktopAnniversaries extends AbstractAnniversaries implements AnniversariesView { + + @Override + public AnniversaryAnnouncement addAnnouncement() { + return addAnniversaryItem(new AnniversaryItem(true)); + } + + @Override + public AnniversaryCountdown addCountdown() { + return addAnniversaryItem(new AnniversaryItem(false)); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventdescription/EventDescription.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventdescription/EventDescription.gss new file mode 100644 index 00000000000..e876110e092 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventdescription/EventDescription.gss @@ -0,0 +1,18 @@ +.box { + font-size: 1rem; + border: 1px solid #dedede; + border-radius: 0.333333333333333em; + overflow: hidden; +} +.box_header { + color: #333; + font-size: 1.2em; + font-weight: 700; + background: #f2f2f2; + padding: 1.111111111111111em; + border-bottom: 1px solid #dedede; +} +.box_content { + margin: 0; + padding: 1em; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventdescription/EventDescription.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventdescription/EventDescription.java new file mode 100644 index 00000000000..79f1d13a5eb --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventdescription/EventDescription.java @@ -0,0 +1,24 @@ +package com.sap.sailing.gwt.home.desktop.partials.eventdescription; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Widget; + +public class EventDescription extends Composite { + + private static StatisticsBoxUiBinder uiBinder = GWT.create(StatisticsBoxUiBinder.class); + + interface StatisticsBoxUiBinder extends UiBinder { + } + + @UiField DivElement descriptionUi; + + public EventDescription(String description) { + EventDescriptionResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + descriptionUi.setInnerText(description); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventdescription/EventDescription.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventdescription/EventDescription.ui.xml new file mode 100644 index 00000000000..6215d693745 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventdescription/EventDescription.ui.xml @@ -0,0 +1,11 @@ + + + + + + +
      +
      +
      +
      \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventdescription/EventDescriptionResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventdescription/EventDescriptionResources.java new file mode 100644 index 00000000000..0dff250df4a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventdescription/EventDescriptionResources.java @@ -0,0 +1,18 @@ +package com.sap.sailing.gwt.home.desktop.partials.eventdescription; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface EventDescriptionResources extends ClientBundle { + public static final EventDescriptionResources INSTANCE = GWT.create(EventDescriptionResources.class); + + @Source("EventDescription.gss") + LocalCss css(); + + public interface LocalCss extends CssResource { + String box(); + String box_header(); + String box_content(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.gss index bbb5545e440..88081595726 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.gss @@ -48,9 +48,3 @@ text-align: right; } } -.sitefooter_language_link { - color: #efaa00; - display: inline-block; - cursor: pointer; - margin-left: 0.666666666666667em; -} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.java index 02fc522ec7e..d56dd87fbc3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.java @@ -1,28 +1,13 @@ package com.sap.sailing.gwt.home.desktop.partials.footer; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; - import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.AnchorElement; -import com.google.gwt.dom.client.DivElement; -import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.http.client.UrlBuilder; -import com.google.gwt.i18n.client.LocaleInfo; -import com.google.gwt.text.shared.Renderer; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.EventListener; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.Window.Location; -import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.ValueListBox; import com.google.gwt.user.client.ui.Widget; import com.google.web.bindery.event.shared.EventBus; import com.sap.sailing.gwt.home.desktop.app.DesktopPlacesNavigator; @@ -30,9 +15,6 @@ import com.sap.sailing.gwt.home.desktop.places.whatsnew.WhatsNewPlace; import com.sap.sailing.gwt.home.desktop.places.whatsnew.WhatsNewPlace.WhatsNewNavigationTabs; import com.sap.sailing.gwt.home.shared.SwitchingEntryPoint; import com.sap.sailing.gwt.home.shared.app.PlaceNavigation; -import com.sap.sse.common.Util; -import com.sap.sse.common.Util.Pair; -import com.sap.sse.gwt.client.GWTLocaleUtil; public class Footer extends Composite { private static FooterPanelUiBinder uiBinder = GWT.create(FooterPanelUiBinder.class); @@ -40,32 +22,8 @@ public class Footer extends Composite { interface FooterPanelUiBinder extends UiBinder { } - @UiField Anchor changeLanguageLink; @UiField AnchorElement imprintAnchorLink; - @UiField DivElement languageSelectionDiv; - @UiField(provided = true) ValueListBox> changeLanguageList = new ValueListBox>(new Renderer>() { - @Override - public String render(Pair object) { - if(object == null) { - return ""; - } - if(object.getB() != null) { - return object.getB(); - } - return object.getA(); - } - - @Override - public void render(Pair object, Appendable appendable) throws IOException { - appendable.append(render(object)); - } - }); - @UiField AnchorElement mobileUi; - private String otherLanguage; - private final LocaleInfo currentLocale = LocaleInfo.getCurrentLocale(); - - @UiField(provided = true) final PlaceNavigation releaseNotesNavigation; @@ -76,7 +34,6 @@ public class Footer extends Composite { initWidget(uiBinder.createAndBindUi(this)); navigator.getImprintNavigation().configureAnchorElement(imprintAnchorLink); - updateUI(); DOM.sinkEvents(mobileUi, Event.ONCLICK); DOM.setEventListener(mobileUi, new EventListener() { @Override @@ -89,58 +46,4 @@ public class Footer extends Composite { }); } - - @UiHandler("changeLanguageLink") - public void changeLanguage(ClickEvent e) { - if(otherLanguage != null) { - UrlBuilder builder = Location.createUrlBuilder().setParameter("locale", otherLanguage); - Window.Location.replace(builder.buildString()); - } - } - - @UiHandler("changeLanguageList") - void onListSelection(ValueChangeEvent> event) { - String selectedLocaleName = event.getValue().getA(); - if(!currentLocale.getLocaleName().equals(selectedLocaleName)) { - UrlBuilder builder = Location.createUrlBuilder().setParameter("locale", selectedLocaleName); - Window.Location.replace(builder.buildString()); - } - } - - private void updateUI() { - final Iterable availableLocales = GWTLocaleUtil.getAvailableLocales(); - final int localeCount = Util.size(availableLocales); - - if (localeCount <= 1) { - // only current language (or removed default) available - changeLanguageLink.removeFromParent(); - changeLanguageList.removeFromParent(); - languageSelectionDiv.removeFromParent(); - return; - } - if (Util.size(availableLocales) == 2) { - changeLanguageList.removeFromParent(); - // current language + one we can switch to - for (String localeName : availableLocales) { - if (!currentLocale.getLocaleName().equals(localeName)) { - otherLanguage = localeName; - changeLanguageLink.setText(LocaleInfo.getLocaleNativeDisplayName(otherLanguage)); - return; - } - } - return; - } - changeLanguageLink.removeFromParent(); - Collection> values = new ArrayList<>(); - Pair selectedValue = null; - for (String localeName : availableLocales) { - Pair value = new Pair(localeName, LocaleInfo.getLocaleNativeDisplayName(localeName)); - values.add(value); - if (currentLocale.getLocaleName().equals(localeName)) { - selectedValue = value; - } - } - changeLanguageList.setValue(selectedValue); - changeLanguageList.setAcceptableValues(values); - } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.ui.xml index a0bfd39be8e..e67cc4cc916 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/Footer.ui.xml @@ -1,37 +1,11 @@ - - .languageSelect::-ms-expand { - display: none; - } - .languageSelect { - -webkit-appearance: none; - -moz-appearance: none; - -ms-appearance: none; - appearance: none; - - margin-top: 25px; - color: #efaa00; - line-height: 1.5em; - height: 1.5em; - display: inline-block; - border: transparent; - cursor: pointer; - padding-right: 1.2em; - background: #333; - cursor: pointer; - - background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZlcnNpb249JzEuMScgaGVpZ2h0PScxOHB4JyB3aWR0aD0nMTZweCc+PHRleHQgeD0nMCcgeT0nMTUnIGZpbGw9JyNlZmFhMDAnIGZvbnQtc2l6ZT0nMTYnPiYjOTY2MDs8L3RleHQ+PC9zdmc+"); - background-position: right center; - background-repeat: no-repeat; - - } -
      @@ -61,11 +35,8 @@
    • -
      - : - - -
      + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/FooterResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/FooterResources.java index ce5aad9d4f1..7c73496fd50 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/FooterResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/footer/FooterResources.java @@ -16,6 +16,5 @@ public interface FooterResources extends ClientBundle { String sitefooter_links(); String sitefooter_links_link(); String sitefooter_language(); - String sitefooter_language_link(); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/mainmedia/MainMedia.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/mainmedia/MainMedia.java index 936e01291ec..b11b27fad4e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/mainmedia/MainMedia.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/mainmedia/MainMedia.java @@ -9,6 +9,7 @@ import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; +import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.communication.media.SailingImageDTO; import com.sap.sailing.gwt.home.communication.media.SailingVideoDTO; @@ -21,20 +22,24 @@ public class MainMedia extends Composite { private static final int MAX_VIDEO_COUNT = 3; @UiField HTMLPanel videosPanel; - @UiField ImageCarousel imageCarousel; + @UiField SimplePanel imageCarouselHolderUi; interface MainMediaUiBinder extends UiBinder { } private static MainMediaUiBinder uiBinder = GWT.create(MainMediaUiBinder.class); + private final DesktopPlacesNavigator navigator; + public MainMedia(DesktopPlacesNavigator navigator) { + this.navigator = navigator; MainMediaResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); - imageCarousel.registerFullscreenViewer(new SailingFullscreenViewer(navigator)); } public void setData(Collection videos, ArrayList photos) { + final ImageCarousel imageCarousel = new ImageCarousel<>(); + imageCarousel.registerFullscreenViewer(new SailingFullscreenViewer(navigator)); Iterator videoIterator = videos.iterator(); int videoCount = 0; while(videoCount < MAX_VIDEO_COUNT && videoIterator.hasNext()) { @@ -47,5 +52,6 @@ public class MainMedia extends Composite { imageCarousel.addImage(image); } imageCarousel.setInfiniteScrolling(true); + imageCarouselHolderUi.setWidget(imageCarousel); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/mainmedia/MainMedia.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/mainmedia/MainMedia.ui.xml index f14667c222e..1305134a516 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/mainmedia/MainMedia.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/mainmedia/MainMedia.ui.xml @@ -20,9 +20,7 @@
      -
      - -
      +
      diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/media/SailingFullscreenViewer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/media/SailingFullscreenViewer.java index 4ee433da454..d68fa1042c9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/media/SailingFullscreenViewer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/media/SailingFullscreenViewer.java @@ -5,7 +5,7 @@ import java.util.Collection; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Image; import com.google.gwt.view.client.SelectionChangeEvent; import com.sap.sailing.gwt.common.client.SharedResources; @@ -28,7 +28,7 @@ public class SailingFullscreenViewer extends FullscreenContainer SailingFullscreenViewer.this.toggleAutoplay()); addToolbarAction(autoRefreshControl); } @@ -94,10 +89,11 @@ public class SailingFullscreenViewer extends FullscreenContainer sapInSailingNavigation; private final PlaceNavigation sailingAnalyticsNavigation; private final PlaceNavigation raceCommitteeAppNavigation; @@ -87,24 +74,8 @@ public class Solutions extends Composite { public Solutions(SolutionsNavigationTabs navigationTab, DesktopPlacesNavigator placesNavigator) { this.navigationTab = navigationTab; this.placesNavigator = placesNavigator; - SolutionsResources.INSTANCE.css().ensureInjected(); - initWidget(uiBinder.createAndBindUi(this)); - - String playstorebadgeSrc = UriUtils.fromString(i18n.playstoreBadge()).asString(); - String insightAppstoreSrc = UriUtils.fromString("images/home/appstore" + i18n.appstoreBadgeSuffix() + ".svg") - .asString(); - - - raceManagerPlayStoreLinkUi.setHref(UriUtils.fromString(i18n.playstoreRacecommitteeApp())); - inSightAppStoreLinkUi.setHref(UriUtils.fromString(i18n.appstoreSapSailInsight())); - buoyPingerPlayStoreLinkUi.setHref(UriUtils.fromString(i18n.playStoreBuoyPingerApp())); - inSightPlayStoreLinkUi.setHref(UriUtils.fromString(i18n.playstoreInsightApp())); - raceManagerPlayStoreImgUi.setSrc(playstorebadgeSrc); - inSightPlayStoreImgUi.setSrc(playstorebadgeSrc); - buoyPingerPlayStoreImgUi.setSrc(playstorebadgeSrc); - inSightAppStoreImgUi.setSrc(insightAppstoreSrc); sailingAnalyticsDetailsNavigation = placesNavigator.getWhatsNewNavigation(WhatsNewNavigationTabs.SailingAnalytics); raceCommitteeAppDetailsNavigation = placesNavigator.getWhatsNewNavigation(WhatsNewNavigationTabs.RaceManagerApp); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/solutions/Solutions.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/solutions/Solutions.ui.xml index a4478960515..0750f70267f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/solutions/Solutions.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/solutions/Solutions.ui.xml @@ -1,21 +1,11 @@ - + - - .storeBadgesContainer>a>img { - height: 60px; - width: auto; - margin-right: 10px; - } - .storeBadgesContainer { - margin: 20px 0px; - } -
      @@ -136,15 +126,7 @@

      -
      - Get it on Google Play -
      + @@ -164,22 +146,8 @@

      -
      - Get it on AppStore -
      -
      - Get it on Google Play -
      + + @@ -199,16 +167,7 @@

      -
      - Get it on Google Play -
      + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/stage/Stage.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/stage/Stage.java index 5afb6c77f0a..5065ba9aa54 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/stage/Stage.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/stage/Stage.java @@ -7,6 +7,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.communication.start.EventStageDTO; import com.sap.sailing.gwt.home.desktop.app.DesktopPlacesNavigator; @@ -19,7 +20,8 @@ public class Stage extends Composite { private static StageUiBinder uiBinder = GWT.create(StageUiBinder.class); - @UiField WidgetCarousel widgetCarousel; + @UiField + SimplePanel widgetCarouselContainerUi; private StageTeaser stageTeaser; private List stageTeaserComposites; @@ -33,6 +35,8 @@ public class Stage extends Composite { } public void setFeaturedEvents(List list) { + final WidgetCarousel widgetCarousel = new WidgetCarousel(); + widgetCarousel.setShowDots(false); for (EventStageDTO event : list) { switch (event.getStageType()) { case POPULAR: @@ -48,5 +52,6 @@ public class Stage extends Composite { widgetCarousel.addWidget(stageTeaser); stageTeaserComposites.add(stageTeaser); } + this.widgetCarouselContainerUi.setWidget(widgetCarousel); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/stage/Stage.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/stage/Stage.ui.xml index d51e1a2a40b..e4645b285ec 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/stage/Stage.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/stage/Stage.ui.xml @@ -1,6 +1,5 @@ - + @@ -8,7 +7,7 @@
      - +
      diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/standings/StandingsListCompetitor.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/standings/StandingsListCompetitor.java index b21c3108dc9..ef2b2c03af8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/standings/StandingsListCompetitor.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/standings/StandingsListCompetitor.java @@ -36,7 +36,7 @@ public class StandingsListCompetitor extends UIObject { teamIdUi.setInnerText(competitor.getSailID()); teamNameUi.setInnerText(competitor.getName()); String pointsString = i18n.pointsValue(item.getNetPoints()); - if(showRaceCounts) { + if (showRaceCounts) { pointsString += " (" + i18n.racesCount(item.getRaceCount()) + ")"; } pointsUi.setInnerText(pointsString); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/statistics/DesktopStatisticsBoxView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/statistics/DesktopStatisticsBoxView.java index 0c3a3f0a48f..63714e02a7a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/statistics/DesktopStatisticsBoxView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/statistics/DesktopStatisticsBoxView.java @@ -28,7 +28,7 @@ public class DesktopStatisticsBoxView extends Composite implements StatisticsBox StatisticsBoxResources.INSTANCE.css().ensureInjected(); initWidget(uiBinder.createAndBindUi(this)); titleUi.setInnerText(title); - if(embedded) { + if (embedded) { addStyleName(StatisticsBoxResources.INSTANCE.css().box_embedded()); } setVisible(false); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/AbstractEventActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/AbstractEventActivity.java old mode 100755 new mode 100644 index 9ea64e21f04..aa8ca601d0f --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/AbstractEventActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/AbstractEventActivity.java @@ -10,11 +10,11 @@ import com.google.gwt.safehtml.shared.SafeUri; import com.google.gwt.safehtml.shared.UriUtils; import com.google.gwt.user.client.rpc.AsyncCallback; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.dto.EventType; import com.sap.sailing.gwt.common.client.controls.tabbar.TabView; import com.sap.sailing.gwt.home.communication.SailingDispatchSystem; import com.sap.sailing.gwt.home.communication.event.EventState; import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO; -import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO.EventType; import com.sap.sailing.gwt.home.communication.eventview.HasRegattaMetadata; import com.sap.sailing.gwt.home.communication.media.GetMediaForEventAction; import com.sap.sailing.gwt.home.communication.media.MediaDTO; @@ -232,7 +232,8 @@ public abstract class AbstractEventActivity ex regattaRaceStatesSettings.getVisibleRegattaSettings().addValue(getRegattaId()); } } - return EntryPointWithSettingsLinkFactory.createRegattaOverviewLink(new RegattaOverviewContextDefinition(getCtx().getEventId()), regattaRaceStatesSettings); + return EntryPointWithSettingsLinkFactory.createRegattaOverviewLink( + new RegattaOverviewContextDefinition(getCtx().getEventId()), regattaRaceStatesSettings, false); } @Override @@ -245,7 +246,7 @@ public abstract class AbstractEventActivity ex if (regattaId != null) { return regattaId; } - if (!eventDTO.getRegattas().isEmpty() && (eventDTO.getType() == EventType.SINGLE_REGATTA || eventDTO.getType() == EventType.SERIES_EVENT)) { + if (!eventDTO.getRegattas().isEmpty() && (eventDTO.getType() == EventType.SINGLE_REGATTA || eventDTO.getType() == EventType.SERIES)) { return eventDTO.getRegattas().iterator().next().getId(); } return null; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/EventActivityProxy.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/EventActivityProxy.java index 7c76f0b6dd2..5a01c544816 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/EventActivityProxy.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/EventActivityProxy.java @@ -1,8 +1,8 @@ package com.sap.sailing.gwt.home.desktop.places.event; import com.google.gwt.core.client.GWT; +import com.sap.sailing.domain.common.dto.EventType; import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO; -import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO.EventType; import com.sap.sailing.gwt.home.desktop.app.DesktopPlacesNavigator; import com.sap.sailing.gwt.home.desktop.app.WithHeader; import com.sap.sailing.gwt.home.desktop.places.event.multiregatta.AbstractMultiregattaEventPlace; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/multiregatta/overviewtab/MultiregattaOverviewTabView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/multiregatta/overviewtab/MultiregattaOverviewTabView.java index 500b8462d71..1cc866501b5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/multiregatta/overviewtab/MultiregattaOverviewTabView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/multiregatta/overviewtab/MultiregattaOverviewTabView.java @@ -8,11 +8,13 @@ import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTMLPanel; +import com.google.gwt.user.client.ui.SimplePanel; import com.sap.sailing.gwt.common.client.controls.tabbar.TabView; import com.sap.sailing.gwt.home.communication.event.GetLiveRacesForEventAction; import com.sap.sailing.gwt.home.communication.event.GetRegattaListViewAction; import com.sap.sailing.gwt.home.communication.event.statistics.GetEventStatisticsAction; import com.sap.sailing.gwt.home.communication.regatta.RegattaWithProgressDTO; +import com.sap.sailing.gwt.home.desktop.partials.eventdescription.EventDescription; import com.sap.sailing.gwt.home.desktop.partials.eventstage.EventOverviewStage; import com.sap.sailing.gwt.home.desktop.partials.liveraces.LiveRacesList; import com.sap.sailing.gwt.home.desktop.partials.multiregattalist.MultiRegattaList; @@ -41,6 +43,7 @@ public class MultiregattaOverviewTabView extends Composite implements Multiregat private static MyBinder ourUiBinder = GWT.create(MyBinder.class); @UiField(provided = true) EventOverviewStage stageUi; + @UiField SimplePanel descriptionUi; @UiField(provided = true) LiveRacesList liveRacesListUi; @UiField(provided = true) DropdownFilter boatCategoryFilterUi; @UiField(provided = true) MultiRegattaList regattaListUi; @@ -71,7 +74,16 @@ public class MultiregattaOverviewTabView extends Composite implements Multiregat boatCategoryFilterUi = new DropdownFilter(StringMessages.INSTANCE.all(), regattaFilterList); regattaListUi = new MultiRegattaList(currentPresenter, false); statisticsBoxUi = new EventStatisticsBox(true, new DesktopStatisticsBoxView()); + initWidget(ourUiBinder.createAndBindUi(this)); + + final String description = currentPresenter.getEventDTO().getDescription(); + if (description != null) { + descriptionUi.add(new EventDescription(description)); + } else { + descriptionUi.removeFromParent(); + } + raceOfficeSectionUi.addLink(StringMessages.INSTANCE.racesOverview(), currentPresenter.getRegattaOverviewLink()); RefreshManager refreshManager = new RefreshManagerWithErrorAndBusy(this, contentArea, currentPresenter.getDispatch(), currentPresenter.getErrorAndBusyClientFactory()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/multiregatta/overviewtab/MultiregattaOverviewTabView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/multiregatta/overviewtab/MultiregattaOverviewTabView.ui.xml index fed58103739..f03927017b1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/multiregatta/overviewtab/MultiregattaOverviewTabView.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/multiregatta/overviewtab/MultiregattaOverviewTabView.ui.xml @@ -1,17 +1,18 @@ - - + +
      + - - + addStyleNames="{res.mainCss.spacermargintopmedium} {res.mainCss.spacermarginbottombig}"/> + +
      diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/EventRegattaActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/EventRegattaActivity.java index cbd65eb2cf8..5e335393e0f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/EventRegattaActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/EventRegattaActivity.java @@ -5,10 +5,10 @@ import java.util.List; import com.google.gwt.event.shared.EventBus; import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.sap.sailing.domain.common.dto.EventType; import com.sap.sailing.gwt.home.communication.event.EventReferenceWithStateDTO; import com.sap.sailing.gwt.home.communication.event.EventState; import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO; -import com.sap.sailing.gwt.home.communication.eventview.EventViewDTO.EventType; import com.sap.sailing.gwt.home.communication.eventview.HasRegattaMetadata; import com.sap.sailing.gwt.home.communication.eventview.HasRegattaMetadata.RegattaState; import com.sap.sailing.gwt.home.communication.eventview.RegattaMetadataDTO; @@ -62,7 +62,7 @@ public class EventRegattaActivity extends AbstractEventActivity navigationItems = new ArrayList<>(); navigationItems.add(new NavigationItem(i18n.home(), getHomeNavigation())); navigationItems.add(new NavigationItem(i18n.events(), getEventsNavigation())); - if(getEventDTO().getType() == EventType.SERIES_EVENT) { + if(getEventDTO().getType() == EventType.SERIES) { navigationItems.add(new NavigationItem(getEventDTO().getSeriesName(), getCurrentEventSeriesNavigation())); } navigationItems.add(new NavigationItem(getEventDTO().getLocationOrDisplayName(), getCurrentEventNavigation())); @@ -83,13 +83,13 @@ public class EventRegattaActivity extends AbstractEventActivity currentEventSeriesNavigation = currentPresenter.getCurrentEventSeriesNavigation(); Anchor seriesAnchor = new Anchor(i18n.overallLeaderboardSelection()); seriesAnchor.setHref(currentEventSeriesNavigation.getTargetUrl()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/leaderboardtab/RegattaLeaderboardTabView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/leaderboardtab/RegattaLeaderboardTabView.java index 1ac1aa71ed5..a0f032e08d3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/leaderboardtab/RegattaLeaderboardTabView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/leaderboardtab/RegattaLeaderboardTabView.java @@ -131,6 +131,7 @@ public class RegattaLeaderboardTabView extends SharedLeaderboardRegattaTabView + xmlns:dp='urn:import:com.sap.sailing.gwt.home.desktop.partials' + xmlns:sp='urn:import:com.sap.sailing.gwt.home.shared.partials'>
      - +
      +
      diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationActivity.java new file mode 100644 index 00000000000..34ee83f77c0 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationActivity.java @@ -0,0 +1,16 @@ +package com.sap.sailing.gwt.home.desktop.places.morelogininformation; + +import com.google.gwt.place.shared.Place; +import com.sap.sailing.gwt.home.desktop.app.DesktopClientFactory; +import com.sap.sailing.gwt.home.shared.places.morelogininformation.AbstractMoreLoginInformationActivity; +import com.sap.sse.security.ui.authentication.AuthenticationPlaces; +import com.sap.sse.security.ui.authentication.AuthenticationRequestEvent; + +public class MoreLoginInformationActivity extends AbstractMoreLoginInformationActivity { + + public MoreLoginInformationActivity(Place place, DesktopClientFactory clientFactory) { + super(clientFactory, new MoreLoginInformationDesktop(() -> clientFactory.getEventBus() + .fireEvent(new AuthenticationRequestEvent(AuthenticationPlaces.CREATE_ACCOUNT)))); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationActivityProxy.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationActivityProxy.java new file mode 100644 index 00000000000..e0f998363b3 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationActivityProxy.java @@ -0,0 +1,29 @@ +package com.sap.sailing.gwt.home.desktop.places.morelogininformation; + +import com.google.gwt.core.client.GWT; +import com.sap.sailing.gwt.home.desktop.app.DesktopClientFactory; +import com.sap.sailing.gwt.home.shared.places.morelogininformation.MoreLoginInformationPlace; +import com.sap.sse.gwt.client.mvp.AbstractActivityProxy; + +public class MoreLoginInformationActivityProxy extends AbstractActivityProxy { + + private final MoreLoginInformationPlace currentPlace; + private final DesktopClientFactory clientFactory; + + public MoreLoginInformationActivityProxy(MoreLoginInformationPlace currentPlace, + DesktopClientFactory clientFactory) { + this.currentPlace = currentPlace; + this.clientFactory = clientFactory; + } + + @Override + protected void startAsync() { + GWT.runAsync(new AbstractRunAsyncCallback() { + @Override + public void onSuccess() { + super.onSuccess(new MoreLoginInformationActivity(currentPlace, clientFactory)); + } + }); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationContent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationContent.java new file mode 100644 index 00000000000..a53d785346a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationContent.java @@ -0,0 +1,48 @@ +package com.sap.sailing.gwt.home.desktop.places.morelogininformation; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.HeadingElement; +import com.google.gwt.dom.client.ImageElement; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiConstructor; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Widget; + +public class MoreLoginInformationContent extends Widget { + + private static MoreLoginInformationContentUiBinder uiBinder = GWT.create(MoreLoginInformationContentUiBinder.class); + + interface MoreLoginInformationContentUiBinder extends UiBinder { + } + + interface Styles extends CssResource { + String left(); + + String right(); + } + + @UiField + HeadingElement titleUi; + @UiField + DivElement textUi; + @UiField + ImageElement imageUi; + @UiField + Styles style; + + @UiConstructor + public MoreLoginInformationContent(String title, String content, ImageResource image, boolean imageOnLeft) { + setElement(uiBinder.createAndBindUi(this)); + titleUi.setInnerText(title); + textUi.setInnerText(content); + if (image != null) { + imageUi.setSrc(image.getSafeUri().asString()); + } + imageUi.addClassName(imageOnLeft ? style.left() : style.right()); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationContent.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationContent.ui.xml new file mode 100644 index 00000000000..5828d79f3d6 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationContent.ui.xml @@ -0,0 +1,39 @@ + + + + .clear { + clear: both; + } + + .image { + width: 40%; + height: auto; + } + + .image.left { + float: left; + padding-right: 1em; + } + + .image.right { + float: right; + padding-left: 1em; + } + + .wrapper { + padding: 1em 0; + } + + .title { + font-size: 1.5em; + font-weight: bold; + padding-bottom: 0.5em; + } + +
      + +

      +
      +
      +
      + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationDesktop.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationDesktop.java new file mode 100644 index 00000000000..757f8bc6974 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationDesktop.java @@ -0,0 +1,22 @@ +package com.sap.sailing.gwt.home.desktop.places.morelogininformation; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.shared.places.morelogininformation.AbstractMoreLoginInformation; + +/** + * Desktop page that shows the benefits of logging in on sapsailing.com. + */ +public class MoreLoginInformationDesktop extends AbstractMoreLoginInformation { + + private static MoreLoginInformationUiBinder uiBinder = GWT.create(MoreLoginInformationUiBinder.class); + + interface MoreLoginInformationUiBinder extends UiBinder { + } + + public MoreLoginInformationDesktop(Runnable registerCallback) { + super(uiBinder, registerCallback); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationDesktop.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationDesktop.ui.xml new file mode 100644 index 00000000000..8f267f3705e --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/morelogininformation/MoreLoginInformationDesktop.ui.xml @@ -0,0 +1,31 @@ + + + + + + + .clear { + clear: both; + } + .registerContainer { + text-align: center; + } + .register { + cursor: pointer; + } + + +

      +

      + + + +
      + + + +
      +
      +
      diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/StartActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/StartActivity.java index fb4ad30f99a..6674727ce03 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/StartActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/StartActivity.java @@ -4,10 +4,13 @@ import com.google.gwt.activity.shared.AbstractActivity; import com.google.gwt.event.shared.EventBus; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.sap.sailing.gwt.home.communication.anniversary.GetAnniversariesAction; import com.sap.sailing.gwt.home.communication.start.GetStartViewAction; import com.sap.sailing.gwt.home.communication.start.StartViewDTO; import com.sap.sailing.gwt.home.shared.app.ActivityCallback; import com.sap.sailing.gwt.home.shared.places.start.StartPlace; +import com.sap.sailing.gwt.home.shared.refresh.RefreshManager; +import com.sap.sailing.gwt.home.shared.refresh.RefreshManagerWithErrorAndBusy; public class StartActivity extends AbstractActivity { private final StartClientFactory clientFactory; @@ -21,14 +24,18 @@ public class StartActivity extends AbstractActivity { @Override public void start(final AcceptsOneWidget panel, EventBus eventBus) { panel.setWidget(clientFactory.createBusyView()); + final StartView view = clientFactory.createStartView(); clientFactory.getDispatch().execute(new GetStartViewAction(), new ActivityCallback(clientFactory, panel) { @Override public void onSuccess(StartViewDTO result) { - final StartView view = clientFactory.createStartView(); panel.setWidget(view.asWidget()); Window.setTitle(place.getTitle()); view.setData(result); } }); + + final RefreshManager refreshManager = new RefreshManagerWithErrorAndBusy(view.asWidget(), panel, + clientFactory.getDispatch(), clientFactory); + refreshManager.add(view.getAnniversariesView(), new GetAnniversariesAction()); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/StartView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/StartView.java index 667af10e33f..23cb4d08079 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/StartView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/StartView.java @@ -2,9 +2,12 @@ package com.sap.sailing.gwt.home.desktop.places.start; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.communication.start.StartViewDTO; +import com.sap.sailing.gwt.home.shared.partials.anniversary.AnniversariesView; public interface StartView { Widget asWidget(); void setData(StartViewDTO data); + + AnniversariesView getAnniversariesView(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/TabletAndDesktopStartView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/TabletAndDesktopStartView.java index 788ce2cdda5..ba417926a98 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/TabletAndDesktopStartView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/TabletAndDesktopStartView.java @@ -7,9 +7,11 @@ import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.communication.start.StartViewDTO; import com.sap.sailing.gwt.home.desktop.app.DesktopPlacesNavigator; +import com.sap.sailing.gwt.home.desktop.partials.anniversary.DesktopAnniversaries; import com.sap.sailing.gwt.home.desktop.partials.mainevents.MainEvents; import com.sap.sailing.gwt.home.desktop.partials.mainmedia.MainMedia; import com.sap.sailing.gwt.home.desktop.partials.stage.Stage; +import com.sap.sailing.gwt.home.shared.partials.anniversary.AnniversariesView; public class TabletAndDesktopStartView extends Composite implements StartView { @@ -19,6 +21,7 @@ public class TabletAndDesktopStartView extends Composite implements StartView { } @UiField(provided=true) Stage stage; + @UiField DesktopAnniversaries anniversaries; // @UiField(provided=true) MainSponsors mainSponsors; @UiField(provided=true) MainEvents mainEvents; @UiField(provided=true) MainMedia mainMedia; @@ -38,4 +41,9 @@ public class TabletAndDesktopStartView extends Composite implements StartView { mainEvents.setRecentEvents(data.getRecentEvents()); mainMedia.setData(data.getVideos(), data.getPhotos()); } + + @Override + public AnniversariesView getAnniversariesView() { + return this.anniversaries; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/TabletAndDesktopStartView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/TabletAndDesktopStartView.ui.xml index f5c57d5c7e9..f06fd2efccc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/TabletAndDesktopStartView.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/start/TabletAndDesktopStartView.ui.xml @@ -1,12 +1,12 @@ - - - - - - - - - + xmlns:dp="urn:import:com.sap.sailing.gwt.home.desktop.partials"> + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/user/profile/UserProfileActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/user/profile/UserProfileActivity.java index fb850c0e925..4e862c97c4a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/user/profile/UserProfileActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/user/profile/UserProfileActivity.java @@ -17,6 +17,7 @@ import com.sap.sailing.gwt.home.shared.places.fakeseries.AbstractSeriesPlace; import com.sap.sailing.gwt.home.shared.places.start.StartPlace; import com.sap.sailing.gwt.home.shared.places.user.profile.AbstractUserProfilePlace; import com.sap.sse.security.ui.authentication.AuthenticationContextEvent; +import com.sap.sse.security.ui.authentication.AuthenticationPlaces; import com.sap.sse.security.ui.authentication.AuthenticationRequestEvent; import com.sap.sse.security.ui.client.i18n.StringMessages; @@ -91,7 +92,7 @@ public class UserProfileActivity extends AbstractActivity implements UserProfile @Override public void doTriggerLoginForm() { - clientFactory.getEventBus().fireEvent(new AuthenticationRequestEvent()); + clientFactory.getEventBus().fireEvent(new AuthenticationRequestEvent(AuthenticationPlaces.SIGN_IN)); } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/user/profile/settingstab/UserSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/user/profile/settingstab/UserSettings.java index 138718895cf..236ce9d5786 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/user/profile/settingstab/UserSettings.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/user/profile/settingstab/UserSettings.java @@ -7,7 +7,6 @@ import com.google.gwt.cell.client.FieldUpdater; import com.google.gwt.cell.client.TextCell; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.DivElement; -import com.google.gwt.dom.client.Style.Display; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.cellview.client.Column; @@ -34,8 +33,6 @@ public class UserSettings extends Composite implements UserSettingsView { interface MyUiBinder extends UiBinder { } - @UiField - DivElement notificationsTextUi; @UiField UserSettingsByKeyTextBoxFilter userSettingsFilterUi; @UiField @@ -107,15 +104,8 @@ public class UserSettings extends Composite implements UserSettingsView { @Override public void setEntries(List entries) { - if (entries.isEmpty()) { - notificationsTextUi.setInnerText(StringMessages.INSTANCE.noDataFound()); - tableWrapper.getStyle().setDisplay(Display.NONE); - } else { - notificationsTextUi.setInnerText(StringMessages.INSTANCE.userProfileSettingsTabDescription()); - tableWrapper.getStyle().clearDisplay(); - userSettingsTable.setPageSize(entries.size()); - userSettingsTable.setList(entries); - } + userSettingsTable.setPageSize(entries.size()); + userSettingsTable.setList(entries); } private static final class StringComparator extends InvertibleComparatorAdapter { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/user/profile/settingstab/UserSettings.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/user/profile/settingstab/UserSettings.ui.xml index c45c113d045..c1440c990c1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/user/profile/settingstab/UserSettings.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/user/profile/settingstab/UserSettings.ui.xml @@ -10,16 +10,25 @@ .table { width: 100%; } + .table .empty { + margin: 1.033333333333333em; + font-style: italic; + line-height: 2em; + } -
      - +
      +
      - + + + + +
      \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/RaceCommitteeAppNotes.html b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/RaceCommitteeAppNotes.html index a971fc83311..9f758ed9201 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/RaceCommitteeAppNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/RaceCommitteeAppNotes.html @@ -4,6 +4,11 @@

      What's New - SAP Sailing Race Manager

      +
      August 2018
      +
        +
      • The result list can now be edited without a finish time.
      • +
      +
      June 2017
      • It is now possible to capture results during any phase of a race, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html index e6e57b1caa4..a931629d8fb 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/whatsnew/resources/SailingAnalyticsNotes.html @@ -4,12 +4,70 @@

        What's New - SAP Sailing Analytics

        +
        November 2017
        +
          +
        • The language preference set in the user profile (if any), now also affects the website's language. Formerly, + this preference affected the language of notification mails only. + Be aware of the fact, that signing in will not force the page to be shown in the user's preferred language. + It will only take effect when refreshing the page manually or opening a new one, in order to prevent the + state of the currently opened page to get lost. + After a sign out (and another page reload) the language preference will stop to take effect. +
        • +
        • There are additional column types in the leaderboard that show per leg, per race and overall statistics + for distance foiled and duration foiled. This allows for quick competitor comparisons regarding these + figures which are so important for any foiling boat class. Note, that this feature is also available + on overall leaderboards, providing foiling stats across an entire season or event series. +
        • +
        • When a leaderboard belongs to a leaderboard group with an overall (series) leaderboard and the + leaderboard has no scores at all yet for a competitor, that competitor won't be assigned a score + in the overall (series) leaderboard either. This way, when an act or event has its leaderboard already + linked to the series leaderboard group, no scores will show in the series leaderboard before the + act/event starts producing scores for the competitors. +
        • +
        + +
        October 2017
        +
          +
        • The DataMining.html module now offers an additional chart type with error bars. This currently + shows minimum and maximum values for averages, and the tool tip visualizes those numerically, together + with the number of elements from which the average was drawn. +
        • +
        • The DataMining.html module now reacts on clicks on bars in the result chart and triggers + a corresponding drill-down operation, adding the first grouping key value as a new filter and removing + the grouping dimension. If that was the last dimension removed, the user is asked to provide a new + grouping dimension. +
        • +
        • If available, an event description is now shown on event overview pages. +
        • +
        + +
        September 2017
        +
          +
        • The system will now detect "anniversary races" and show those on the start page. + Anniversary races are detected based on the total number of tracked races known (races having GPS data and a valid start date). + Anniversary types currently known are round quarters like 10'000, 25'000, and 50'000 as well as numbers where all digits are the same like 11'111 or 22'222. + Some time before such a milestone is reached, a countdown is shown on the start page. When an anniversary is reached, the determined race is shown at the same place. +
        • +
        • Users that aren't authenticated will now see a message near the login icon in the page header. + This message gently hints about additional functionality being available for authenticated users. + This message lets you show details the additional functionality being available for authenticated users only. + When either logging in, showing details or dismissing this message, the message will disappear for some days until it reappears. +
        • +
        +
        August 2017
        • In the list of past events, you can now find some statistics for the current and past years. The statistics can be found at the end of the list of events for a year. To see statistics of a past year, you need to expand the list of events for that year.
        • +
        • The simulation overlay is now also available prior to the start of a live race. It can + therefore, e.g., be used to "predict" the best course on the first upwind leg based on + the latest wind measurements. +
        • +
        • In several places you can now find statistics regarding the highest speed and the competitor that produced it (fastest competitor). + These statistics are available for events and regattas as well as in the yearly statistics. +
        July 2017
        diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileActivityMapper.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileActivityMapper.java index 9c6551fab4b..f489cfc78d0 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileActivityMapper.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileActivityMapper.java @@ -7,6 +7,7 @@ import com.google.gwt.place.shared.Place; import com.sap.sailing.gwt.home.mobile.places.error.ErrorActivityProxy; import com.sap.sailing.gwt.home.mobile.places.event.EventActivityProxy; import com.sap.sailing.gwt.home.mobile.places.events.EventsActivityProxy; +import com.sap.sailing.gwt.home.mobile.places.morelogininformation.MoreLoginInformationActivityProxy; import com.sap.sailing.gwt.home.mobile.places.series.SeriesActivityProxy; import com.sap.sailing.gwt.home.mobile.places.series.minileaderboard.SeriesMiniOverallLeaderboardActivityProxy; import com.sap.sailing.gwt.home.mobile.places.series.minileaderboard.SeriesMiniOverallLeaderboardPlace; @@ -23,6 +24,7 @@ import com.sap.sailing.gwt.home.shared.places.events.EventsPlace; import com.sap.sailing.gwt.home.shared.places.fakeseries.AbstractSeriesPlace; import com.sap.sailing.gwt.home.shared.places.imprint.ImprintActivityProxy; import com.sap.sailing.gwt.home.shared.places.imprint.ImprintPlace; +import com.sap.sailing.gwt.home.shared.places.morelogininformation.MoreLoginInformationPlace; import com.sap.sailing.gwt.home.shared.places.searchresult.SearchResultActivityProxy; import com.sap.sailing.gwt.home.shared.places.searchresult.SearchResultPlace; import com.sap.sailing.gwt.home.shared.places.solutions.SolutionsPlace; @@ -76,14 +78,14 @@ public class MobileActivityMapper implements ActivityMapper { return new PasswordResetActivityProxy((PasswordResetPlace) place, clientFactory); } else if (place instanceof AbstractUserProfilePlace) { return new UserProfileActivityProxy((AbstractUserProfilePlace) place, clientFactory); - } else if (place instanceof AuthenticationPlace) { - return new AuthenticationActivityProxy((AuthenticationPlace) place, clientFactory); } else if (place instanceof ConfirmationPlace) { return new ConfirmationActivityProxy((ConfirmationPlace) place, clientFactory); } else if (place instanceof PasswordResetPlace) { return new PasswordResetActivityProxy((PasswordResetPlace) place, clientFactory); } else if (place instanceof ImprintPlace) { return new ImprintActivityProxy((ImprintPlace) place); + } else if (place instanceof MoreLoginInformationPlace) { + return new MoreLoginInformationActivityProxy((MoreLoginInformationPlace) place, clientFactory); } else { return null; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileApplicationClientFactory.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileApplicationClientFactory.java index 63194d0b2a7..a68b0f6d49e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileApplicationClientFactory.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileApplicationClientFactory.java @@ -30,6 +30,7 @@ import com.sap.sse.security.ui.authentication.AuthenticationManager; import com.sap.sse.security.ui.authentication.AuthenticationManagerImpl; import com.sap.sse.security.ui.authentication.WithAuthenticationManager; import com.sap.sse.security.ui.authentication.WithUserService; +import com.sap.sse.security.ui.authentication.login.LoginHintContent; import com.sap.sse.security.ui.client.SecureClientFactoryImpl; import com.sap.sse.security.ui.client.i18n.StringMessages; @@ -62,13 +63,27 @@ public class MobileApplicationClientFactory extends this(new MobileApplicationView(navigator, eventBus), eventBus, placeController, navigator); } - public MobileApplicationClientFactory(MobileApplicationView root, EventBus eventBus, + public MobileApplicationClientFactory(final MobileApplicationView root, EventBus eventBus, PlaceController placeController, final MobilePlacesNavigator navigator) { super(root, eventBus, placeController); this.navigator = navigator; this.authenticationManager = new AuthenticationManagerImpl(this, eventBus, getNavigator() .getMailVerifiedConfirmationNavigation().getFullQualifiedUrl(), getNavigator() .getPasswordResetNavigation().getFullQualifiedUrl()); + + authenticationManager.checkNewUserPopup(() -> root.setSubHeaderContent(null), dismissCallback -> { + final LoginHintContent content = new LoginHintContent(() -> { + root.setSubHeaderContent(null); + dismissCallback.run(); + }, () -> { + root.setSubHeaderContent(null); + dismissCallback.run(); + navigator.goToPlace(navigator.getMoreLoginInfo()); + },() -> { + navigator.goToPlace(navigator.getSignInNavigation()); + }); + root.setSubHeaderContent(content); + }); } public MobilePlacesNavigator getNavigator() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileApplicationView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileApplicationView.java index 5a4840c3b1b..cff885a6dad 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileApplicationView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileApplicationView.java @@ -7,6 +7,7 @@ import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.IsWidget; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; import com.google.web.bindery.event.shared.EventBus; @@ -39,6 +40,9 @@ public class MobileApplicationView extends Composite implements ApplicationTopLe @UiField SimplePanel mainContentPanel; + + @UiField + SimplePanel subHeaderPanel; public MobileApplicationView(MobilePlacesNavigator placeNavigator, EventBus eventBus) { headerPanel = new Header(placeNavigator, eventBus); @@ -74,4 +78,8 @@ public class MobileApplicationView extends Composite implements ApplicationTopLe public ResettableNavigationPathDisplay getNavigationPathDisplay() { return headerPanel.getNavigationPathDisplay(); } + + public void setSubHeaderContent(IsWidget content) { + subHeaderPanel.setWidget(content); + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileApplicationView.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileApplicationView.ui.xml index b965d5be933..caf55118da6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileApplicationView.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobileApplicationView.ui.xml @@ -9,11 +9,16 @@ .mainContent { height: 100%; } + .subHeaderPanel>* { + padding: 0.5em 1em; + border-bottom: 1px solid #ccc; + }
        +
        diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobilePlacesNavigator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobilePlacesNavigator.java index 95b583c87f7..b01aa5da44c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobilePlacesNavigator.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/app/MobilePlacesNavigator.java @@ -9,6 +9,7 @@ import com.sap.sailing.gwt.home.mobile.places.user.authentication.Authentication import com.sap.sailing.gwt.home.shared.app.HomePlacesNavigator; import com.sap.sailing.gwt.home.shared.app.PlaceNavigation; import com.sap.sailing.gwt.home.shared.places.event.EventContext; +import com.sap.sse.security.ui.authentication.AuthenticationPlaces; public class MobilePlacesNavigator extends HomePlacesNavigator { @@ -23,6 +24,11 @@ public class MobilePlacesNavigator extends HomePlacesNavigator { } public PlaceNavigation getSignInNavigation() { - return createLocalPlaceNavigation(new AuthenticationPlace()); + return createLocalPlaceNavigation(new AuthenticationPlace(AuthenticationPlaces.SIGN_IN)); } + + public PlaceNavigation getCreateAccountNavigation() { + return createLocalPlaceNavigation(new AuthenticationPlace(AuthenticationPlaces.CREATE_ACCOUNT)); + } + } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/solutions/Solutions.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/Accordion.gss similarity index 64% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/solutions/Solutions.gss rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/Accordion.gss index 344cac56efa..8bb359c154e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/solutions/Solutions.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/Accordion.gss @@ -1,43 +1,44 @@ .accordion { font-size: 1rem; } -.accordion_trigger { - font-size: inherit; -} -.accordion_content { - font-size: inherit; -} -.solutions { - font-size: 1rem; -} -.solutions .accordioncollapsed .solutions_item_header_arrow { +.accordion .accordioncollapsed .accordion_item_header_arrow { -webkit-transform: rotate(0deg); -moz-transform: rotate(0deg); -ms-transform: rotate(0deg); transform: rotate(0deg); } -.solutions_item { +.accordion_item { padding: 0; } -.solutions_item:last-child .solutions_item_header { +.withHeader .accordion_item:first-child { + border-top: 1px solid #ccc; +} +.accordion_item:last-child .accordion_item_header { border-bottom: none; } -.solutions_item:last-child .solutions_item_body { +.withFooter .accordion_item:last-child .accordion_item_header { + border-bottom: 1px solid #ccc; +} +.accordion_item:last-child .accordion_item_body { border-bottom: none; border-top: 1px solid #ccc; } -.solutions_item_header { +.withFooter .accordion_item:last-child .accordion_item_body { + border-bottom: 1px solid #ccc; + border-top: none; +} +.accordion_item_header { padding: 1em 1.333333333333333em; border-bottom: 1px solid #ccc; } -.solutions_item_header_title { +.accordion_item_header_title { margin: 0; font-size: 1.066666666666667em; font-weight: 700; display: inline-block; max-width: 15.625em; } -.solutions_item_header_arrow { +.accordion_item_header_arrow { float: right; display: inline-block; width: 1.333333333333333em; @@ -50,7 +51,7 @@ -moz-transition: -moz-transform 0.4s; transition: transform 0.4s; } -.solutions_item_body { +.accordion_item_body { font-size: 0.933333333333333em; line-height: 1.428571428571429em; border-bottom: 1px solid #ccc; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/Accordion.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/Accordion.java new file mode 100644 index 00000000000..6d3ac55e588 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/Accordion.java @@ -0,0 +1,49 @@ +package com.sap.sailing.gwt.home.mobile.partials.accordion; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiChild; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.Widget; + +public class Accordion extends Composite { + + private static AccordionUiBinder uiBinder = GWT.create(AccordionUiBinder.class); + + interface AccordionUiBinder extends UiBinder { + } + + @UiField + SimplePanel headerUi; + + @UiField + SimplePanel footerUi; + + @UiField + FlowPanel itemsUi; + + public Accordion() { + AccordionResources.INSTANCE.css().ensureInjected(); + initWidget(uiBinder.createAndBindUi(this)); + } + + @UiChild + public void addHeader(Widget headerWidget) { + headerUi.setWidget(headerWidget); + itemsUi.setStyleName(AccordionResources.INSTANCE.css().withHeader(), headerWidget != null); + } + + @UiChild + public void addFooter(Widget footerWidget) { + footerUi.setWidget(footerWidget); + itemsUi.setStyleName(AccordionResources.INSTANCE.css().withFooter(), footerWidget != null); + } + + @UiChild + public void addItem(Widget item) { + itemsUi.add(item); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/Accordion.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/Accordion.ui.xml new file mode 100644 index 00000000000..be731bb1332 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/Accordion.ui.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/AccordionItem.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/AccordionItem.java new file mode 100644 index 00000000000..2703ff34cdf --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/AccordionItem.java @@ -0,0 +1,81 @@ +package com.sap.sailing.gwt.home.mobile.partials.accordion; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.DivElement; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.HeadingElement; +import com.google.gwt.dom.client.ImageElement; +import com.google.gwt.resources.client.ImageResource; +import com.google.gwt.safehtml.shared.SafeUri; +import com.google.gwt.uibinder.client.UiBinder; +import com.google.gwt.uibinder.client.UiChild; +import com.google.gwt.uibinder.client.UiField; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.EventListener; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.UIObject; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.home.shared.utils.CollapseAnimation; + +public class AccordionItem extends Composite { + + private static final String ACCORDION_COLLAPSED_STYLE = AccordionResources.INSTANCE.css().accordioncollapsed(); + + private static AccordionItemUiBinder uiBinder = GWT.create(AccordionItemUiBinder.class); + + interface AccordionItemUiBinder extends UiBinder { + } + + @UiField + DivElement headerUi; + + @UiField + HeadingElement titleUi; + + @UiField + ImageElement imageUi; + + @UiField + SimplePanel contentUi; + + @UiField + DivElement contentWrapperUi; + + public AccordionItem(String title, String imageUrl, String imageAltText, boolean showInitial) { + this(title, () -> imageUrl, imageAltText, showInitial); + } + + public AccordionItem(String title, ImageResource image, String imageAltText, boolean showInitial) { + this(title, image.getSafeUri(), imageAltText, showInitial); + } + + public AccordionItem(String title, SafeUri imageUrl, String imageAltText, boolean showInitial) { + initWidget(uiBinder.createAndBindUi(this)); + titleUi.setInnerText(title); + imageUi.setSrc(imageUrl.asString()); + imageUi.setAlt(imageAltText); + initAnimation(showInitial); + } + + private void initAnimation(boolean showInitial) { + final CollapseAnimation animation = new CollapseAnimation(contentWrapperUi, showInitial); + Element rootElement = getWidget().getElement(); + UIObject.setStyleName(rootElement, ACCORDION_COLLAPSED_STYLE, !showInitial); + DOM.sinkEvents(headerUi, Event.ONCLICK); + DOM.setEventListener(headerUi, new EventListener() { + @Override + public void onBrowserEvent(Event event) { + boolean collapsed = rootElement.hasClassName(ACCORDION_COLLAPSED_STYLE); + UIObject.setStyleName(rootElement, ACCORDION_COLLAPSED_STYLE, !collapsed); + animation.animate(collapsed); + } + }); + } + + @UiChild + public void addContent(Widget content) { + contentUi.setWidget(content); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/AccordionItem.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/AccordionItem.ui.xml new file mode 100644 index 00000000000..7265fe6c5af --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/AccordionItem.ui.xml @@ -0,0 +1,15 @@ + + + + + +
        +

        + +
        +
        + + +
        +
        +
        \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/AccordionResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/AccordionResources.java new file mode 100644 index 00000000000..392ef4a462b --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/accordion/AccordionResources.java @@ -0,0 +1,24 @@ +package com.sap.sailing.gwt.home.mobile.partials.accordion; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface AccordionResources extends ClientBundle { + public static final AccordionResources INSTANCE = GWT.create(AccordionResources.class); + + @Source("Accordion.gss") + LocalCss css(); + + public interface LocalCss extends CssResource { + String accordion(); + String accordioncollapsed(); + String accordion_item_header_arrow(); + String accordion_item(); + String accordion_item_header(); + String accordion_item_body(); + String accordion_item_header_title(); + String withHeader(); + String withFooter(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/anniversary/AnniversaryItem.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/anniversary/AnniversaryItem.java new file mode 100644 index 00000000000..c6b86e1526a --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/anniversary/AnniversaryItem.java @@ -0,0 +1,20 @@ +package com.sap.sailing.gwt.home.mobile.partials.anniversary; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Element; +import com.google.gwt.uibinder.client.UiBinder; +import com.sap.sailing.gwt.home.shared.partials.anniversary.AbstractAnniversaryItem; + +class AnniversaryItem extends AbstractAnniversaryItem { + + interface AnniversaryItemUiBinder extends UiBinder { + } + + private static AnniversaryItemUiBinder uiBinder = GWT.create(AnniversaryItemUiBinder.class); + + AnniversaryItem(boolean isAnnouncement) { + setElement(uiBinder.createAndBindUi(this)); + super.initLayout(isAnnouncement); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/anniversary/AnniversaryItem.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/anniversary/AnniversaryItem.ui.xml new file mode 100644 index 00000000000..fabb149e069 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/anniversary/AnniversaryItem.ui.xml @@ -0,0 +1,96 @@ + + + + + + .content { + padding: 1em .7em; + background-color: #f8f8f8; + margin: 1em 0; + } + .symbolContainer { + width: 7em; + height: 7em; + margin: auto; + border: 0.2em solid #fff; + background: #008fcc; + color: #fff; + text-align: center; + padding: 0.5em 0.333333333333333em; + border-radius: 0.666666666666667em; + box-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); + text-transform: uppercase; + } + .icon { + background-repeat: no-repeat; + background-size: contain; + width: 3em; + height: 3em; + margin: 0 1.75em; + } + .count { + font-weight: bold; + font-size: 1.75em; + line-height: 1.333333333333333em; + } + .unit { + line-height: .75em; + font-size: 1.125em; + } + .countdown .count { + padding-top: .5em; + } + .countdown .unit { + line-height: 1.333333333333333em; + } + .announcement .count { + font-size: 1.5em; + } + .announcement .unit { + font-size: 0.833333333333333em; + } + .teaser, .description { + padding: 0 .5em; + } + .teaser, .description, .link { + margin-top: 1em; + } + .teaser { + font-size: 1.333333333333333em; + font-weight: bold; + color: #008fcc; + } + .description { + position: relative; + } + .legalNotice { + font-size: 1.5em; + cursor: pointer; + color: #008fcc; + line-height: 1em; + position: absolute; + } + .link { + width: 100%; + text-align: center; + border-radius: .3846154em; + } + +
        +
        +
        +
        +
        +
        +
        +
        + + * +
        + + + +
        +
        \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/anniversary/MobileAnniversaries.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/anniversary/MobileAnniversaries.java new file mode 100644 index 00000000000..c2b03caa744 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/anniversary/MobileAnniversaries.java @@ -0,0 +1,21 @@ +package com.sap.sailing.gwt.home.mobile.partials.anniversary; + +import com.sap.sailing.gwt.home.shared.partials.anniversary.AbstractAnniversaries; +import com.sap.sailing.gwt.home.shared.partials.anniversary.AnniversariesView; + +/** + * {@link AnniversariesView} implementation for mobile. + */ +public class MobileAnniversaries extends AbstractAnniversaries implements AnniversariesView { + + @Override + public AnniversaryAnnouncement addAnnouncement() { + return addAnniversaryItem(new AnniversaryItem(true)); + } + + @Override + public AnniversaryCountdown addCountdown() { + return addAnniversaryItem(new AnniversaryItem(false)); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventdescription/EventDescription.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventdescription/EventDescription.java new file mode 100644 index 00000000000..33ec796951f --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/eventdescription/EventDescription.java @@ -0,0 +1,24 @@ +package com.sap.sailing.gwt.home.mobile.partials.eventdescription; + +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Label; +import com.sap.sailing.gwt.home.mobile.partials.section.MobileSection; +import com.sap.sailing.gwt.home.mobile.partials.sectionHeader.SectionHeaderContent; +import com.sap.sailing.gwt.ui.client.StringMessages; + +public class EventDescription extends Composite { + + public EventDescription(String description) { + final MobileSection section = new MobileSection(); + SectionHeaderContent header = new SectionHeaderContent(); + header.setSectionTitle(StringMessages.INSTANCE.description()); + section.addHeader(header); + final Label content = new Label(description); + content.getElement().getStyle().setPaddingTop(0.5, Unit.EM); + content.getElement().getStyle().setPaddingBottom(0.75, Unit.EM); + section.addContent(content); + initWidget(section); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/footer/Footer.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/footer/Footer.gss index 70cd072f687..32b7a0e8c59 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/footer/Footer.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/footer/Footer.gss @@ -14,11 +14,8 @@ .sitefooter_language { display: block; } -a.sitefooter_language_link { - color: #fff; -} -a.sitefooter_language_linkactive { - color: #efaa00; +.sitefooter_language select { + font-size: 110%; } .sitefooter_links { font-weight: 600; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/footer/Footer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/footer/Footer.java index a07f9cbce7d..a7f75d1530c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/footer/Footer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/footer/Footer.java @@ -1,34 +1,19 @@ package com.sap.sailing.gwt.home.mobile.partials.footer; -import java.io.IOException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.AnchorElement; -import com.google.gwt.dom.client.DivElement; import com.google.gwt.event.dom.client.ClickEvent; -import com.google.gwt.event.logical.shared.ValueChangeEvent; -import com.google.gwt.http.client.UrlBuilder; -import com.google.gwt.i18n.client.LocaleInfo; -import com.google.gwt.text.shared.Renderer; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.DOM; import com.google.gwt.user.client.Event; import com.google.gwt.user.client.EventListener; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.Window.Location; -import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Composite; -import com.google.gwt.user.client.ui.ValueListBox; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.gwt.home.desktop.places.whatsnew.WhatsNewPlace; import com.sap.sailing.gwt.home.mobile.app.MobilePlacesNavigator; import com.sap.sailing.gwt.home.shared.SwitchingEntryPoint; -import com.sap.sse.common.Util.Pair; /** * Mobile page footer with several links and the ability to switch the language. @@ -39,32 +24,10 @@ public class Footer extends Composite { interface FooterPanelUiBinder extends UiBinder { } - private String otherLanguage; - private final LocaleInfo currentLocale = LocaleInfo.getCurrentLocale(); - private MobilePlacesNavigator placeNavigator; - - @UiField Anchor changeLanguageLink; @UiField AnchorElement imprintAnchorLink; - @UiField DivElement languageSelectionDiv; @UiField AnchorElement desktopUi; - @UiField(provided = true) ValueListBox> changeLanguageList = new ValueListBox>(new Renderer>() { - @Override - public String render(Pair object) { - if(object == null) { - return ""; - } - if(object.getB() != null) { - return object.getB(); - } - return object.getA(); - } - - @Override - public void render(Pair object, Appendable appendable) throws IOException { - appendable.append(render(object)); - } - }); + private final MobilePlacesNavigator placeNavigator; public Footer(MobilePlacesNavigator placeNavigator) { this.placeNavigator = placeNavigator; @@ -73,7 +36,6 @@ public class Footer extends Composite { initWidget(uiBinder.createAndBindUi(this)); placeNavigator.getImprintNavigation().configureAnchorElement(imprintAnchorLink); - updateUI(); DOM.sinkEvents(desktopUi, Event.ONCLICK); DOM.setEventListener(desktopUi, new EventListener() { @Override @@ -86,62 +48,9 @@ public class Footer extends Composite { }); } - @UiHandler("changeLanguageLink") - public void changeLanguage(ClickEvent e) { - if(otherLanguage != null) { - UrlBuilder builder = Location.createUrlBuilder().setParameter("locale", otherLanguage); - Window.Location.replace(builder.buildString()); - } - } - - @UiHandler("changeLanguageList") - void onListSelection(ValueChangeEvent> event) { - String selectedLocaleName = event.getValue().getA(); - if(!currentLocale.getLocaleName().equals(selectedLocaleName)) { - UrlBuilder builder = Location.createUrlBuilder().setParameter("locale", selectedLocaleName); - Window.Location.replace(builder.buildString()); - } - } - @UiHandler("whatsNewLinkUi") void onWhatsNew(ClickEvent e) { placeNavigator.getWhatsNewNavigation(WhatsNewPlace.WhatsNewNavigationTabs.SailingAnalytics).goToPlace(); } - private void updateUI() { - Set availableLocales = new HashSet(Arrays.asList(LocaleInfo.getAvailableLocaleNames())); - availableLocales.remove("default"); - - if(availableLocales.size() <= 1) { - // only current language (or removed default) available - changeLanguageLink.removeFromParent(); - changeLanguageList.removeFromParent(); - languageSelectionDiv.removeFromParent(); - return; - } - if(availableLocales.size() == 2) { - changeLanguageList.removeFromParent(); - // current language + one we can switch to - for(String localeName : availableLocales) { - if(!currentLocale.getLocaleName().equals(localeName)) { - otherLanguage = localeName; - changeLanguageLink.setText(LocaleInfo.getLocaleNativeDisplayName(otherLanguage)); - return; - } - } - return; - } - changeLanguageLink.removeFromParent(); - Set> values = new HashSet<>(); - Pair selectedValue = null; - for(String localeName : availableLocales) { - Pair value = new Pair(localeName, LocaleInfo.getLocaleNativeDisplayName(localeName)); - values.add(value); - if(currentLocale.getLocaleName().equals(localeName)) { - selectedValue = value; - } - } - changeLanguageList.setValue(selectedValue); - changeLanguageList.setAcceptableValues(values); - } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/footer/Footer.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/footer/Footer.ui.xml index 14bf6904283..c48a3dd984e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/footer/Footer.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/footer/Footer.ui.xml @@ -1,60 +1,17 @@ - - - - - - - .languageSelect::-ms-expand { - display: none; - } - - .languageSelect { - -webkit-appearance: none; - -moz-appearance: none; - -ms-appearance: none; - appearance: none; - margin-top: 25px; - color: #efaa00; - line-height: 1.5em; - height: 1.5em; - display: inline-block; - border: transparent; - cursor: pointer; - padding-right: 1.2em; - background: #333; - cursor: pointer; - background-image: - url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZlcnNpb249JzEuMScgaGVpZ2h0PScxOHB4JyB3aWR0aD0nMTZweCc+PHRleHQgeD0nMCcgeT0nMTUnIGZpbGw9JyNlZmFhMDAnIGZvbnQtc2l6ZT0nMTYnPiYjOTY2MDs8L3RleHQ+PC9zdmc+"); - background-position: right center; - background-repeat: no-repeat; - font-size: 110%; - } - + + + +