diff --git a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java index 2a6f7b10e63..1342432c705 100644 --- a/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java +++ b/java/com.sap.sailing.dashboards.gwt/src/main/java/com/sap/sailing/dashboards/gwt/client/widgets/startanalysis/card/StartAnalysisCard.java @@ -161,7 +161,7 @@ public class StartAnalysisCard extends Composite implements HasWidgets, StartAna final RaceMapSettings raceMapSettings = new RaceMapSettings(raceMapZoomSettings, getHelpLineSettings(), defaultRaceMapSettings.getTransparentHoverlines(), defaultRaceMapSettings.getHoverlineStrokeWeight(), startAnalysisDTO.tailLenghtInMilliseconds, defaultRaceMapSettings.isWindUp(), - defaultRaceMapSettings.getBuoyZoneRadiusInMeters(), defaultRaceMapSettings.isShowOnlySelectedCompetitors(), + defaultRaceMapSettings.getBuoyZoneRadius(), defaultRaceMapSettings.isShowOnlySelectedCompetitors(), defaultRaceMapSettings.isShowSelectedCompetitorsInfo(), defaultRaceMapSettings.isShowWindStreamletColors(), defaultRaceMapSettings.isShowWindStreamletOverlay(), defaultRaceMapSettings.isShowSimulationOverlay(), defaultRaceMapSettings.isShowMapControls(), defaultRaceMapSettings.getManeuverTypesToShow(), 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 b75cfd020b1..984af8fdb66 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 @@ -2,6 +2,9 @@ package com.sap.sailing.datamining.shared; 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.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; @@ -16,6 +19,9 @@ public final class SailingDataMiningSerializationDummy implements SerializationD private CentralAngleDistance centralAngleDistance; private MeterDistance meterDistance; private NauticalMileDistance nauticalMileDistance; + private ManeuverType maneuverType; + private Tack tack; + private NauticalSide nauticalSide; private SailingDataMiningSerializationDummy() { } diff --git a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/data/TestLinearDoubleClusterGroup.java b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/data/TestLinearDoubleClusterGroup.java new file mode 100644 index 00000000000..0394eafa2a5 --- /dev/null +++ b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/data/TestLinearDoubleClusterGroup.java @@ -0,0 +1,86 @@ +package com.sap.sailing.datamining.impl.data; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertThat; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sse.datamining.data.Cluster; +import com.sap.sse.datamining.impl.data.ClusterWithLowerAndUpperBoundaries; +import com.sap.sse.datamining.impl.data.ClusterWithSingleBoundary; +import com.sap.sse.datamining.impl.data.ComparableClusterBoundary; +import com.sap.sse.datamining.impl.data.ComparisonStrategy; + +// TODO Move to sse bundle, after 49er analysis +public class TestLinearDoubleClusterGroup { + + private LinearDoubleClusterGroup hardPercentageClusterGroup; + private LinearDoubleClusterGroup softLinearClusterGroup; + + @Before + public void initialize() { + hardPercentageClusterGroup = new LinearDoubleClusterGroup(0.0, 1.0, 0.1, true); + softLinearClusterGroup = new LinearDoubleClusterGroup(0, 100, 10, false); + } + + @Test + public void testGetClusterForValueOutOfBounds() { + Cluster cluster = hardPercentageClusterGroup.getClusterFor(-0.01); + assertThat(cluster, nullValue()); + + cluster = hardPercentageClusterGroup.getClusterFor(1.01); + assertThat(cluster, nullValue()); + + cluster = softLinearClusterGroup.getClusterFor(-0.1); + Cluster expectedCluster = createCluster(softLinearClusterGroup.getStepSize(), ComparisonStrategy.LOWER_THAN); + assertThat(cluster, is(expectedCluster)); + + cluster = softLinearClusterGroup.getClusterFor(100.1); + expectedCluster = createCluster(softLinearClusterGroup.getUpperGroupBound() - softLinearClusterGroup.getStepSize(), ComparisonStrategy.GREATER_EQUALS_THAN); + assertThat(cluster, is(expectedCluster)); + } + + @Test + public void testGetClusterForBoundaryValues() { + Cluster cluster = hardPercentageClusterGroup.getClusterFor(0.0); + Cluster expectedCluster = createCluster(hardPercentageClusterGroup.getLowerGroupBound(), ComparisonStrategy.GREATER_EQUALS_THAN, + hardPercentageClusterGroup.getLowerGroupBound() + hardPercentageClusterGroup.getStepSize(), ComparisonStrategy.LOWER_THAN); + assertThat(cluster, is(expectedCluster)); + + cluster = hardPercentageClusterGroup.getClusterFor(1.0); + expectedCluster = createCluster(hardPercentageClusterGroup.getUpperGroupBound() - hardPercentageClusterGroup.getStepSize(), ComparisonStrategy.GREATER_EQUALS_THAN, + hardPercentageClusterGroup.getUpperGroupBound(), ComparisonStrategy.LOWER_EQUALS_THAN); + assertThat(cluster, is(expectedCluster)); + + cluster = softLinearClusterGroup.getClusterFor(0.0); + expectedCluster = createCluster(softLinearClusterGroup.getStepSize(), ComparisonStrategy.LOWER_THAN); + assertThat(cluster, is(expectedCluster)); + + cluster = softLinearClusterGroup.getClusterFor(100.0); + expectedCluster = createCluster(softLinearClusterGroup.getUpperGroupBound() - softLinearClusterGroup.getStepSize(), ComparisonStrategy.GREATER_EQUALS_THAN); + assertThat(cluster, is(expectedCluster)); + } + + @Test + public void testGetClusterFor() { + LinearDoubleClusterGroup clusterGroup = softLinearClusterGroup; + double value = (clusterGroup.getUpperGroupBound() + clusterGroup.getStepSize()) / 2; + Cluster cluster = clusterGroup.getClusterFor(value); + Cluster expectedCluster = createCluster(clusterGroup.getUpperGroupBound() / 2, ComparisonStrategy.GREATER_EQUALS_THAN, + (clusterGroup.getUpperGroupBound() / 2) + clusterGroup.getStepSize(), ComparisonStrategy.LOWER_THAN); + assertThat(cluster, is(expectedCluster)); + } + + private Cluster createCluster(double lowerBoundValue, ComparisonStrategy lowerComparisonStrategy, double upperBoundValue, ComparisonStrategy upperComparisonStrategy) { + ComparableClusterBoundary lowerBound = new ComparableClusterBoundary<>(lowerBoundValue, lowerComparisonStrategy); + ComparableClusterBoundary upperBound = new ComparableClusterBoundary<>(upperBoundValue, upperComparisonStrategy); + return new ClusterWithLowerAndUpperBoundaries<>(lowerBound, upperBound); + } + + private Cluster createCluster(double boundValue, ComparisonStrategy comparisonStrategy) { + return new ClusterWithSingleBoundary<>(new ComparableClusterBoundary<>(boundValue, comparisonStrategy)); + } + +} diff --git a/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/data/TestPercentageClusterFormatter.java b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/data/TestPercentageClusterFormatter.java new file mode 100644 index 00000000000..c2406269671 --- /dev/null +++ b/java/com.sap.sailing.datamining.test/src/com/sap/sailing/datamining/impl/data/TestPercentageClusterFormatter.java @@ -0,0 +1,30 @@ +package com.sap.sailing.datamining.impl.data; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sse.datamining.data.Cluster; +import com.sap.sse.datamining.data.ClusterGroup; + +public class TestPercentageClusterFormatter { + + private ClusterGroup percentageClusterGroup; + private PercentageClusterFormatter formatter; + + @Before + public void initialize() { + percentageClusterGroup = new LinearDoubleClusterGroup(0.0, 1.0, 0.1, true); + formatter = new PercentageClusterFormatter(); + } + + @Test + public void testFormat() { + Cluster cluster = percentageClusterGroup.getClusterFor(0.05); + String formattedCluster = formatter.format(cluster); + assertThat(formattedCluster, is("[0% - 10%[")); + } + +} 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 4dbc578b18f..c3317d84fd8 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties @@ -16,6 +16,7 @@ Race=Race Competitor=Competitor Boat=Boat Team=Team +CompetitorSearchTag=Competitor Search Tag LeaderboardSailingDomainRetrieverChain=Leaderboards RaceSailingDomainRetrieverChain=Races LegSailingDomainRetrieverChain=Legs @@ -23,12 +24,14 @@ GPSFixSailingDomainRetrieverChain=GPS-Fixes MarkPassingSailingDomainRetrieverChain=Mark Passings RaceResultSailingDomainRetrieverChain=Race Results RaceOfCompetitorSailingDomainRetrieverChain=Races of Competitors +ManeuverSailingDomainRetrieverChain=Maneuver LeaderboardGroup=Leaderboard Group Leaderboard=Leaderboard Leg=Leg MarkPassing=Mark Passing LegOfCompetitor=Leg of competitor GpsFix=GPS-Fix +Maneuver=Maneuver WindStrengthInBeaufort=Wind Strength in Beaufort Bft0=Calm Bft1=Light air @@ -43,11 +46,14 @@ Bft9=Strong gale Bft10=Storm Bft11=Violent storm Bft12=Hurricane -RelativeScoreInRace=Relative score in race (0 best; 1 worst) -WindSpeedInBeaufort=Wind speed in Bft -DistanceAtStart=Distance to start at start -SpeedWhenStarting=Speed when starting -SpeedTenSecondsBeforeStart=Speed 10s before start +RelativeScore=Relative Score (0 best; 1 worst) +RelativeScoreInPercent=Relative Score in % +RelativeScoreInRaceInPercent=Relative Score in Race in % +AbsoluteRank=Absolute Rank +WindSpeedInBeaufort=Wind Speed in Bft +DistanceAtStart=Distance to Start at Start +SpeedWhenStarting=Speed at Start +SpeedTenSecondsBeforeStart=Speed 10s Before Start RankAtFirstMark=Rank at First Mark NumberOfManeuvers=Number of Maneuvers NumberOfTacks=Number of Tacks @@ -60,4 +66,20 @@ NumberOfWins=Number of Wins RankGainsOrLossesBetweenFirstMarkAndFinish=Rank Gains or Losses (1st Mark -> Finish) IsTracked=Is Tracked NumberOfCompetitorFixes=Number of Competitor Fixes -NumberOfMarkFixes=Number of Mark Fixes \ No newline at end of file +NumberOfMarkFixes=Number of Mark Fixes +ManeuverType=Maneuver Type +Tack=Tack +SpeedBefore=Speed Before Maneuver +SpeedAfter=Speed After Maneuver +DirectionChange=Direction Change in Degrees +ManeuverLoss=Maneuver Loss +Waypoint=Waypoint +PassingSide=Passing Side +TackAtStart=Tack at Start +SpeedTenSecondsAfterStart=Speed 10s After Start +RankThirtySecondsAfterStart=Rank 30s After Start +WindwardDistanceToAdvantageousEndOfLine=Windward Distance to Advantageous End of Line +RankAfterHalfOfTheFirstLeg=Rank after the half of the first leg +AdvantageousEndOfLine=Advantageous End of Line +TimeSpentInSeconds=Time Spent in Seconds +MedalRace=Medal Race \ No newline at end of file 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 e8ec0210da2..4c6d486e4ca 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 @@ -3,6 +3,7 @@ DistanceTraveled=zur Boat=Boot Team=Team Name=Name +CompetitorSearchTag=Teilnehmer Such-Tag Nationality=Nationalitäts Acronym=Abkürzung SailID=Segelnummer @@ -21,12 +22,14 @@ GPSFixSailingDomainRetrieverChain=GPS-Fixes MarkPassingSailingDomainRetrieverChain=Tonnenrundungen RaceResultSailingDomainRetrieverChain=Rennergebnisse RaceOfCompetitorSailingDomainRetrieverChain=Rennen eines Teilnehmers +ManeuverSailingDomainRetrieverChain=Manöver LeaderboardGroup=Ranglisten Gruppe Leaderboard=Rangliste Leg=Schenkel MarkPassing=Tonnenrundung LegOfCompetitor=Schenkel eines Teilnehmers GpsFix=GPS-Fix +Maneuver=Manöver Speed=Geschwindigkeit SpeedTenSecondsBefore=Geschwindigkeit 10s vorher WindStrengthInBeaufort=Windstärke in Beaufort @@ -43,7 +46,10 @@ Bft9=Sturm Bft10=schwerer Sturm Bft11=orkanartiger Sturm Bft12=Orkan -RelativeScoreInRace=Relative Wertung im Rennen (0 am besten; 1 am schlechtesten) +RelativeScore=Relative Wertung (0 am besten; 1 am schlechtesten) +RelativeScoreInPercent=Relative Wertung in % +RelativeScoreInRaceInPercent=Relative Wertung im Rennen in % +AbsoluteRank=Absolute Platzierung WindSpeedInBeaufort=Windgeschwindigkeit in Bft. DistanceAtStart=Distanz zur Startlinie zum Startzeitpunkt SpeedWhenStarting=Geschwindigkeit beim Überqueren der Startlinie @@ -60,4 +66,20 @@ NumberOfWins=Anzahl der Siege RankGainsOrLossesBetweenFirstMarkAndFinish=Positions-Verluste oder -Gewinne (1ter Wegpunkt -> Ziel) IsTracked=Ist getracked NumberOfCompetitorFixes=Anzahl der Teilnehmer-Fixes -NumberOfMarkFixes=Anzahl der Tonnen-Fixes \ No newline at end of file +NumberOfMarkFixes=Anzahl der Tonnen-Fixes +ManeuverType=Art des Manövers +Tack=Bug +SpeedBefore=Geschwindigkeit bevor dem Manöver +SpeedAfter=Geschwindigkeit nach dem Manöver +DirectionChange=Richtungsänderung in Grad +ManeuverLoss=Manöververlust +Waypoint=Wegpunkt +PassingSide=Passierende Seite +TackAtStart=Startbug +SpeedTenSecondsAfterStart=Geschwindigkeit 10s nach dem Startzeitpunkt +RankThirtySecondsAfterStart=Absolute Platzierung 30s nach dem Startzeitpunkt +WindwardDistanceToAdvantageousEndOfLine=Windwärtige Distanz zum Bevorteilten Ende der Startlinie +RankAfterHalfOfTheFirstLeg=Platzierung nach der Hälfte des ersten Schenkels +AdvantageousEndOfLine=Bevorteiltes Ende der Startlinie +TimeSpentInSeconds=Benötigte Zeit in Sekunden +MedalRace=Medaillenrennen \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java index c24bc340a5c..183cba04870 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/Activator.java @@ -10,6 +10,7 @@ import org.osgi.framework.BundleContext; import com.sap.sailing.datamining.data.HasGPSFixContext; import com.sap.sailing.datamining.data.HasLeaderboardContext; import com.sap.sailing.datamining.data.HasLeaderboardGroupContext; +import com.sap.sailing.datamining.data.HasManeuverContext; import com.sap.sailing.datamining.data.HasMarkPassingContext; import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext; import com.sap.sailing.datamining.data.HasRaceResultOfCompetitorContext; @@ -22,11 +23,9 @@ import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceMa import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceMedianAggregationProcessor; import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceMinAggregationProcessor; import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceSumAggregationProcessor; -import com.sap.sailing.domain.common.Speed; import com.sap.sse.datamining.DataSourceProvider; import com.sap.sse.datamining.components.AggregationProcessorDefinition; import com.sap.sse.datamining.components.DataRetrieverChainDefinition; -import com.sap.sse.datamining.data.ClusterGroup; import com.sap.sse.datamining.impl.AbstractDataMiningActivatorWithPredefinedQueries; import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO; import com.sap.sse.datamining.shared.impl.PredefinedQueryIdentifier; @@ -82,6 +81,7 @@ public class Activator extends AbstractDataMiningActivatorWithPredefinedQueries internalClasses.add(HasTrackedLegContext.class); internalClasses.add(HasTrackedLegOfCompetitorContext.class); internalClasses.add(HasGPSFixContext.class); + internalClasses.add(HasManeuverContext.class); internalClasses.add(HasMarkPassingContext.class); internalClasses.add(HasRaceOfCompetitorContext.class); internalClasses.add(HasLeaderboardGroupContext.class); @@ -124,9 +124,9 @@ public class Activator extends AbstractDataMiningActivatorWithPredefinedQueries dataSourceProviders = new HashSet<>(); dataSourceProviders.add(new RacingEventServiceProvider(context)); } - - public static ClusterGroup getWindStrengthInBeaufortClusterGroup() { - return clusterGroups.getWindStrengthInBeaufortCluster(); + + public static SailingClusterGroups getClusterGroups() { + return clusterGroups; } public static Activator getDefault() { diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingClusterGroups.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingClusterGroups.java index 888d6f848c2..c6404225ab3 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingClusterGroups.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/SailingClusterGroups.java @@ -3,6 +3,9 @@ package com.sap.sailing.datamining; import java.util.ArrayList; import java.util.Collection; +import com.sap.sailing.datamining.data.ClusterFormatter; +import com.sap.sailing.datamining.impl.data.LinearDoubleClusterGroup; +import com.sap.sailing.datamining.impl.data.PercentageClusterFormatter; import com.sap.sailing.domain.common.Speed; import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sse.datamining.data.Cluster; @@ -17,7 +20,10 @@ import com.sap.sse.datamining.impl.data.LocalizedCluster; public class SailingClusterGroups { - private final ClusterGroup windStrengthInBeaufortCluster; + private final ClusterGroup windStrengthInBeaufortClusterGroup; + + private final ClusterGroup percentageClusterGroup; + private final ClusterFormatter percentageClusterFormatter; public SailingClusterGroups() { Collection> clusters = new ArrayList<>(); @@ -98,11 +104,22 @@ public class SailingClusterGroups { lowerBound = new ComparableClusterBoundary(lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); clusters.add(new LocalizedCluster("Bft12", new ClusterWithSingleBoundary(lowerBound))); - windStrengthInBeaufortCluster = new FixClusterGroup(clusters); + windStrengthInBeaufortClusterGroup = new FixClusterGroup(clusters); + + percentageClusterGroup = new LinearDoubleClusterGroup(0.0, 1.0, 0.1, true); + percentageClusterFormatter = new PercentageClusterFormatter(); } - public ClusterGroup getWindStrengthInBeaufortCluster() { - return windStrengthInBeaufortCluster; + public ClusterGroup getWindStrengthInBeaufortClusterGroup() { + return windStrengthInBeaufortClusterGroup; + } + + public ClusterGroup getPercentageClusterGroup() { + return percentageClusterGroup; + } + + public ClusterFormatter getPercentageClusterFormatter() { + return percentageClusterFormatter; } } 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 6e5345237db..d5dedde95c0 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 @@ -5,6 +5,7 @@ import java.util.Collection; import com.sap.sailing.datamining.data.HasGPSFixContext; import com.sap.sailing.datamining.data.HasLeaderboardContext; +import com.sap.sailing.datamining.data.HasManeuverContext; import com.sap.sailing.datamining.data.HasMarkPassingContext; import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext; import com.sap.sailing.datamining.data.HasRaceResultOfCompetitorContext; @@ -15,6 +16,7 @@ import com.sap.sailing.datamining.impl.components.CompetitorOfRaceInLeaderboardR import com.sap.sailing.datamining.impl.components.GPSFixRetrievalProcessor; import com.sap.sailing.datamining.impl.components.LeaderboardGroupRetrievalProcessor; import com.sap.sailing.datamining.impl.components.LeaderboardRetrievalProcessor; +import com.sap.sailing.datamining.impl.components.ManeuverRetrievalProcessor; import com.sap.sailing.datamining.impl.components.MarkPassingRetrievalProcessor; import com.sap.sailing.datamining.impl.components.RaceOfCompetitorRetrievalProcessor; import com.sap.sailing.datamining.impl.components.TrackedLegOfCompetitorRetrievalProcessor; @@ -48,11 +50,6 @@ public class SailingDataRetrievalChainDefinitions { trackedRaceRetrieverChainDefinition.endWith(LeaderboardRetrievalProcessor.class, TrackedRaceRetrievalProcessor.class, HasTrackedRaceContext.class, "Race"); dataRetrieverChainDefinitions.add(trackedRaceRetrieverChainDefinition); - - final DataRetrieverChainDefinition markPassingRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( - trackedRaceRetrieverChainDefinition, HasMarkPassingContext.class, "MarkPassingSailingDomainRetrieverChain"); - markPassingRetrieverChainDefinition.endWith(TrackedRaceRetrievalProcessor.class, MarkPassingRetrievalProcessor.class, HasMarkPassingContext.class, "MarkPassing"); - dataRetrieverChainDefinitions.add(markPassingRetrieverChainDefinition); final DataRetrieverChainDefinition raceOfCompetitorRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( trackedRaceRetrieverChainDefinition, HasRaceOfCompetitorContext.class, "RaceOfCompetitorSailingDomainRetrieverChain"); @@ -71,6 +68,17 @@ public class SailingDataRetrievalChainDefinitions { gpsFixRetrieverChainDefinition.endWith(TrackedLegOfCompetitorRetrievalProcessor.class, GPSFixRetrievalProcessor.class, HasGPSFixContext.class, "GpsFix"); dataRetrieverChainDefinitions.add(gpsFixRetrieverChainDefinition); + + final DataRetrieverChainDefinition maneuverRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( + legOfCompetitorRetrieverChainDefinition, HasManeuverContext.class, "ManeuverSailingDomainRetrieverChain"); + maneuverRetrieverChainDefinition.endWith(TrackedLegOfCompetitorRetrievalProcessor.class, ManeuverRetrievalProcessor.class, + HasManeuverContext.class, "Maneuver"); + dataRetrieverChainDefinitions.add(maneuverRetrieverChainDefinition); + + final DataRetrieverChainDefinition markPassingRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>( + legOfCompetitorRetrieverChainDefinition, HasMarkPassingContext.class, "MarkPassingSailingDomainRetrieverChain"); + markPassingRetrieverChainDefinition.endWith(TrackedLegOfCompetitorRetrievalProcessor.class, MarkPassingRetrievalProcessor.class, HasMarkPassingContext.class, "MarkPassing"); + dataRetrieverChainDefinitions.add(markPassingRetrieverChainDefinition); } public Iterable> get() { diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/ClusterFormatter.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/ClusterFormatter.java new file mode 100644 index 00000000000..821e13f3bec --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/ClusterFormatter.java @@ -0,0 +1,12 @@ +package com.sap.sailing.datamining.data; + +import java.io.Serializable; + +import com.sap.sse.datamining.data.Cluster; + +// TODO Move to sse bundle, after 49er analysis +public interface ClusterFormatter { + + String format(Cluster cluster); + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasManeuverContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasManeuverContext.java new file mode 100644 index 00000000000..6ec4fba870f --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasManeuverContext.java @@ -0,0 +1,38 @@ +package com.sap.sailing.datamining.data; + +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.ManeuverType; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.domain.tracking.Maneuver; +import com.sap.sse.datamining.annotations.Connector; +import com.sap.sse.datamining.annotations.Dimension; +import com.sap.sse.datamining.annotations.Statistic; + +public interface HasManeuverContext { + + @Connector(scanForStatistics=false) + HasTrackedLegOfCompetitorContext getTrackedLegOfCompetitorContext(); + + Maneuver getManeuver(); + + //TODO Clean-Up: + // Move Dimensions and Statistics to Maneuver and connect to it + @Dimension(messageKey="ManeuverType", ordinal=12) + ManeuverType getManeuverType(); + + @Dimension(messageKey="Tack", ordinal=13) + Tack getTack(); + + @Connector(messageKey="SpeedBefore", ordinal=0) + SpeedWithBearing getSpeedBefore(); + @Connector(messageKey="SpeedAfter", ordinal=1) + SpeedWithBearing getSpeedAfter(); + + @Statistic(messageKey="DirectionChange", resultDecimals=2, ordinal=2) + Double getDirectionChangeInDegrees(); + + @Statistic(messageKey="ManeuverLoss", resultDecimals=0, ordinal=3) + Distance getManeuverLoss(); + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasMarkPassingContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasMarkPassingContext.java index 78a88fadba0..8712a394a45 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasMarkPassingContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasMarkPassingContext.java @@ -1,29 +1,45 @@ package com.sap.sailing.datamining.data; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.common.NoWindException; -import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.NauticalSide; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.domain.tracking.MarkPassingManeuver; import com.sap.sse.datamining.annotations.Connector; import com.sap.sse.datamining.annotations.Dimension; +import com.sap.sse.datamining.annotations.Statistic; public interface HasMarkPassingContext { - @Connector - HasTrackedRaceContext getTrackedRaceContext(); + @Connector(scanForStatistics=false) + HasTrackedLegOfCompetitorContext getTrackedLegOfCompetitorContext(); + + MarkPassingManeuver getManeuver(); + + //TODO Clean-Up: + // Move Dimensions and Statistics to Maneuver/MarkPassingManeuver and connect to it + // Find a way to implement HasManeuverContext and HasMarkPassingManeuver with a base class + @Dimension(messageKey="Tack", ordinal=12) + Tack getTack(); + + @Connector(messageKey="Waypoint", ordinal=13) + Waypoint getWaypoint(); + + @Dimension(messageKey="PassingSide", ordinal=13) + NauticalSide getPassingSide(); - @Connector - MarkPassing getMarkPassing(); + @Connector(messageKey="SpeedBefore", ordinal=0) + SpeedWithBearing getSpeedBefore(); + @Connector(messageKey="SpeedAfter", ordinal=1) + SpeedWithBearing getSpeedAfter(); - @Connector(messageKey="Competitor") - Competitor getCompetitor(); + @Statistic(messageKey="DirectionChange", resultDecimals=2, ordinal=2) + Double getDirectionChangeInDegrees(); - @Connector(messageKey="Speed", ordinal=1) - Speed getSpeed(); + @Statistic(messageKey="RelativeScore", ordinal=3, resultDecimals=2) + Double getRelativeRank(); - @Connector(messageKey="SpeedTenSecondsBefore", ordinal=2) - Speed getSpeedTenSecondsBefore(); + @Statistic(messageKey="AbsoluteRank", ordinal=4, resultDecimals=2) + Double getAbsoluteRank(); - @Dimension(messageKey="LegType", ordinal=6) - String getPreviousLegTypeSignifier() throws NoWindException; } \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasRaceOfCompetitorContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasRaceOfCompetitorContext.java index 8c6334b84e2..97bde57e8e8 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasRaceOfCompetitorContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasRaceOfCompetitorContext.java @@ -2,9 +2,13 @@ package com.sap.sailing.datamining.data; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.Tack; import com.sap.sse.datamining.annotations.Connector; +import com.sap.sse.datamining.annotations.Dimension; import com.sap.sse.datamining.annotations.Statistic; +import com.sap.sse.datamining.shared.impl.dto.ClusterDTO; public interface HasRaceOfCompetitorContext { @@ -14,34 +18,58 @@ public interface HasRaceOfCompetitorContext { @Connector(messageKey="Competitor") public Competitor getCompetitor(); + @Dimension(messageKey="CompetitorSearchTag", ordinal=11) // TODO Clean me: Move Dimension to Competitor when possible + public String getCompetitorSearchTag(); + + @Dimension(messageKey="TackAtStart", ordinal=12) + public Tack getTackAtStart() throws NoWindException; + + @Dimension(messageKey="DistanceToStarboardSideAtStart", ordinal=13) + public ClusterDTO getPercentageClusterForDistanceToStarboardSideAtStart(); + + @Dimension(messageKey="RelativeScoreInPercent", ordinal=14) + public ClusterDTO getPercentageClusterForRelativeScore(); + @Statistic(messageKey="DistanceAtStart", resultDecimals=2, ordinal=0) public Distance getDistanceToStartLineAtStart(); @Statistic(messageKey="DistanceToStarboardSideAtStart", resultDecimals=2, ordinal=1) public Double getNormalizedDistanceToStarboardSideAtStart(); - @Connector(messageKey="SpeedWhenStarting", ordinal=2) + @Statistic(messageKey="WindwardDistanceToAdvantageousEndOfLine", resultDecimals=2, ordinal=2) + public Distance getWindwardDistanceToAdvantageousLineEndAtStart(); + + @Connector(messageKey="SpeedWhenStarting", ordinal=3) public Speed getSpeedWhenStarting(); - @Connector(messageKey="SpeedTenSecondsBeforeStart", ordinal=3) + @Connector(messageKey="SpeedTenSecondsBeforeStart", ordinal=4) public Speed getSpeedTenSecondsBeforeStart(); - @Statistic(messageKey="RankAtFirstMark", resultDecimals=2, ordinal=4) - public Double getRankAtFirstMark(); + @Connector(messageKey="SpeedTenSecondsAfterStart", ordinal=5) + public Speed getSpeedTenSecondsAfterStart(); - @Statistic(messageKey="NumberOfManeuvers", resultDecimals=0, ordinal=5) - public int getNumberOfManeuvers(); - - @Statistic(messageKey="NumberOfTacks", resultDecimals=2, ordinal=6) - public int getNumberOfTacks(); - - @Statistic(messageKey="NumberOfJibes", resultDecimals=2, ordinal=7) - public int getNumberOfJibes(); - - @Statistic(messageKey="NumberOfPenaltyCircles", resultDecimals=2, ordinal=8) - public int getNumberOfPenaltyCircles(); + @Statistic(messageKey="RankThirtySecondsAfterStart", resultDecimals=2, ordinal=6) + public Double getRankThirtySecondsAfterStart(); + + @Statistic(messageKey="RankAfterHalfOfTheFirstLeg", resultDecimals=2, ordinal=7) + public Double getRankAfterHalfOfTheFirstLeg(); + + @Statistic(messageKey="RankAtFirstMark", resultDecimals=2, ordinal=8) + public Double getRankAtFirstMark(); @Statistic(messageKey="RankGainsOrLossesBetweenFirstMarkAndFinish", resultDecimals=2, ordinal=9) public Double getRankGainsOrLossesBetweenFirstMarkAndFinish(); + @Statistic(messageKey="NumberOfManeuvers", resultDecimals=0, ordinal=10) + public int getNumberOfManeuvers(); + + @Statistic(messageKey="NumberOfTacks", resultDecimals=2, ordinal=11) + public int getNumberOfTacks(); + + @Statistic(messageKey="NumberOfJibes", resultDecimals=2, ordinal=12) + public int getNumberOfJibes(); + + @Statistic(messageKey="NumberOfPenaltyCircles", resultDecimals=2, ordinal=13) + public int getNumberOfPenaltyCircles(); + } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasRaceResultOfCompetitorContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasRaceResultOfCompetitorContext.java index 36e5292a1ba..18cb994a6d1 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasRaceResultOfCompetitorContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasRaceResultOfCompetitorContext.java @@ -12,22 +12,28 @@ public interface HasRaceResultOfCompetitorContext { @Connector(messageKey="Competitor") public Competitor getCompetitor(); - - /** - * 0 means the competitor won the race, 1 means the competitor ranked last - */ - @Statistic(messageKey="RelativeScoreInRace", ordinal=1, resultDecimals=2) - public double getRelativeRank(); - - @Dimension(messageKey="WindSpeedInBeaufort") - int getAverageWindSpeedInRoundedBeaufort(); @Dimension(messageKey="Regatta") String getRegattaName(); - @Statistic(messageKey="NumberOfPodiumFinish", ordinal=2) + @Dimension(messageKey="CompetitorSearchTag", ordinal=11) // TODO Clean me: Move Dimension to Competitor when possible + public String getCompetitorSearchTag(); + + @Dimension(messageKey="WindSpeedInBeaufort") + int getAverageWindSpeedInRoundedBeaufort(); + + /** + * 0 means the competitor won the race, 1 means the competitor ranked last + */ + @Statistic(messageKey="RelativeScore", ordinal=1, resultDecimals=2) + public Double getRelativeRank(); + + @Statistic(messageKey="AbsoluteRank", ordinal=2, resultDecimals=2) + public Double getAbsoluteRank(); + + @Statistic(messageKey="NumberOfPodiumFinish", ordinal=3) public Boolean isPodiumFinish(); - @Statistic(messageKey="NumberOfWins", ordinal=3) + @Statistic(messageKey="NumberOfWins", ordinal=4) public Boolean isWin(); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedLegOfCompetitorContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedLegOfCompetitorContext.java index c863c6bb2f7..4f5cb3a3ea9 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedLegOfCompetitorContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedLegOfCompetitorContext.java @@ -4,7 +4,9 @@ import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sse.datamining.annotations.Connector; +import com.sap.sse.datamining.annotations.Dimension; import com.sap.sse.datamining.annotations.Statistic; +import com.sap.sse.datamining.shared.impl.dto.ClusterDTO; public interface HasTrackedLegOfCompetitorContext { @@ -12,6 +14,12 @@ public interface HasTrackedLegOfCompetitorContext { public HasTrackedLegContext getTrackedLegContext(); public TrackedLegOfCompetitor getTrackedLegOfCompetitor(); + + @Dimension(messageKey="CompetitorSearchTag", ordinal=11) // TODO Clean me: Move Dimension to Competitor when possible + public String getCompetitorSearchTag(); + + @Dimension(messageKey="RelativeScoreInRaceInPercent", ordinal=12) + public ClusterDTO getPercentageClusterForRelativeScoreInRace(); @Connector(messageKey="Competitor") public Competitor getCompetitor(); @@ -21,5 +29,14 @@ public interface HasTrackedLegOfCompetitorContext { @Statistic(messageKey="RankGainsOrLosses", resultDecimals=2, ordinal=1) public Double getRankGainsOrLosses(); + + @Statistic(messageKey="RelativeScore", resultDecimals=2, ordinal=2) + public Double getRelativeRank(); + + @Statistic(messageKey="AbsoluteRank", resultDecimals=2, ordinal=3) + public Double getAbsoluteRank(); + + @Statistic(messageKey="TimeSpentInSeconds", resultDecimals=0, ordinal=4) + public Long getTimeTakenInSeconds(); } \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java index aed8f28d024..1b5f0200163 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasTrackedRaceContext.java @@ -1,10 +1,12 @@ package com.sap.sailing.datamining.data; 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.Fleet; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.common.NauticalSide; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.datamining.annotations.Connector; import com.sap.sse.datamining.annotations.Dimension; @@ -35,7 +37,13 @@ public interface HasTrackedRaceContext { @Dimension(messageKey="Year", ordinal=2) public Integer getYear(); - @Dimension(messageKey="IsTracked", ordinal=6) + @Dimension(messageKey="AdvantageousEndOfLine", ordinal=6) + public NauticalSide getAdvantageousEndOfLine(); + + @Dimension(messageKey="MedalRace", ordinal=7) + public Boolean isMedalRace(); + + @Dimension(messageKey="IsTracked", ordinal=7) public Boolean isTracked(); @Statistic(messageKey="NumberOfCompetitorFixes", resultDecimals=0, ordinal=0) @@ -44,4 +52,9 @@ public interface HasTrackedRaceContext { @Statistic(messageKey="NumberOfMarkFixes", resultDecimals=0, ordinal=1) public int getNumberOfMarkFixes(); + // Convenience methods for race dependent calculation to avoid code duplication + public Double getRelativeScoreForCompetitor(Competitor competitor); + + public Double getRankAtFinishForCompetitor(Competitor competitor); + } \ No newline at end of file diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/ManeuverRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/ManeuverRetrievalProcessor.java new file mode 100644 index 00000000000..c11ab0f524e --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/ManeuverRetrievalProcessor.java @@ -0,0 +1,40 @@ +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.HasManeuverContext; +import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; +import com.sap.sailing.datamining.impl.data.ManeuverWithContext; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.tracking.Maneuver; +import com.sap.sse.common.TimePoint; +import com.sap.sse.datamining.components.Processor; +import com.sap.sse.datamining.impl.components.AbstractRetrievalProcessor; + +public class ManeuverRetrievalProcessor extends AbstractRetrievalProcessor { + + public ManeuverRetrievalProcessor(ExecutorService executor, Collection> resultReceivers, int retrievalLevel) { + super(HasTrackedLegOfCompetitorContext.class, HasManeuverContext.class, executor, resultReceivers, retrievalLevel); + } + + @Override + protected Iterable retrieveData(HasTrackedLegOfCompetitorContext element) { + Collection maneuversWithContext = new ArrayList<>(); + TimePoint finishTime = element.getTrackedLegOfCompetitor().getFinishTime(); + if (finishTime != null) { + try { + List maneuvers = element.getTrackedLegOfCompetitor().getManeuvers(finishTime, false); + for (Maneuver maneuver : maneuvers) { + maneuversWithContext.add(new ManeuverWithContext(element, maneuver)); + } + } catch (NoWindException e) { + throw new IllegalStateException("No wind retrieving the maneuvers", e); + } + } + return maneuversWithContext; + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java index 537acff7c8a..8b178c44678 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/components/MarkPassingRetrievalProcessor.java @@ -1,46 +1,45 @@ package com.sap.sailing.datamining.impl.components; +import java.util.ArrayList; import java.util.Collection; -import java.util.HashSet; -import java.util.NavigableSet; -import java.util.Set; +import java.util.List; import java.util.concurrent.ExecutorService; import com.sap.sailing.datamining.data.HasMarkPassingContext; -import com.sap.sailing.datamining.data.HasTrackedRaceContext; +import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.datamining.impl.data.MarkPassingWithContext; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.tracking.MarkPassing; -import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sailing.domain.common.ManeuverType; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.tracking.Maneuver; +import com.sap.sailing.domain.tracking.MarkPassingManeuver; +import com.sap.sse.common.TimePoint; import com.sap.sse.datamining.components.Processor; import com.sap.sse.datamining.impl.components.AbstractRetrievalProcessor; -public class MarkPassingRetrievalProcessor extends AbstractRetrievalProcessor { +public class MarkPassingRetrievalProcessor extends AbstractRetrievalProcessor { public MarkPassingRetrievalProcessor(ExecutorService executor, Collection> resultReceivers, int retrievalLevel) { - super(HasTrackedRaceContext.class, HasMarkPassingContext.class, executor, resultReceivers, retrievalLevel); + super(HasTrackedLegOfCompetitorContext.class, HasMarkPassingContext.class, executor, resultReceivers, retrievalLevel); } @Override - protected Iterable retrieveData(HasTrackedRaceContext element) { - TrackedRace trackedRace = element.getTrackedRace(); - Set result = new HashSet<>(); - trackedRace.getRace().getCourse().lockForRead(); - try { - for (Competitor competitor : trackedRace.getRace().getCompetitors()) { - NavigableSet markPassingsForCompetitor = trackedRace.getMarkPassings(competitor); - trackedRace.lockForRead(markPassingsForCompetitor); - try { - markPassingsForCompetitor.stream().forEach(mp->result.add(new MarkPassingWithContext(element, mp))); - } finally { - trackedRace.unlockAfterRead(markPassingsForCompetitor); + protected Iterable retrieveData(HasTrackedLegOfCompetitorContext element) { + Collection maneuversWithContext = new ArrayList<>(); + TimePoint finishTime = element.getTrackedLegOfCompetitor().getFinishTime(); + if (finishTime != null) { + try { + List maneuvers = element.getTrackedLegOfCompetitor().getManeuvers(finishTime, false); + for (Maneuver maneuver : maneuvers) { + if (maneuver.getType() == ManeuverType.MARK_PASSING) { + maneuversWithContext.add(new MarkPassingWithContext(element, (MarkPassingManeuver) maneuver)); + } } + } catch (NoWindException e) { + throw new IllegalStateException("No wind retrieving the mark passings", e); } - } finally { - trackedRace.getRace().getCourse().unlockAfterRead(); } - return result; + return maneuversWithContext; } } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/AbstractClusterFormatter.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/AbstractClusterFormatter.java new file mode 100644 index 00000000000..f5fd3af28e9 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/AbstractClusterFormatter.java @@ -0,0 +1,143 @@ +package com.sap.sailing.datamining.impl.data; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.sap.sailing.datamining.data.ClusterFormatter; +import com.sap.sse.datamining.data.Cluster; +import com.sap.sse.datamining.data.ClusterBoundary; +import com.sap.sse.datamining.impl.data.AbstractCluster; +import com.sap.sse.datamining.impl.data.AbstractClusterBoundary; +import com.sap.sse.datamining.impl.data.ClusterWithLowerAndUpperBoundaries; +import com.sap.sse.datamining.impl.data.ClusterWithSingleBoundary; +import com.sap.sse.datamining.impl.data.ComparisonStrategy; +import com.sap.sse.datamining.impl.data.LocalizedCluster; + +//TODO Clean and move to sse bundle after 49er analysis +public abstract class AbstractClusterFormatter implements ClusterFormatter { + + private static final char INFINITE = '\u221e'; + + private final ClusterBoundariesAccessor boundariesAccessor = new ClusterBoundariesAccessor(); + private final BoundaryValueAccessor valueAccessor = new BoundaryValueAccessor(); + + @Override + public String format(Cluster cluster) { + if (cluster instanceof LocalizedCluster) { + // TODO recursive call format(localizedCluster.getInnerCluster()) after 49er analysis + } + if (cluster instanceof ClusterWithSingleBoundary) { + return formatClusterWithSingleBoundary((ClusterWithSingleBoundary) cluster); + } + if (cluster instanceof ClusterWithLowerAndUpperBoundaries) { + return formatClusterWithLowerAndUpperBoundaries((ClusterWithLowerAndUpperBoundaries) cluster); + } + throw new IllegalArgumentException("Can't format clusters of type " + cluster.getClass().getName()); + } + + protected String formatClusterWithSingleBoundary(ClusterWithSingleBoundary cluster) { + List> boundaries = boundariesAccessor.getBoundaries(cluster); + if (boundaries == null) { + return ""; + } + + ClusterBoundary boundary = boundaries.get(0); + + StringBuilder builder = new StringBuilder(); + if (boundary.getStrategy() == ComparisonStrategy.LOWER_EQUALS_THAN || + boundary.getStrategy() == ComparisonStrategy.LOWER_THAN) { + builder.append("-" + INFINITE + " - "); + } + builder.append(formatBoundary(boundary)); + if (boundary.getStrategy() == ComparisonStrategy.GREATER_EQUALS_THAN || + boundary.getStrategy() == ComparisonStrategy.GREATER_THAN) { + builder.append(" - " + INFINITE); + } + return builder.toString(); + } + + protected String formatClusterWithLowerAndUpperBoundaries(ClusterWithLowerAndUpperBoundaries cluster) { + List> boundaries = boundariesAccessor.getBoundaries(cluster); + if (boundaries == null) { + return ""; + } + + return String.format("%s - %s", formatBoundary(boundaries.get(0)), formatBoundary(boundaries.get(1))); + } + + protected String formatBoundary(ClusterBoundary boundary) { + String value = formatValue(valueAccessor.getValue((AbstractClusterBoundary) boundary)); + if (value == null) { + return ""; + } + + StringBuilder builder = new StringBuilder(); + ComparisonStrategy strategy = boundary.getStrategy(); + if (strategy == ComparisonStrategy.GREATER_EQUALS_THAN || strategy == ComparisonStrategy.GREATER_THAN) { + builder.append(strategy.getSignifier()); + } + builder.append(value); + if (strategy == ComparisonStrategy.LOWER_EQUALS_THAN || strategy == ComparisonStrategy.LOWER_THAN) { + builder.append(strategy.getSignifier()); + } + return builder.toString(); + } + + protected abstract String formatValue(T value); + + //FIXME Please delete me + private class ClusterBoundariesAccessor { + + private static final String boundariesFieldName = "boundaries"; + + public List> getBoundaries(AbstractCluster cluster) { + List> boundaries = null; + Field boundariesField = null; + try { + boundariesField = AbstractCluster.class.getDeclaredField(boundariesFieldName); + boundariesField.setAccessible(true); + @SuppressWarnings("unchecked") + Collection> reflectedBoundaries = (Collection>) boundariesField.get(cluster); + boundaries = new ArrayList<>(); + for (ClusterBoundary boundary : reflectedBoundaries) { + boundaries.add(boundary); + } + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + } finally { + if (boundariesField != null) { + boundariesField.setAccessible(false); + } + } + return boundaries; + } + + } + + //FIXME Please delete me + private class BoundaryValueAccessor { + + private static final String valueFieldName = "boundaryValue"; + + @SuppressWarnings("unchecked") + public T getValue(AbstractClusterBoundary boundary) { + T value = null; + Field valueField = null; + try { + valueField = AbstractClusterBoundary.class.getDeclaredField(valueFieldName); + valueField.setAccessible(true); + value = (T) valueField.get(boundary); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + } finally { + if (valueField != null) { + valueField.setAccessible(false); + } + } + return value; + } + + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java index d555d03b652..65830923d3e 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/GPSFixWithContext.java @@ -36,7 +36,7 @@ public class GPSFixWithContext implements HasGPSFixContext { @Override public ClusterDTO getWindStrengthAsBeaufortCluster(Locale locale, ResourceBundleStringMessages stringMessages) { Wind wind = getWind(); - Cluster cluster = Activator.getWindStrengthInBeaufortClusterGroup().getClusterFor(wind); + Cluster cluster = Activator.getClusterGroups().getWindStrengthInBeaufortClusterGroup().getClusterFor(wind); return new ClusterDTO(cluster.asLocalizedString(locale, stringMessages)); } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/LinearDoubleClusterGroup.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/LinearDoubleClusterGroup.java new file mode 100644 index 00000000000..3b01d97ddf6 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/LinearDoubleClusterGroup.java @@ -0,0 +1,110 @@ +package com.sap.sailing.datamining.impl.data; + +import java.util.HashMap; +import java.util.Map; + +import com.sap.sse.datamining.data.Cluster; +import com.sap.sse.datamining.data.ClusterGroup; +import com.sap.sse.datamining.impl.data.ClusterWithLowerAndUpperBoundaries; +import com.sap.sse.datamining.impl.data.ClusterWithSingleBoundary; +import com.sap.sse.datamining.impl.data.ComparableClusterBoundary; +import com.sap.sse.datamining.impl.data.ComparisonStrategy; + +// TODO: Improve and move to com.sap.sse.datamining +public class LinearDoubleClusterGroup implements ClusterGroup { + private static final long serialVersionUID = 1559316450183865115L; + + private final double lowerBound; + private final double upperBound; + private final double stepSize; + private final boolean hardBounds; + + private final transient Map> clusterCache; + private final int lastClusterIndex; + + public LinearDoubleClusterGroup(double lowerBound, double upperBound, double stepSize, boolean hardBounds) { + this.lowerBound = lowerBound; + this.upperBound = upperBound; + this.stepSize = stepSize; + this.hardBounds = hardBounds; + + clusterCache = new HashMap<>(); + lastClusterIndex = calculateLastClusterIndex(this.upperBound, this.stepSize); + } + + private static int calculateLastClusterIndex(double upperBound, double stepSize) { + double doubleIndex = upperBound / stepSize; + int index = (int) doubleIndex; + double fractionalPart = doubleIndex - index; + if (fractionalPart <= 0.0001) { + index--; + } + return index; + } + + @Override + public Cluster getClusterFor(Double valueObject) { + double value = valueObject.doubleValue(); + if (value < lowerBound) { + return hardBounds ? null : getCluster(0); + } + if (value > upperBound) { + return hardBounds ? null : getCluster(lastClusterIndex); + } + + if (Math.abs(upperBound - value) < 0.0001) { + return getCluster(lastClusterIndex); + } + return getCluster((int) (value / stepSize)); + } + + private Cluster getCluster(int index) { + if (!clusterCache.containsKey(index)) { + clusterCache.put(index, createCluster(index)); + } + return clusterCache.get(index); + } + + private Cluster createCluster(int index) { + if (index == 0 && !hardBounds) { + return new ClusterWithSingleBoundary<>(createUpperBound(index, ComparisonStrategy.LOWER_THAN)); + } + if (index == lastClusterIndex && !hardBounds) { + return new ClusterWithSingleBoundary<>(createLowerBound(index, ComparisonStrategy.GREATER_EQUALS_THAN)); + } + + ComparisonStrategy upperComparisonStrategy = index == lastClusterIndex ? ComparisonStrategy.LOWER_EQUALS_THAN : ComparisonStrategy.LOWER_THAN; + return new ClusterWithLowerAndUpperBoundaries<>(createLowerBound(index, ComparisonStrategy.GREATER_EQUALS_THAN), + createUpperBound(index, upperComparisonStrategy)); + } + + private ComparableClusterBoundary createLowerBound(int index, ComparisonStrategy comparisonStrategy) { + return new ComparableClusterBoundary(this.lowerBound + stepSize * index, comparisonStrategy); + } + + private ComparableClusterBoundary createUpperBound(int index, ComparisonStrategy comparisonStrategy) { + return new ComparableClusterBoundary(this.lowerBound + stepSize * (index + 1.0), comparisonStrategy); + } + + @Override + public Class getClusterElementsType() { + return Double.class; + } + + public double getLowerGroupBound() { + return lowerBound; + } + + public double getUpperGroupBound() { + return upperBound; + } + + public double getStepSize() { + return stepSize; + } + + public boolean isHardBounds() { + return hardBounds; + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/ManeuverWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/ManeuverWithContext.java new file mode 100644 index 00000000000..5d1b975b27b --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/ManeuverWithContext.java @@ -0,0 +1,61 @@ +package com.sap.sailing.datamining.impl.data; + +import com.sap.sailing.datamining.data.HasManeuverContext; +import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.ManeuverType; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.domain.tracking.Maneuver; + +public class ManeuverWithContext implements HasManeuverContext { + + private final HasTrackedLegOfCompetitorContext trackedLegOfCompetitor; + private final Maneuver maneuver; + + public ManeuverWithContext(HasTrackedLegOfCompetitorContext trackedLegOfCompetitor, Maneuver maneuver) { + this.trackedLegOfCompetitor = trackedLegOfCompetitor; + this.maneuver = maneuver; + } + + @Override + public HasTrackedLegOfCompetitorContext getTrackedLegOfCompetitorContext() { + return trackedLegOfCompetitor; + } + + @Override + public Maneuver getManeuver() { + return maneuver; + } + + @Override + public ManeuverType getManeuverType() { + return getManeuver().getType(); + } + + @Override + public Tack getTack() { + return getManeuver().getNewTack(); + } + + @Override + public SpeedWithBearing getSpeedBefore() { + return getManeuver().getSpeedWithBearingBefore(); + } + + @Override + public SpeedWithBearing getSpeedAfter() { + return getManeuver().getSpeedWithBearingAfter(); + } + + @Override + public Double getDirectionChangeInDegrees() { + return getManeuver().getDirectionChangeInDegrees(); + } + + @Override + public Distance getManeuverLoss() { + return getManeuver().getManeuverLoss(); + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/MarkPassingWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/MarkPassingWithContext.java index 45fb928efee..442faad5dc8 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/MarkPassingWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/MarkPassingWithContext.java @@ -1,74 +1,87 @@ package com.sap.sailing.datamining.impl.data; import com.sap.sailing.datamining.data.HasMarkPassingContext; -import com.sap.sailing.datamining.data.HasTrackedRaceContext; +import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.common.NoWindException; -import com.sap.sailing.domain.common.Speed; -import com.sap.sailing.domain.tracking.MarkPassing; -import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.common.NauticalSide; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.domain.leaderboard.Leaderboard; +import com.sap.sailing.domain.tracking.MarkPassingManeuver; import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.Util; public class MarkPassingWithContext implements HasMarkPassingContext { - private final HasTrackedRaceContext trackedRaceContext; - private final MarkPassing markPassing; - public MarkPassingWithContext(HasTrackedRaceContext trackedRaceContext, MarkPassing markPassing) { - super(); - this.trackedRaceContext = trackedRaceContext; - this.markPassing = markPassing; + private final HasTrackedLegOfCompetitorContext trackedLegOfCompetitor; + private final MarkPassingManeuver maneuver; + + private Double absoluteRank; + private boolean rankHasBeenInitialized; + + public MarkPassingWithContext(HasTrackedLegOfCompetitorContext trackedLegOfCompetitor, MarkPassingManeuver maneuver) { + this.trackedLegOfCompetitor = trackedLegOfCompetitor; + this.maneuver = maneuver; } @Override - public HasTrackedRaceContext getTrackedRaceContext() { - return trackedRaceContext; + public HasTrackedLegOfCompetitorContext getTrackedLegOfCompetitorContext() { + return trackedLegOfCompetitor; } @Override - public MarkPassing getMarkPassing() { - return markPassing; + public MarkPassingManeuver getManeuver() { + return maneuver; } @Override - public Competitor getCompetitor() { - return markPassing.getCompetitor(); - } - - @Override - public Speed getSpeed() { - return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(getMarkPassing().getTimePoint()); - } - - @Override - public Speed getSpeedTenSecondsBefore() { - return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(getMarkPassing().getTimePoint().minus(10000 /* ms */)); - } - - private TrackedRace getTrackedRace() { - return trackedRaceContext.getTrackedRace(); + public Tack getTack() { + return getManeuver().getNewTack(); } @Override - public String getPreviousLegTypeSignifier() throws NoWindException { - final String result; - TrackedLeg previousLeg = getPreviousLeg(); - if (previousLeg == null) { - result = "Start"; - } else { - result = previousLeg.getLegType(getMarkPassing().getTimePoint()).toString(); - } - return result; + public Waypoint getWaypoint() { + return getManeuver().getWaypointPassed(); + } + + @Override + public NauticalSide getPassingSide() { + return getManeuver().getSide(); } - private TrackedLeg getPreviousLeg() { - final TrackedLeg result; - Waypoint waypoint = markPassing.getWaypoint(); - if (waypoint == getTrackedRace().getRace().getCourse().getFirstWaypoint()) { - result = null; - } else { - result = getTrackedRace().getTrackedLegFinishingAt(waypoint); - } - return result; + @Override + public SpeedWithBearing getSpeedBefore() { + return getManeuver().getSpeedWithBearingBefore(); } + + @Override + public SpeedWithBearing getSpeedAfter() { + return getManeuver().getSpeedWithBearingAfter(); + } + + @Override + public Double getDirectionChangeInDegrees() { + return getManeuver().getDirectionChangeInDegrees(); + } + + @Override + public Double getRelativeRank() { + Leaderboard leaderboard = getTrackedLegOfCompetitorContext().getTrackedLegContext().getTrackedRaceContext().getLeaderboardContext().getLeaderboard(); + double competitorCount = Util.size(leaderboard.getCompetitors()); + return getAbsoluteRank() == null ? null : getAbsoluteRank() / competitorCount; + } + + @Override + public Double getAbsoluteRank() { + if (!rankHasBeenInitialized) { + TrackedRace trackedRace = getTrackedLegOfCompetitorContext().getTrackedLegContext().getTrackedRaceContext().getTrackedRace(); + Competitor competitor = getTrackedLegOfCompetitorContext().getCompetitor(); + int rank = trackedRace.getRank(competitor, getManeuver().getTimePoint()); + absoluteRank = rank == 0 ? null : Double.valueOf(rank); + rankHasBeenInitialized = true; + } + return absoluteRank; + } + } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/PercentageClusterFormatter.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/PercentageClusterFormatter.java new file mode 100644 index 00000000000..9423e734263 --- /dev/null +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/PercentageClusterFormatter.java @@ -0,0 +1,10 @@ +package com.sap.sailing.datamining.impl.data; + +public class PercentageClusterFormatter extends AbstractClusterFormatter { + + @Override + protected String formatValue(Double value) { + return (int) (value * 100) + "%"; + } + +} diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java index b9d8ca50086..82c38d7dad6 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/RaceOfCompetitorWithContext.java @@ -2,18 +2,32 @@ package com.sap.sailing.datamining.impl.data; import java.util.concurrent.TimeUnit; +import com.sap.sailing.datamining.Activator; +import com.sap.sailing.datamining.SailingClusterGroups; import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext; import com.sap.sailing.datamining.data.HasTrackedRaceContext; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.ManeuverType; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.Speed; +import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.domain.common.tracking.GPSFix; +import com.sap.sailing.domain.common.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.LineDetails; import com.sap.sailing.domain.tracking.Maneuver; +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.WindPositionMode; import com.sap.sse.common.TimePoint; +import com.sap.sse.datamining.data.Cluster; +import com.sap.sse.datamining.shared.impl.dto.ClusterDTO; public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext { @@ -38,12 +52,89 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext { public Competitor getCompetitor() { return competitor; } + + @Override + public String getCompetitorSearchTag() { + return getCompetitor().getSearchTag(); + } + + @Override + public Tack getTackAtStart() throws NoWindException { + TimePoint startOfRace = getTrackedRace().getStartOfRace(); + return startOfRace == null ? null : getTrackedRace().getTack(getCompetitor(), startOfRace); + } + + @Override + public ClusterDTO getPercentageClusterForDistanceToStarboardSideAtStart() { + Double normalizedDistance = getNormalizedDistanceToStarboardSideAtStart(); + if (normalizedDistance == null) { + return null; + } + + SailingClusterGroups clusterGroups = Activator.getClusterGroups(); + Cluster cluster = clusterGroups.getPercentageClusterGroup().getClusterFor(normalizedDistance); + return new ClusterDTO(clusterGroups.getPercentageClusterFormatter().format(cluster)); + } @Override public Distance getDistanceToStartLineAtStart() { return getTrackedRace().getDistanceToStartLine(getCompetitor(), 0); } + @Override + public Double getNormalizedDistanceToStarboardSideAtStart() { + TrackedRace trackedRace = getTrackedRace(); + TrackedLegOfCompetitor firstTrackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, trackedRace.getRace().getCourse().getFirstLeg()); + TimePoint competitorStartTime = firstTrackedLegOfCompetitor.getStartTime(); + if (competitorStartTime == null) { + return null; + } + + Double distance = trackedRace.getDistanceFromStarboardSideOfStartLine(getCompetitor(), competitorStartTime).getMeters(); + Double length = trackedRace.getStartLine(competitorStartTime).getLength().getMeters(); + return distance / length; + } + + @Override + public Distance getWindwardDistanceToAdvantageousLineEndAtStart() { + TrackedRace trackedRace = getTrackedRace(); + TimePoint startOfRace = trackedRace.getStartOfRace(); + LineDetails startLine = trackedRace.getStartLine(startOfRace); + Mark advantageousMark = null; + switch (startLine.getAdvantageousSideWhileApproachingLine()) { + case PORT: + advantageousMark = startLine.getPortMarkWhileApproachingLine(); + break; + case STARBOARD: + advantageousMark = startLine.getStarboardMarkWhileApproachingLine(); + break; + } + if (advantageousMark == null) { + return null; + } + + GPSFixTrack advantageousMarkTrack = trackedRace.getOrCreateTrack(advantageousMark); + Position advantageousMarkPosition = advantageousMarkTrack.getEstimatedPosition(startOfRace, false); + GPSFixTrack competitorTrack = trackedRace.getTrack(getCompetitor()); + Position competitorPosition = competitorTrack.getEstimatedPosition(startOfRace, false); + + TrackedLeg trackedLeg = trackedRace.getTrackedLeg(trackedRace.getRace().getCourse().getFirstLeg()); + Distance distance = trackedLeg.getWindwardDistance(competitorPosition, advantageousMarkPosition, startOfRace, WindPositionMode.LEG_MIDDLE); + return distance; + } + + @Override + public ClusterDTO getPercentageClusterForRelativeScore() { + Double relativeScore = getTrackedRaceContext().getRelativeScoreForCompetitor(getCompetitor()); + if (relativeScore == null) { + return null; + } + + SailingClusterGroups clusterGroups = Activator.getClusterGroups(); + Cluster cluster = clusterGroups.getPercentageClusterGroup().getClusterFor(relativeScore); + return new ClusterDTO(clusterGroups.getPercentageClusterFormatter().format(cluster)); + } + @Override public Speed getSpeedWhenStarting() { return getTrackedRace().getSpeedWhenCrossingStartLine(getCompetitor()); @@ -54,14 +145,57 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext { return getTrackedRace().getSpeed(getCompetitor(), TimeUnit.SECONDS.toMillis(10)); } + @Override + public Speed getSpeedTenSecondsAfterStart() { + TimePoint startOfRace = getTrackedRace().getStartOfRace(); + if (startOfRace == null) { + return null; + } + return getTrackedRace().getTrack(getCompetitor()).getEstimatedSpeed(startOfRace.plus(TimeUnit.SECONDS.toMillis(10))); + } + + @Override + public Double getRankThirtySecondsAfterStart() { + TimePoint startOfRace = getTrackedRace().getStartOfRace(); + if (startOfRace == null) { + return null; + } + + int rank = getTrackedRace().getRank(getCompetitor(), startOfRace.plus(TimeUnit.SECONDS.toMillis(30))); + return rank == 0 ? null : Double.valueOf(rank); + } + + @Override + public Double getRankAfterHalfOfTheFirstLeg() { + Course course = getTrackedRace().getRace().getCourse(); + TrackedLegOfCompetitor trackedLeg = getTrackedRace().getTrackedLeg(getCompetitor(), course.getFirstLeg()); + TimePoint startTime = trackedLeg.getStartTime(); + TimePoint finishTime = trackedLeg.getFinishTime(); + if (startTime == null || finishTime == null) { + return null; + } + + long halfOffset = (finishTime.asMillis() - startTime.asMillis()) / 2; + int rank = getTrackedRace().getRank(getCompetitor(), startTime.plus(halfOffset)); + return rank == 0 ? null : Double.valueOf(rank); + } + @Override public Double getRankAtFirstMark() { Course course = getTrackedRace().getRace().getCourse(); Waypoint firstMark = course.getFirstLeg().getTo(); Competitor competitor = getCompetitor(); - return Double.valueOf(getTrackedRace().getRank(competitor, getTrackedRace().getMarkPassing(competitor, firstMark).getTimePoint())); + int rank = getTrackedRace().getRank(competitor, getTrackedRace().getMarkPassing(competitor, firstMark).getTimePoint()); + return rank == 0 ? null : Double.valueOf(rank); } + @Override + public Double getRankGainsOrLossesBetweenFirstMarkAndFinish() { + Double rankAtFirstMark = getRankAtFirstMark(); + Double rankAtFinish = getTrackedRaceContext().getRankAtFinishForCompetitor(getCompetitor()); + return rankAtFirstMark != null && rankAtFinish != null ? rankAtFirstMark - rankAtFinish : null; + } + @Override public int getNumberOfManeuvers() { return getNumberOfTacks() + getNumberOfJibes(); @@ -92,26 +226,5 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext { } return number; } - - @Override - public Double getRankGainsOrLossesBetweenFirstMarkAndFinish() { - Double rankAtFirstMark = getRankAtFirstMark(); - Double rankAtFinish = getRankAtFinish(); - return rankAtFirstMark - rankAtFinish; - } - - private Double getRankAtFinish() { - return Double.valueOf(getTrackedRace().getRank(getCompetitor(), getTrackedRace().getEndOfTracking())); - } - - @Override - public Double getNormalizedDistanceToStarboardSideAtStart() { - TrackedRace trackedRace = getTrackedRace(); - TrackedLegOfCompetitor firstTrackedLegOfCompetitor = trackedRace.getTrackedLeg(competitor, trackedRace.getRace().getCourse().getFirstLeg()); - TimePoint competitorStartTime = firstTrackedLegOfCompetitor.getStartTime(); - Double distance = trackedRace.getDistanceFromStarboardSideOfStartLine(getCompetitor(), competitorStartTime).getMeters(); - Double length = trackedRace.getStartLine(competitorStartTime).getLength().getMeters(); - return distance / length; - } } \ No newline at end of file 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 964b04bdfce..8fe8d82efc5 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 @@ -62,17 +62,17 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti } @Override - public double getRelativeRank() { - Leaderboard leaderboard = getLeaderboard(); - final TimePoint now = MillisecondsTimePoint.now(); - double competitorCount = Util.size(leaderboard.getCompetitors()); - double points = leaderboard.getTotalPoints(competitor, raceColumn, now); - double relativeLowPoints = leaderboard.getScoringScheme().isHigherBetter() ? - competitorCount - points : points; - final double result = relativeLowPoints / competitorCount; + public String getRegattaName() { + Leaderboard leaderboard = getLeaderboard();; + final String result = leaderboard.getName(); return result; } + @Override + public String getCompetitorSearchTag() { + return getCompetitor().getSearchTag(); + } + @Override public int getAverageWindSpeedInRoundedBeaufort() { Speed exactResult = getAverageWindSpeed(); @@ -157,10 +157,23 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti } @Override - public String getRegattaName() { - Leaderboard leaderboard = getLeaderboard();; - final String result = leaderboard.getName(); - return result; + public Double getRelativeRank() { + Leaderboard leaderboard = getLeaderboard(); + final TimePoint now = MillisecondsTimePoint.now(); + double competitorCount = Util.size(leaderboard.getCompetitors()); + Double points = leaderboard.getTotalPoints(competitor, raceColumn, now); + if (points != null) { + double relativeLowPoints = leaderboard.getScoringScheme().isHigherBetter() ? competitorCount - points + : points; + final double result = relativeLowPoints / competitorCount; + return result; + } + return null; + } + + @Override + public Double getAbsoluteRank() { + return getLeaderboard().getTotalPoints(competitor, raceColumn, MillisecondsTimePoint.now()); } @Override 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 5df1a9c689b..09e719dbe9d 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 @@ -1,12 +1,18 @@ package com.sap.sailing.datamining.impl.data; +import com.sap.sailing.datamining.Activator; +import com.sap.sailing.datamining.SailingClusterGroups; import com.sap.sailing.datamining.data.HasTrackedLegContext; import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.leaderboard.Leaderboard; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.datamining.data.Cluster; +import com.sap.sse.datamining.shared.impl.dto.ClusterDTO; public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompetitorContext { @@ -15,6 +21,11 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti private final TrackedLegOfCompetitor trackedLegOfCompetitor; private final Competitor competitor; + private Double rankAtStart; + private boolean isRankAtStartInitialized; + private Double rankAtFinish; + private boolean isRankAtFinishInitialized; + public TrackedLegOfCompetitorWithContext(HasTrackedLegContext trackedLegContext, TrackedLegOfCompetitor trackedLegOfCompetitor) { this.trackedLegContext = trackedLegContext; this.trackedLegOfCompetitor = trackedLegOfCompetitor; @@ -35,6 +46,23 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti public Competitor getCompetitor() { return competitor; } + + @Override + public String getCompetitorSearchTag() { + return getCompetitor().getSearchTag(); + } + + @Override + public ClusterDTO getPercentageClusterForRelativeScoreInRace() { + Double relativeScore = getTrackedLegContext().getTrackedRaceContext().getRelativeScoreForCompetitor(getCompetitor()); + if (relativeScore == null) { + return null; + } + + SailingClusterGroups clusterGroups = Activator.getClusterGroups(); + Cluster cluster = clusterGroups.getPercentageClusterGroup().getClusterFor(relativeScore); + return new ClusterDTO(clusterGroups.getPercentageClusterFormatter().format(cluster)); + } @Override public Distance getDistanceTraveled() { @@ -44,10 +72,53 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti @Override public Double getRankGainsOrLosses() { - TrackedRace trackedRace = getTrackedLegContext().getTrackedRaceContext().getTrackedRace(); - Double rankAtStart = Double.valueOf(trackedRace.getRank(getCompetitor(), getTrackedLegOfCompetitor().getStartTime())); - Double rankAtFinish = Double.valueOf(trackedRace.getRank(getCompetitor(), getTrackedLegOfCompetitor().getFinishTime())); - return rankAtStart - rankAtFinish; + Double rankAtStart = getRankAtStart(); + Double rankAtFinish = getRankAtFinish(); + return rankAtStart != null && rankAtFinish != null ? rankAtStart - rankAtFinish : null; + } + + private Double getRankAtStart() { + if (!isRankAtStartInitialized) { + TrackedRace trackedRace = getTrackedLegContext().getTrackedRaceContext().getTrackedRace(); + int rank = trackedRace.getRank(getCompetitor(), getTrackedLegOfCompetitor().getStartTime()); + rankAtStart = rank == 0 ? null : Double.valueOf(rank); + isRankAtStartInitialized = true; + } + return rankAtStart; } + @Override + public Double getRelativeRank() { + Leaderboard leaderboard = getTrackedLegContext().getTrackedRaceContext().getLeaderboardContext().getLeaderboard(); + double competitorCount = Util.size(leaderboard.getCompetitors()); + Double rankAtFinish = getRankAtFinish(); + return rankAtFinish == null ? null : rankAtFinish / competitorCount; + } + + @Override + public Double getAbsoluteRank() { + return getRankAtFinish(); + } + + private Double getRankAtFinish() { + if (!isRankAtFinishInitialized) { + TrackedRace trackedRace = getTrackedLegContext().getTrackedRaceContext().getTrackedRace(); + int rank = trackedRace.getRank(getCompetitor(), getTrackedLegOfCompetitor().getFinishTime()); + rankAtFinish = rank == 0 ? null : Double.valueOf(rank); + isRankAtFinishInitialized = true; + } + return rankAtFinish; + } + + @Override + public Long getTimeTakenInSeconds() { + TimePoint startTime = getTrackedLegOfCompetitor().getStartTime(); + TimePoint finishTime = getTrackedLegOfCompetitor().getFinishTime(); + if (startTime == null || finishTime == null) { + return null; + } + + return (finishTime.asMillis() - startTime.asMillis()) / 1000; + } + } diff --git a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java index d899829ec8d..49409d39887 100644 --- a/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java +++ b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/impl/data/TrackedRaceWithContext.java @@ -11,9 +11,11 @@ import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.common.NauticalSide; import com.sap.sailing.domain.common.tracking.GPSFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.LineDetails; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; @@ -90,6 +92,17 @@ public class TrackedRaceWithContext implements HasTrackedRaceContext { calendar.setTime(time.asDate()); return calendar.get(Calendar.YEAR); } + + @Override + public NauticalSide getAdvantageousEndOfLine() { + LineDetails startLine = getTrackedRace().getStartLine(getTrackedRace().getStartOfRace()); + return startLine.getAdvantageousSideWhileApproachingLine(); + } + + @Override + public Boolean isMedalRace() { + return getLeaderboardContext().getLeaderboard().getRaceColumnAndFleet(getTrackedRace()).getA().isMedalRace(); + } @Override public Boolean isTracked() { @@ -125,5 +138,20 @@ public class TrackedRaceWithContext implements HasTrackedRaceContext { } return number; } + + // Convenience methods for race dependent calculation to avoid code duplication + public Double getRelativeScoreForCompetitor(Competitor competitor) { + Double rankAtFinish = getRankAtFinishForCompetitor(competitor); + if (rankAtFinish == null) { + return null; + } + return rankAtFinish / Util.size(getTrackedRace().getRace().getCompetitors()); + } + + @Override + public Double getRankAtFinishForCompetitor(Competitor competitor) { + int rank = getTrackedRace().getRank(competitor, getTrackedRace().getEndOfTracking()); + return rank == 0 ? null : Double.valueOf(rank); + } } \ No newline at end of file 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 57777951acf..0bf539a6b6a 100644 --- 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 @@ -113,20 +113,20 @@ public enum BoatClassMasterdata { private final String displayName; private final String[] alternativeNames; - private final double hullLengthInMeter; - private final double hullBeamInMeter; + private final double hullLengthInMeters; + private final double hullBeamInMeters; private final BoatHullType hullType; private final boolean typicallyStartsUpwind; private final boolean hasAdditionalDownwindSail; private static Map fromUnifiedDisplayAndAlternativeNamesToBoatClassMasterdata; - private BoatClassMasterdata(String displayName, boolean typicallyStartsUpwind, double hullLengthInMeter, - double hullBeamInMeter, BoatHullType hullType, boolean hasAdditionalDownwindSail, String... alternativeNames) { + private BoatClassMasterdata(String displayName, boolean typicallyStartsUpwind, double hullLengthInMeters, + double hullBeamInMeters, BoatHullType hullType, boolean hasAdditionalDownwindSail, String... alternativeNames) { this.displayName = displayName; this.typicallyStartsUpwind = typicallyStartsUpwind; - this.hullLengthInMeter = hullLengthInMeter; - this.hullBeamInMeter = hullBeamInMeter; + this.hullLengthInMeters = hullLengthInMeters; + this.hullBeamInMeters = hullBeamInMeters; this.hullType = hullType; this.hasAdditionalDownwindSail = hasAdditionalDownwindSail; this.alternativeNames = alternativeNames; @@ -147,8 +147,8 @@ public enum BoatClassMasterdata { double hullBeamInMeter, BoatHullType hullType, boolean hasAdditionalDownwindSail) { this.displayName = displayName; this.typicallyStartsUpwind = typicallyStartsUpwind; - this.hullLengthInMeter = hullLengthInMeter; - this.hullBeamInMeter = hullBeamInMeter; + this.hullLengthInMeters = hullLengthInMeter; + this.hullBeamInMeters = hullBeamInMeter; this.hullType = hullType; this.hasAdditionalDownwindSail = hasAdditionalDownwindSail; this.alternativeNames = null; @@ -192,7 +192,7 @@ public enum BoatClassMasterdata { } public Distance getHullLength() { - return new MeterDistance(hullLengthInMeter); + return new MeterDistance(hullLengthInMeters); } public String getDisplayName() { @@ -204,7 +204,7 @@ public enum BoatClassMasterdata { } public Distance getHullBeam() { - return new MeterDistance(hullBeamInMeter); + return new MeterDistance(hullBeamInMeters); } public BoatHullType getHullType() { diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Distance.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Distance.java index ddf0678164e..dda8d69f82f 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Distance.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Distance.java @@ -13,7 +13,7 @@ import com.sap.sse.common.Duration; * */ public interface Distance extends Comparable, Serializable { - static final Distance NULL = new Distance() { + static class NullDistance implements Distance { private static final long serialVersionUID = -3167560884686340893L; @Override @@ -96,7 +96,9 @@ public interface Distance extends Comparable, Serializable { public Distance abs() { return this; } - }; + } + + static final NullDistance NULL = new NullDistance(); double getGeographicalMiles(); diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/RegattaName.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/RegattaName.java index 536838c1895..dfccefc8dd8 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/RegattaName.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/RegattaName.java @@ -17,6 +17,11 @@ public class RegattaName implements RegattaIdentifier { return regattaName; } + @Override + public String toString() { + return getRegattaName(); + } + @Override public Object getRegatta(RegattaFetcher regattaFetcher) { return regattaFetcher.getRegatta(this); diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BoatClassDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BoatClassDTO.java index 4282ada013f..b4e6fdc1cd4 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BoatClassDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/BoatClassDTO.java @@ -2,6 +2,8 @@ package com.sap.sailing.domain.common.dto; import java.io.Serializable; +import com.sap.sailing.domain.common.Distance; + public class BoatClassDTO extends NamedDTO implements Serializable, Comparable { private static final long serialVersionUID = 1981789833769906676L; @@ -11,24 +13,24 @@ public class BoatClassDTO extends NamedDTO implements Serializable, Comparable singletonList(series), false, - new HighPoint(), UUID.randomUUID(), /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + new HighPoint(), UUID.randomUUID(), /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); series.addRaceColumn(columnName, /* trackedRegattaRegistry */null); leaderboard = service.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "RegattaLeaderboard", new int[] {}); adapter = RaceLogTrackingAdapterFactory.INSTANCE.getAdapter(DomainFactory.INSTANCE); 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 0fe9590a085..0d5f03e9a81 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 @@ -49,9 +49,11 @@ 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.Distance; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata; import com.sap.sailing.domain.common.tracking.DoubleVectorFix; import com.sap.sailing.domain.common.tracking.GPSFixMoving; @@ -95,9 +97,9 @@ public class SensorFixStoreAndLoadTest { private static final long FIX_TIMESTAMP2 = 120; private static final long FIX_TIMESTAMP3 = 210; private static final long AFTER_LAST_FIX = FIX_TIMESTAMP3 + 1; - private static final double FIX_RIDE_HEIGHT = 1337.0; - private static final double FIX_RIDE_HEIGHT2 = 1338.0; - private static final double FIX_RIDE_HEIGHT3 = 1336.0; + private static final Distance FIX_RIDE_HEIGHT = new MeterDistance(1337.0); + private static final Distance FIX_RIDE_HEIGHT2 = new MeterDistance(1338.0); + private static final Distance FIX_RIDE_HEIGHT3 = new MeterDistance(1336.0); private static final double FIX_TEST_VALUE = 12.0; protected final MockSmartphoneImeiServiceFinderFactory serviceFinderFactory = new MockSmartphoneImeiServiceFinderFactory(); protected final DeviceIdentifier device = new SmartphoneImeiIdentifier("a"); @@ -384,20 +386,14 @@ public class SensorFixStoreAndLoadTest { public void testBravoFixIsCorrectlyWrapped() 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(); - BravoFixTrack bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME); assertEquals(FIX_RIDE_HEIGHT, bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getRideHeight()); - fixLoaderAndTracker.stop(true); } @@ -482,9 +478,9 @@ public class SensorFixStoreAndLoadTest { } private void addBravoFixes() { - store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP, FIX_RIDE_HEIGHT)); - store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2)); - store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP3, FIX_RIDE_HEIGHT3)); + store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP, FIX_RIDE_HEIGHT.getMeters())); + store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2.getMeters())); + store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP3, FIX_RIDE_HEIGHT3.getMeters())); } private FixLoaderAndTracker createFixLoaderAndTracker() { 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 5a4d89e2f8b..bba44934b60 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 @@ -250,7 +250,7 @@ public class TransientCompetitorStoreImpl implements CompetitorStore, Serializab c.getFlagImage() == null ? null : c.getFlagImage().toString(), new BoatDTO(c.getBoat().getName(), c.getBoat().getSailID(), c.getBoat().getColor()), new BoatClassDTO(c.getBoat().getBoatClass() - .getName(), c.getBoat().getBoatClass().getDisplayName(), c.getBoat().getBoatClass().getHullLength().getMeters()), + .getName(), c.getBoat().getBoatClass().getDisplayName(), c.getBoat().getBoatClass().getHullLength()), c.getTimeOnTimeFactor(), c.getTimeOnDistanceAllowancePerNauticalMile(), c.getSearchTag()); weakCompetitorDTOCache.put(c, competitorDTO); } 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 feb5b68187a..080fde075c1 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 @@ -35,6 +35,7 @@ import com.sap.sailing.domain.common.dto.LeaderboardRowDTO; import com.sap.sailing.domain.common.dto.LegEntryDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.test.StoredTrackBasedTest; import com.sap.sse.common.Cloner; import com.sap.sse.common.Color; @@ -234,7 +235,7 @@ public class LeaderboardDTODiffingTest { public void testCompetitorListChange() { newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "912p09871203987", - /* imageURL */ null, /* flagImageURL */ null, new BoatDTO("LSC", "GER 1234"), new BoatClassDTO("505", 5.05), /* timeOnTimeFactor */ null, + /* imageURL */ null, /* flagImageURL */ null, new BoatDTO("LSC", "GER 1234"), new BoatClassDTO("505", new MeterDistance(5.05)), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null); newVersion.competitors.add(13, somebodyNew); // insert a competitor; this should mess up all others' indexes; check if this works CompetitorDTO wolfgang = getPreviousCompetitorByName("HUNGER +JESS"); @@ -255,7 +256,7 @@ public class LeaderboardDTODiffingTest { newVersion.setSuppressedCompetitors(newSuppressedCompetitors); newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "912p09871203987", - /* imageURL */ null, /* flagImageURL */ null, new BoatDTO("LSC", "GER 1234"), new BoatClassDTO("505", 5.05), /* timeOnTimeFactor */ null, + /* imageURL */ null, /* flagImageURL */ null, new BoatDTO("LSC", "GER 1234"), new BoatClassDTO("505", new MeterDistance(5.05)), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null); newVersion.setSuppressed(newVersion.competitors.get(13), true); // suppress an existing competitor; compaction should reduce this to a single number only newVersion.setSuppressed(somebodyNew, true); // check that mixed mode with existing and new competitors works as well @@ -278,7 +279,7 @@ public class LeaderboardDTODiffingTest { public void testDisplayNameChange() { newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "912p09871203987", - /* imageURL */ null, /* flagImageURL */ null, new BoatDTO("LSC", "GER 1234"), new BoatClassDTO("505", 5.05), /* timeOnTimeFactor */ null, + /* imageURL */ null, /* flagImageURL */ null, new BoatDTO("LSC", "GER 1234"), new BoatClassDTO("505", new MeterDistance(5.05)), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null); newVersion.competitors.add(somebodyNew); newVersion.competitorDisplayNames = new HashMap(newVersion.competitorDisplayNames); @@ -308,7 +309,7 @@ public class LeaderboardDTODiffingTest { newVersion.setCompetitorsFromBestToWorst(r9, newOrdering); newVersion.competitors = new ArrayList(newVersion.competitors); // clone competitor list so it's not identical to that of previous version CompetitorDTO somebodyNew = new CompetitorDTOImpl("Someone New", Color.RED, "someone@nobody.de", "DE", "GER", "Germany", "912p09871203987", - /* imageURL */ null, /* flagImageURL */ null, new BoatDTO("LSC", "GER 1234"), new BoatClassDTO("505", 5.05), /* timeOnTimeFactor */ null, + /* imageURL */ null, /* flagImageURL */ null, new BoatDTO("LSC", "GER 1234"), new BoatClassDTO("505", new MeterDistance(5.05)), /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null); newVersion.competitors.add(somebodyNew); newOrdering.add(somebodyNew); 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 8302305296e..1d76669219e 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 @@ -14,6 +14,7 @@ import com.sap.sailing.domain.common.SpeedWithBearing; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.common.impl.NauticalMileDistance; import com.sap.sailing.domain.common.tracking.impl.CompactGPSFixImpl; import com.sap.sailing.domain.common.tracking.impl.CompactGPSFixMovingImpl; @@ -67,6 +68,12 @@ public class PositionTest { assertEquals(20004, Math.abs(southPole.getDistance(northPole).getKilometers()), 0.0001); } + @Test + public void radTest() { + assertEquals(180, new MeterDistance(20005000).getCentralAngleDeg(), 0.01); + assertEquals(Math.PI, new MeterDistance(20005000).getCentralAngleRad(), 0.01); + } + @Test public void bearingTest() { // Note that the bearing is a bearing on a great circle! 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 faedaf6d37e..2f5c7bf7de5 100644 --- 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 @@ -374,6 +374,27 @@ public class TrackTest { getB().getKnots(), 0.001); } + @Test + public void testMaxSpeedForFixOverwrittenWithLowerSpeed() { + DynamicGPSFixMovingTrackImpl track = new DynamicGPSFixMovingTrackImpl(new Object(), /* millisecondsOverWhichToAverage */ 30000l); + GPSFixMoving slow = new GPSFixMovingImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(3600000), new KnotSpeedWithBearingImpl( + 1, new DegreeBearingImpl(123))); + GPSFixMoving fast = new GPSFixMovingImpl(new DegreePosition(0, 0), new MillisecondsTimePoint(3600000), new KnotSpeedWithBearingImpl( + 2, new DegreeBearingImpl(123))); + track.addGPSFix(fast); + assertEquals(2., track.getMaximumSpeedOverGround(new MillisecondsTimePoint(0), new MillisecondsTimePoint(10800000)). + getB().getKnots(), 0.001); + track.addGPSFix(slow); + track.lockForRead(); + try { + assertEquals(1, track.getFixes().size()); + } finally { + track.unlockAfterRead(); + } + assertEquals(1., track.getMaximumSpeedOverGround(new MillisecondsTimePoint(0), new MillisecondsTimePoint(10800000)). + getB().getKnots(), 0.001); + } + @Test public void testMaxSpeedForMovingTrackWithFixAtIntervalBoundaryAfterQuery() { DynamicGPSFixMovingTrackImpl track = new DynamicGPSFixMovingTrackImpl(new Object(), /* millisecondsOverWhichToAverage */ 30000l); 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 be7078afd44..23b7ebb5e21 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 @@ -447,6 +447,15 @@ public class MockedTrackedRace implements DynamicTrackedRace { } + @Override + public Double getBuoyZoneRadiusInHullLengths(){ + return 1.0; + } + + @Override + public void setBuoyZoneRadiusInHullLengths(Double buoyZoneRadiusInHullLengths){ + } + @Override public RegattaLikeIdentifier getRegattaLikeIdentifier() { return null; @@ -532,15 +541,12 @@ public class MockedTrackedRace implements DynamicTrackedRace { @Override public boolean isControlTrackingFromStartAndFinishTimes() { - // TODO Auto-generated method stub return false; } @Override public void setControlTrackingFromStartAndFinishTimes( boolean controlTrackingFromStartAndFinishTimes) { - // TODO Auto-generated method stub - } }; } @@ -1121,4 +1127,14 @@ public class MockedTrackedRace implements DynamicTrackedRace { public Iterable getAttachedRaceLogs() { return Collections.emptySet(); } + + @Override + public NavigableSet getMarkPassings(Competitor competitor, boolean waitForLatestUpdates) { + return null; + } + + @Override + public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint) { + 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 fefad810f2b..9f596ddc595 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 @@ -681,4 +681,14 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { public Iterable getAttachedRaceLogs() { return Collections.emptySet(); } + + @Override + public NavigableSet getMarkPassings(Competitor competitor, boolean waitForLatestUpdates) { + return null; + } + + @Override + public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint) { + return null; + } } 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 2a0f19e5877..0ab5678b885 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 @@ -82,8 +82,8 @@ public class BravoFixTrackSerializationTest { assertRideHeight(track, testFixData.timePoint, testFixData.rideHeight); } - private void assertRideHeight(BravoFixTrack track, TimePoint timePoint, double expected) { - assertEquals(expected, track.getRideHeight(timePoint), 0.0); + private void assertRideHeight(BravoFixTrack track, TimePoint timePoint, double expectedRideHeightInMeters) { + assertEquals(expectedRideHeightInMeters, track.getRideHeight(timePoint).getMeters(), 0.0); } @SuppressWarnings("unchecked") 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 7baa54ce040..43ee61255ca 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 @@ -24,6 +24,9 @@ import com.sap.sse.common.TimePoint; * */ public interface Regatta extends NamedWithID, IsRegattaLike, HasRaceColumnsAndRegattaLike { + + public static final double DEFAULT_BUOY_ZONE_RADIUS_IN_HULL_LENGTHS = 2; + ScoringScheme getScoringScheme(); /** @@ -155,6 +158,14 @@ public interface Regatta extends NamedWithID, IsRegattaLike, HasRaceColumnsAndRe RegattaAndRaceIdentifier getRaceIdentifier(RaceDefinition race); + /** + * Define the value which would be multipled by hull length from {@link BoatClass}. + * Next the calculated value {@link RegattaUtil} would be used to fill out radius of buoy on race map setting. + */ + public Double getBuoyZoneRadiusInHullLengths(); + + public void setBuoyZoneRadiusInHullLengths(Double buoyZoneRadiusInHullLengths); + /** * When there is no race committee app in place and no operator is managing the race start times for this regatta, * start times can optionally be inferred from the start mark passings by the {@link TrackedRace}s in this regatta. 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 ec26712ea67..161ac53a3e7 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 @@ -115,6 +115,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene private transient RaceLogStore raceLogStore; private final IsRegattaLike regattaLikeHelper; private final RankingMetricConstructor rankingMetricConstructor; + private Double buoyZoneRadiusInHullLengths; private CourseArea defaultCourseArea; private RegattaConfiguration configuration; @@ -162,7 +163,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene Iterable series, boolean persistent, ScoringScheme scoringScheme, Serializable id, CourseArea courseArea, RankingMetricConstructor rankingMetricConstructor) { this(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, name, boatClass, startDate, endDate, series, - persistent, scoringScheme, id, courseArea, /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, + persistent, scoringScheme, id, courseArea, 0.0, /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, rankingMetricConstructor); } @@ -204,7 +205,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene /* isMedal */false, /* isFleetsCanRunInParallel */ true, Collections .singletonList(new FleetImpl(LeaderboardNameConstants.DEFAULT_FLEET_NAME)), /* race column names */new ArrayList(), trackedRegattaRegistry)), /* persistent */false, - scoringScheme, id, courseArea, /* useStartTimeInference */true, controlTrackingFromStartAndFinishTimes, + scoringScheme, id, courseArea, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */true, controlTrackingFromStartAndFinishTimes, rankingMetricConstructor); } @@ -215,7 +216,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene */ public RegattaImpl(RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, String name, BoatClass boatClass, TimePoint startDate, TimePoint endDate, Iterable series, boolean persistent, - ScoringScheme scoringScheme, Serializable id, CourseArea courseArea, boolean useStartTimeInference, + ScoringScheme scoringScheme, Serializable id, CourseArea courseArea, Double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes, RankingMetricConstructor rankingMetricConstructor) { super(name); this.rankingMetricConstructor = rankingMetricConstructor; @@ -241,6 +242,7 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene this.scoringScheme = scoringScheme; this.defaultCourseArea = courseArea; this.configuration = null; + this.buoyZoneRadiusInHullLengths = buoyZoneRadiusInHullLengths; this.regattaLikeHelper = new BaseRegattaLikeImpl(new RegattaAsRegattaLikeIdentifier(this), regattaLogStore) { private static final long serialVersionUID = 8546222568682770206L; @@ -572,6 +574,16 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene this.endDate = endDate; } + @Override + public Double getBuoyZoneRadiusInHullLengths() { + return buoyZoneRadiusInHullLengths; + } + + @Override + public void setBuoyZoneRadiusInHullLengths(Double buoyZoneRadiusInHullLengths) { + this.buoyZoneRadiusInHullLengths = buoyZoneRadiusInHullLengths; + } + @Override public CourseArea getDefaultCourseArea() { return defaultCourseArea; 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 083e5915d18..471d4f5ff89 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 @@ -1437,6 +1437,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race } else { entryDTO.timeSinceLastPositionFixInSeconds = null; } + final Distance averageRideHeight = trackedRace.getAverageRideHeight(competitor, timePoint); + entryDTO.averageRideHeightInMeters = averageRideHeight == null ? null : averageRideHeight.getMeters(); } if (addLegDetails && trackedRace != null) { try { @@ -1721,11 +1723,13 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race Double speedOverGroundInKnots; if (trackedLeg.hasFinishedLeg(timePoint)) { speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); - result.currentRideHeightInMeters = trackedLeg.getAverageRideHeight(timePoint); + final Distance averageRideHeight = trackedLeg.getAverageRideHeight(timePoint); + result.currentRideHeightInMeters = averageRideHeight == null ? null : averageRideHeight.getMeters(); } else { final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint); speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots(); - result.currentRideHeightInMeters = trackedLeg.getRideHeight(timePoint); + final Distance rideHeight = trackedLeg.getRideHeight(timePoint); + result.currentRideHeightInMeters = rideHeight == null ? null : rideHeight.getMeters(); } result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots; Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java index 32f29af56d5..8539ef86d47 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java @@ -26,6 +26,8 @@ import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.Util.Triple; import com.sap.sse.common.util.IntHolder; +import com.sap.sse.concurrent.LockUtil; +import com.sap.sse.concurrent.NamedReentrantReadWriteLock; import com.sap.sse.util.ThreadPoolUtil; /** @@ -60,14 +62,16 @@ public class MarkPassingCalculator { * started and then terminated again. */ private final Thread listenerThread; + + private final Listen listen; /** * Synchronized using the {@link #listenerThread} as monitor object. */ private boolean listenerThreadStarted; - public MarkPassingCalculator(DynamicTrackedRace race, boolean listen, boolean waitForInitialMarkPassingCalculation) { - if (listen) { + public MarkPassingCalculator(DynamicTrackedRace race, boolean doListen, boolean waitForInitialMarkPassingCalculation) { + if (doListen) { listener = new MarkPassingUpdateListener(race); } else { listener = null; @@ -75,10 +79,12 @@ public class MarkPassingCalculator { this.race = race; finder = new CandidateFinderImpl(race, executor); chooser = new CandidateChooserImpl(race); - if (listen) { - listenerThread = new Thread(new Listen(race.getRace().getName()), "MarkPassingCalculator for race " + race.getRace().getName()); + if (doListen) { + listen = new Listen(race.getRace().getName()); + listenerThread = new Thread(listen, "MarkPassingCalculator for race " + race.getRace().getName()); } else { listenerThread = null; + listen = null; } Thread t = new Thread(() -> { final Set> tasks = new HashSet<>(); @@ -94,7 +100,7 @@ public class MarkPassingCalculator { } catch (Exception e) { logger.log(Level.SEVERE, "Error trying to compute initial set of mark passings for race "+race.getRace().getName(), e); } - if (listen) { + if (doListen) { listenerThread.setDaemon(true); listenerThread.start(); synchronized (listenerThread) { @@ -131,6 +137,28 @@ public class MarkPassingCalculator { } } + /** + * It's used for locking the calculation thread for read. You will be blocked if the calculation is in progress. + * Make sure that you unlock the thread with the method {@link MarkPassingCalculator#unlockForRead()} once you don't + * need it any more, using a {@code finally} clause to ensure unlocking even with abnormal termination. + */ + public void lockForRead() { + if (listen != null) { + listen.lockForRead(); + } + } + + /** + * Unlocks the calculation thread for read. + * + * @see #lockForRead + */ + public void unlockForRead() { + if (listen != null) { + listen.unlockAfterRead(); + } + } + /** * Waits until an object is in the queue, then drains it entirely. After that the information is sorted depending on * whether it is a fix, an updated waypoint or an updated fixed markpassing. Finally, if suspended is @@ -143,8 +171,25 @@ public class MarkPassingCalculator { private class Listen implements Runnable { private final String raceName; + /** + * The write lock is being held by the thread running this {@link Runnable} after successfully having + * waited for the next update to be pushed to the {@link MarkPassingUpdateListener#getQueue() queue}, + * before draining the queue starts and until the processing of all events from the queue has completed. + * This way, trying to obtain the read lock will block until all pending updates have been processed. + */ + private final NamedReentrantReadWriteLock lock; + public Listen(String raceName) { this.raceName = raceName; + lock = new NamedReentrantReadWriteLock("lock for calculation thread (" + Listen.class.getSimpleName() + ")", /* fair */ false); + } + + public void lockForRead() { + LockUtil.lockForRead(lock); + } + + public void unlockAfterRead() { + LockUtil.unlockAfterRead(lock); } @Override @@ -171,6 +216,7 @@ public class MarkPassingCalculator { logger.log(Level.SEVERE, "MarkPassingCalculator for "+raceName+" threw exception " + e.getMessage() + " while waiting for new GPSFixes"); } + LockUtil.lockForWrite(lock); listener.getQueue().drainTo(allNewFixInsertions); logger.fine("MPC for "+raceName+" received "+ allNewFixInsertions.size()+" new updates."); for (StorePositionUpdateStrategy fixInsertion : allNewFixInsertions) { @@ -225,6 +271,8 @@ public class MarkPassingCalculator { } } catch (Exception e) { logger.log(Level.SEVERE, "Error while calculating markpassings for race "+raceName+": " + e.getMessage(), e); + } finally { + LockUtil.unlockAfterWrite(lock); } } } finally { 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 fcd4163de00..c5b77ab96b1 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,6 +2,7 @@ package com.sap.sailing.domain.tracking; import java.io.Serializable; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.tracking.BravoFix; import com.sap.sse.common.TimePoint; import com.sap.sse.common.WithID; @@ -22,7 +23,7 @@ public interface BravoFixTrack extends S * @return the ride height for the given {@link TimePoint} or null if the {@link TimePoint} is not in the range * where we have fixes for. */ - Double getRideHeight(TimePoint timePoint); + Distance getRideHeight(TimePoint timePoint); /** * Calculates the average ride height for the given time range. @@ -34,5 +35,5 @@ public interface BravoFixTrack extends S * @return the average ride height for the given time range or null if there are no fixes in the given * time range. */ - Double getAverageRideHeight(TimePoint from, TimePoint to); + Distance getAverageRideHeight(TimePoint from, TimePoint to); } 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 c4a8ab28c45..dc47af1cce1 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 @@ -97,7 +97,7 @@ public interface TrackedLegOfCompetitor extends Serializable { * timePoint. If the competitor already completed the leg at timePoint and the respective * mark passing event was already received, the average ride height for the entire leg (and no further) is computed. */ - Double getAverageRideHeight(TimePoint timePoint); + Distance getAverageRideHeight(TimePoint timePoint); /** * @return null if the competitor hasn't started this leg yet, otherwise the fix where the maximum speed was @@ -237,7 +237,7 @@ public interface TrackedLegOfCompetitor extends Serializable { * timePoint, returns the current ride height for this time point. If the competitor has already * finished the leg, the ride height at the time the competitor finished the leg is returned. */ - Double getRideHeight(TimePoint at); + Distance getRideHeight(TimePoint at); /** * Computes the distance along the wind track to the wind-projected position of the race's overall leader. If leader 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 b9f36f9e1aa..c9f4e7ec66d 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 @@ -45,8 +45,10 @@ 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.markpassingcalculation.MarkPassingCalculator; import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException; import com.sap.sailing.domain.polars.PolarDataService; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.ranking.RankingMetric; import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; import com.sap.sailing.domain.tracking.impl.NonCachingMarkPositionAtTimePointCache; @@ -434,6 +436,17 @@ public interface TrackedRace extends Serializable, IsManagedByCache getMarkPassings(Competitor competitor); + + /** + * Returns competitor's mark passings. + * + * @param waitForLatestUpdates + * if any mark passing updates are pending because some calculations are currently going on and updates + * haven't all been processed yet then the call will block until these updates have been processed in + * case this parameter is set to {@code true}. For this the method uses a lock on the + * {@link MarkPassingCalculator }to block the thread until all calculations will be finished. + */ + NavigableSet getMarkPassings(Competitor competitor, boolean waitForLatestUpdates); /** * This obtains the course's read lock before asking for the read lock for the markPassings structure. @@ -685,6 +698,8 @@ public interface TrackedRace extends Serializable, IsManagedByCache extends S } @Override - public Double getRideHeight(TimePoint timePoint) { + public Distance getRideHeight(TimePoint timePoint) { BravoFix fixAfter = getFirstFixAtOrAfter(timePoint); if (fixAfter != null && fixAfter.getTimePoint().compareTo(timePoint) == 0) { // exact match of timepoint -> no interpolation necessary @@ -54,17 +52,24 @@ public class BravoFixTrackImpl extends S } @Override - public Double getAverageRideHeight(TimePoint from, TimePoint to) { + public Distance getAverageRideHeight(TimePoint from, TimePoint to) { + final Distance result; lockForRead(); try { - Spliterator fixes = getFixes(from, true, to, true).spliterator(); - OptionalDouble average = StreamSupport.stream(fixes, false).mapToDouble(BravoFix::getRideHeight).average(); - if (average.isPresent()) { - return average.getAsDouble(); + Distance sum = Distance.NULL; + int count = 0; + for (final BravoFix fix : getFixes(from, true, to, true)) { + sum = sum.add(fix.getRideHeight()); + count++; + } + if (count > 0) { + result = sum.scale(1./count); + } else { + result = null; } } finally { unlockAfterRead(); } - return null; + return result; } } 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 2600557528a..ccd012a2176 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 @@ -723,7 +723,7 @@ public class GPSFixTrackImpl extends MappedTra * then the next fix is added to the result interval. This is because for that next fix, fix in that case will be relevant * for speed estimation because it's the closest fix. */ - protected Util.Pair getTimeIntervalWhoseEstimatedSpeedMayHaveChangedAfterAddingFix(FixType fix) { + protected TimeRange getTimeIntervalWhoseEstimatedSpeedMayHaveChangedAfterAddingFix(FixType fix) { TimePoint intervalStart = null; TimePoint intervalEnd = null; lockForRead(); @@ -783,7 +783,7 @@ public class GPSFixTrackImpl extends MappedTra } finally { unlockAfterRead(); } - return new Util.Pair(intervalStart, intervalEnd); + return new TimeRangeImpl(intervalStart, intervalEnd); } protected FixType createDummyGPSFix(TimePoint at) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MaxSpeedCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MaxSpeedCache.java index 681473708d0..2ec7bce92cb 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MaxSpeedCache.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MaxSpeedCache.java @@ -13,8 +13,10 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.GPSTrackListener; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.TimeRange; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.impl.TimeRangeImpl; import com.sap.sse.concurrent.LockUtil; import com.sap.sse.concurrent.NamedReentrantReadWriteLock; import com.sap.sse.util.impl.ArrayListNavigableSet; @@ -58,9 +60,9 @@ public class MaxSpeedCache implements GPSTrack * Keys are the "from" time points as passed to {@link #getMaxSpeed(TimePoint, TimePoint)}. Values are navigable * sets of pairs whose {@link Pair#getA() a} component is the "to" parameter as passed to * {@link #getMaxSpeed(TimePoint, TimePoint)}, and whose {@link Pair#getB() b} component is the track's fix where - * the averaged maximum speed for that interval was achieved together with the averaged speed at that point. The - * navigable set is ordered according to ascending to time points, yielding shorter cache intervals - * before longer intervals. + * the maximum speed for that interval was achieved together with the speed at that point. The navigable set is + * ordered according to ascending to time points, yielding shorter cache intervals before longer + * intervals. */ private final Map>>> cache; @@ -88,7 +90,7 @@ public class MaxSpeedCache implements GPSTrack @Override public void gpsFixReceived(FixType fix, ItemType item, boolean firstFixInTrack) { // find the invalidation interval such that getFixesRelevantForSpeedEstimation, when passed any time point from that interval, produces "fix" - Util.Pair invalidationInterval = track.getTimeIntervalWhoseEstimatedSpeedMayHaveChangedAfterAddingFix(fix); + TimeRange invalidationInterval = track.getTimeIntervalWhoseEstimatedSpeedMayHaveChangedAfterAddingFix(fix); LockUtil.lockForWrite(lock); HashMap>>> additionalCacheEntries = new HashMap>>>(); @@ -131,33 +133,35 @@ public class MaxSpeedCache implements GPSTrack private Map>>> invalidateEntriesAndReturnAdditionalCacheEntries( Entry>>> cacheEntry, - Util.Pair invalidationInterval) { + TimeRange invalidationInterval) { assert lock.writeLock().isHeldByCurrentThread(); // cannot modify cache here because caller is in an iteration over cache's entry set; request additions by returning them Map>>> result = new HashMap<>(); - if (!cacheEntry.getKey().after(invalidationInterval.getB())) { + final TimePoint cacheEntryFrom = cacheEntry.getKey(); + if (!cacheEntryFrom.after(invalidationInterval.to())) { // invalidation can only become necessary if the cache entry doesn't start after the end of the invalidation interval - TimePoint croppedFrom; // start of the remaining valid part of the cache entries - if (cacheEntry.getKey().before(invalidationInterval.getA())) { - croppedFrom = cacheEntry.getKey(); - } else { - croppedFrom = invalidationInterval.getB(); // cache entry starts in the invalidationInterval; earliest valid point is end of invalidation interval - } // now scan all entries whose "to" is at or after the invalidationInterval's start: for (Iterator>> toAndResultIter = cacheEntry.getValue().tailSet( - new Util.Pair>(invalidationInterval.getA(), null), /* inclusive */ true).iterator(); + new Util.Pair>(invalidationInterval.from(), null), /* inclusive */ true).iterator(); toAndResultIter.hasNext(); ) { Util.Pair> toAndResult = toAndResultIter.next(); // cacheEntry's from is before or in the invalidation interval; the current "to" in this loop iteration is in or after the interval // and at or after "from"; in any case the record needs to be deleted: toAndResultIter.remove(); - // if the current "to" is at or after croppedFrom, check if the old max fix is in the cropped interval; if so, request - // creation of a new cache entry: + // check if the old max fix is outside the invalidation interval; if so, request creation of a new cropped cache entry if (toAndResult.getB() != null) { final TimePoint maxFixTimePoint = toAndResult.getB().getA().getTimePoint(); - if (!toAndResult.getA().before(croppedFrom) && !maxFixTimePoint.before(croppedFrom) - && !maxFixTimePoint.after(toAndResult.getA())) { - addEntryToMap(croppedFrom, toAndResult.getA(), toAndResult.getB(), result); + if (!invalidationInterval.includes(maxFixTimePoint)) { + final TimePoint cacheEntryTo = toAndResult.getA(); + final TimeRange croppedCacheEntryTimeRangeContainingMaxFix; + if (invalidationInterval.startsAfter(maxFixTimePoint)) { + croppedCacheEntryTimeRangeContainingMaxFix = new TimeRangeImpl(cacheEntryFrom, invalidationInterval.from()); + } else { // invalidation interval must end before the maxFixTimePoint + assert invalidationInterval.endsBefore(maxFixTimePoint); + croppedCacheEntryTimeRangeContainingMaxFix = new TimeRangeImpl(invalidationInterval.to(), cacheEntryTo); + } + addEntryToMap(croppedCacheEntryTimeRangeContainingMaxFix.from(), + croppedCacheEntryTimeRangeContainingMaxFix.to(), toAndResult.getB(), result); } } } 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 1807ba07df7..fba23c8af4a 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 @@ -174,8 +174,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { } if (timePointToUse != null) { Distance d = getDistanceTraveled(timePointToUse); - long millis = timePointToUse.asMillis() - legStart.getTimePoint().asMillis(); - result = d.inTime(millis); + result = d.inTime(legStart.getTimePoint().until(timePointToUse)); } else { result = null; } @@ -184,7 +183,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { } @Override - public Double getAverageRideHeight(TimePoint timePoint) { + public Distance getAverageRideHeight(TimePoint timePoint) { MarkPassing legStart = getMarkPassingForLegStart(); if (legStart != null) { BravoFixTrack track = getTrackedRace() @@ -677,15 +676,17 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor { } @Override - public Double getRideHeight(TimePoint at) { + public Distance getRideHeight(TimePoint at) { + final Distance result; if (hasStartedLeg(at)) { TimePoint timePoint =hasFinishedLeg(at) ? getMarkPassingForLegEnd().getTimePoint() : at; BravoFixTrack track = getTrackedRace() .> getSensorTrack(competitor, BravoFixTrack.TRACK_NAME); - return track == null ? null : track.getRideHeight(timePoint); + result = track == null ? null : track.getRideHeight(timePoint); } else { - return null; + result = null; } + return result; } @Override 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 da48741957d..2d40b49208c 100644 --- 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 @@ -707,7 +707,21 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl @Override public NavigableSet getMarkPassings(Competitor competitor) { - return markPassingsForCompetitor.get(competitor); + return getMarkPassings(competitor, /* waitForLatestUpdates */ false); + } + + @Override + public NavigableSet getMarkPassings(Competitor competitor, boolean waitForLatestUpdates) { + if (waitForLatestUpdates && markPassingCalculator != null) { + markPassingCalculator.lockForRead(); + } + try { + return markPassingsForCompetitor.get(competitor); + } finally { + if (waitForLatestUpdates && markPassingCalculator != null) { + markPassingCalculator.unlockForRead(); + } + } } protected NavigableSet getMarkPassingsInOrderAsNavigableSet(Waypoint waypoint) { @@ -1538,6 +1552,22 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl return result; } + @Override + public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint) { + final Distance result; + BravoFixTrack track = getSensorTrack(competitor, BravoFixTrack.TRACK_NAME); + final Leg firstLeg; + final TrackedLegOfCompetitor firstTrackedLeg; + if (track != null && (firstLeg = getRace().getCourse().getFirstLeg()) != null && (firstTrackedLeg = getTrackedLeg(competitor, firstLeg)).hasStartedLeg(timePoint)) { + final TrackedLegOfCompetitor lastTrackedLeg = getTrackedLegFinishingAt(getRace().getCourse().getLastWaypoint()).getTrackedLeg(competitor); + TimePoint endTimePoint = lastTrackedLeg.hasFinishedLeg(timePoint) ? lastTrackedLeg.getFinishTime() : timePoint; + result = track.getAverageRideHeight(firstTrackedLeg.getStartTime(), endTimePoint); + } else { + result = null; + } + return result; + } + @Override public TrackedLegOfCompetitor getCurrentLeg(Competitor competitor, TimePoint timePoint) { // If the mark passing that starts a leg happened exactly at timePoint, the MarkPassingByTimeComparator won't consider diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/util/RegattaUtil.java b/java/com.sap.sailing.domain/src/com/sap/sailing/util/RegattaUtil.java new file mode 100644 index 00000000000..9b35378b91e --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/util/RegattaUtil.java @@ -0,0 +1,24 @@ +package com.sap.sailing.util; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.impl.MeterDistance; + +public class RegattaUtil { + + public static final Distance DEFAULT_BUOY_ZONE_RADIUS = new MeterDistance(15); + + public static Distance getCalculatedRegattaBuoyZoneRadius(Regatta regatta, BoatClass boatClass) { + final double buoyZoneRadiusInHullLengths; + if (regatta != null && regatta.getBuoyZoneRadiusInHullLengths() != null) { + buoyZoneRadiusInHullLengths = regatta.getBuoyZoneRadiusInHullLengths(); + } else { + buoyZoneRadiusInHullLengths = Regatta.DEFAULT_BUOY_ZONE_RADIUS_IN_HULL_LENGTHS; + } + final Distance boatHullLength = boatClass == null ? null : boatClass.getHullLength(); + final Distance buyZoneRadius = boatHullLength == null ? DEFAULT_BUOY_ZONE_RADIUS + : boatHullLength.scale(buoyZoneRadiusInHullLengths); + return buyZoneRadius; + } +} 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 b58478a3a82..c948deb2535 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 @@ -860,4 +860,14 @@ public class MockedTrackedRace implements DynamicTrackedRace { public Iterable getAttachedRaceLogs() { return Collections.emptySet(); } + + @Override + public NavigableSet getMarkPassings(Competitor competitor, boolean waitForLatestUpdates) { + return null; + } + + @Override + public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint) { + return null; + } } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java index 1f039de6673..e15055ff800 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/PingMarkViaRegattaLogTest.java @@ -41,7 +41,7 @@ public class PingMarkViaRegattaLogTest { Collections.singletonList(columnName), service); Regatta regatta = service.createRegatta(RegattaImpl.getDefaultName("regatta", "Laser"), "Laser", /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), Collections.singletonList(series), - false, new HighPoint(), UUID.randomUUID(), /* useStartTimeInference */ true, + false, new HighPoint(), UUID.randomUUID(), /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, RankingMetricsFactory.getRankingMetricConstructor(RankingMetrics.ONE_DESIGN)); RegattaLeaderboard leaderboard = service.addRegattaLeaderboard(regatta.getRegattaIdentifier(), "RegattaLeaderboard", new int[] {}); diff --git a/java/com.sap.sailing.gwt.ui/AutoPlay.html b/java/com.sap.sailing.gwt.ui/AutoPlay.html index 492805e23f1..f1fdefdeaec 100644 --- a/java/com.sap.sailing.gwt.ui/AutoPlay.html +++ b/java/com.sap.sailing.gwt.ui/AutoPlay.html @@ -20,7 +20,8 @@ SAP Sailing Analytics AutoPlay - + + diff --git a/java/com.sap.sailing.gwt.ui/DataMining.css b/java/com.sap.sailing.gwt.ui/DataMining.css index 0843a6f990d..b972e33da0e 100644 --- a/java/com.sap.sailing.gwt.ui/DataMining.css +++ b/java/com.sap.sailing.gwt.ui/DataMining.css @@ -1,256 +1,260 @@ -/** Add css rules here for your application. */ -/* - body #calendar:after - content: " "; - display: block; - height: 0; - clear: both; - visibility: hidden; -*/ -.clearafter { - clear: both; - font-size: 1px; - height: 0px; - line-height: 0px; - margin: -1px 0 0; - width: 100%; -} - -.cover { - left: -1000px; - overflow: hidden; - position: absolute; - top: -1000px; -} - - -body { - background: url(images/spectatorview-bg.png) no-repeat left top #ebebeb; - font-family: 'Open Sans', Arial, Verdana, sans-serif; - line-height: 1; - font-size: 14px; - font-weight: 400; - padding: 85px 0 0 0; - margin: 0; - color: #3b3b3b; -} - -a { - color: #2968c0; - text-decoration: underline; -} - -table td,select,button { - line-height: 1; - font-family: 'Open Sans', Arial, Verdana, sans-serif; - font-size: 14px; -} - -.gwt-DialogBox { - z-index: 20; -} - -.gwt-ToggleButton { - display: inline-block; -} - -.gwt-PopupPanelGlass { - z-index: 19; -} - -.contentOuterPanel { - background: #dddcdc; - margin: 0 12px 0 12px; - padding: 12px; -} - -/** Example rules used by the template application (remove for your app) */ -h1 { - font-size: 2em; - font-weight: bold; - color: #777777; - margin: 40px 0px 70px; - text-align: center; -} - -table { - border-collapse: separate; -} - -.LogoAndTitlePanel { - height: 68px; - font-family: Arial, Verdana, sans-serif; - position: fixed; - width: 100%; - top: 0; - background-image: url(images/white_gradient_header.png), url(images/header_bg_modern.png); - background-position: center top, 0 0; - background-repeat: no-repeat, repeat-x; - box-shadow: 0 0 20px #858585; - -moz-box-shadow: 0 0 20px #858585; - -webkit-box-shadow: 0 0 20px #858585; - -o-box-shadow: 0 0 20px #858585; - -ms-box-shadow: 0 0 20px #858585; -} - -.LogoAndTitlePanel:after { - content: " "; - display: block; - height: 0; - clear: both; - visibility: hidden; -} - -.RaceBoardHeaderPanel { - 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; - padding: 0 0 6px 0; -} - -.refreshAndSettings { - background: #f2f2f2; - border-bottom: 2px solid #fff; - border-top: 2px solid #fff; - height: 30px; -} - -.refreshAndSettings table td { - padding: 0 10px 0 0; -} - -.refreshAndSettings table table td { - padding: 0 0 0 0; -} - -.race-name { - margin: 2px 10px 0 0; - line-height: 1.0; - float: left; -} - -input.magnifier { - display: block; - margin: 3px auto 0 auto !important; -} - -.footerShadowPanel { - position: fixed; - height: 50px; - bottom: 0; - background: url(images/footer_shadow.png) left bottom repeat-x; - width: 100%; - z-index: 2000; -} - -.mainPanel { - width: 100%; - margin-left: 13px; -} - -.errorLabel { - color: #FF0000; -} - -.chart-importantMessage { - font-family: 'Open Sans',Arial,Verdana,sans-serif; - font-weight: 600; - font-size: 20px; - height: 70px; - margin: 0 auto; - padding: 49px 0 0 80px; - width: 400px; - color: #7c7c7c; - background: url(images/important-message.png) left center no-repeat; -} - -.chart-busyMessage { - font-family: 'Open Sans',Arial,Verdana,sans-serif; - font-weight: 600; - font-size: 20px; - height: 70px; - margin: 0 auto; - padding: 49px 0 0 40px; - width: 400px; - color: #7c7c7c; - background: url(images/busy_indicator_circle.gif) left center no-repeat; -} - -.dataMiningPanel { - margin-left: 10px; - margin-right: 10px; -} - -.dataMiningPanel-filledElement { - background: url("images/btn-cta-orangeleft.png") repeat-x scroll center 0 transparent; - border-bottom-left-radius: 3px; - border-top-left-radius: 3px; - color: #FFFFFF; - cursor: pointer; - font-family: 'Open Sans',Verdana,Arial,sans-serif; - font-weight: 600; - font-size: 14px; - margin: 0 0 0 0; - padding: 2px 7px 2px 3px; - height: 18px; - display: inline-block; - text-decoration: none; - -moz-border-radius: 5px 0 0 5px; - -webkit-border-radius: 5px 0 0 5px; - -o-border-radius: 5px 0 0 5px; - -ms-border-radius: 5px 0 0 5px; - border-radius: 5px 0 0 5px; - -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - -o-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - -ms-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); - border-bottom: 1px solid rgba(0, 0, 0, 0.25); -} - -.settingsAnchor { - position: relative; - top: 9px; -} - -.groupingProviderElement { - margin: 2px; -} - -.statisticProviderElement { - margin: 2px; -} - -.definitionProviderControls { - margin: 2px; -} - -.definitionProviderControlsElements { - margin: 2px; -} - -.queryDefinitionViewerContent { - background-color: white; - padding: 5px; +/** Add css rules here for your application. */ +/* + body #calendar:after + content: " "; + display: block; + height: 0; + clear: both; + visibility: hidden; +*/ +html { + font-size: 15px; +} + +.clearafter { + clear: both; + font-size: 1px; + height: 0px; + line-height: 0px; + margin: -1px 0 0; + width: 100%; +} + +.cover { + left: -1000px; + overflow: hidden; + position: absolute; + top: -1000px; +} + + +body { + background: url(images/spectatorview-bg.png) no-repeat left top #ebebeb; + font-family: 'Open Sans', Arial, Verdana, sans-serif; + line-height: 1; + font-size: 14px; + font-weight: 400; + padding: 85px 0 0 0; + margin: 0; + color: #3b3b3b; +} + +a { + color: #2968c0; + text-decoration: underline; +} + +table td,select,button { + line-height: 1; + font-family: 'Open Sans', Arial, Verdana, sans-serif; + font-size: 14px; +} + +.gwt-DialogBox { + z-index: 20; +} + +.gwt-ToggleButton { + display: inline-block; +} + +.gwt-PopupPanelGlass { + z-index: 19; +} + +.contentOuterPanel { + background: #dddcdc; + margin: 0 12px 0 12px; + padding: 12px; +} + +/** Example rules used by the template application (remove for your app) */ +h1 { + font-size: 2em; + font-weight: bold; + color: #777777; + margin: 40px 0px 70px; + text-align: center; +} + +table { + border-collapse: separate; +} + +.LogoAndTitlePanel { + height: 68px; + font-family: Arial, Verdana, sans-serif; + position: fixed; + width: 100%; + top: 0; + background-image: url(images/white_gradient_header.png), url(images/header_bg_modern.png); + background-position: center top, 0 0; + background-repeat: no-repeat, repeat-x; + box-shadow: 0 0 20px #858585; + -moz-box-shadow: 0 0 20px #858585; + -webkit-box-shadow: 0 0 20px #858585; + -o-box-shadow: 0 0 20px #858585; + -ms-box-shadow: 0 0 20px #858585; +} + +.LogoAndTitlePanel:after { + content: " "; + display: block; + height: 0; + clear: both; + visibility: hidden; +} + +.RaceBoardHeaderPanel { + 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; + padding: 0 0 6px 0; +} + +.refreshAndSettings { + background: #f2f2f2; + border-bottom: 2px solid #fff; + border-top: 2px solid #fff; + height: 30px; +} + +.refreshAndSettings table td { + padding: 0 10px 0 0; +} + +.refreshAndSettings table table td { + padding: 0 0 0 0; +} + +.race-name { + margin: 2px 10px 0 0; + line-height: 1.0; + float: left; +} + +input.magnifier { + display: block; + margin: 3px auto 0 auto !important; +} + +.footerShadowPanel { + position: fixed; + height: 50px; + bottom: 0; + background: url(images/footer_shadow.png) left bottom repeat-x; + width: 100%; + z-index: 2000; +} + +.mainPanel { + width: 100%; + margin-left: 13px; +} + +.errorLabel { + color: #FF0000; +} + +.chart-importantMessage { + font-family: 'Open Sans',Arial,Verdana,sans-serif; + font-weight: 600; + font-size: 20px; + height: 70px; + margin: 0 auto; + padding: 49px 0 0 80px; + width: 400px; + color: #7c7c7c; + background: url(images/important-message.png) left center no-repeat; +} + +.chart-busyMessage { + font-family: 'Open Sans',Arial,Verdana,sans-serif; + font-weight: 600; + font-size: 20px; + height: 70px; + margin: 0 auto; + padding: 49px 0 0 40px; + width: 400px; + color: #7c7c7c; + background: url(images/busy_indicator_circle.gif) left center no-repeat; +} + +.dataMiningPanel { + margin-left: 10px; + margin-right: 10px; +} + +.dataMiningPanel-filledElement { + background: url("images/btn-cta-orangeleft.png") repeat-x scroll center 0 transparent; + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; + color: #FFFFFF; + cursor: pointer; + font-family: 'Open Sans',Verdana,Arial,sans-serif; + font-weight: 600; + font-size: 14px; + margin: 0 0 0 0; + padding: 2px 7px 2px 3px; + height: 18px; + display: inline-block; + text-decoration: none; + -moz-border-radius: 5px 0 0 5px; + -webkit-border-radius: 5px 0 0 5px; + -o-border-radius: 5px 0 0 5px; + -ms-border-radius: 5px 0 0 5px; + border-radius: 5px 0 0 5px; + -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -o-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + -ms-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); + text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25); + border-bottom: 1px solid rgba(0, 0, 0, 0.25); +} + +.settingsAnchor { + position: relative; + top: 9px; +} + +.groupingProviderElement { + margin: 2px; +} + +.statisticProviderElement { + margin: 2px; +} + +.definitionProviderControls { + margin: 2px; +} + +.definitionProviderControlsElements { + margin: 2px; +} + +.queryDefinitionViewerContent { + background-color: white; + padding: 5px; } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/LeaderboardNewHome.css b/java/com.sap.sailing.gwt.ui/LeaderboardNewHome.css index 4aa2705763b..a305454a353 100644 --- a/java/com.sap.sailing.gwt.ui/LeaderboardNewHome.css +++ b/java/com.sap.sailing.gwt.ui/LeaderboardNewHome.css @@ -5,9 +5,7 @@ table { } .leaderboardContent td, .leaderboardContent th { - vertical-align: bottom; line-height: 1.3; - padding-top: 2px; } .leaderboardContent-toolbar { diff --git a/java/com.sap.sailing.gwt.ui/images/home/launch-play.svg b/java/com.sap.sailing.gwt.ui/images/home/launch-play.svg new file mode 100644 index 00000000000..c9ef6af35dd --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/images/home/launch-play.svg @@ -0,0 +1,4 @@ + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/PlayerActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/PlayerActivity.java index d2e3019ccaa..2d8831be66f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/PlayerActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/PlayerActivity.java @@ -7,8 +7,16 @@ import com.google.gwt.event.shared.EventBus; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AcceptsOneWidget; +import com.sap.sailing.gwt.autoplay.client.shared.leaderboard.LeaderboardWithHeaderPerspectiveLifecycle; +import com.sap.sailing.gwt.autoplay.client.shared.leaderboard.LeaderboardWithHeaderPerspectiveSettings; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.raceboard.RaceBoardPerspectiveLifecycle; +import com.sap.sailing.gwt.ui.raceboard.RaceBoardPerspectiveSettings; import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; +import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO; import com.sap.sse.gwt.client.mvp.ErrorView; +import com.sap.sse.gwt.client.shared.perspective.PerspectiveLifecycleWithAllSettings; import com.sap.sse.gwt.client.useragent.UserAgentDetails; import com.sap.sse.gwt.shared.GwtHttpRequestUtils; @@ -26,6 +34,17 @@ public class PlayerActivity extends AbstractActivity { this.autoPlayController = null; } + private 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; + } + @Override public void start(final AcceptsOneWidget panel, EventBus eventBus) { final boolean showRaceDetails = GwtHttpRequestUtils.getBooleanParameter(PARAM_SHOW_RACE_DETAILS, false); @@ -36,6 +55,16 @@ public class PlayerActivity extends AbstractActivity { clientFactory.getSailingService().getEventById(eventUUID, true, new AsyncCallback() { @Override public void onSuccess(final EventDTO event) { + + //This place fixes a specific null pointer bug3950, once the settings are redone, this should be removed, and instead be done via playerplace (see comment there) + StrippedLeaderboardDTO leaderBoardDTO = getSelectedLeaderboard(event,playerPlace.getConfiguration().getLeaderboardName()); + LeaderboardWithHeaderPerspectiveLifecycle leaderboardPerspectiveLifecycle = new LeaderboardWithHeaderPerspectiveLifecycle(leaderBoardDTO, StringMessages.INSTANCE); + PerspectiveLifecycleWithAllSettings leaderboardPerspectiveLifecyclesAndSettings = new PerspectiveLifecycleWithAllSettings<>(leaderboardPerspectiveLifecycle, leaderboardPerspectiveLifecycle.createDefaultSettings()); + RaceBoardPerspectiveLifecycle raceboardPerspectiveLifecycle = new RaceBoardPerspectiveLifecycle(leaderBoardDTO, StringMessages.INSTANCE); + PerspectiveLifecycleWithAllSettings raceboardPerspectiveLifecyclesAndSettings = new PerspectiveLifecycleWithAllSettings<>(raceboardPerspectiveLifecycle, raceboardPerspectiveLifecycle.createDefaultSettings()); + + + UserAgentDetails userAgent = new UserAgentDetails(Window.Navigator.getUserAgent()); PlayerView view = clientFactory.createPlayerView(); @@ -44,8 +73,8 @@ public class PlayerActivity extends AbstractActivity { autoPlayController = new AutoPlayController(clientFactory.getSailingService(), clientFactory .getMediaService(), clientFactory.getUserService(), clientFactory.getErrorReporter(), playerPlace.getConfiguration(), userAgent, delayToLiveMillis, showRaceDetails, view, - playerPlace.getLeaderboardPerspectiveLifecycleWithAllSettings(), - playerPlace.getRaceboardPerspectiveLifecycleWithAllSettings()); + leaderboardPerspectiveLifecyclesAndSettings, + raceboardPerspectiveLifecyclesAndSettings); autoPlayController.updatePlayMode(AutoPlayModes.Leaderboard); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/PlayerPlace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/PlayerPlace.java index a5736f46d65..e0c45b26985 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/PlayerPlace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/autoplay/client/place/player/PlayerPlace.java @@ -23,6 +23,8 @@ public class PlayerPlace extends AbstractBasePlace { Integer timeToSwitchBeforeRaceStartInSeconds = Integer.valueOf(getParameter(AutoPlayerConfiguration.PARAM_TIME_TO_SWITCH_BEFORE_RACE_START)); this.playerConfiguration = new AutoPlayerConfiguration(eventUuidAsString, leaderboardName, fullscreen, timeToSwitchBeforeRaceStartInSeconds); + + //these are currently regenerated in the PlayerActivity, based on the historydata. Once the settings are properly stored, this should be replaced. this.leaderboardPerspectiveLifecycleWithAllSettings = null; this.raceboardPerspectiveLifecycleWithAllSettings = null; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventview/RegattaMetadataDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventview/RegattaMetadataDTO.java index 36b47abaecd..b8c694b6a6a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventview/RegattaMetadataDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/eventview/RegattaMetadataDTO.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import com.sap.sailing.domain.common.Distance; import com.sap.sse.gwt.dispatch.shared.commands.DTO; public class RegattaMetadataDTO extends RegattaReferenceDTO implements HasRegattaMetadata { @@ -18,6 +19,7 @@ public class RegattaMetadataDTO extends RegattaReferenceDTO implements HasRegatt private RegattaState state; private boolean flexibleLeaderboard; private RaceDataInfo raceDataInfo; + private Distance buoyZoneRadius; public RegattaMetadataDTO() { } @@ -122,7 +124,15 @@ public class RegattaMetadataDTO extends RegattaReferenceDTO implements HasRegatt public void setRaceDataInfo(RaceDataInfo raceDataInfo) { this.raceDataInfo = raceDataInfo; } - + + public Distance getBuoyZoneRadius() { + return buoyZoneRadius; + } + + public void setBuoyZoneRadius(Distance buoyZoneRadius) { + this.buoyZoneRadius = buoyZoneRadius; + } + /** * Holder class for flags, which inform about GPS, wind, video or audio data availability. */ diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/user/profile/GetFavoritesAction.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/user/profile/GetFavoritesAction.java index 880c5ea966d..b4539d6d405 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/user/profile/GetFavoritesAction.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/communication/user/profile/GetFavoritesAction.java @@ -34,7 +34,7 @@ public class GetFavoritesAction implements SailingAction { if (preferences != null) { for (BoatClassNotificationPreference pref : preferences.getBoatClasses()) { String name = pref.getBoatClass().getName(), displayName = pref.getBoatClass().getDisplayName(); - selected.add(new BoatClassDTO(name, displayName, pref.getBoatClass().getHullLength().getMeters())); + selected.add(new BoatClassDTO(name, displayName, pref.getBoatClass().getHullLength())); notifyAboutUpcomingRaces |= pref.isNotifyAboutUpcomingRaces(); notifyAboutResults |= pref.isNotifyAboutResults(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/DropdownItem.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/DropdownItem.java index 9d1e9a4fd7f..f4e6f8e5508 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/DropdownItem.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/DropdownItem.java @@ -28,7 +28,7 @@ public class DropdownItem extends Widget { setElement(uiBinder.createAndBindUi(this)); if(active) { - getElement().addClassName(local_res.css().dropdownregattas_content_linkactive()); + getElement().addClassName(local_res.css().dropdown_content_linkactive()); } this.link.setHref(link); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/DropdownItem.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/DropdownItem.ui.xml index 076b9ba44f0..d6b308be8ce 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/DropdownItem.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/partials/eventheader/DropdownItem.ui.xml @@ -8,8 +8,8 @@ font-weight: bold; } - - -
-
-

- -

- -
-
- {i18n.i18nDeSolutionsTrainingHeadline} -

- -

-
- - - -
-
-
diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/stage/Stage.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/stage/Stage.gss index d7236ecc751..eb236c161f0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/stage/Stage.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/stage/Stage.gss @@ -74,7 +74,7 @@ width: 100%; padding: 3.333333333333333em 4em 5.2em 4em; line-height: 0.9; - text-shadow: 0 0 18px rgba(0,0,0,0.13), 0 2px 6px rgba(0,0,0,0.25); + text-shadow: 0 0 18px rgba(0,0,0,0.50), 0 2px 6px rgba(0,0,0,0.50); text-align: center; opacity: 1; filter: none; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/ToggleButton.gss b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/BigButton.gss similarity index 84% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/ToggleButton.gss rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/BigButton.gss index 58735330d2e..faca954d2a9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/ToggleButton.gss +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/BigButton.gss @@ -1,13 +1,13 @@ -.togglebutton { - padding: 1em; - border-top: 1px solid #ccc; -} -.togglebutton_button { - font-size: 0.866666666666667em; - font-weight: 700; - color: #008fcc; - padding: 0.769230769230769em; - text-align: center; - border: 1px solid #ebebeb; - border-radius: 0.384615384615385em; -} +.bigbutton { + padding: 1em; + border-top: 1px solid #ccc; +} +.bigbutton_button { + font-size: 0.866666666666667em; + font-weight: 700; + color: #008fcc; + padding: 0.769230769230769em; + text-align: center; + border: 1px solid #ebebeb; + border-radius: 0.384615384615385em; +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/BigButton.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/BigButton.java new file mode 100644 index 00000000000..237c5536783 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/BigButton.java @@ -0,0 +1,34 @@ +package com.sap.sailing.gwt.home.mobile.partials.toggleButton; + +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.FocusWidget; + +public class BigButton extends FocusWidget { + + private static StyledButtonUiBinder uiBinder = GWT.create(StyledButtonUiBinder.class); + + interface StyledButtonUiBinder extends UiBinder { + } + + @UiField DivElement buttonUi; + + public BigButton() { + this(null); + } + + public BigButton(String label) { + BigButtonResources.INSTANCE.css().ensureInjected(); + setElement(uiBinder.createAndBindUi(this)); + if(label != null) { + setLabel(label); + } + } + + public void setLabel(String label) { + buttonUi.setInnerText(label); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/ToggleButton.ui.xml b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/BigButton.ui.xml similarity index 72% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/ToggleButton.ui.xml rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/BigButton.ui.xml index f50d6c52c53..8de4c4f399f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/ToggleButton.ui.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/BigButton.ui.xml @@ -1,10 +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/toggleButton/ToggleButtonResources.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/BigButtonResources.java similarity index 52% rename from java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/ToggleButtonResources.java rename to java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/BigButtonResources.java index 03fa42775fd..a4d6b93c350 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/ToggleButtonResources.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/BigButtonResources.java @@ -1,17 +1,17 @@ -package com.sap.sailing.gwt.home.mobile.partials.toggleButton; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.resources.client.ClientBundle; -import com.google.gwt.resources.client.CssResource; - -public interface ToggleButtonResources extends ClientBundle { - public static final ToggleButtonResources INSTANCE = GWT.create(ToggleButtonResources.class); - - @Source("ToggleButton.gss") - LocalCss css(); - - public interface LocalCss extends CssResource { - String togglebutton(); - String togglebutton_button(); - } -} +package com.sap.sailing.gwt.home.mobile.partials.toggleButton; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.resources.client.ClientBundle; +import com.google.gwt.resources.client.CssResource; + +public interface BigButtonResources extends ClientBundle { + public static final BigButtonResources INSTANCE = GWT.create(BigButtonResources.class); + + @Source("BigButton.gss") + LocalCss css(); + + public interface LocalCss extends CssResource { + String bigbutton(); + String bigbutton_button(); + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/ToggleButton.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/ToggleButton.java index 7f658929556..ddd0ca286d4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/ToggleButton.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/partials/toggleButton/ToggleButton.java @@ -1,57 +1,42 @@ -package com.sap.sailing.gwt.home.mobile.partials.toggleButton; - -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.Command; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.ui.client.StringMessages; - -public class ToggleButton extends Widget { - - private static final StringMessages I18N = StringMessages.INSTANCE; - private static ToggleButtonUiBinder uiBinder = GWT.create(ToggleButtonUiBinder.class); - - interface ToggleButtonUiBinder extends UiBinder { - } - - @UiField DivElement toggleButtonUi; - private final ToggleButtonCommand toggleCommand; - private String label; - - public ToggleButton(ToggleButtonCommand toggleCommand) { - this.toggleCommand = toggleCommand; - ToggleButtonResources.INSTANCE.css().ensureInjected(); - setElement(uiBinder.createAndBindUi(this)); - sinkEvents(Event.ONCLICK); - } - - public void setLabel(String label) { - toggleButtonUi.setInnerText(toggleCommand.expanded ? I18N.collapseX(label) : I18N.showAllX(label)); - this.label = label; - } - - @Override - public void onBrowserEvent(Event event) { - if (event.getTypeInt() == Event.ONCLICK) { - toggleCommand.execute(); - toggleButtonUi.setInnerText(toggleCommand.expanded ? I18N.collapseX(label) : I18N.showAllX(label)); - return; - } - super.onBrowserEvent(event); - } - - public static abstract class ToggleButtonCommand implements Command { - private boolean expanded = false; - @Override - public final void execute() { - this.expanded = !expanded; - this.execute(expanded); - } - protected abstract void execute(boolean expanded); - } - -} +package com.sap.sailing.gwt.home.mobile.partials.toggleButton; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.Command; +import com.sap.sailing.gwt.ui.client.StringMessages; + +public class ToggleButton extends BigButton { + + private static final StringMessages I18N = StringMessages.INSTANCE; + private final ToggleButtonCommand toggleCommand; + private String label; + + public ToggleButton(final ToggleButtonCommand toggleCommand) { + this.toggleCommand = toggleCommand; + this.addClickHandler(new ClickHandler() { + + @Override + public void onClick(ClickEvent event) { + toggleCommand.execute(); + buttonUi.setInnerText(toggleCommand.expanded ? I18N.collapseX(label) : I18N.showAllX(label)); + } + }); + } + + @Override + public void setLabel(String label) { + buttonUi.setInnerText(toggleCommand.expanded ? I18N.collapseX(label) : I18N.showAllX(label)); + this.label = label; + } + + public static abstract class ToggleButtonCommand implements Command { + private boolean expanded = false; + @Override + public final void execute() { + this.expanded = !expanded; + this.execute(expanded); + } + protected abstract void execute(boolean expanded); + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java index 6d2d9dbf542..880126c98ce 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/mobile/places/event/AbstractEventActivity.java @@ -274,12 +274,9 @@ public abstract class AbstractEventActivity ex } linkParams.put("raceName", trackedRaceName); linkParams.put("regattaName", regattaName); - // TODO this must only be forwarded if there is a logged-on user - // linkParams.put(RaceBoardViewConfiguration.PARAM_CAN_REPLAY_DURING_LIVE_RACES, "true"); return linkParams; } - public String getRegattaId() { String regattaId = place.getRegattaId(); if(regattaId != null) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/LeaderboardContext.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/LeaderboardContext.java index c57209a12cd..4cf5dcc3eb5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/LeaderboardContext.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/server/LeaderboardContext.java @@ -9,11 +9,13 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import com.sap.sailing.domain.base.BoatClass; import com.sap.sailing.domain.base.Event; import com.sap.sailing.domain.base.EventBase; import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.RaceColumn; import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.dto.LeaderboardDTO; import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO; @@ -33,6 +35,7 @@ import com.sap.sailing.gwt.home.communication.regatta.RegattaWithProgressDTO; import com.sap.sailing.gwt.home.server.EventActionUtil.RaceCallback; import com.sap.sailing.gwt.server.HomeServiceUtil; import com.sap.sailing.server.RacingEventService; +import com.sap.sailing.util.RegattaUtil; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; @@ -248,8 +251,15 @@ public class LeaderboardContext { RegattaRaceDataInfoCalculator regattaRaceDataInfoCalculator = new RegattaRaceDataInfoCalculator(); forRaces(regattaRaceDataInfoCalculator); regattaDTO.setRaceDataInfo(regattaRaceDataInfoCalculator.getRaceDataInfo()); + regattaDTO.setBuoyZoneRadius(getRegattaBuoyZoneRadius()); } - + + private Distance getRegattaBuoyZoneRadius() { + Regatta regatta = service.getRegattaByName(getLeaderboardName()); + BoatClass boatClass = HomeServiceUtil.getBoatClass(leaderboard); + return RegattaUtil.getCalculatedRegattaBuoyZoneRadius(regatta, boatClass); + } + private static boolean hasMultipleLeaderboardGroups(EventBase event) { return Util.size(event.getLeaderboardGroups()) > 1; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/multiselection/AbstractSuggestedMultiSelectionBoatClassDataProvider.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/multiselection/AbstractSuggestedMultiSelectionBoatClassDataProvider.java index dae04151ded..6245a0eab63 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/multiselection/AbstractSuggestedMultiSelectionBoatClassDataProvider.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/multiselection/AbstractSuggestedMultiSelectionBoatClassDataProvider.java @@ -40,7 +40,7 @@ public abstract class AbstractSuggestedMultiSelectionBoatClassDataProvider exten List boatClasses = Arrays.asList(BoatClassMasterdata.values()); List suggestionItems = new ArrayList<>(); for (BoatClassMasterdata bcm : filter.applyFilter(queryTokens, boatClasses)) { - suggestionItems.add(new BoatClassDTO(bcm.getDisplayName(), bcm.getHullLength().getMeters())); + suggestionItems.add(new BoatClassDTO(bcm.getDisplayName(), bcm.getHullLength())); } callback.setSuggestionItems(suggestionItems); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/regattacompetition/AbstractRegattaCompetitionFleetRace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/regattacompetition/AbstractRegattaCompetitionFleetRace.java index a9ad9d10aa4..f88a27148e2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/regattacompetition/AbstractRegattaCompetitionFleetRace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/partials/regattacompetition/AbstractRegattaCompetitionFleetRace.java @@ -1,111 +1,122 @@ -package com.sap.sailing.gwt.home.shared.partials.regattacompetition; - -import static com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil.shortTimeFormatter; -import static com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil.weekdayMonthAbbrDayDateFormatter; -import static com.sap.sse.common.impl.MillisecondsTimePoint.now; - -import java.util.Date; - -import com.google.gwt.core.client.Scheduler; -import com.google.gwt.core.client.Scheduler.ScheduledCommand; -import com.google.gwt.dom.client.Element; -import com.google.gwt.text.client.DateTimeFormatRenderer; -import com.google.gwt.user.client.Event; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.PopupPanel; -import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.gwt.common.client.DateUtil; -import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO; -import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO.RaceTrackingState; -import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO.RaceViewState; -import com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad.RaceviewerLaunchPad; -import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionView.RegattaCompetitionRaceView; -import com.sap.sailing.gwt.ui.client.StringMessages; - -public abstract class AbstractRegattaCompetitionFleetRace extends Widget implements RegattaCompetitionRaceView { - - private static final StringMessages I18N = StringMessages.INSTANCE; - protected final Element mainElement; - private final SimpleRaceMetadataDTO race; - private final RegattaCompetitionPresenter presenter; - private final PopupPanel panel = new PopupPanel(true, false); - - protected AbstractRegattaCompetitionFleetRace(final SimpleRaceMetadataDTO race, - RegattaCompetitionPresenter presenter) { - this.race = race; - this.presenter = presenter; - this.mainElement = getMainUiElement(); - setupRaceState(race.getTrackingState(), race.getViewState()); - getRaceNameUiElement().setInnerText(race.getRaceName()); - setupRaceStart(race.getStart()); - setElement(mainElement); - if (race.hasValidTrackingData()) { - sinkEvents(Event.ONCLICK); - } - } - - @Override - public void onBrowserEvent(Event event) { - if (race.hasValidTrackingData() && event.getTypeInt() == Event.ONCLICK) { - this.getElement().scrollIntoView(); - panel.setWidget(new RaceviewerLaunchPad(race, panel) { - @Override - protected String getRaceViewerURL(SimpleRaceMetadataDTO data, String mode) { - return presenter.getRaceViewerURL(data, mode); - } - }); - panel.setVisible(false); - panel.show(); - Scheduler.get().scheduleDeferred(new ScheduledCommand() { - @Override - public void execute() { - Widget button = AbstractRegattaCompetitionFleetRace.this, panelContent = panel.getWidget(); - int alignRight = button.getAbsoluteLeft() + button.getOffsetWidth() - panelContent.getOffsetWidth(); - int left = (alignRight - Window.getScrollLeft() < 0 ? button.getAbsoluteLeft() - 1 : alignRight + 1); - int alignBottom = button.getAbsoluteTop() + button.getOffsetHeight() - panelContent.getOffsetHeight(); - int top = (alignBottom - Window.getScrollTop() < 0 ? button.getAbsoluteTop() - 1 : alignBottom + 1); - panel.setPopupPosition(left, top); - panel.setVisible(true); - } - }); - } - } - - private void setupRaceState(RaceTrackingState trackingState, RaceViewState viewState) { - boolean isUntrackedRace = trackingState != RaceTrackingState.TRACKED_VALID_DATA; - if (viewState == RaceViewState.RUNNING) { - mainElement.addClassName(getRaceLiveStyleName()); - getRaceStateUiElement().setInnerText(isUntrackedRace ? I18N.live() : I18N.actionWatch()); - } else if (viewState == RaceViewState.FINISHED) { - getRaceStateUiElement().setInnerText(isUntrackedRace ? I18N.raceIsFinished() : I18N.actionAnalyze()); - } else { - mainElement.addClassName(getRacePlannedStyleName()); - if (viewState == RaceViewState.SCHEDULED) getRaceStateUiElement().setInnerText(I18N.raceIsPlanned()); - else getRaceStateUiElement().setInnerText(viewState.getLabel()); - } - setStyleName(mainElement, getRaceUntrackedStyleName(), isUntrackedRace); - } - - private void setupRaceStart(Date startDate) { - if (startDate != null) { - boolean showTime = DateUtil.isSameDayOfMonth(now().asDate(), startDate); - DateTimeFormatRenderer renderer = showTime ? shortTimeFormatter : weekdayMonthAbbrDayDateFormatter; - getRaceDateUiElement().setInnerText(renderer.render(startDate)); - } - } - - protected abstract Element getMainUiElement(); - - protected abstract Element getRaceNameUiElement(); - - protected abstract Element getRaceStateUiElement(); - - protected abstract Element getRaceDateUiElement(); - - protected abstract String getRaceLiveStyleName(); - - protected abstract String getRacePlannedStyleName(); - - protected abstract String getRaceUntrackedStyleName(); - -} +package com.sap.sailing.gwt.home.shared.partials.regattacompetition; + +import static com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil.shortTimeFormatter; +import static com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil.weekdayMonthAbbrDayDateFormatter; +import static com.sap.sse.common.impl.MillisecondsTimePoint.now; + +import java.util.Date; + +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; +import com.google.gwt.dom.client.Element; +import com.google.gwt.text.client.DateTimeFormatRenderer; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.common.client.DateUtil; +import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO; +import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO.RaceTrackingState; +import com.sap.sailing.gwt.home.communication.race.SimpleRaceMetadataDTO.RaceViewState; +import com.sap.sailing.gwt.home.desktop.partials.raceviewerlaunchpad.RaceviewerLaunchPad; +import com.sap.sailing.gwt.home.shared.partials.regattacompetition.RegattaCompetitionView.RegattaCompetitionRaceView; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.raceboard.RaceBoardModes; + +public abstract class AbstractRegattaCompetitionFleetRace extends Widget implements RegattaCompetitionRaceView { + + private static final StringMessages I18N = StringMessages.INSTANCE; + protected final Element mainElement; + private final SimpleRaceMetadataDTO race; + private final RegattaCompetitionPresenter presenter; + private final PopupPanel panel = new PopupPanel(true, false); + + protected AbstractRegattaCompetitionFleetRace(final SimpleRaceMetadataDTO race, + RegattaCompetitionPresenter presenter) { + this.race = race; + this.presenter = presenter; + this.mainElement = getMainUiElement(); + setupRaceState(race.getTrackingState(), race.getViewState()); + getRaceNameUiElement().setInnerText(race.getRaceName()); + setupRaceStart(race.getStart()); + setElement(mainElement); + if (race.hasValidTrackingData()) { + sinkEvents(Event.ONCLICK); + } + } + + @Override + public void onBrowserEvent(Event event) { + // Only handle click events if valid tracking data is available + if (!race.hasValidTrackingData() || event.getTypeInt() != Event.ONCLICK) { + return; + } + + // If rendered as direct link button, open link in new tab directly instead of showing the menu popup + if (!race.isFinished() && !race.isRunning()) { + Window.open(presenter.getRaceViewerURL(race, RaceBoardModes.PLAYER.name()), "_blank", ""); + return; + } + + // Show menu popup relative to button by ensuring it stays with the viewport + this.getElement().scrollIntoView(); + panel.setWidget(new RaceviewerLaunchPad(race, panel) { + @Override + protected String getRaceViewerURL(SimpleRaceMetadataDTO data, String mode) { + return presenter.getRaceViewerURL(data, mode); + } + }); + panel.setVisible(false); + panel.show(); + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + @Override + public void execute() { + Widget button = AbstractRegattaCompetitionFleetRace.this, panelContent = panel.getWidget(); + int alignRight = button.getAbsoluteLeft() + button.getOffsetWidth() - panelContent.getOffsetWidth(); + int left = (alignRight - Window.getScrollLeft() < 0 ? button.getAbsoluteLeft() - 1 : alignRight + 1); + int alignBottom = button.getAbsoluteTop() + button.getOffsetHeight() - panelContent.getOffsetHeight(); + int top = (alignBottom - Window.getScrollTop() < 0 ? button.getAbsoluteTop() - 1 : alignBottom + 1); + panel.setPopupPosition(left, top); + panel.setVisible(true); + } + }); + } + + private void setupRaceState(RaceTrackingState trackingState, RaceViewState viewState) { + boolean isUntrackedRace = trackingState != RaceTrackingState.TRACKED_VALID_DATA; + if (viewState == RaceViewState.RUNNING) { + mainElement.addClassName(getRaceLiveStyleName()); + getRaceStateUiElement().setInnerText(isUntrackedRace ? I18N.live() : I18N.actionWatch()); + } else if (viewState == RaceViewState.FINISHED) { + getRaceStateUiElement().setInnerText(isUntrackedRace ? I18N.raceIsFinished() : I18N.actionAnalyze()); + } else { + mainElement.addClassName(getRacePlannedStyleName()); + if (viewState == RaceViewState.SCHEDULED) getRaceStateUiElement().setInnerText(I18N.raceIsPlanned()); + else getRaceStateUiElement().setInnerText(viewState.getLabel()); + } + setStyleName(mainElement, getRaceUntrackedStyleName(), isUntrackedRace); + } + + private void setupRaceStart(Date startDate) { + if (startDate != null) { + boolean showTime = DateUtil.isSameDayOfMonth(now().asDate(), startDate); + DateTimeFormatRenderer renderer = showTime ? shortTimeFormatter : weekdayMonthAbbrDayDateFormatter; + getRaceDateUiElement().setInnerText(renderer.render(startDate)); + } + } + + protected abstract Element getMainUiElement(); + + protected abstract Element getRaceNameUiElement(); + + protected abstract Element getRaceStateUiElement(); + + protected abstract Element getRaceDateUiElement(); + + protected abstract String getRaceLiveStyleName(); + + protected abstract String getRacePlannedStyleName(); + + protected abstract String getRaceUntrackedStyleName(); + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/places/solutions/SolutionsPlace.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/places/solutions/SolutionsPlace.java index 7fe16bff784..7bb80af79e6 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/places/solutions/SolutionsPlace.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/shared/places/solutions/SolutionsPlace.java @@ -15,7 +15,7 @@ import com.sap.sailing.gwt.ui.client.StringMessages; public class SolutionsPlace extends AbstractBasePlace implements HasLocationTitle, HasMobileVersion { public enum SolutionsNavigationTabs { - SapInSailing, SailingAnalytics, RaceManagerApp, InSightApp, BuoyPingerApp, PostRaceAnalytics, TrainingDiary, SailingSimulator + SapInSailing, SailingAnalytics, RaceManagerApp, InSightApp, BuoyPingerApp, PostRaceAnalytics, SailingSimulator }; private final SolutionsNavigationTabs navigationTab; private final static String PARAM_NAVIGATION_TAB = "navigationTab"; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaRaceStatesComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaRaceStatesComponent.java index aff6371303f..0bdbf0014d8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaRaceStatesComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/regattaoverview/client/RegattaRaceStatesComponent.java @@ -937,6 +937,7 @@ public class RegattaRaceStatesComponent extends SimplePanel implements Component } return null; } + private String createRegattaLink(RegattaOverviewEntryDTO entryDTO) { String leaderboardLink = EntryPointLinkFactory.createLeaderboardTabLink(eventId.toString(), entryDTO.regattaName); return leaderboardLink; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java index 6ed4b53e7e8..f15a302c1d9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/server/HomeServiceUtil.java @@ -228,7 +228,7 @@ public final class HomeServiceUtil { return boatClass == null ? null : boatClass.getName(); } - private static BoatClass getBoatClass(Leaderboard leaderboard) { + public static BoatClass getBoatClass(Leaderboard leaderboard) { if(leaderboard instanceof RegattaLeaderboard) { RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard; BoatClass boatClassFromRegatta = regattaLeaderboard.getRegatta().getBoatClass(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java index 4192a0490cb..da4ce7eee83 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractLeaderboardConfigPanel.java @@ -187,6 +187,9 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement leaderboardTable.setWidth("100%"); leaderboardSelectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() { public void onSelectionChange(SelectionChangeEvent event) { + if (trackedRacesListComposite != null) { + trackedRacesListComposite.setRegattaFilterValue(getSelectedLeaderboardName()); + } leaderboardSelectionChanged(); raceColumnTable.setSelectedLeaderboardName(getSelectedLeaderboardName()); } @@ -706,7 +709,7 @@ public abstract class AbstractLeaderboardConfigPanel extends FormPanel implement * Looks up the regatta for the selected leaderboard by name in {@link #allRegattas} */ protected RegattaDTO getSelectedRegatta() { - final String regattaName = getSelectedLeaderboard().regattaName; + final String regattaName = getSelectedLeaderboard() == null ? "" : getSelectedLeaderboard().regattaName; return getRegattaByName(regattaName); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRegattaWithSeriesAndFleetsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRegattaWithSeriesAndFleetsDialog.java index cfe246a21a6..05892c799fc 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRegattaWithSeriesAndFleetsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractRegattaWithSeriesAndFleetsDialog.java @@ -9,6 +9,7 @@ import com.google.gwt.dom.client.SelectElement; import com.google.gwt.event.dom.client.ChangeEvent; import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.DoubleBox; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.ListBox; @@ -42,7 +43,7 @@ public abstract class AbstractRegattaWithSeriesAndFleetsDialog extends DataEn protected StringMessages stringMessages; private final RegattaDTO regatta; - + protected final BetterDateTimeBox startDateBox; protected final BetterDateTimeBox endDateBox; protected final ListBox scoringSchemeListBox; @@ -50,6 +51,7 @@ public abstract class AbstractRegattaWithSeriesAndFleetsDialog extends DataEn protected final ListBox sailingEventsListBox; protected final CheckBox useStartTimeInferenceCheckBox; protected final CheckBox controlTrackingFromStartAndFinishTimesCheckBox; + protected final DoubleBox buoyZoneRadiusInHullLengthsDoubleBox; protected final ListEditorComposite seriesEditor; private final ListBox rankingMetricListBox; @@ -93,7 +95,10 @@ public abstract class AbstractRegattaWithSeriesAndFleetsDialog extends DataEn controlTrackingFromStartAndFinishTimesCheckBox = createCheckbox(stringMessages.controlTrackingFromStartAndFinishTimes()); controlTrackingFromStartAndFinishTimesCheckBox.ensureDebugId("ControlTrackingFromStartAndFinishTimesCheckBox"); controlTrackingFromStartAndFinishTimesCheckBox.setValue(regatta.controlTrackingFromStartAndFinishTimes); - + + buoyZoneRadiusInHullLengthsDoubleBox = createDoubleBox(regatta.buoyZoneRadiusInHullLengths, 10); + buoyZoneRadiusInHullLengthsDoubleBox.ensureDebugId("BuoyZoneRadiusInHullLengthsDoubleBox"); + courseAreaListBox = createListBox(false); courseAreaListBox.ensureDebugId("CourseAreaListBox"); courseAreaListBox.setEnabled(false); @@ -142,7 +147,7 @@ public abstract class AbstractRegattaWithSeriesAndFleetsDialog extends DataEn if (additionalWidget != null) { panel.add(additionalWidget); } - Grid formGrid = new Grid(8, 2); + Grid formGrid = new Grid(9, 2); panel.add(formGrid); formGrid.setWidget(0, 0, new Label(stringMessages.timeZone() + ":")); @@ -161,6 +166,8 @@ public abstract class AbstractRegattaWithSeriesAndFleetsDialog extends DataEn formGrid.setWidget(6, 1, useStartTimeInferenceCheckBox); formGrid.setWidget(7, 0, new Label(stringMessages.controlTrackingFromStartAndFinishTimes() + ":")); formGrid.setWidget(7, 1, controlTrackingFromStartAndFinishTimesCheckBox); + formGrid.setWidget(8, 0, new Label(stringMessages.buoyZoneRadiusInHullLengths() + ":")); + formGrid.setWidget(8, 1, buoyZoneRadiusInHullLengthsDoubleBox); setupAdditionalWidgetsOnPanel(panel, formGrid); return panel; } @@ -285,6 +292,7 @@ public abstract class AbstractRegattaWithSeriesAndFleetsDialog extends DataEn result.scoringScheme = getSelectedScoringSchemeType(); result.useStartTimeInference = useStartTimeInferenceCheckBox.getValue(); result.controlTrackingFromStartAndFinishTimes = controlTrackingFromStartAndFinishTimesCheckBox.getValue(); + result.buoyZoneRadiusInHullLengths = buoyZoneRadiusInHullLengthsDoubleBox.getValue(); setCourseAreaInRegatta(result); result.series = getSeriesEditor().getValue(); return result; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractTrackedRacesListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractTrackedRacesListComposite.java index 90f8b8625f3..06eacf8ad37 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractTrackedRacesListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/AbstractTrackedRacesListComposite.java @@ -7,8 +7,14 @@ import java.util.Set; import com.google.gwt.cell.client.AbstractCell; import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Style.FontWeight; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.event.dom.client.DomEvent; import com.google.gwt.safehtml.shared.SafeHtml; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SafeHtmlUtils; @@ -20,6 +26,7 @@ import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.HasVerticalAlignment; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; @@ -39,18 +46,19 @@ import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.shared.RegattaDTO; import com.sap.sse.common.Util; +import com.sap.sse.common.filter.Filter; import com.sap.sse.common.util.NaturalComparator; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.MarkedAsyncCallback; import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; -import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; +import com.sap.sse.gwt.client.panels.CustomizableFilterablePanel; import com.sap.sse.gwt.client.shared.components.Component; import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; -public abstract class AbstractTrackedRacesListComposite extends SimplePanel implements Component, - RegattasDisplayer { +public abstract class AbstractTrackedRacesListComposite extends SimplePanel + implements Component, RegattasDisplayer { protected final long DEFAULT_LIVE_DELAY_IN_MILLISECONDS = 5000; @@ -75,10 +83,12 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl private Button btnRefresh; - private LabeledAbstractFilterablePanel filterablePanelRaces; + private CustomizableFilterablePanel filterablePanelRaces; protected TrackedRacesSettings settings; + private ListBox listBoxRegattas; + public static class AnchorCell extends AbstractCell { @Override public void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml safeHtml, SafeHtmlBuilder sb) { @@ -96,6 +106,23 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl this.stringMessages = stringMessages; } + public void setRegattaFilterValue(String regattaName) { + for (int i = 0; i < listBoxRegattas.getItemCount(); i++) { + if (listBoxRegattas.getValue(i).equals(regattaName)) { + listBoxRegattas.setSelectedIndex(i); + // Firing change event on combobox to filter + DomEvent.fireNativeEvent(Document.get().createChangeEvent(), listBoxRegattas); + return; + } + } + + // Set 'All' option in case there are no tracked race related to regatta + if (listBoxRegattas.getItemCount() > 0) { + listBoxRegattas.setSelectedIndex(0); + DomEvent.fireNativeEvent(Document.get().createChangeEvent(), listBoxRegattas); + } + } + protected void createUI() { AdminConsoleTableResources tableResources = GWT.create(AdminConsoleTableResources.class); raceList = new ListDataProvider<>(); @@ -105,33 +132,61 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl setWidget(panel); HorizontalPanel filterPanel = new HorizontalPanel(); panel.add(filterPanel); - Label lblFilterRaces = new Label(stringMessages.filterRacesByName() + ":"); - lblFilterRaces.setWordWrap(false); - filterPanel.setSpacing(5); - filterPanel.add(lblFilterRaces); - filterPanel.setCellVerticalAlignment(lblFilterRaces, HasVerticalAlignment.ALIGN_MIDDLE); + noTrackedRacesLabel = new Label(stringMessages.noRacesYet()); noTrackedRacesLabel.setWordWrap(false); panel.add(noTrackedRacesLabel); + AdminConsoleTableResources tableRes = GWT.create(AdminConsoleTableResources.class); raceTable = new FlushableCellTable(/* pageSize */10000, tableRes); raceTable.ensureDebugId("TrackedRacesCellTable"); - filterablePanelRaces = new LabeledAbstractFilterablePanel(lblFilterRaces, allRaces, raceTable, - raceList) { + Label lblFilterRaces = new Label(stringMessages.filterRaces()+":"); + lblFilterRaces.setWordWrap(false); + lblFilterRaces.getElement().getStyle().setFontWeight(FontWeight.BOLD); + lblFilterRaces.getElement().getStyle().setMarginRight(10, Unit.PX); + filterPanel.add(lblFilterRaces); + filterPanel.setCellVerticalAlignment(lblFilterRaces, HasVerticalAlignment.ALIGN_MIDDLE); + + filterablePanelRaces = new CustomizableFilterablePanel(allRaces, raceTable, raceList) { @Override public List getSearchableStrings(RaceDTO t) { List strings = new ArrayList(); strings.add(t.getName()); strings.add(t.boatClass); strings.add(t.getRegattaName()); - strings.add(t.toString()); return strings; } }; + Label lblFilterByRegatta = new Label(stringMessages.filterByRegatta()); + lblFilterByRegatta.setWordWrap(false); + listBoxRegattas = new ListBox(); + listBoxRegattas.addChangeHandler(new ChangeHandler() { + @Override + public void onChange(ChangeEvent event) { + filterablePanelRaces.filter(); + } + }); + filterablePanelRaces.add(lblFilterByRegatta, listBoxRegattas, new Filter() { + @Override + public boolean matches(RaceDTO t) { + return listBoxRegattas.getSelectedIndex() == 0 /* All */ || Util.equalsWithNull(listBoxRegattas.getSelectedValue(), t.getRegattaName()); + } + @Override + public String getName() { + return "TrackedRacesByRegattaFilter"; + } + }); + + Label lblFilterRacesByName = new Label(stringMessages.filterByNameOrBoatClass() + ":"); + lblFilterRacesByName.setWordWrap(false); + filterablePanelRaces.add(lblFilterRacesByName); + filterablePanelRaces.addDefaultTextBox(); filterablePanelRaces.getTextBox().ensureDebugId("TrackedRacesFilterTextBox"); - + filterPanel.add(filterablePanelRaces); + filterPanel.setCellVerticalAlignment(filterablePanelRaces, HasVerticalAlignment.ALIGN_MIDDLE); + final EntityIdentityComparator entityIdentityComparator = new EntityIdentityComparator() { @Override public boolean representSameEntity(RaceDTO dto1, RaceDTO dto2) { @@ -456,6 +511,9 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl refreshableSelectionModel.clear(); } + /** + * @param regattas + */ @Override public void fillRegattas(Iterable regattas) { makeControlsReactToFillRegattas(regattas); @@ -468,16 +526,28 @@ public abstract class AbstractTrackedRacesListComposite extends SimplePanel impl } List newAllRaces = new ArrayList(); List newAllRegattas = new ArrayList(); + List regattaNames = new ArrayList<>(); for (RegattaDTO regatta : regattas) { newAllRegattas.add(regatta); for (RaceDTO race : regatta.races) { if (race != null) { if (raceIsToBeAddedToList(race)) { + //We need only those regatta names which are available + //at tracking table + if (!regattaNames.contains(regatta.getName())) { + regattaNames.add(regatta.getName()); + } newAllRaces.add(race); } } } } + + listBoxRegattas.clear(); + listBoxRegattas.addItem(stringMessages.all(), ""); + for (String regatta : regattaNames) { + listBoxRegattas.addItem(regatta, regatta); + } allRaces = newAllRaces; filterablePanelRaces.updateAll(allRaces); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java index 185c3442b14..ca595671334 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorEditDialog.java @@ -13,6 +13,7 @@ import com.google.gwt.user.client.ui.ListBox; import com.google.gwt.user.client.ui.SuggestBox; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.dto.BoatClassDTO; import com.sap.sailing.domain.common.dto.BoatDTO; import com.sap.sailing.domain.common.dto.CompetitorDTO; @@ -187,7 +188,7 @@ public class CompetitorEditDialog extends DataEntryDialog { color = new InvalidColor(iae); } } - BoatClassDTO boatClass = new BoatClassDTO(boatClassName.getValue(), 0); + BoatClassDTO boatClass = new BoatClassDTO(boatClassName.getValue(), Distance.NULL); BoatDTO boat = new BoatDTO(name.getText(), sailId.getText()); CompetitorDTO result = new CompetitorDTOImpl(name.getText(), color, email.getText(), /* twoLetterIsoCountryCode */ null, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CopyCourseAndCompetitorsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CopyCourseAndCompetitorsDialog.java index 8be9fd0699f..85aad2b078a 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CopyCourseAndCompetitorsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CopyCourseAndCompetitorsDialog.java @@ -9,6 +9,7 @@ import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.gwt.ui.adminconsole.AbstractLeaderboardConfigPanel.RaceColumnDTOAndFleetDTOWithNameBasedEquality; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -27,7 +28,7 @@ public class CopyCourseAndCompetitorsDialog extends DataEntryDialog races, - String leaderboardName, DialogCallback dialogCallback) { + String leaderboardName, Distance buoyZoneRadius, DialogCallback dialogCallback) { super(stringMessages.selectRaces(), stringMessages.selectRaces(), stringMessages.ok(), stringMessages.cancel(), new Validator() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java index 0ea2ac020f8..aeb4a68096c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CreateRegattaCallback.java @@ -66,7 +66,7 @@ public class CreateRegattaCallback implements DialogCallback{ sailingService.createRegatta(newRegatta.getName(), newRegatta.boatClass==null?null:newRegatta.boatClass.getName(), newRegatta.startDate, newRegatta.endDate, new RegattaCreationParametersDTO(seriesStructure), true, - newRegatta.scoringScheme, newRegatta.defaultCourseAreaUuid, newRegatta.useStartTimeInference, + newRegatta.scoringScheme, newRegatta.defaultCourseAreaUuid, newRegatta.buoyZoneRadiusInHullLengths, newRegatta.useStartTimeInference, newRegatta.controlTrackingFromStartAndFinishTimes, newRegatta.rankingMetricType, new AsyncCallback() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationQRIdentifierDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationQRIdentifierDialog.java index 262c9887b4f..e2151dfc039 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationQRIdentifierDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/DeviceConfigurationQRIdentifierDialog.java @@ -41,7 +41,7 @@ public class DeviceConfigurationQRIdentifierDialog extends DialogBox { Window.alert(stringMessages.notCapableOfGeneratingACodeForIdentifier()); } else if (!identifierBox.getValue().isEmpty() && !serverBox.getValue().isEmpty()) { String apkUrl = getServerUrlWithoutFinalSlash() + rcAppApkPath; - return DeviceConfigurationQRCodeUtils.composeQRContent(URL.encodeQueryString(identifierBox.getValue()), apkUrl, accessToken); + return DeviceConfigurationQRCodeUtils.composeQRContent(URL.encodeQueryString(identifierBox.getValue()), apkUrl, URL.encodeQueryString(accessToken)); } return null; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java index 6ad13ea3c6c..246c5882ff5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventCreateDialog.java @@ -37,14 +37,11 @@ public class EventCreateDialog extends EventDialog { endDateBox = createDateTimeBox(now); endDateBox.setFormat("dd/mm/yyyy hh:ii"); endDateBox.ensureDebugId("EndDateTimeBox"); - officialWebsiteURLEntryField = createTextBox(null); - officialWebsiteURLEntryField.setVisibleLength(50); - baseURLEntryField = createTextBox(null); - baseURLEntryField.setVisibleLength(50); - sailorsInfoWebsiteURLEntryFields = createTextBoxesForLocalesAndDefault(Collections. emptyMap()); isPublicCheckBox = createCheckbox(""); isPublicCheckBox.setValue(false); isPublicCheckBox.ensureDebugId("IsPublicCheckBox"); + baseURLEntryField = createTextBox(null); + baseURLEntryField.setVisibleLength(50); imagesListComposite.fillImages(Collections.emptyList()); videosListComposite.fillVideos(Collections.emptyList()); // add default course area diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java index adadf4aae41..828edc88239 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventDialog.java @@ -5,7 +5,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -32,7 +31,6 @@ import com.sap.sailing.gwt.ui.shared.CourseAreaDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.VenueDTO; -import com.sap.sse.gwt.client.GWTLocaleUtil; import com.sap.sse.gwt.client.IconResources; import com.sap.sse.gwt.client.controls.listedit.GenericStringListInlineEditorComposite; import com.sap.sse.gwt.client.controls.listedit.StringConstantsListEditorComposite; @@ -48,14 +46,13 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap protected BetterDateTimeBox endDateBox; protected CheckBox isPublicCheckBox; protected UUID id; - protected TextBox officialWebsiteURLEntryField; protected TextBox baseURLEntryField; - protected Map sailorsInfoWebsiteURLEntryFields = new HashMap<>(); protected CourseAreaListInlineEditorComposite courseAreaNameList; protected StringConstantsListEditorComposite leaderboardGroupList; protected Map availableLeaderboardGroupsByName; protected ImagesListComposite imagesListComposite; protected VideosListComposite videosListComposite; + protected ExternalLinksComposite externalLinksComposite; protected static class EventParameterValidator implements Validator { @@ -162,6 +159,7 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap imagesListComposite = new ImagesListComposite(sailingService, stringMessages); videosListComposite = new VideosListComposite(stringMessages); + externalLinksComposite = new ExternalLinksComposite(stringMessages); } @Override @@ -176,13 +174,9 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap } result.setName(nameEntryField.getText()); result.setDescription(descriptionEntryField.getText()); - result.setOfficialWebsiteURL(officialWebsiteURLEntryField.getText().trim().isEmpty() ? null : officialWebsiteURLEntryField.getText().trim()); + result.setOfficialWebsiteURL(externalLinksComposite.getOfficialWebsiteURLValue()); result.setBaseURL(baseURLEntryField.getText().trim().isEmpty() ? null : baseURLEntryField.getText().trim()); - for (Map.Entry sailorsInfoWebsiteUrlEntry : sailorsInfoWebsiteURLEntryFields.entrySet()) { - TextBox sailorsInfoWebsiteURLEntryField = sailorsInfoWebsiteUrlEntry.getValue(); - String sailorsInfoWebsiteURL = sailorsInfoWebsiteURLEntryField.getText().trim(); - result.setSailorsInfoWebsiteURL(sailorsInfoWebsiteUrlEntry.getKey(), sailorsInfoWebsiteURL.isEmpty() ? null : sailorsInfoWebsiteURLEntryField.getText().trim()); - } + result.setSailorsInfoWebsiteURLs(externalLinksComposite.getSailorsInfoWebsiteURLs()); result.startDate = startDateBox.getValue(); result.endDate = endDateBox.getValue(); result.isPublic = isPublicCheckBox.getValue(); @@ -206,7 +200,7 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap if (additionalWidget != null) { panel.add(additionalWidget); } - Grid formGrid = new Grid(10 + GWTLocaleUtil.getLanguageCountWithDefault(), 2); + Grid formGrid = new Grid(8, 2); int rowIndex = 0; formGrid.setWidget(rowIndex, 0, new Label(stringMessages.name() + ":")); formGrid.setWidget(rowIndex++, 1, nameEntryField); @@ -222,24 +216,18 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap formGrid.setWidget(rowIndex++, 1, endDateBox); formGrid.setWidget(rowIndex, 0, new Label(stringMessages.isPublic() + ":")); formGrid.setWidget(rowIndex++, 1, isPublicCheckBox); - formGrid.setWidget(rowIndex, 0, new Label(stringMessages.eventOfficialWebsiteURL() + ":")); - formGrid.setWidget(rowIndex++, 1, officialWebsiteURLEntryField); formGrid.setWidget(rowIndex, 0, new Label(stringMessages.eventBaseURL() + ":")); formGrid.setWidget(rowIndex++, 1, baseURLEntryField); - for (Map.Entry sailorsInfoWebsiteUrlEntry : sailorsInfoWebsiteURLEntryFields.entrySet()) { - String suffix = " ["+ (sailorsInfoWebsiteUrlEntry.getKey() == null ? stringMessages.defaultLocale() + "*" : sailorsInfoWebsiteUrlEntry.getKey()) + "]"; - formGrid.setWidget(rowIndex, 0, new Label(stringMessages.eventSailorsInfoWebsiteURL() + suffix + ":")); - formGrid.setWidget(rowIndex, 1, sailorsInfoWebsiteUrlEntry.getValue()); - rowIndex++; - } - formGrid.setText(rowIndex, 1, "[*] " + stringMessages.defaultSailorsInfoLinkInfoText()); TabLayoutPanel tabPanel = new TabLayoutPanel(30, Unit.PX); tabPanel.ensureDebugId("EventDialogTabs"); - tabPanel.setHeight("525px"); + tabPanel.setHeight("400px"); panel.add(tabPanel); final ScrollPanel eventTab = new ScrollPanel(formGrid); eventTab.ensureDebugId("EventTab"); tabPanel.add(eventTab, stringMessages.event()); + final ScrollPanel externalLinksCompositeTab = new ScrollPanel(externalLinksComposite); + externalLinksCompositeTab.ensureDebugId("ExternalLinksCompositeTab"); + tabPanel.add(externalLinksCompositeTab, stringMessages.externalLinks()); final ScrollPanel leaderboardGroupTab = new ScrollPanel(leaderboardGroupList); leaderboardGroupTab.ensureDebugId("LeaderboardGroupsTab"); tabPanel.add(leaderboardGroupTab, stringMessages.leaderboardGroups()); @@ -259,14 +247,4 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap protected FocusWidget getInitialFocusWidget() { return nameEntryField; } - - protected Map createTextBoxesForLocalesAndDefault(Map initialValues) { - Map result = new LinkedHashMap<>(); - for(String localeName : GWTLocaleUtil.getAvailableLocalesAndDefault()) { - TextBox sailorsInfoWebsiteURLEntryField = createTextBox(initialValues.get(localeName)); - sailorsInfoWebsiteURLEntryField.setVisibleLength(50); - result.put(localeName, sailorsInfoWebsiteURLEntryField); - } - return result; - } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java index a426618a9bd..e85f8f25bf0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/EventEditDialog.java @@ -27,12 +27,9 @@ public class EventEditDialog extends EventDialog { endDateBox.setFormat("dd/mm/yyyy hh:ii"); isPublicCheckBox = createCheckbox(""); isPublicCheckBox.setValue(event.isPublic); - id = event.id; - officialWebsiteURLEntryField = createTextBox(event.getOfficialWebsiteURL()); - officialWebsiteURLEntryField.setVisibleLength(50); baseURLEntryField = createTextBox(event.getBaseURL()); baseURLEntryField.setVisibleLength(50); - sailorsInfoWebsiteURLEntryFields = createTextBoxesForLocalesAndDefault(event.getSailorsInfoWebsiteURLs()); + id = event.id; courseAreaNameList.setValue(new ArrayList<>(event.venue.getCourseAreas())); List leaderboardGroupNames = new ArrayList<>(); for(LeaderboardGroupDTO leaderboardGroupDTO: event.getLeaderboardGroups()) { @@ -41,5 +38,6 @@ public class EventEditDialog extends EventDialog { leaderboardGroupList.setValue(leaderboardGroupNames); imagesListComposite.fillImages(event.getImages()); videosListComposite.fillVideos(event.getVideos()); + externalLinksComposite.fillExternalLinks(event); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExternalLinksComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExternalLinksComposite.java new file mode 100644 index 00000000000..9cc9e1fa51d --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/ExternalLinksComposite.java @@ -0,0 +1,88 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.i18n.client.LocaleInfo; +import com.google.gwt.user.client.ui.Composite; +import com.google.gwt.user.client.ui.Grid; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.TextBox; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sse.gwt.client.GWTLocaleUtil; + +public class ExternalLinksComposite extends Composite { + + private Map sailorsInfoWebsiteURLEntryFields = new HashMap<>(); + private TextBox officialWebsiteURLEntryField; + + public ExternalLinksComposite(StringMessages stringMessages) { + VerticalPanel panel = new VerticalPanel(); + panel.setWidth("100%"); + + officialWebsiteURLEntryField = new TextBox(); + officialWebsiteURLEntryField.setVisibleLength(50); + sailorsInfoWebsiteURLEntryFields = createTextBoxesForLocalesAndDefault(Collections. emptyMap()); + + int rowIndex = 0; + Grid formGrid = new Grid(GWTLocaleUtil.getLanguageCountWithDefault() + 3, 2); + formGrid.setWidget(rowIndex, 0, new Label(stringMessages.eventOfficialWebsiteURL() + ":")); + formGrid.setWidget(rowIndex++, 1, officialWebsiteURLEntryField); + formGrid.setWidget(rowIndex++, 0, new Label(stringMessages.eventSailorsInfoWebsiteURL())); + for (Map.Entry sailorsInfoWebsiteUrlEntry : sailorsInfoWebsiteURLEntryFields.entrySet()) { + String locale = sailorsInfoWebsiteUrlEntry.getKey(); + String suffix = locale == null ? "*" : "[" + locale + "]"; + String text = locale == null ? stringMessages.urlForAllLanguages() + : stringMessages.urlForLanguage(LocaleInfo.getLocaleNativeDisplayName(locale)); + Label urlLabel = new Label(text + " " + suffix + ":"); + urlLabel.getElement().getStyle().setMarginLeft(10, Unit.PX); + formGrid.setWidget(rowIndex, 0, urlLabel); + formGrid.setWidget(rowIndex, 1, sailorsInfoWebsiteUrlEntry.getValue()); + rowIndex++; + } + + panel.add(formGrid); + panel.add(new Label("[*] " + stringMessages.defaultSailorsInfoLinkInfoText())); + initWidget(panel); + } + + public String getOfficialWebsiteURLValue() { + String value = officialWebsiteURLEntryField.getText().trim(); + return value.isEmpty() ? null : value; + } + + public Map getSailorsInfoWebsiteURLs() { + Map sailorsInfoWebsiteURLsMap = new HashMap<>(); + for (Map.Entry sailorsInfoWebsiteUrlEntry : sailorsInfoWebsiteURLEntryFields.entrySet()) { + TextBox sailorsInfoWebsiteURLEntryField = sailorsInfoWebsiteUrlEntry.getValue(); + String sailorsInfoWebsiteURL = sailorsInfoWebsiteURLEntryField.getText().trim(); + sailorsInfoWebsiteURLsMap.put(sailorsInfoWebsiteUrlEntry.getKey(), + sailorsInfoWebsiteURL.isEmpty() ? null : sailorsInfoWebsiteURL); + } + return sailorsInfoWebsiteURLsMap; + } + + public void fillExternalLinks(EventDTO event) { + Map initialValues = event.getSailorsInfoWebsiteURLs(); + for (String localeName : GWTLocaleUtil.getAvailableLocalesAndDefault()) { + sailorsInfoWebsiteURLEntryFields.get(localeName).setValue(initialValues.get(localeName)); + } + officialWebsiteURLEntryField.setValue(event.getOfficialWebsiteURL()); + } + + private Map createTextBoxesForLocalesAndDefault(Map initialValues) { + Map result = new LinkedHashMap<>(); + for (String localeName : GWTLocaleUtil.getAvailableLocalesAndDefault()) { + TextBox sailorsInfoWebsiteURLEntryField = new TextBox(); + sailorsInfoWebsiteURLEntryField.setValue(initialValues.get(localeName)); + sailorsInfoWebsiteURLEntryField.setVisibleLength(50); + result.put(localeName, sailorsInfoWebsiteURLEntryField); + } + return result; + } +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java index 49a48b0daa0..879284e8fad 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/LeaderboardConfigPanel.java @@ -690,6 +690,7 @@ TrackedRaceChangedListener, LeaderboardsDisplayer { selectedLeaderBoardPanel.setVisible(true); selectedLeaderBoardPanel.setCaptionText("Details of leaderboard '" + selectedLeaderboard.name + "'"); if (!selectedLeaderboard.type.isMetaLeaderboard()) { + trackedRacesListComposite.setRegattaFilterValue(selectedLeaderboard.regattaName); trackedRacesCaptionPanel.setVisible(true); } addRaceColumnsButton.setVisible(!selectedLeaderboard.type.isRegattaLeaderboard()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java index 62defaaa2a0..3888df7821e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaDetailsComposite.java @@ -49,7 +49,7 @@ public class RegattaDetailsComposite extends Composite { private final ErrorReporter errorReporter; private final StringMessages stringMessages; private final RegattaRefresher regattaRefresher; - + private final Label regattaId; private final Label regattaName; private final Label startDate; @@ -61,7 +61,8 @@ public class RegattaDetailsComposite extends Composite { private final Label useStartTimeInference; private final Label controlTrackingFromStartAndFinishTimes; private final Label configuration; - + private final Label buoyZoneRadiusInHullLengths; + private final SelectionModel seriesSelectionModel; private final CellTable seriesTable; private ListDataProvider seriesListDataProvider; @@ -79,7 +80,7 @@ public class RegattaDetailsComposite extends Composite { VerticalPanel vPanel = new VerticalPanel(); mainPanel.add(vPanel); - int rows = 11; + int rows = 12; Grid grid = new Grid(rows, 2); vPanel.add(grid); @@ -92,10 +93,11 @@ public class RegattaDetailsComposite extends Composite { defaultCourseArea = createLabelAndValueWidget(grid, currentRow++, stringMessages.courseArea(), "CourseAreaLabel"); useStartTimeInference = createLabelAndValueWidget(grid, currentRow++, stringMessages.useStartTimeInference(), "UseStartTimeInferenceLabel"); controlTrackingFromStartAndFinishTimes = createLabelAndValueWidget(grid, currentRow++, stringMessages.controlTrackingFromStartAndFinishTimes(), "UseStartTimeInferenceLabel"); + buoyZoneRadiusInHullLengths = createLabelAndValueWidget(grid, currentRow++, stringMessages.buoyZoneRadiusInHullLengths(), "BuoyZoneRadiusInHullLengthsLabel"); configuration = createLabelAndValueWidget(grid, currentRow++, stringMessages.racingProcedureConfiguration(), "RacingProcedureLabel"); scoringSystem = createLabelAndValueWidget(grid, currentRow++, stringMessages.scoringSystem(), "ScoringSystemLabel"); rankingMetric = createLabelAndValueWidget(grid, currentRow++, stringMessages.rankingMetric(), "RankingMetricLabel"); - + seriesTable = createRegattaSeriesTable(); seriesTable.ensureDebugId("SeriesCellTable"); seriesSelectionModel = new SingleSelectionModel(); @@ -398,6 +400,8 @@ public class RegattaDetailsComposite extends Composite { defaultCourseArea.setText(regatta.defaultCourseAreaUuid == null ? "" : regatta.defaultCourseAreaName); useStartTimeInference.setText(regatta.useStartTimeInference ? stringMessages.yes() : stringMessages.no()); controlTrackingFromStartAndFinishTimes.setText(regatta.controlTrackingFromStartAndFinishTimes ? stringMessages.yes() : stringMessages.no()); + buoyZoneRadiusInHullLengths.setText(String.valueOf(regatta.buoyZoneRadiusInHullLengths)); + if (regatta.configuration != null) { configuration.setText(stringMessages.configured()); } else { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java index 50fff1a62c8..03f6c5872fc 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaListComposite.java @@ -290,7 +290,7 @@ public class RegattaListComposite extends Composite implements RegattasDisplayer final RegattaIdentifier regattaName = new RegattaName(editedRegatta.getName()); sailingService.updateRegatta(regattaName, editedRegatta.startDate, editedRegatta.endDate, editedRegatta.defaultCourseAreaUuid, - editedRegatta.configuration, editedRegatta.useStartTimeInference, editedRegatta.controlTrackingFromStartAndFinishTimes, + editedRegatta.configuration, editedRegatta.buoyZoneRadiusInHullLengths, editedRegatta.useStartTimeInference, editedRegatta.controlTrackingFromStartAndFinishTimes, new MarkedAsyncCallback(new AsyncCallback() { @Override public void onFailure(Throwable caught) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLogTrackingDeviceMappingsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLogTrackingDeviceMappingsDialog.java index 8c18dcc2079..adf3fffae0f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLogTrackingDeviceMappingsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaLogTrackingDeviceMappingsDialog.java @@ -41,7 +41,7 @@ import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.shared.DeviceMappingDTO; import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sailing.gwt.ui.shared.TypedDeviceMappingDTO; -import com.sap.sse.common.filter.AbstractListFilter; +import com.sap.sse.common.filter.Filter; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.dialog.DataEntryDialog; import com.sap.sse.gwt.client.panels.LabeledAbstractFilterablePanel; @@ -179,30 +179,19 @@ public class RegattaLogTrackingDeviceMappingsDialog extends DataEntryDialog() { + @Override + public boolean matches(DeviceMappingDTO dm) { + return showPingMappingsCb.getValue() || !"PING".equals(dm.deviceIdentifier.deviceType); + } @Override - protected AbstractListFilter getFilterer() { - final AbstractListFilter baseFilterer = super.getFilterer(); - return new AbstractListFilter() { - @Override - public Iterable getStrings(DeviceMappingDTO t) { - return baseFilterer.getStrings(t); - } - - @Override - public Iterable applyFilter(Iterable keywords, - Iterable all) { - final List result = new ArrayList<>(); - for (final DeviceMappingDTO dm : super.applyFilter(keywords, all)) { - if (showPingMappingsCb.getValue() || !"PING".equals(dm.deviceIdentifier.deviceType)) { - result.add(dm); - } - } - return result; - } - }; + public String getName() { + return "ShowPingMappingsFilter"; } - }; + }); + deviceMappingTable.registerSelectionModelOnNewDataProvider(filterField.getAllListDataProvider()); final VerticalPanel vp = new VerticalPanel(); deviceMappingPanel.add(vp); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsCreateDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsCreateDialog.java index bd7ad5495bc..31a0137c8bb 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsCreateDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsCreateDialog.java @@ -12,6 +12,7 @@ import com.google.gwt.user.client.ui.FocusWidget; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.TabPanel; import com.google.gwt.user.client.ui.VerticalPanel; +import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Series; import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.dto.FleetDTO; @@ -72,6 +73,10 @@ public class RegattaWithSeriesAndFleetsCreateDialog extends RegattaWithSeriesAnd errorMessage = stringMessages.regattaWithThisNameAlreadyExists(); } + if (regattaToValidate.buoyZoneRadiusInHullLengths == null) { + errorMessage = stringMessages.incorrectValueForRegattaBuoyZoneRadiusInHullLengths(); + } + if (errorMessage == null) { List seriesToValidate = regattaToValidate.series; int index = 0; @@ -109,6 +114,7 @@ public class RegattaWithSeriesAndFleetsCreateDialog extends RegattaWithSeriesAnd List existingEvents, EventDTO correspondingEvent, StringMessages stringMessages, DialogCallback callback) { super(new RegattaDTO(), Collections.emptySet(), existingEvents, correspondingEvent, stringMessages.addRegatta(), stringMessages.ok(), stringMessages, new RegattaParameterValidator(stringMessages, existingRegattas), callback); + buoyZoneRadiusInHullLengthsDoubleBox.setValue(Regatta.DEFAULT_BUOY_ZONE_RADIUS_IN_HULL_LENGTHS); SeriesDTO series = new SeriesDTO(); series.setName(Series.DEFAULT_NAME); series.setMedal(false); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java index 967aa8bff78..e3192b400f0 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsDialog.java @@ -7,6 +7,7 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.SuggestBox; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.dto.BoatClassDTO; import com.sap.sailing.gwt.common.client.suggestion.BoatClassMasterdataSuggestOracle; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -47,7 +48,7 @@ public abstract class RegattaWithSeriesAndFleetsDialog extends AbstractRegattaWi protected RegattaDTO getResult() { RegattaDTO result = getRegattaDTO(); result.setName(nameEntryField.getText().trim()); // trim to particularly avoid trailing blanks - result.boatClass = new BoatClassDTO(boatClassEntryField.getText(), 0.0); + result.boatClass = new BoatClassDTO(boatClassEntryField.getText(), Distance.NULL); return result; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsEditDialog.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsEditDialog.java index 65034bd10a8..b6ea7c13731 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsEditDialog.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RegattaWithSeriesAndFleetsEditDialog.java @@ -25,10 +25,27 @@ public class RegattaWithSeriesAndFleetsEditDialog extends RegattaWithSeriesAndFl private RegattaConfigurationDTO currentRegattaConfiguration; + protected static class RegattaParameterValidator implements Validator { + private StringMessages stringMessages; + + public RegattaParameterValidator(StringMessages stringMessages) { + this.stringMessages = stringMessages; + } + + @Override + public String getErrorMessage(RegattaDTO regattaToValidate) { + String errorMessage = null; + if (regattaToValidate.buoyZoneRadiusInHullLengths == null) { + errorMessage = stringMessages.incorrectValueForRegattaBuoyZoneRadiusInHullLengths(); + } + return errorMessage; + } + } + public RegattaWithSeriesAndFleetsEditDialog(RegattaDTO regatta, Collection existingRegattas, List existingEvents, EventDTO correspondingEvent, final StringMessages stringMessages, DialogCallback callback) { super(regatta, regatta.series, existingEvents, correspondingEvent, stringMessages.editRegatta(), stringMessages.ok(), stringMessages, - null, callback); + new RegattaParameterValidator(stringMessages), callback); ensureDebugId("RegattaWithSeriesAndFleetsEditDialog"); currentRegattaConfiguration = regatta.configuration; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java index 4c424bd5b85..c250564b8c5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SmartphoneTrackingEventManagementPanel.java @@ -31,6 +31,7 @@ import com.google.gwt.view.client.ListDataProvider; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.sap.sailing.domain.common.CourseDesignerMode; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaIdentifier; @@ -49,6 +50,7 @@ import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.controls.FlushableCellTable; import com.sap.sailing.gwt.ui.client.shared.controls.SelectionCheckboxColumn; +import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapSettings; import com.sap.sailing.gwt.ui.shared.ControlPointDTO; import com.sap.sailing.gwt.ui.shared.DeviceConfigurationDTO; import com.sap.sailing.gwt.ui.shared.EventDTO; @@ -336,8 +338,11 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC List races = new ArrayList<>(raceColumnTable.getDataProvider().getList()); races.remove(raceColumnDTOAndFleetDTO); + Distance buoyZoneRadius = getSelectedRegatta() == null + ? RaceMapSettings.DEFAULT_BUOY_ZONE_RADIUS + : getSelectedRegatta().getCalculatedBuoyZoneRadius(); new CopyCourseAndCompetitorsDialog(sailingService, errorReporter, stringMessages, races, - leaderboardName, new DialogCallback() { + leaderboardName, buoyZoneRadius, new DialogCallback() { @Override public void ok(CourseAndCompetitorCopyOperation operation) { operation.perform(leaderboardName, raceColumnDTOAndFleetDTO, /* onSuccessCallback */ new Runnable() { @@ -518,6 +523,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC selectedLeaderBoardPanel.setVisible(true); selectedLeaderBoardPanel.setCaptionText("Details of leaderboard '" + selectedLeaderboard.name + "'"); if (!selectedLeaderboard.type.isMetaLeaderboard()) { + trackedRacesListComposite.setRegattaFilterValue(selectedLeaderboard.regattaName); trackedRacesCaptionPanel.setVisible(true); } sailingService.doesRegattaLogContainCompetitors(((StrippedLeaderboardDTO) leaderboardSelectionModel.getSelectedSet().toArray()[0]).name, new RegattaLogCallBack()); @@ -618,7 +624,7 @@ public class SmartphoneTrackingEventManagementPanel extends AbstractLeaderboardC DeviceConfigurationDTO.RegattaConfigurationDTO configuration) { final RegattaIdentifier regattaIdentifier = new RegattaName(regatta.getName()); sailingService.updateRegatta(regattaIdentifier, regatta.startDate, regatta.endDate, - regatta.defaultCourseAreaUuid, configuration, regatta.useStartTimeInference, + regatta.defaultCourseAreaUuid, configuration, regatta.buoyZoneRadiusInHullLengths, regatta.useStartTimeInference, regatta.controlTrackingFromStartAndFinishTimes, new MarkedAsyncCallback(new AsyncCallback() { @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java index 0a91d0d1f86..e9aea45d22e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/StructureImportManagementPanel.java @@ -35,6 +35,7 @@ import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.sap.sailing.domain.common.RankingMetrics; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.dto.BoatClassDTO; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.gwt.ui.adminconsole.StructureImportListComposite.RegattaStructureProvider; import com.sap.sailing.gwt.ui.client.RegattaRefresher; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; @@ -341,7 +342,7 @@ public class StructureImportManagementPanel extends SimplePanel implements Regat */ private RegattaDTO createRegattaDefaults(RegattaDTO regatta) { RegattaDTO result = new RegattaDTO("Default", ScoringSchemeType.LOW_POINT); - result.boatClass = new BoatClassDTO(BoatClassDTO.DEFAULT_NAME, /* hull length in meters */ 5); + result.boatClass = new BoatClassDTO(BoatClassDTO.DEFAULT_NAME, /* hull length */ new MeterDistance(5)); result.series = regatta.series; result.rankingMetricType = RankingMetrics.ONE_DESIGN; return result; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java index 70a1a95e498..b498b2a6266 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java @@ -241,7 +241,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane filterPanel.setSpacing(5); trackableRacesPanel.add(filterPanel); - Label lblFilterEvents = new Label(stringConstants.filterRacesByName() + ":"); + Label lblFilterEvents = new Label(stringConstants.filterRaces() + ":"); filterPanel.add(lblFilterEvents); filterPanel.setCellVerticalAlignment(lblFilterEvents, HasVerticalAlignment.ALIGN_MIDDLE); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingReplayConnectorPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingReplayConnectorPanel.java index af717b7c9f0..afcb3c08683 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingReplayConnectorPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingReplayConnectorPanel.java @@ -172,7 +172,7 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane filterPanel.setSpacing(5); racesPanel.add(filterPanel); - Label lblFilterEvents = new Label(stringMessages.filterRacesByName()+ ":"); + Label lblFilterEvents = new Label(stringMessages.filterRaces()+ ":"); filterPanel.add(lblFilterEvents); filterPanel.setCellVerticalAlignment(lblFilterEvents, HasVerticalAlignment.ALIGN_MIDDLE); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java index 5e4e5272b6d..7b921543829 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java @@ -285,7 +285,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { layoutTable.setWidget(++row, 1, ignoreTracTracMarkPassingsCheckbox); // Filter - Label racesFilterLabel = new Label(stringMessages.filterRacesByName() + ":"); + Label racesFilterLabel = new Label(stringMessages.filterRaces() + ":"); AdminConsoleTableResources tableResources = GWT.create(AdminConsoleTableResources.class); racesTable = new FlushableCellTable(10000, tableResources); racesTable.ensureDebugId("TrackableRacesCellTable"); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java index 2d4272927b8..5dc61e13e33 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java @@ -235,7 +235,7 @@ public class RaceTimePanel extends TimePanel implements R // neither do we know about the end time if both, startOfTracking and endOfTracking are null // and we have neither a valid newestTrackingEvent nor an endOfRace value; if we do, we would // have to be before the later one plus some leeway. - final Date endTimeOfLivePeriod; + Date endTimeOfLivePeriod; if (lastRaceTimesInfo.startOfTracking == null && lastRaceTimesInfo.endOfTracking == null) { Date latestOfNewestTrackingEventAndEndOfRace = lastRaceTimesInfo.newestTrackingEvent; if (latestOfNewestTrackingEventAndEndOfRace == null || (lastRaceTimesInfo.endOfRace != null && lastRaceTimesInfo.endOfRace.after(latestOfNewestTrackingEventAndEndOfRace))) { @@ -244,7 +244,15 @@ public class RaceTimePanel extends TimePanel implements R endTimeOfLivePeriod = latestOfNewestTrackingEventAndEndOfRace == null ? null : new Date(latestOfNewestTrackingEventAndEndOfRace.getTime() + RaceTimesCalculationUtil.TIME_AFTER_LIVE); } else { - endTimeOfLivePeriod = lastRaceTimesInfo.endOfTracking == null ? null : lastRaceTimesInfo.endOfTracking; + endTimeOfLivePeriod = lastRaceTimesInfo.endOfTracking == null ? null : lastRaceTimesInfo.endOfTracking; + if (lastRaceTimesInfo.endOfRace != null) { + // this is a failsafe, if the tracking was never finsihed, the race was always assumed to be live. + Date latestAllowedTime = new Date( + lastRaceTimesInfo.endOfRace.getTime() + RaceTimesCalculationUtil.MAX_TIME_AFTER_RACE_END); + if (endTimeOfLivePeriod == null || endTimeOfLivePeriod.after(latestAllowedTime)) { + endTimeOfLivePeriod = latestAllowedTime; + } + } } isLiveModeToBeMadePossible = endTimeOfLivePeriod == null /* meaning we don't know an end time */ || endTimeOfLivePeriod.getTime() >= liveTimePointInMillis; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index edc40108a01..4b17e2bcd66 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -215,7 +215,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS RegattaDTO createRegatta(String regattaName, String boatClassName, Date startDate, Date endDate, RegattaCreationParametersDTO seriesNamesWithFleetNamesAndFleetOrderingAndMedal, boolean persistent, - ScoringSchemeType scoringSchemeType, UUID defaultCourseAreaId, boolean useStartTimeInference, + ScoringSchemeType scoringSchemeType, UUID defaultCourseAreaId, Double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes, RankingMetrics rankingMetricType); void removeRegatta(RegattaIdentifier regattaIdentifier); @@ -225,7 +225,7 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS void removeRegattas(Collection regattas); void updateRegatta(RegattaIdentifier regattaIdentifier, Date startDate, Date endDate, UUID defaultCourseAreaUuid, - RegattaConfigurationDTO regattaConfiguration, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes); + RegattaConfigurationDTO regattaConfiguration, Double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes); List addRaceColumnsToSeries(RegattaIdentifier regattaIdentifier, String seriesName, List> columnNames); @@ -528,7 +528,11 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS PolarSheetsXYDiagramData createXYDiagramForBoatClass(String itemText); - Map getCompetitorMarkPassings(RegattaAndRaceIdentifier race, CompetitorDTO competitorDTO); + /** + * @see SailingServiceAsync#getCompetitorMarkPassings(RegattaAndRaceIdentifier, CompetitorDTO, boolean, com.google.gwt.user.client.rpc.AsyncCallback) + */ + Map getCompetitorMarkPassings(RegattaAndRaceIdentifier race, CompetitorDTO competitorDTO, + boolean waitForCalculations); /** * Obtains fixed mark passings and mark passing suppressions from the race log identified by diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index 11109ad8e55..28533bf2032 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -441,7 +441,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan void createRegatta(String regattaName, String boatClassName, Date startDate, Date endDate, RegattaCreationParametersDTO seriesNamesWithFleetNamesAndFleetOrderingAndMedal, boolean persistent, - ScoringSchemeType scoringSchemeType, UUID defaultCourseAreaId, boolean useStartTimeInference, + ScoringSchemeType scoringSchemeType, UUID defaultCourseAreaId, Double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes, RankingMetrics rankingMetricType, AsyncCallback callback); @@ -528,7 +528,7 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan void storeSwissTimingArchiveConfiguration(String swissTimingUrl, AsyncCallback asyncCallback); void updateRegatta(RegattaIdentifier regattaIdentifier, Date startDate, Date endDate, UUID defaultCourseAreaUuid, - RegattaConfigurationDTO regattaConfiguration, boolean useStartTimeInference, + RegattaConfigurationDTO regattaConfiguration, Double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes, AsyncCallback callback); /** @@ -695,9 +695,14 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan * the URL pointing to a Manage2Sail JSON document that contains the link to the XRR document */ void getRegattas(String manage2SailJsonUrl, AsyncCallback> asyncCallback); - + + /** + * Returns mark passings for the competitor. Using the {@code waitForCalculations} parameter callers can control + * whether to obtain a snapshot immediately of wait for pending updates. Waiting may be desirable, e.g., when having + * submitted a fixed mark passing into a race log which triggers the re-calculations asynchronously. + */ void getCompetitorMarkPassings(RegattaAndRaceIdentifier race, CompetitorDTO competitorDTO, - AsyncCallback> callback); + boolean waitForCalculations, AsyncCallback> callback); void getCompetitorRaceLogMarkPassingData(String leaderboardName, String raceColumnName, String fleetName, CompetitorDTO competitor, AsyncCallback> callback); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index ababa9b7772..b3fabd259e2 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -205,7 +205,9 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String mark(); String otherManeuver(); String douglasPeuckerPoints(); - String filterRacesByName(); + String filterRaces(); + String filterByNameOrBoatClass(); + String filterByRegatta(); String filterMediaByName(); String actionEdit(); String actionEditScores(); @@ -1550,7 +1552,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String playstoreBadge(); String appstoreSapSailInsight(); - String appstoreStgTrainingstagebuch(); String appstoreBadgeSuffix(); String markFixes(); @@ -1701,7 +1702,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String solutionsInSightHeadline(); String solutionsBuoyPingerHeadline(); String solutionsPostHeadline(); - String solutionsTrainingHeadline(); String solutionsSimulatorHeadline(); String sapInSailing(); String contentSapInSailing1(); @@ -1732,12 +1732,16 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, String postRaceAnalytics(); String contentPostRaceAnalytics1(); String contentPostRaceAnalytics2(); - String i18nDeSolutionsTrainingHeadline(); - String i18nDeSolutionsTrainingText(); String strategySimulator(); String contentStrategySimulator(); String strategySimulatorReadMore(); String impressum(); String lastFix(); String filterDeviceMappings(); + String raceStartAnalysis(); + String urlForLanguage(String language); + String urlForAllLanguages(); + String externalLinks(); + String buoyZoneRadiusInHullLengths(); + String incorrectValueForRegattaBuoyZoneRadiusInHullLengths(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index fb88d793607..67f1ad8ea60 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -206,7 +206,9 @@ markPassing=Mark passing mark=Mark otherManeuver=Unknown maneuver douglasPeuckerPoints=Douglas peucker points -filterRacesByName=Filter races by name +filterRaces=Filter races +filterByNameOrBoatClass=by name or boat class +filterByRegatta=by regatta filterMediaByName=Filter media by name trackSettings=Track settings actionEdit=Edit @@ -1517,7 +1519,6 @@ playstoreRacecommitteeApp=https://play.google.com/store/apps/details?id=com.sap. playStoreBuoyPingerApp=https://play.google.com/store/apps/details?id=com.sap.sailing.android.buoy.positioning.app playstoreBadge=https://play.google.com/intl/en/badges/images/generic/en-play-badge.png appstoreSapSailInsight=http://appstore.com/sapsailinsight -appstoreStgTrainingstagebuch=http://appstore.com/stgtrainingstagebuch appstoreBadgeSuffix=_en distanceFromAveragePosition=Distance from average position useATouchOptimizedUI=Use a touch optimized UI to move the fix @@ -1616,10 +1617,12 @@ raceAnalysis=Race analysis startAnalysis=Start analysis winningLanes=Winning lanes maximumNumberOfDiscards=Maximum number of discards +externalLinks=External links +urlForAllLanguages=URL for all languages +urlForLanguage=URL for language ''{0}'' notificationRegattaConfigurationUpdatedUsingByMarks=Active regatta configuration was set, using the "By Marks" course designer. warningOverrideRegattaConfigurationCourseDesignerToByMarks=Active regatta configuration already exists, but with the wrong course designer setting.\nWould you like to override course designer setting to "By Marks"? allRacesHaveBeenDenoted=All races have been denoted for racelog tracking. - chartSelectionHint=Hint: You can also use the leaderboard (on the LEADERBOARD tab) to select the competitors displayed in the chart. chooseALanguage=Choose a language solutions=Solutions @@ -1674,7 +1677,6 @@ solutionsRaceHeadline=SAP Sailing Race Manager solutionsInSightHeadline=SAP Sail InSight solutionsBuoyPingerHeadline=SAP Sailing Buoy Pinger solutionsPostHeadline=Post-Race Analytics -solutionsTrainingHeadline=Training Diary solutionsSimulatorHeadline=Strategy Simulator sapInSailing=SAP in Sailing contentSapInSailing1=Sailing provides the perfect platform for SAP to showcase solutions and help the sport run like never before. SAP’s involvement in the sport has transformed the sailing experience by providing tools, which: @@ -1705,11 +1707,12 @@ sapSailingBuoyPingerReadMore=Learn More postRaceAnalytics=Post-Race Analytics contentPostRaceAnalytics1=Access to data collected at sailing events gives sailors the opportunity to assess and analyse their performances to help develop strategies and sail better. contentPostRaceAnalytics2=SAP Business Analytics solutions provide sailors, coaches, fans and media with tools to browse and analyse historical data across many regattas and races. SAP HANA allows them to learn about performance patterns, tendencies and sailor profiles in a simple, fast and efficient way in order to optimize their own performance and develop successful strategies for future regattas. -i18nDeSolutionsTrainingHeadline=SAP Sail Better – STG Training Diary -i18nDeSolutionsTrainingText=The STG Training Diary supports the daily documentation and analysis of an athlete’s physical condition and training efforts in the areas of water training, physical training, boat work, mental, theory as well as other activities like education or travel. strategySimulator=Strategy Simulator contentStrategySimulator=Choosing the right strategy can provide great competitive advantage during a race. The Strategy Simulator simplifies determining the best sailing strategy for various wind conditions and water currents. Since wind and current can be changed in the simulation right away, the robustness of a strategy can be easily evaluated even for uncertain weather conditions. strategySimulatorReadMore=Learn More impressum=Imprint lastFix=Last fix -filterDeviceMappings=Filter device mappings \ No newline at end of file +filterDeviceMappings=Filter device mappings +raceStartAnalysis=Race Start Analysis +buoyZoneRadiusInHullLengths=Buoy zone radius in hull lengths (e.g., 2 or 3) +incorrectValueForRegattaBuoyZoneRadiusInHullLengths=Incorrect value for the buoy zone radius in hull lengths diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 398ba328acf..69c4f8183c7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -209,7 +209,9 @@ markPassing=Tonnenpassierung mark=Tonne otherManeuver=Manövertyp unbekannt douglasPeuckerPoints=Douglas-Peucker-Punkte -filterRacesByName=Rennen nach Name filtern +filterRaces=Rennen filtern +filterByNameOrBoatClass=nach Name oder Bootsklasse +filterByRegatta=nach Regatta filterMediaByName=Medien nach Name filtern actionEdit=Ändern actionEditCompetitors=Teilnehmer ändern @@ -1600,10 +1602,12 @@ raceAnalysis=Renn-Analyse startAnalysis=Start-Analyse winningLanes=Sieger-Kurse maximumNumberOfDiscards=Maximale Anzahl von Streichern +externalLinks=Externe Links +urlForAllLanguages=URL für alle Sprachen +urlForLanguage=URL für die Sprache ''{0}'' notificationRegattaConfigurationUpdatedUsingByMarks=Aktive Regattakonfiguration wurde gesetzt, "By Marks" Kursdesigner ist ausgewählt. warningOverrideRegattaConfigurationCourseDesignerToByMarks=Aktive Regattakonfiguration existiert bereits, verwendet aber den falschenKursdesigner.\nKursdesigner auf "By Marks" umstellen? allRacesHaveBeenDenoted=Alle Rennen sind für Smartphone Tracking gekennzeichnet worden. - chartSelectionHint=Hinweis: Du kannst auch die Ergebnistabelle (im Menü ERGEBNISSE) nutzen, um die im Diagramm angezeigten Teilnehmer auszuwählen. chooseALanguage=Wähle eine Sprache solutions=Lösungen @@ -1658,7 +1662,6 @@ solutionsRaceHeadline=SAP Sailing Race Manager solutionsInSightHeadline=SAP Sail InSight solutionsBuoyPingerHeadline=SAP Sailing Buoy Pinger solutionsPostHeadline=Analysen nach den Rennen -solutionsTrainingHeadline=Trainingstagebuch solutionsSimulatorHeadline=Strategie-Simulator sapInSailing=SAP im Segelsport contentSapInSailing1=Der Segelsport bietet SAP eine perfekte Plattform, um seine Lösungen zu demonstrieren und dem Sport zu neuen Bestleistungen zu verhelfen. SAPs Unterstützung hat das Segelerlebnis vollkommen verwandelt. Tools von SAP: @@ -1689,11 +1692,12 @@ sapSailingBuoyPingerReadMore=Learn More postRaceAnalytics=Analysen nach den Rennen contentPostRaceAnalytics1=Daten von den Segelveranstaltungen bieten Seglern die Möglichkeit, ihre Leistungen zu analysieren und zu bewerten, um ihre Strategien zu verfeinern und letztendlich besser zu segeln. contentPostRaceAnalytics2=Lösungen von SAP Business Analytics geben Seglern, Trainern, Fans und Medien die Mittel in die Hand, um historische Daten von vielen Regatten und Rennen abzurufen und zu analysieren. Mit SAP HANA sind Informationen über Leistungsmuster, Trends und Segler-Profile einfach, schnell und effizient abrufbar, so dass Segler ihre eigene Leistung optimieren und Erfolgsstrategien für zukünftige Regatten entwickeln können. -i18nDeSolutionsTrainingHeadline=SAP Sail Better – STG Trainingstagebuch -i18nDeSolutionsTrainingText=Das STG Trainingstagebuch unterstützt die tägliche Dokumentation und Analyse der Kondition und Trainingsaktivitäten eines Sportlers, sowohl in Bereichen wie Training auf dem Wasser, Körpertraining, Arbeit auf dem Boot, Mentaltraining und Theorie als auch bei anderen Aktivitäten wie Weiterbildung oder Reisen. strategySimulator=Strategie-Simulator contentStrategySimulator=Während eines Rennens hängt viel von der Wahl der richtigen Strategie ab. Der Strategie-Simulator vereinfacht die Ermittlung der besten Segelstrategie für diverse Wind- und Strömungsbedingungen. Da Wind und Strömung sofort in der Simulation geändert werden können, lässt sich die Robustheit einer Strategie selbst für unsichere Wetterbedingungen problemlos bewerten. strategySimulatorReadMore=Mehr erfahren impressum=Impressum lastFix=Letzter Fix -filterDeviceMappings=Gerätezuordnungen filtern \ No newline at end of file +filterDeviceMappings=Gerätezuordnungen filtern +raceStartAnalysis=Startanalyse +buoyZoneRadiusInHullLengths=Anzahl Bootslängen für Bahnmarkenkreis (z.B. 2 oder 3) +incorrectValueForRegattaBuoyZoneRadiusInHullLengths=Ungültiger WErt für den Bootslängen-Bahnmarkenkreis \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties index afebcf5d100..20aac725269 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ja.properties @@ -206,7 +206,9 @@ markPassing=マーク通過 mark=マーク otherManeuver=不明のマニューバー douglasPeuckerPoints=Douglas peucker ポイント -filterRacesByName=レースをフィルタリングする名称 +filterRaces=Filter races +filterByNameOrBoatClass=by name or boat class +filterByRegatta=by regatta filterMediaByName=メディアをフィルタリングする名称 trackSettings=設定追跡 actionEdit=編集 @@ -1514,7 +1516,6 @@ playstoreRacecommitteeApp=https://play.google.com/store/apps/details?id=com.sap. playStoreBuoyPingerApp=https://play.google.com/store/apps/details?id=com.sap.sailing.android.buoy.positioning.app playstoreBadge=https://play.google.com/intl/en/badges/images/generic/en-play-badge.png appstoreSapSailInsight=http://appstore.com/sapsailinsight -appstoreStgTrainingstagebuch=http://appstore.com/stgtrainingstagebuch appstoreBadgeSuffix=_en distanceFromAveragePosition=平均位置からの距離 useATouchOptimizedUI=タッチ最適化 UI を使用してフィックスを移動 @@ -1613,6 +1614,9 @@ raceAnalysis=レース分析 startAnalysis=スタート分析 winningLanes=勝利レーン maximumNumberOfDiscards=カットレースの最大数 +externalLinks=External links +urlForAllLanguages=URL for all languages +urlForLanguage=URL for language ''{0}'' notificationRegattaConfigurationUpdatedUsingByMarks="マーク別" コースデザイナーを使用して、有効なレガッタ設定が行われました。 warningOverrideRegattaConfigurationCourseDesignerToByMarks=有効なレガッタ設定がすでに登録されていますが、コースデザイナーの設定が適切でありません。\n"マーク別" へのコースデザイナー設定を上書きしますか。 allRacesHaveBeenDenoted=すべてのレースがレースログ追跡に表示されています。 @@ -1671,7 +1675,6 @@ solutionsRaceHeadline=SAP Sailing Race Manager solutionsInSightHeadline=SAP Sail InSight solutionsBuoyPingerHeadline=SAP Sailing Buoy Pinger solutionsPostHeadline=レース後分析 -solutionsTrainingHeadline=トレーニング日記 solutionsSimulatorHeadline=戦略シミュレータ sapInSailing=SAP in Sailing contentSapInSailing1=セーリングは、SAP にとってソリューションを紹介するためのプラットフォームとしてうってつけのものであり、これまでになかったやり方でスポーツの運営を支援しています。SAP によるスポーツへの関与は、以下のことを行うツールを提供することによってセーリングから得られる体験を別次元のものに変えました。 @@ -1702,8 +1705,6 @@ sapSailingBuoyPingerReadMore=詳細情報 postRaceAnalytics=レース後分析 contentPostRaceAnalytics1=セーリングイベントで収集されたデータへのアクセスにより、戦略の策定や帆走の向上に役立つ、セーラーがそのパフォーマンスを評価および分析する機会が提供されます。 contentPostRaceAnalytics2=SAP Business Analytics ソリューションは、多くのレガッタおよびレースにわたって履歴データを照会および分析するツールをセーラー、コーチ、ファン、そして報道関係者に提供します。SAP HANA は、こうした人たちが自身のパフォーマンスを最適化し、予定されているレガッタで成功を収める戦略を策定するために簡単、迅速、かつ効率的な方法でパフォーマンスのパターン、傾向、およびセーラーのプロファイルを知ることを可能にします。 -i18nDeSolutionsTrainingHeadline=SAP Sail Better – STG Training Diary -i18nDeSolutionsTrainingText=STG Training Diary は、選手の体調、ならびに水上トレーニング、身体的トレーニング、操艇、メンタル、理論、さらに教育や移動などのその他の活動の領域におけるトレーニングへの取り組みの日々の記録および分析をサポートします。 strategySimulator=戦略シミュレータ contentStrategySimulator=適切な戦略を選択すれば、レース中に大きな競争上の優位性を得ることが可能です。Strategy Simulator により、さまざまな風況および水流に対して最良のセーリング戦略を決定することが容易になります。風と水流をシミュレーションで直ちに変更することができるため、不確かな気象条件に対しても戦略の確かさを簡単に評価することができます。 strategySimulatorReadMore=詳細情報 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 933a32810e3..6c1629da515 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -206,7 +206,9 @@ markPassing=Прохождение отметки mark=Отметка otherManeuver=Неизвестный маневр douglasPeuckerPoints=Точки Дугласа – Пекера -filterRacesByName=Фильтровать гонки по названию +filterRaces=Filter races +filterByNameOrBoatClass=by name or boat class +filterByRegatta=by regatta filterMediaByName=Фильтровать по названию trackSettings=Параметры отслеживания actionEdit=Изменить @@ -1515,7 +1517,6 @@ playstoreRacecommitteeApp=https://play.google.com/store/apps/details?id=com.sap. playStoreBuoyPingerApp=https://play.google.com/store/apps/details?id=com.sap.sailing.android.buoy.positioning.app playstoreBadge=https://play.google.com/intl/en/badges/images/generic/en-play-badge.png appstoreSapSailInsight=http://appstore.com/sapsailinsight -appstoreStgTrainingstagebuch=http://appstore.com/stgtrainingstagebuch appstoreBadgeSuffix=_en distanceFromAveragePosition=Дистанция от средней позиции useATouchOptimizedUI=Используйте сенсорный UI для перемещения замера @@ -1614,6 +1615,9 @@ raceAnalysis=Анализ гонки startAnalysis=Начать анализ winningLanes=Выигрышные маршруты maximumNumberOfDiscards=Максимальное число исключений +externalLinks=External links +urlForAllLanguages=URL for all languages +urlForLanguage=URL for language ''{0}'' notificationRegattaConfigurationUpdatedUsingByMarks=Активная конфигурация регаты была настроена с использованием дизайнера курса "По отметкам". warningOverrideRegattaConfigurationCourseDesignerToByMarks=Активная конфигурация регаты уже существует, но с неверно выбранным дизайнером курса.\nПереопределить выбор дизайнера курса на "По отметкам"? allRacesHaveBeenDenoted=Отслеживание журнала всех гонок отменено. @@ -1672,7 +1676,6 @@ solutionsRaceHeadline=SAP Sailing Race Manager solutionsInSightHeadline=SAP Sail InSight solutionsBuoyPingerHeadline=SAP Sailing Buoy Pinger solutionsPostHeadline=Аналитика после гонки -solutionsTrainingHeadline=Дневник тренировок solutionsSimulatorHeadline=Симулятор стратегии sapInSailing=SAP в парусном спорте contentSapInSailing1=Парусный спорт предоставляет отличную платформу для демонстрации решений SAP и беспрецедентной помощи развитию спорта. Вовлеченность SAP преобразила парусный спорт, предоставив инструменты, которые: @@ -1703,8 +1706,6 @@ sapSailingBuoyPingerReadMore=Узнать больше postRaceAnalytics=Аналитика после гонки contentPostRaceAnalytics1=Доступ к данным, собранным на парусных событиях, дает мореходам возможность оценить и проанализировать свои показатели, помогая разрабатывать стратегии и повышать мастерство. contentPostRaceAnalytics2=Решения SAP Business Analytics предоставляют мореходам, тренерам, болельщикам и СМИ инструменты для просмотра и анализа исторических данных по множеству регат и гонок. SAP HANA позволяет изучать паттерны эффективности, тенденции и профили мореходов простым, быстрым и эффективным образом для оптимизации своих показателей и разработки успешных стратегий на будущие регаты. -i18nDeSolutionsTrainingHeadline=SAP Sail Better – Дневник тренировок STG -i18nDeSolutionsTrainingText=Дневник тренировок STG поддерживает ежедневное документирования и анализ физического состояния атлета и его усилий в тренировках на воде, физической подготовке, работе с лодкой, моральной, теоретической подготовке, а также других видах деятельности, таких как образование и путешествия. strategySimulator=Симулятор стратегии contentStrategySimulator=Выбор правильной стратегии может обеспечить большое конкурентное преимущество во время гонки. Симулятор стратегии упрощает определение лучшей стратегии хождения под парусом для разных атмосферных условий и водных течений. Возможность мгновенного изменения ветра и течения в симуляции позволяет легко оценить надежность стратегии даже для неопределенных погодных условий. strategySimulatorReadMore=Узнать больше diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties index 2e9d25b1b7f..d397477e7e8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh.properties @@ -206,7 +206,9 @@ markPassing=绕标 mark=标志 otherManeuver=未知操作 douglasPeuckerPoints=道格拉斯-普克得分 -filterRacesByName=按名称筛选比赛轮次 +filterRaces=Filter races +filterByNameOrBoatClass=by name or boat class +filterByRegatta=by regatta filterMediaByName=按名称筛选媒体 trackSettings=跟踪设置 actionEdit=编辑 @@ -1512,7 +1514,6 @@ playstoreRacecommitteeApp=https://play.google.com/store/apps/details?id=com.sap. playStoreBuoyPingerApp=https://play.google.com/store/apps/details?id=com.sap.sailing.android.buoy.positioning.app playstoreBadge=https://play.google.com/intl/en/badges/images/generic/en-play-badge.png appstoreSapSailInsight=http://appstore.com/sapsailinsight -appstoreStgTrainingstagebuch=http://appstore.com/stgtrainingstagebuch appstoreBadgeSuffix=_en distanceFromAveragePosition=与平均位置的距离 useATouchOptimizedUI=使用触摸优化的 UI 移动修复 @@ -1611,7 +1612,9 @@ raceAnalysis=比赛轮次分析 startAnalysis=起航分析 winningLanes=获胜的船道 maximumNumberOfDiscards=最大放弃次数 - +externalLinks=External links +urlForAllLanguages=URL for all languages +urlForLanguage=URL for language ''{0}'' chartSelectionHint=提示:您还可以使用积分榜(在 LEADERBOARD 选项卡中)选择在记录中显示的参赛队。 chooseALanguage=选择一种语言 solutions=解决方案 @@ -1666,7 +1669,6 @@ solutionsRaceHeadline=SAP Sailing Race Manager solutionsInSightHeadline=SAP Sail InSight solutionsBuoyPingerHeadline=SAP Sailing Buoy Pinger solutionsPostHeadline=赛后分析 -solutionsTrainingHeadline=训练日记 solutionsSimulatorHeadline=策略模拟器 sapInSailing=SAP in Sailing contentSapInSailing1=SAP 凭借此航行应用提供完美的平台,向用户展示解决方案,并以前所未有的顺畅速度呈现体育赛事。 SAP 通过提供各种工具转换其在参与此项运动中获得的航行经验,这些工具: @@ -1697,8 +1699,6 @@ sapSailingBuoyPingerReadMore=Learn More postRaceAnalytics=赛后分析 contentPostRaceAnalytics1=水手通过访问航行活动期间收集的数据评估和分析其表现,帮助他们制定策略和优化航行方案。 contentPostRaceAnalytics2=SAP Business Analytics 解决方案为水手、教练、粉丝和媒体提供跨比赛和轮次浏览和分析历史数据的工具。 SAP HANA 通过简单、快速和高效的方式使其了解表现模式、趋势和水手资料,从而提高自己的表现并为今后的比赛制定成功的策略。 -i18nDeSolutionsTrainingHeadline=SAP Sail Better – STG Training Diary -i18nDeSolutionsTrainingText=STG Training Diary 可每天记录和分析运动员的身体状况,在水上训练、体能训练、船艇工作、心理、理论以及教育或旅行等其他活动领域的训练力度。 strategySimulator=策略模拟器 contentStrategySimulator=选择正确的策略可在比赛期间极大地提高竞争优势。 策略模拟器为不同的风力条件和水流简化确定最佳的航行策略。 由于可以在模拟中即时更改风和水流,即便在不确定的天气条件下,也可以轻松地评估策略的稳健性。 strategySimulatorReadMore=了解更多 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties index f7e35229132..32af065a8d3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_zh_CN.properties @@ -206,7 +206,9 @@ markPassing=绕标 mark=标志 otherManeuver=未知操作 douglasPeuckerPoints=道格拉斯-普克得分 -filterRacesByName=按名称筛选比赛轮次 +filterRaces=Filter races +filterByNameOrBoatClass=by name or boat class +filterByRegatta=by regatta filterMediaByName=按名称筛选媒体 trackSettings=跟踪设置 actionEdit=编辑 @@ -1514,7 +1516,6 @@ playstoreRacecommitteeApp=https://play.google.com/store/apps/details?id=com.sap. playStoreBuoyPingerApp=https://play.google.com/store/apps/details?id=com.sap.sailing.android.buoy.positioning.app playstoreBadge=https://play.google.com/intl/en/badges/images/generic/en-play-badge.png appstoreSapSailInsight=http://appstore.com/sapsailinsight -appstoreStgTrainingstagebuch=http://appstore.com/stgtrainingstagebuch appstoreBadgeSuffix=_en distanceFromAveragePosition=与平均位置的距离 useATouchOptimizedUI=使用触摸优化的 UI 移动修复 @@ -1613,6 +1614,9 @@ raceAnalysis=比赛轮次分析 startAnalysis=起航分析 winningLanes=获胜的船道 maximumNumberOfDiscards=最大放弃次数 +externalLinks=External links +urlForAllLanguages=URL for all languages +urlForLanguage=URL for language ''{0}'' notificationRegattaConfigurationUpdatedUsingByMarks=已使用“按标志”场地设计器设置有效的比赛配置。 warningOverrideRegattaConfigurationCourseDesignerToByMarks=有效的比赛配置已存在,但是场地设计器设置错误。\n是否将场地设计器设置覆盖为“按标志”? allRacesHaveBeenDenoted=已为比赛轮次日志跟踪描述所有比赛轮次。 @@ -1671,7 +1675,6 @@ solutionsRaceHeadline=SAP Sailing Race Manager solutionsInSightHeadline=SAP Sail InSight solutionsBuoyPingerHeadline=SAP Sailing Buoy Pinger solutionsPostHeadline=赛后分析 -solutionsTrainingHeadline=训练日记 solutionsSimulatorHeadline=策略模拟器 sapInSailing=SAP in Sailing contentSapInSailing1=SAP 凭借此航行应用提供完美的平台,向用户展示解决方案,并以前所未有的顺畅速度呈现体育赛事。 SAP 通过提供各种工具转换其在参与此项运动中获得的航行经验,这些工具: @@ -1702,8 +1705,6 @@ sapSailingBuoyPingerReadMore=了解更多 postRaceAnalytics=赛后分析 contentPostRaceAnalytics1=水手通过访问航行活动期间收集的数据评估和分析其表现,帮助他们制定策略和优化航行方案。 contentPostRaceAnalytics2=SAP Business Analytics 解决方案为水手、教练、粉丝和媒体提供跨比赛和轮次浏览和分析历史数据的工具。SAP HANA 通过简单、快速和高效的方式使其了解表现模式、趋势和水手资料,从而提高自己的表现并为今后的比赛制定成功的策略。 -i18nDeSolutionsTrainingHeadline=SAP Sail Better – STG Training Diary -i18nDeSolutionsTrainingText=STG Training Diary 可每天记录和分析运动员的身体状况,在水上训练、体能训练、船艇工作、心理、理论以及教育或旅行等其他活动领域的训练力度。 strategySimulator=策略模拟器 contentStrategySimulator=选择正确的策略可在比赛期间极大地提高竞争优势。策略模拟器为不同的风力条件和水流简化确定最佳的航行策略。由于可以在模拟中即时更改风和水流,即便在不确定的天气条件下,也可以轻松地评估策略的稳健性。 strategySimulatorReadMore=了解更多 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorRaceChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorRaceChart.java index 1051db9b49f..71a165375af 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorRaceChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractCompetitorRaceChart.java @@ -33,9 +33,10 @@ import org.moxieapps.gwt.highcharts.client.plotOptions.LinePlotOptions; import org.moxieapps.gwt.highcharts.client.plotOptions.Marker; import org.moxieapps.gwt.highcharts.client.plotOptions.ScatterPlotOptions; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.i18n.client.NumberFormat; import com.google.gwt.user.client.rpc.AsyncCallback; -import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.Widget; @@ -64,8 +65,8 @@ import com.sap.sse.gwt.client.player.Timer.PlayModes; import com.sap.sse.gwt.client.shared.components.Component; /** - * AbstractCompetitorChart is a chart that can show one sort of competitor data (e.g. current speed over ground, windward - * distance to leader) for different races in a chart. + * AbstractCompetitorChart is a chart that can show one sort of competitor data (e.g. current speed over ground, + * windward distance to leader) for different races in a chart. * * When calling the constructor a chart is created that creates a final amount of series (so the maximum number of * competitors cannot be changed in one chart) which are connected to competitors, when the sailing service returns the @@ -96,19 +97,9 @@ public abstract class AbstractCompetitorRaceChart, Series> dataSeriesForDetailTypeAndCompetitor = new HashMap<>(); private final Map, Series> markPassingSeriesByCompetitor = new HashMap<>(); - private Long timeOfEarliestRequestInMillis; - private Long timeOfLatestRequestInMillis; - protected AbstractCompetitorRaceChart(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, - CompetitorSelectionProvider competitorSelectionProvider, RegattaAndRaceIdentifier selectedRaceIdentifier, - Timer timer, TimeRangeWithZoomProvider timeRangeWithZoomProvider, Button settingsButton, - final StringMessages stringMessages, ErrorReporter errorReporter, DetailType firstDetailType, - DetailType secondDetailType, boolean compactChart, boolean allowTimeAdjust) { - this(sailingService, asyncActionsExecutor, competitorSelectionProvider, selectedRaceIdentifier, timer, - timeRangeWithZoomProvider, stringMessages, errorReporter, firstDetailType, secondDetailType, - compactChart, allowTimeAdjust, - null, null); - } + private final TimingHolder primary = new TimingHolder(); + private final TimingHolder secondary = new TimingHolder(); AbstractCompetitorRaceChart(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, CompetitorSelectionProvider competitorSelectionProvider, RegattaAndRaceIdentifier selectedRaceIdentifier, @@ -145,6 +136,7 @@ public abstract class AbstractCompetitorRaceChart competitorsToLoad, final DetailType selectedDataTypeToRetrieve) { + ArrayList competitorsToLoad, final DetailType selectedDataTypeToRetrieve,final TimingHolder tholder) { long stepSize = Math.max(getStepSizeInMillis(), from==null||to==null ? 0 : Math.abs(to.getTime()-from.getTime())/SailingServiceConstants.MAX_NUMBER_OF_FIXES_TO_QUERY); GetCompetitorsRaceDataAction getCompetitorsRaceDataAction = new GetCompetitorsRaceDataAction(sailingService, @@ -267,7 +252,7 @@ public abstract class AbstractCompetitorRaceChart competitorsToLoad = new ArrayList(); competitorsToLoad.add(competitor); - Date fromDate = timeOfEarliestRequestInMillis == null ? null : new Date(timeOfEarliestRequestInMillis); - Date toDate = timeOfLatestRequestInMillis == null ? null : new Date(timeOfLatestRequestInMillis); - loadData(fromDate, toDate, competitorsToLoad, false); + + { + Date fromDate = primary.timeOfEarliestRequestInMillis == null ? null : new Date(primary.timeOfEarliestRequestInMillis); + Date toDate = primary.timeOfLatestRequestInMillis == null ? null : new Date(primary.timeOfLatestRequestInMillis); + loadData(fromDate, toDate, competitorsToLoad, false); + } + { + Date fromDate = secondary.timeOfEarliestRequestInMillis == null ? null : new Date(secondary.timeOfEarliestRequestInMillis); + Date toDate = secondary.timeOfLatestRequestInMillis == null ? null : new Date(secondary.timeOfLatestRequestInMillis); + loadData(fromDate, toDate, competitorsToLoad, false); + } } } @@ -334,7 +327,7 @@ public abstract class AbstractCompetitorRaceChart */ private synchronized void updateChartSeries(CompetitorsRaceDataDTO chartData, DetailType retrievedDataType, - boolean append) { + boolean append,TimingHolder tholder) { // Make sure the busy indicator is removed at this point, or plotting the data results in an exception setWidget(chart); for (CompetitorDTO competitor : chartData.getCompetitors()) { @@ -368,8 +361,8 @@ public abstract class AbstractCompetitorRaceChart timeOfLatestRequestInMillis) { + if(append == false || (tholder.timeOfEarliestRequestInMillis == null || dataPointTimeAsMillis < tholder.timeOfEarliestRequestInMillis) || + tholder.timeOfLatestRequestInMillis == null || dataPointTimeAsMillis > tholder.timeOfLatestRequestInMillis) { raceDataPointsToAdd[currentPointIndex++] = new Point(dataPointTimeAsMillis, dataPointValue); } } @@ -384,6 +377,7 @@ public abstract class AbstractCompetitorRaceChart chartSeries = Arrays.asList(chart.getSeries()); @@ -394,11 +388,11 @@ public abstract class AbstractCompetitorRaceChart chartData.getRequestedFromTime().getTime()) { - timeOfEarliestRequestInMillis = chartData.getRequestedFromTime().getTime(); + if (tholder.timeOfEarliestRequestInMillis == null || tholder.timeOfEarliestRequestInMillis > chartData.getRequestedFromTime().getTime()) { + tholder.timeOfEarliestRequestInMillis = chartData.getRequestedFromTime().getTime(); } - if (timeOfLatestRequestInMillis == null || timeOfLatestRequestInMillis < chartData.getRequestedToTime().getTime()) { - timeOfLatestRequestInMillis = chartData.getRequestedToTime().getTime(); + if (tholder.timeOfLatestRequestInMillis == null || tholder.timeOfLatestRequestInMillis < chartData.getRequestedToTime().getTime()) { + tholder.timeOfLatestRequestInMillis = chartData.getRequestedToTime().getTime(); } chart.redraw(); } @@ -472,10 +466,36 @@ public abstract class AbstractCompetitorRaceChart the old title appears - chart.setTitle(hasSecondYAxis() ? null : new ChartTitle().setText(chartTitleFromDetailTypes()), null); + Scheduler.get().scheduleDeferred(new ScheduledCommand() { + + @Override + public void execute() { + // Workaround for a highcharts bug: Set a chart title, overwrite the title, + // switch chart to invisible and visible again -> the old title appears + if (compactChart) { + applyCompactTitle(chartTitleFromDetailTypes()); + } else { + chart.setTitle(hasSecondYAxis() ? null : new ChartTitle().setText(chartTitleFromDetailTypes()), + null); + } + + chart.setSizeToMatchContainer(); + // it's important here to recall the redraw method, otherwise the bug fix for wrong checkbox + // positions + // (nativeAdjustCheckboxPosition) + // in the BaseChart class would not be called + chart.redraw(); + } + }); } + + } + + private void applyCompactTitle(String title) { + chart.setSpacingLeft(10).setSpacingRight(10).setSpacingTop(20).setOption("legend/margin", 2) + .setChartSubtitle(null).getXAxis().setAxisTitleText(null); + chart.setTitle(hasSecondYAxis() ? null : new ChartTitle().setText(title).setOption("floating",true), null); + chart.redraw(); } private int yAxisIndex(DetailType seriesDetailType) { @@ -500,8 +520,10 @@ public abstract class AbstractCompetitorRaceChart timeOfLatestRequestInMillis) { - updateChart(new Date(timeOfLatestRequestInMillis), timeRangeWithZoomProvider.getToTime(), /* append */true); + } else if (newTime.getTime() > primary.timeOfLatestRequestInMillis) { + updateChart(new Date(primary.timeOfLatestRequestInMillis), timeRangeWithZoomProvider.getToTime(), /* append */true); } // otherwise the cache spans across date and so we don't need to load anything break; } case Replay: { - if (timeOfLatestRequestInMillis == null) { + if (primary.timeOfLatestRequestInMillis == null) { // pure replay mode updateChart(timeRangeWithZoomProvider.getFromTime(), timeRangeWithZoomProvider.getToTime(), /* append */false); } else { // replay mode during live play - if (timeOfEarliestRequestInMillis == null || newTime.getTime() < timeOfEarliestRequestInMillis) { + if (primary.timeOfEarliestRequestInMillis == null || newTime.getTime() < primary.timeOfEarliestRequestInMillis) { updateChart(timeRangeWithZoomProvider.getFromTime(), newTime, /* append */true); - } else if (newTime.getTime() > timeOfLatestRequestInMillis) { - updateChart(new Date(timeOfLatestRequestInMillis), timeRangeWithZoomProvider.getToTime(), /* append */true); + } else if (newTime.getTime() > primary.timeOfLatestRequestInMillis) { + updateChart(new Date(primary.timeOfLatestRequestInMillis), timeRangeWithZoomProvider.getToTime(), /* append */true); } } break; @@ -784,5 +811,10 @@ public abstract class AbstractCompetitorRaceChart> newFilterSet) { // nothing to do; if it changes the filtered competitor list, a separate call to filteredCompetitorsListChanged will occur } + + static class TimingHolder{ + private Long timeOfEarliestRequestInMillis; + private Long timeOfLatestRequestInMillis; + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java index 9fbb374c863..0f72b6ae08e 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/AbstractRaceChart.java @@ -27,6 +27,8 @@ import com.sap.sse.common.settings.Settings; import com.sap.sse.gwt.client.ErrorReporter; import com.sap.sse.gwt.client.async.AsyncActionsExecutor; import com.sap.sse.gwt.client.controls.busyindicator.SimpleBusyIndicator; +import com.sap.sse.gwt.client.controls.slider.TimeTicksCalculator; +import com.sap.sse.gwt.client.controls.slider.TimeTicksCalculator.NormalizedInterval; import com.sap.sse.gwt.client.player.TimeListener; import com.sap.sse.gwt.client.player.TimeRangeChangeListener; import com.sap.sse.gwt.client.player.TimeRangeWithZoomProvider; @@ -66,6 +68,8 @@ public abstract class AbstractRaceChart extends A /** the tick count must be the same as TimeSlider.TICKCOUNT, otherwise the time ticks will be not synchronized */ private final int TICKCOUNT = 10; + + public static final long MINUTE_IN_MILLIS = 60 * 1000; private boolean ignoreNextClickEvent; @@ -158,7 +162,12 @@ public abstract class AbstractRaceChart extends A if (!isZoomed) { isZoomed = true; } - timeRangeWithZoomProvider.setTimeZoom(new Date(xAxisMin), new Date(xAxisMax), this); + //Set a minute as max time zoom just as for chart + if (xAxisMax - xAxisMin > MINUTE_IN_MILLIS) { + timeRangeWithZoomProvider.setTimeZoom(new Date(xAxisMin), new Date(xAxisMax), this); + } else { + return false; + } } catch (Exception e) { Scheduler.get().scheduleDeferred(new ScheduledCommand() { @Override @@ -193,7 +202,9 @@ public abstract class AbstractRaceChart extends A if (minTimepoint != null && maxTimepoint != null) { xAxis.setExtremes(minTimepoint.getTime(), maxTimepoint.getTime(), /* redraw */ false, false); long tickInterval = (maxTimepoint.getTime() - minTimepoint.getTime()) / TICKCOUNT; - xAxis.setTickInterval(tickInterval); + TimeTicksCalculator calculator = new TimeTicksCalculator(); + NormalizedInterval normalizedInterval = calculator.normalizeTimeTickInterval(tickInterval); + xAxis.setTickInterval(normalizedInterval.count * normalizedInterval.unitRange); } if (minTimepoint != null) { xAxis.setMin(minTimepoint.getTime()); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java index 9d980b6c97f..e1e8d07f2d0 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java @@ -196,10 +196,11 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component() { @Override public void onFailure(Throwable caught) { @@ -335,11 +336,17 @@ public class EditMarkPassingsPanel extends AbsolutePanel implements Component>() { + sailingService.getCompetitorMarkPassings(raceIdentifier, competitor, true, new AsyncCallback>() { @Override public void onFailure(Throwable caught) { errorReporter.reportError(stringMessages.errorTryingToObtainMarkPassing(caught.getMessage())); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatOverlay.java index 3674aeb9f81..e82a4ab81c8 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/BoatOverlay.java @@ -150,7 +150,7 @@ public class BoatOverlay extends CanvasOverlayV3 { public Util.Pair getBoatScaleAndSize(BoatClassDTO boatClass) { // the minimum boat length is related to the hull of the boat, not the overall length double minBoatHullLengthInPx = boatVectorGraphics.getMinHullLengthInPx(); - double boatHullLengthInPixel = calculateDistanceAlongX(mapProjection, boatFix.position, boatClass.getHullLengthInMeters()); + double boatHullLengthInPixel = calculateDistanceAlongX(mapProjection, boatFix.position, boatClass.getHullLength()); if (boatHullLengthInPixel < minBoatHullLengthInPx) { boatHullLengthInPixel = minBoatHullLengthInPx; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanel.java index ebc0d0e9371..acea6980623 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CombinedWindPanel.java @@ -49,7 +49,7 @@ public class CombinedWindPanel extends FlowPanel { final RaceMapSettings newRaceMapSettings = new RaceMapSettings(oldRaceMapSettings.getZoomSettings(), oldRaceMapSettings.getHelpLinesSettings(), oldRaceMapSettings.getTransparentHoverlines(), oldRaceMapSettings.getHoverlineStrokeWeight(), oldRaceMapSettings.getTailLengthInMilliseconds(), oldRaceMapSettings.isWindUp(), - oldRaceMapSettings.getBuoyZoneRadiusInMeters(), oldRaceMapSettings.isShowOnlySelectedCompetitors(), + oldRaceMapSettings.getBuoyZoneRadius(), oldRaceMapSettings.isShowOnlySelectedCompetitors(), oldRaceMapSettings.isShowSelectedCompetitorsInfo(), oldRaceMapSettings.isShowWindStreamletColors(), newShowStreamletsOverlaySetting, oldRaceMapSettings.isShowSimulationOverlay(), oldRaceMapSettings.isShowMapControls(), oldRaceMapSettings.getManeuverTypesToShow(), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CompetitorInfoOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CompetitorInfoOverlay.java index 38b993dc938..946ea21aa71 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CompetitorInfoOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CompetitorInfoOverlay.java @@ -29,7 +29,10 @@ public class CompetitorInfoOverlay extends CanvasOverlayV3 { private int canvasWidth; private int canvasHeight; - private int infoBoxHeight = 20; + private int infoBoxHeight; + private int defaultInfoBoxHeight = 14; + public static final int X_TEXT_COORDINATE = 8; + public static final int Y_TEXT_COORDINATE = 14; private Color competitorColor; @@ -52,10 +55,11 @@ public class CompetitorInfoOverlay extends CanvasOverlayV3 { CssColor grayTransparentColor = CssColor.make("rgba(255,255,255,0.75)"); ctx.setFont("12px bold Verdana sans-serif"); - TextMetrics measureText = ctx.measureText(infoText); - double textWidth = measureText.getWidth(); - - canvasWidth = (int) textWidth + 10 + infoBoxHeight; + String[] textLines = infoText.split("\n"); + TextMetrics measureText = ctx.measureText(findLargestLine(textLines)); + double largestLineWidth = measureText.getWidth(); + infoBoxHeight = defaultInfoBoxHeight + textLines.length * 10; + canvasWidth = (int)largestLineWidth + infoBoxHeight; setCanvasSize(canvasWidth, canvasHeight); ctx.save(); @@ -77,25 +81,17 @@ public class CompetitorInfoOverlay extends CanvasOverlayV3 { ctx.beginPath(); ctx.moveTo(1.0,1.0); ctx.lineTo(canvasWidth,1.0); - ctx.lineTo(canvasWidth-infoBoxHeight,infoBoxHeight); + double lenghtOfTheLastLine = ctx.measureText(textLines[textLines.length - 1]).getWidth(); + int bottomLineWidth = 2 * X_TEXT_COORDINATE + (int) lenghtOfTheLastLine; + ctx.lineTo(bottomLineWidth, infoBoxHeight); ctx.lineTo(1.0,infoBoxHeight); ctx.closePath(); ctx.fill(); ctx.stroke(); - // show a second measure -// ctx.beginPath(); -// ctx.moveTo(1.0,26.4); -// ctx.lineTo(66.2,26.4); -// ctx.lineTo(53.1,39.3); -// ctx.lineTo(1.0,39.3); -// ctx.closePath(); -// ctx.fill(); -// ctx.stroke(); - ctx.beginPath(); ctx.setFillStyle("black"); - ctx.fillText(infoText, 8, 14); + drawText(textLines, ctx); ctx.stroke(); ctx.restore(); @@ -121,4 +117,22 @@ public class CompetitorInfoOverlay extends CanvasOverlayV3 { public void setInfoText(String infoText) { this.infoText = infoText; } + + private void drawText(String[] lines, Context2d ctx) { + for (int i = 0; i < lines.length; i++) { + ctx.fillText(lines[i], X_TEXT_COORDINATE, Y_TEXT_COORDINATE + i * 12); + } + } + + private String findLargestLine(String[] lines) { + int index = 0; + int maxLength = lines[0].length(); + for (int i = 0; i < lines.length; i++) { + if (maxLength < lines[i].length()) { + index = i; + maxLength = lines[i].length(); + } + } + return lines[index]; + } } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CourseMarkOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CourseMarkOverlay.java index 268431605c6..a0a973708a5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CourseMarkOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/CourseMarkOverlay.java @@ -9,8 +9,10 @@ import com.google.gwt.maps.client.MapWidget; import com.google.gwt.maps.client.base.LatLng; import com.google.gwt.maps.client.base.Point; import com.google.gwt.maps.client.base.Size; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.MarkType; import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.gwt.ui.shared.MarkDTO; import com.sap.sailing.gwt.ui.shared.racemap.CanvasOverlayV3; import com.sap.sailing.gwt.ui.shared.racemap.MarkVectorGraphics; @@ -27,7 +29,7 @@ public class CourseMarkOverlay extends CanvasOverlayV3 { private Position position; - private double buoyZoneRadiusInMeter; + private Distance buoyZoneRadius; private boolean showBuoyZone; @@ -42,13 +44,13 @@ public class CourseMarkOverlay extends CanvasOverlayV3 { private Double lastScaleFactor; private Boolean lastShowBuoyZone; private Boolean lastIsSelected; - private Double lastBuoyZoneRadiusInMeter; + private Distance lastBuoyZoneRadius; public CourseMarkOverlay(MapWidget map, int zIndex, MarkDTO markDTO, CoordinateSystem coordinateSystem) { super(map, zIndex, coordinateSystem); this.mark = markDTO; this.position = markDTO.position; - this.buoyZoneRadiusInMeter = 0.0; + this.buoyZoneRadius = new MeterDistance(0.0); this.showBuoyZone = false; markVectorGraphics = new MarkVectorGraphics(markDTO.type, markDTO.color, markDTO.shape, markDTO.pattern); markScaleAndSizePerZoomCache = new HashMap>(); @@ -72,13 +74,13 @@ public class CourseMarkOverlay extends CanvasOverlayV3 { double buoyZoneRadiusInPixel = -1; if (showBuoyZone && isMarkWithBuoyZone(mark)) { buoyZoneRadiusInPixel = calculateRadiusOfBoundingBoxInPixels(mapProjection, position, - buoyZoneRadiusInMeter); + buoyZoneRadius); if (buoyZoneRadiusInPixel > MIN_BUOYZONE_RADIUS_IN_PX) { canvasWidth = (buoyZoneRadiusInPixel + 1) * 2; canvasHeight = (buoyZoneRadiusInPixel + 1) * 2; } } - if (needToDraw(showBuoyZone, isSelected, buoyZoneRadiusInMeter, canvasWidth, canvasHeight, markSizeScaleFactor)) { + if (needToDraw(showBuoyZone, isSelected, buoyZoneRadius, canvasWidth, canvasHeight, markSizeScaleFactor)) { setCanvasSize((int) canvasWidth, (int) canvasHeight); Context2d context2d = getCanvas().getContext2d(); // draw the course mark @@ -95,7 +97,7 @@ public class CourseMarkOverlay extends CanvasOverlayV3 { context2d.closePath(); context2d.stroke(); } - lastBuoyZoneRadiusInMeter = buoyZoneRadiusInMeter; + lastBuoyZoneRadius = buoyZoneRadius; lastScaleFactor = markSizeScaleFactor; lastShowBuoyZone = showBuoyZone; lastIsSelected = isSelected; @@ -120,10 +122,10 @@ public class CourseMarkOverlay extends CanvasOverlayV3 { * Compares the drawing parameters to {@link #lastLegType} and the other last.... If anything has * changed, the result is true. */ - private boolean needToDraw(boolean showBuoyZone, boolean isSelected, double buoyZoneRadiusInMeters, double width, double height, double scaleFactor) { + private boolean needToDraw(boolean showBuoyZone, boolean isSelected, Distance buoyZoneRadius, double width, double height, double scaleFactor) { return lastShowBuoyZone == null || lastShowBuoyZone != showBuoyZone || lastIsSelected == null || lastIsSelected != isSelected || - lastBuoyZoneRadiusInMeter == null || lastBuoyZoneRadiusInMeter != buoyZoneRadiusInMeters || + lastBuoyZoneRadius == null || !lastBuoyZoneRadius.equals(buoyZoneRadius) || lastScaleFactor == null || lastScaleFactor != scaleFactor || lastWidth == null || lastWidth != width || lastHeight == null || lastHeight != height; @@ -131,22 +133,18 @@ public class CourseMarkOverlay extends CanvasOverlayV3 { public Util.Pair getMarkScaleAndSize(Position markPosition) { double minMarkHeight = 20; - // the original buoy vector graphics is too small (2.1m x 1.5m) for higher zoom levels // therefore we scale the buoys with factor 2 by default double buoyScaleFactor = 2.0; - Size markSizeInPixel = calculateBoundingBox(mapProjection, markPosition, - markVectorGraphics.getMarkWidthInMeters() * buoyScaleFactor, markVectorGraphics.getMarkHeightInMeters() * buoyScaleFactor); - + markVectorGraphics.getMarkWidth().scale(buoyScaleFactor), markVectorGraphics.getMarkHeight().scale(buoyScaleFactor)); double markHeightInPixel = markSizeInPixel.getHeight(); - if(markHeightInPixel < minMarkHeight) + if (markHeightInPixel < minMarkHeight) { markHeightInPixel = minMarkHeight; - + } // The coordinates of the canvas drawing methods are based on the 'centimeter' unit (1px = 1cm). // To calculate the display real mark size the scale factor from canvas units to the real - double markSizeScaleFactor = markHeightInPixel / (markVectorGraphics.getMarkHeightInMeters() * 100); - + double markSizeScaleFactor = markHeightInPixel / markVectorGraphics.getMarkHeight().scale(100).getMeters(); return new Util.Pair(markSizeScaleFactor, Size.newInstance(markHeightInPixel * 2.0, markHeightInPixel * 2.0)); } @@ -191,11 +189,11 @@ public class CourseMarkOverlay extends CanvasOverlayV3 { return coordinateSystem.toLatLng(position); } - public double getBuoyZoneRadiusInMeter() { - return buoyZoneRadiusInMeter; + public Distance getBuoyZoneRadius() { + return buoyZoneRadius; } - public void setBuoyZoneRadiusInMeter(double buoyZoneRadiusInMeter) { - this.buoyZoneRadiusInMeter = buoyZoneRadiusInMeter; + public void setBuoyZoneRadius(Distance buoyZoneRadius) { + this.buoyZoneRadius = buoyZoneRadius; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixOverlay.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixOverlay.java index fe22bf50ad1..de9d7ba79cf 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixOverlay.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixOverlay.java @@ -70,22 +70,17 @@ public class FixOverlay extends CanvasOverlayV3 { public Util.Pair getFixScaleAndSize() { double minFixHeight = 20; - // the original fix vector graphics is too small (2.1m x 1.5m) for higher zoom levels // therefore we scale the fixes with factor 2 by default double buoyScaleFactor = 2.0; - Size fixSizeInPixel = calculateBoundingBox(mapProjection, fix.position, - fixVectorGraphics.getFixWidthInMeters() * buoyScaleFactor, fixVectorGraphics.getFixHeightInMeters() * buoyScaleFactor); - + fixVectorGraphics.getFixWidth().scale(buoyScaleFactor), fixVectorGraphics.getFixHeight().scale(buoyScaleFactor)); double fixHeightInPixel = fixSizeInPixel.getHeight(); if(fixHeightInPixel < minFixHeight) fixHeightInPixel = minFixHeight; - // The coordinates of the canvas drawing methods are based on the 'centimeter' unit (1px = 1cm). // To calculate the display real fix size the scale factor from canvas units to the real - double fixSizeScaleFactor = fixHeightInPixel / (fixVectorGraphics.getFixHeightInMeters() * 100); - + double fixSizeScaleFactor = fixHeightInPixel / (fixVectorGraphics.getFixHeight().scale(100).getMeters()); return new Util.Pair(fixSizeScaleFactor, Size.newInstance(fixHeightInPixel * 2.0, fixHeightInPixel * 2.0)); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.css index 21e42e365db..bf42dd4a7a4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.css @@ -10,6 +10,12 @@ border: 1px solid #C9CACA; } +html, +body { + font-size: 100%; + line-height: 1; +} + .raceMapIndicatorPanelTextLabel { height: 12px; width: 55px; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java index 031347fa9ff..cf0841873e9 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java @@ -73,6 +73,7 @@ import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Bounds; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.ManeuverType; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.RaceIdentifier; @@ -82,6 +83,7 @@ import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing; import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition; import com.sap.sailing.gwt.ui.actions.GetBoatPositionsAction; @@ -1125,7 +1127,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (courseMarkOverlay == null) { courseMarkOverlay = new CourseMarkOverlay(map, RaceMapOverlaysZIndexes.COURSEMARK_ZINDEX, markDTO, coordinateSystem); courseMarkOverlay.setShowBuoyZone(settings.getHelpLinesSettings().isVisible(HelpLineTypes.BUOYZONE)); - courseMarkOverlay.setBuoyZoneRadiusInMeter(settings.getBuoyZoneRadiusInMeters()); + courseMarkOverlay.setBuoyZoneRadius(settings.getBuoyZoneRadius()); courseMarkOverlay.setSelected(isSelected); courseMarkOverlays.put(markDTO.getName(), courseMarkOverlay); markDTOs.put(markDTO.getName(), markDTO); @@ -1134,7 +1136,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } else { courseMarkOverlay.setMarkPosition(markDTO.position, transitionTimeInMillis); courseMarkOverlay.setShowBuoyZone(settings.getHelpLinesSettings().isVisible(HelpLineTypes.BUOYZONE)); - courseMarkOverlay.setBuoyZoneRadiusInMeter(settings.getBuoyZoneRadiusInMeters()); + courseMarkOverlay.setBuoyZoneRadius(settings.getBuoyZoneRadius()); courseMarkOverlay.setSelected(isSelected); courseMarkOverlay.draw(); toRemoveCourseMarks.remove(markDTO.getName()); @@ -1216,9 +1218,11 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (competitorInfoOverlay == null) { competitorInfoOverlay = createCompetitorInfoOverlay(RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, competitorDTO); competitorInfoOverlays.put(competitorDTO, competitorInfoOverlay); + competitorInfoOverlay.setInfoText(createInfoText(competitorDTO, lastBoatFix)); competitorInfoOverlay.setPosition(lastBoatFix.position, timeForPositionTransitionMillis); competitorInfoOverlay.addToMap(); } else { + competitorInfoOverlay.setInfoText(createInfoText(competitorDTO, lastBoatFix)); competitorInfoOverlay.setPosition(lastBoatFix.position, timeForPositionTransitionMillis); competitorInfoOverlay.draw(); } @@ -1315,15 +1319,15 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe * quarter of the circumference of the earth in longitude. A completely general, but more complicated algorithm is * necessary if greater distances are allowed. */ - public LatLng calculatePositionAlongRhumbline(LatLng position, double bearingDeg, double distanceInKm) { - double distianceRad = distanceInKm / 6371.0; // r = 6371 means earth's radius in km + public LatLng calculatePositionAlongRhumbline(LatLng position, double bearingDeg, Distance distance) { + double distanceRad = distance.getCentralAngleRad(); double lat1 = position.getLatitude() / 180. * Math.PI; double lon1 = position.getLongitude() / 180. * Math.PI; double bearingRad = bearingDeg / 180. * Math.PI; - double lat2 = Math.asin(Math.sin(lat1) * Math.cos(distianceRad) + - Math.cos(lat1) * Math.sin(distianceRad) * Math.cos(bearingRad)); - double lon2 = lon1 + Math.atan2(Math.sin(bearingRad)*Math.sin(distianceRad)*Math.cos(lat1), - Math.cos(distianceRad)-Math.sin(lat1)*Math.sin(lat2)); + double lat2 = Math.asin(Math.sin(lat1) * Math.cos(distanceRad) + + Math.cos(lat1) * Math.sin(distanceRad) * Math.cos(bearingRad)); + double lon2 = lon1 + Math.atan2(Math.sin(bearingRad)*Math.sin(distanceRad)*Math.cos(lat1), + Math.cos(distanceRad)-Math.sin(lat1)*Math.sin(lat2)); lon2 = (lon2+3*Math.PI) % (2*Math.PI) - Math.PI; // normalize to -180..+180� // position is already in LatLng space, so no mapping through coordinateSystem is required here return LatLng.newInstance(lat2 / Math.PI * 180., lon2 / Math.PI * 180.); @@ -1349,6 +1353,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe return null; } + final static Distance advantageLineLength = new MeterDistance(1000); // TODO this should probably rather scale with the visible area of the map; bug 616 private void showAdvantageLine(Iterable competitorsToShow, Date date, long timeForPositionTransitionMillis) { if (map != null && lastRaceTimesInfo != null && quickRanks != null && lastCombinedWindTrackInfoDTO != null) { boolean drawAdvantageLine = false; @@ -1377,10 +1382,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe lastBoatFix = getBoatFix(visibleLeaderInfo.getB(), date); } if (isVisibleLeaderInfoComplete && isLegTypeKnown && lastBoatFix != null && lastBoatFix.speedWithBearing != null) { - double advantageLineLengthInKm = 1.0; // TODO this should probably rather scale with the visible - // area of the map; bug 616 - double distanceFromBoatPositionInKm = visibleLeaderInfo.getB().getBoatClass() - .getHullLengthInMeters() / 1000.; // one hull length + Distance distanceFromBoatPositionInKm = visibleLeaderInfo.getB().getBoatClass().getHullLength(); // one hull length // implement and use Position.translateRhumb() double bearingOfBoatInDeg = lastBoatFix.speedWithBearing.bearingInDegrees; LatLng boatPosition = coordinateSystem.toLatLng(lastBoatFix.position); @@ -1417,9 +1419,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } MVCArray nextPath = MVCArray.newInstance(); LatLng advantageLinePos1 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, - coordinateSystem.mapDegreeBearing(rotatedBearingDeg1), advantageLineLengthInKm / 2.0); + coordinateSystem.mapDegreeBearing(rotatedBearingDeg1), advantageLineLength.scale(0.5)); LatLng advantageLinePos2 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, - coordinateSystem.mapDegreeBearing(rotatedBearingDeg2), advantageLineLengthInKm / 2.0); + coordinateSystem.mapDegreeBearing(rotatedBearingDeg2), advantageLineLength.scale(0.5)); if (advantageLine == null) { PolylineOptions options = PolylineOptions.newInstance(); options.setClickable(true); @@ -1938,8 +1940,20 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } private CompetitorInfoOverlay createCompetitorInfoOverlay(int zIndex, final CompetitorDTO competitorDTO) { - String infoText = competitorDTO.getSailID() == null || competitorDTO.getSailID().isEmpty() ? competitorDTO.getName() : competitorDTO.getSailID(); - return new CompetitorInfoOverlay(map, zIndex, competitorSelection.getColor(competitorDTO, raceIdentifier), infoText, coordinateSystem); + GPSFixDTOWithSpeedWindTackAndLegType gpsFixDTO = getBoatFix(competitorDTO, timer.getTime()); + return new CompetitorInfoOverlay(map, zIndex, competitorSelection.getColor(competitorDTO, raceIdentifier), createInfoText(competitorDTO, gpsFixDTO), coordinateSystem); + } + + private String createInfoText(CompetitorDTO competitorDTO, GPSFixDTOWithSpeedWindTackAndLegType gpsFixDTO) { + StringBuilder infoText = new StringBuilder(); + infoText.append(competitorDTO.getSailID()).append("\n"); + infoText.append(NumberFormatterFactory.getDecimalFormat(1).format(gpsFixDTO.speedWithBearing.speedInKnots)) + .append(" ").append(stringMessages.knotsUnit()).append("\n"); + QuickRankDTO quickRankDTO = quickRanks.get(competitorDTO); + if (quickRankDTO != null) { + infoText.append(stringMessages.rank()).append(" : ").append(quickRanks.get(competitorDTO).rank); + } + return infoText.toString(); } private BoatOverlay createBoatOverlay(int zIndex, final CompetitorDTO competitorDTO, boolean highlighted) { @@ -2491,7 +2505,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (newSettings.getTailLengthInMilliseconds() != settings.getTailLengthInMilliseconds()) { requiresRedraw = true; } - if (newSettings.getBuoyZoneRadiusInMeters() != settings.getBuoyZoneRadiusInMeters()) { + if (newSettings.getBuoyZoneRadius() != settings.getBuoyZoneRadius()) { requiresRedraw = true; } if (newSettings.isShowOnlySelectedCompetitors() != settings.isShowOnlySelectedCompetitors()) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettings.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettings.java index ac9929cc983..8d595602e18 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettings.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettings.java @@ -3,8 +3,10 @@ package com.sap.sailing.gwt.ui.client.shared.racemap; import java.util.HashSet; import java.util.Set; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.ManeuverType; import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes; import com.sap.sse.common.Util; import com.sap.sse.common.settings.AbstractSettings; @@ -17,6 +19,9 @@ public class RaceMapSettings extends AbstractSettings { public static final String PARAM_VIEW_SHOW_STREAMLETS = "viewShowStreamlets"; public static final String PARAM_VIEW_SHOW_STREAMLET_COLORS = "viewShowStreamletColors"; public static final String PARAM_VIEW_SHOW_SIMULATION = "viewShowSimulation"; + public static final String PARAM_BUOY_ZONE_RADIUS_IN_METERS = "buoyZoneRadiusInMeters"; + + public static final Distance DEFAULT_BUOY_ZONE_RADIUS = new MeterDistance(15); private final boolean showDouglasPeuckerPoints; @@ -34,7 +39,7 @@ public class RaceMapSettings extends AbstractSettings { private final long tailLengthInMilliseconds; - private final double buoyZoneRadiusInMeters; + private final Distance buoyZoneRadius; private final boolean showSelectedCompetitorsInfo; @@ -61,6 +66,7 @@ public class RaceMapSettings extends AbstractSettings { /* showMapControls */ true, new RaceMapHelpLinesSettings(), /* windUp */ false, + /* buoyZoneRadius */ DEFAULT_BUOY_ZONE_RADIUS, /* showWindStreamletOverlay */ false, /* showWindStreamletColors */ false, /* showSimulationOverlay */ false); @@ -68,7 +74,7 @@ public class RaceMapSettings extends AbstractSettings { public RaceMapSettings(RaceMapZoomSettings zoomSettings, RaceMapHelpLinesSettings helpLinesSettings, boolean transparentHoverlines, int hoverlineStrokeWeight, long tailLengthInMilliseconds, boolean windUp, - double buoyZoneRadiusInMeters, boolean showOnlySelectedCompetitors, boolean showSelectedCompetitorsInfo, + Distance buoyZoneRadius, boolean showOnlySelectedCompetitors, boolean showSelectedCompetitorsInfo, boolean showWindStreamletColors, boolean showWindStreamletOverlay, boolean showSimulationOverlay, boolean showMapControls, Set maneuverTypesToShow, boolean showDouglasPeuckerPoints) { this.zoomSettings = zoomSettings; @@ -77,7 +83,7 @@ public class RaceMapSettings extends AbstractSettings { this.hoverlineStrokeWeight = hoverlineStrokeWeight; this.tailLengthInMilliseconds = tailLengthInMilliseconds; this.windUp = windUp; - this.buoyZoneRadiusInMeters = buoyZoneRadiusInMeters; + this.buoyZoneRadius = buoyZoneRadius; this.showOnlySelectedCompetitors = showOnlySelectedCompetitors; this.showSelectedCompetitorsInfo = showSelectedCompetitorsInfo; this.showWindStreamletColors = showWindStreamletColors; @@ -88,11 +94,11 @@ public class RaceMapSettings extends AbstractSettings { this.showDouglasPeuckerPoints = showDouglasPeuckerPoints; } - private RaceMapSettings(boolean showMapControls, boolean showCourseGeometry, boolean windUp, boolean showWindStreamletOverlay, boolean showWindStreamletColors, boolean showSimulationOverlay) { - this(showMapControls, new RaceMapHelpLinesSettings(createHelpLineSettings(showCourseGeometry)), windUp, showWindStreamletOverlay, showWindStreamletColors, showSimulationOverlay); + private RaceMapSettings(boolean showMapControls, boolean showCourseGeometry, boolean windUp, Distance buoyZoneRadius, boolean showWindStreamletOverlay, boolean showWindStreamletColors, boolean showSimulationOverlay) { + this(showMapControls, new RaceMapHelpLinesSettings(createHelpLineSettings(showCourseGeometry)), windUp, buoyZoneRadius, showWindStreamletOverlay, showWindStreamletColors, showSimulationOverlay); } - - private RaceMapSettings(boolean showMapControls, RaceMapHelpLinesSettings helpLineSettings, boolean windUp, boolean showWindStreamletOverlay, boolean showWindStreamletColors, boolean showSimulationOverlay) { + + private RaceMapSettings(boolean showMapControls, RaceMapHelpLinesSettings helpLineSettings, boolean windUp, Distance buoyZoneRadius, boolean showWindStreamletOverlay, boolean showWindStreamletColors, boolean showSimulationOverlay) { this( new RaceMapZoomSettings(), helpLineSettings, @@ -100,7 +106,7 @@ public class RaceMapSettings extends AbstractSettings { /* hoverlineStrokeWeight as discussed with Stefan on 2015-12-08 */ 15, /* tailLengthInMilliseconds */ 100000l, /* windUp */ windUp, - /* buoyZoneRadiusInMeters */ 0.0, + /* buoyZoneRadius */ buoyZoneRadius, /* showOnlySelectedCompetitors */ false, /* showSelectedCompetitorsInfo */ true, /* showWindStreamletColors */ showWindStreamletColors, @@ -133,7 +139,7 @@ public class RaceMapSettings extends AbstractSettings { * copy constructor that produces a new settings object that equals the one passed as argument but takes the zoom settings from the second parameter */ public RaceMapSettings(RaceMapSettings settings, RaceMapZoomSettings zoomSettings) { - this.buoyZoneRadiusInMeters = settings.buoyZoneRadiusInMeters; + this.buoyZoneRadius = settings.buoyZoneRadius; this.helpLinesSettings = new RaceMapHelpLinesSettings(settings.getHelpLinesSettings().getVisibleHelpLineTypes()); this.transparentHoverlines = settings.transparentHoverlines; this.hoverlineStrokeWeight = settings.hoverlineStrokeWeight; @@ -209,8 +215,8 @@ public class RaceMapSettings extends AbstractSettings { return showSelectedCompetitorsInfo; } - public double getBuoyZoneRadiusInMeters() { - return buoyZoneRadiusInMeters; + public Distance getBuoyZoneRadius() { + return buoyZoneRadius; } public boolean isWindUp() { @@ -239,7 +245,8 @@ public class RaceMapSettings extends AbstractSettings { final boolean showWindStreamletOverlay = GwtHttpRequestUtils.getBooleanParameter(PARAM_VIEW_SHOW_STREAMLETS, defaultForViewShowStreamlets /* default */); final boolean showWindStreamletColors = GwtHttpRequestUtils.getBooleanParameter(PARAM_VIEW_SHOW_STREAMLET_COLORS, defaultForViewShowStreamletColors /* default */); final boolean showSimulationOverlay = GwtHttpRequestUtils.getBooleanParameter(PARAM_VIEW_SHOW_SIMULATION, defaultForViewShowSimulation /* default */); - return new RaceMapSettings(showMapControls, showCourseGeometry, windUp, showWindStreamletOverlay, showWindStreamletColors, showSimulationOverlay); + final double buoyZoneRadiusInMeters = GwtHttpRequestUtils.getDoubleParameter(PARAM_BUOY_ZONE_RADIUS_IN_METERS, DEFAULT_BUOY_ZONE_RADIUS.getMeters() /* default */); + return new RaceMapSettings(showMapControls, showCourseGeometry, windUp, new MeterDistance(buoyZoneRadiusInMeters), showWindStreamletOverlay, showWindStreamletColors, showSimulationOverlay); } public Set getManeuverTypesToShow() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java index 6d7b97f8a10..bfa11d89382 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMapSettingsDialogComponent.java @@ -18,7 +18,9 @@ import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.LongBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.ManeuverType; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.gwt.ui.client.ManeuverTypeFormatter; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes; @@ -30,7 +32,7 @@ import com.sap.sse.gwt.client.dialog.DataEntryDialog.Validator; import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; public class RaceMapSettingsDialogComponent implements SettingsDialogComponent { - private static final int MAX_BUOY_ZONE_RADIUS_IN_METERS = 100; + private static final Distance MAX_BUOY_ZONE_RADIUS = new MeterDistance(100); private static final int MAX_STROKE_WEIGHT = 33; //Initializing the lists to prevent a null pointer exception in the first validation call private List> checkboxAndManeuverType = new ArrayList>(); @@ -45,7 +47,7 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent vce) { boolean newValue = vce.getValue(); - RaceMapSettingsDialogComponent.this.buoyZoneRadiusBox.setEnabled(newValue); + RaceMapSettingsDialogComponent.this.buoyZoneRadiusInMetersBox.setEnabled(newValue); } }); Label buoyZoneRadiusLabel = new Label(stringMessages.radiusInMeters() + ":"); buoyZoneRadiusLabel.getElement().getStyle().setMarginLeft(25, Unit.PX); buoyZoneSettingsPanel.add(buoyZoneRadiusLabel); buoyZoneSettingsPanel.setCellVerticalAlignment(buoyZoneRadiusLabel, HasVerticalAlignment.ALIGN_MIDDLE); - buoyZoneRadiusBox = dialog.createDoubleBox(Double.valueOf((int) (initialSettings.getBuoyZoneRadiusInMeters())), 4); - buoyZoneRadiusBox.setEnabled(initialSettings.getHelpLinesSettings().isVisible(HelpLineTypes.BOATTAILS)); - buoyZoneSettingsPanel.add(buoyZoneRadiusBox); - buoyZoneSettingsPanel.setCellVerticalAlignment(buoyZoneRadiusBox, HasVerticalAlignment.ALIGN_MIDDLE); + buoyZoneRadiusInMetersBox = dialog.createDoubleBox(initialSettings.getBuoyZoneRadius().getMeters(), 4); + buoyZoneRadiusInMetersBox.setEnabled(initialSettings.getHelpLinesSettings().isVisible(HelpLineTypes.BOATTAILS)); + buoyZoneSettingsPanel.add(buoyZoneRadiusInMetersBox); + buoyZoneSettingsPanel.setCellVerticalAlignment(buoyZoneRadiusInMetersBox, HasVerticalAlignment.ALIGN_MIDDLE); vp.add(buoyZoneSettingsPanel); vp.add(createHelpLineCheckBox(dialog, HelpLineTypes.STARTLINE)); @@ -272,14 +274,14 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent MAX_BUOY_ZONE_RADIUS_IN_METERS)) { + && (valueToValidate.getBuoyZoneRadius().compareTo(Distance.NULL) < 0 || valueToValidate.getBuoyZoneRadius().compareTo(MAX_BUOY_ZONE_RADIUS) > 0)) { errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.buoyZone(), 0, 100); } else if (valueToValidate.getHoverlineStrokeWeight() < 0 || valueToValidate.getHoverlineStrokeWeight() > MAX_STROKE_WEIGHT) { errorMessage = stringMessages.valueMustBeBetweenMinMax(stringMessages.bufferLineStrokeWeight(), 0, MAX_STROKE_WEIGHT); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/TrueNorthIndicatorPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/TrueNorthIndicatorPanel.java index 48cd14e3424..4d6089c6981 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/TrueNorthIndicatorPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/TrueNorthIndicatorPanel.java @@ -49,7 +49,7 @@ public class TrueNorthIndicatorPanel extends FlowPanel { final RaceMapSettings newRaceMapSettings = new RaceMapSettings(oldRaceMapSettings.getZoomSettings(), oldRaceMapSettings.getHelpLinesSettings(), oldRaceMapSettings.getTransparentHoverlines(), oldRaceMapSettings.getHoverlineStrokeWeight(), oldRaceMapSettings.getTailLengthInMilliseconds(), newWindUpSettings, - oldRaceMapSettings.getBuoyZoneRadiusInMeters(), oldRaceMapSettings.isShowOnlySelectedCompetitors(), + oldRaceMapSettings.getBuoyZoneRadius(), oldRaceMapSettings.isShowOnlySelectedCompetitors(), oldRaceMapSettings.isShowSelectedCompetitorsInfo(), oldRaceMapSettings.isShowWindStreamletColors(), oldRaceMapSettings.isShowWindStreamletOverlay(), oldRaceMapSettings.isShowSimulationOverlay(), oldRaceMapSettings.isShowMapControls(), oldRaceMapSettings.getManeuverTypesToShow(), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/DataMiningEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/DataMiningEntryPoint.java index 57b7e5caea5..4e88d39267d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/DataMiningEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/datamining/DataMiningEntryPoint.java @@ -1,71 +1,83 @@ -package com.sap.sailing.gwt.ui.datamining; - -import java.util.UUID; - -import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.user.client.rpc.ServiceDefTarget; -import com.google.gwt.user.client.ui.DockLayoutPanel; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.RootLayoutPanel; -import com.google.gwt.user.client.ui.SplitLayoutPanel; -import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint; -import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel; -import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; -import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; -import com.sap.sailing.gwt.ui.datamining.execution.SimpleQueryRunner; -import com.sap.sailing.gwt.ui.datamining.presentation.TabbedResultsPresenter; -import com.sap.sailing.gwt.ui.datamining.selection.BufferingQueryDefinitionProviderWithControls; -import com.sap.sse.datamining.shared.DataMiningSession; -import com.sap.sse.datamining.shared.impl.UUIDDataMiningSession; -import com.sap.sse.gwt.client.EntryPointHelper; -import com.sap.sse.gwt.client.shared.components.ComponentResources; -import com.sap.sse.gwt.resources.Highcharts; - -public class DataMiningEntryPoint extends AbstractSailingEntryPoint { - - public static final ComponentResources resources = GWT.create(ComponentResources.class); - - private final DataMiningServiceAsync dataMiningService = GWT.create(DataMiningService.class); - - private DataMiningSession session; - - @Override - protected void doOnModuleLoad() { - Highcharts.ensureInjectedWithMore(); - super.doOnModuleLoad(); - session = new UUIDDataMiningSession(UUID.randomUUID()); - - EntryPointHelper.registerASyncService((ServiceDefTarget) dataMiningService, RemoteServiceMappingConstants.dataMiningServiceRemotePath); - - RootLayoutPanel rootPanel = RootLayoutPanel.get(); - SplitLayoutPanel splitPanel = new SplitLayoutPanel(15); - rootPanel.add(splitPanel); - - DataMiningSettingsControl settingsControl = new AnchorDataMiningSettingsControl(getStringMessages()); - final ResultsPresenter resultsPresenter = new TabbedResultsPresenter(getStringMessages()); - - DockLayoutPanel selectionDockPanel = new DockLayoutPanel(Unit.PX); - selectionDockPanel.addNorth(createLogoAndTitlePanel(), 68); - BufferingQueryDefinitionProviderWithControls queryDefinitionProviderWithControls = - new BufferingQueryDefinitionProviderWithControls(session, getStringMessages(), dataMiningService, this, settingsControl, resultsPresenter); - queryDefinitionProviderWithControls.getEntryWidget().addStyleName("dataMiningPanel"); - selectionDockPanel.add(queryDefinitionProviderWithControls.getEntryWidget()); - - splitPanel.addSouth(resultsPresenter.getEntryWidget(), 350); - splitPanel.add(selectionDockPanel); - - final QueryRunner queryRunner = new SimpleQueryRunner(session, getStringMessages(), dataMiningService, this, queryDefinitionProviderWithControls, resultsPresenter); - queryDefinitionProviderWithControls.addControl(queryRunner.getEntryWidget()); - settingsControl.addSettingsComponent(queryRunner); - } - - private LogoAndTitlePanel createLogoAndTitlePanel() { - LogoAndTitlePanel logoAndTitlePanel = new LogoAndTitlePanel(getStringMessages().dataMining(), getStringMessages(), this, getUserService()); - logoAndTitlePanel.addStyleName("LogoAndTitlePanel"); - FlowPanel globalNavigationPanel = new GlobalNavigationPanel(getStringMessages(), true, null, null, /* event */ null, null); - logoAndTitlePanel.add(globalNavigationPanel); - return logoAndTitlePanel; - } - +package com.sap.sailing.gwt.ui.datamining; + +import java.util.UUID; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.rpc.ServiceDefTarget; +import com.google.gwt.user.client.ui.DockLayoutPanel; +import com.google.gwt.user.client.ui.RootLayoutPanel; +import com.google.gwt.user.client.ui.SplitLayoutPanel; +import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication; +import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint; +import com.sap.sailing.gwt.ui.client.RemoteServiceMappingConstants; +import com.sap.sailing.gwt.ui.datamining.execution.SimpleQueryRunner; +import com.sap.sailing.gwt.ui.datamining.presentation.TabbedResultsPresenter; +import com.sap.sailing.gwt.ui.datamining.selection.BufferingQueryDefinitionProviderWithControls; +import com.sap.sse.datamining.shared.DataMiningSession; +import com.sap.sse.datamining.shared.impl.UUIDDataMiningSession; +import com.sap.sse.gwt.client.EntryPointHelper; +import com.sap.sse.gwt.client.shared.components.ComponentResources; +import com.sap.sse.gwt.resources.Highcharts; +import com.sap.sse.security.ui.authentication.decorator.AuthorizedContentDecorator; +import com.sap.sse.security.ui.authentication.decorator.WidgetFactory; +import com.sap.sse.security.ui.authentication.generic.GenericAuthentication; +import com.sap.sse.security.ui.authentication.generic.GenericAuthorizedContentDecorator; +import com.sap.sse.security.ui.authentication.generic.sapheader.SAPHeaderWithAuthentication; + +public class DataMiningEntryPoint extends AbstractSailingEntryPoint { + + public static final ComponentResources resources = GWT.create(ComponentResources.class); + + private final DataMiningServiceAsync dataMiningService = GWT.create(DataMiningService.class); + + private DataMiningSession session; + + @Override + protected void doOnModuleLoad() { + Highcharts.ensureInjectedWithMore(); + super.doOnModuleLoad(); + session = new UUIDDataMiningSession(UUID.randomUUID()); + + EntryPointHelper.registerASyncService((ServiceDefTarget) dataMiningService, RemoteServiceMappingConstants.dataMiningServiceRemotePath); + createDataminingPanel(); + } + + private void createDataminingPanel() { + SAPHeaderWithAuthentication header = new SAPHeaderWithAuthentication(getStringMessages().sapSailingAnalytics(), getStringMessages().dataMining()); + GenericAuthentication genericSailingAuthentication = new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView()); + AuthorizedContentDecorator authorizedContentDecorator = new GenericAuthorizedContentDecorator(genericSailingAuthentication); + authorizedContentDecorator.setContentWidgetFactory(new WidgetFactory() { + @Override + public Widget get() { + DataMiningSettingsControl settingsControl = new AnchorDataMiningSettingsControl(getStringMessages()); + ResultsPresenter resultsPresenter = new TabbedResultsPresenter(getStringMessages()); + + DockLayoutPanel selectionDockPanel = new DockLayoutPanel(Unit.PX); + BufferingQueryDefinitionProviderWithControls queryDefinitionProviderWithControls = + new BufferingQueryDefinitionProviderWithControls(session, getStringMessages(), + dataMiningService, DataMiningEntryPoint.this, settingsControl, resultsPresenter); + queryDefinitionProviderWithControls.getEntryWidget().addStyleName("dataMiningPanel"); + selectionDockPanel.add(queryDefinitionProviderWithControls.getEntryWidget()); + + QueryRunner queryRunner = new SimpleQueryRunner(session, getStringMessages(), dataMiningService, + DataMiningEntryPoint.this, queryDefinitionProviderWithControls, resultsPresenter); + queryDefinitionProviderWithControls.addControl(queryRunner.getEntryWidget()); + settingsControl.addSettingsComponent(queryRunner); + + SplitLayoutPanel splitPanel = new SplitLayoutPanel(15); + splitPanel.addSouth(resultsPresenter.getEntryWidget(), 350); + splitPanel.add(selectionDockPanel); + return splitPanel; + } + }); + + RootLayoutPanel rootPanel = RootLayoutPanel.get(); + DockLayoutPanel panel = new DockLayoutPanel(Unit.PX); + panel.addNorth(header, 75); + panel.add(authorizedContentDecorator); + rootPanel.add(panel); + } + } \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java index 44e0e66d4dd..107d1277112 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java @@ -21,9 +21,9 @@ import com.sap.sailing.domain.common.LeaderboardType; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO; +import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication; +import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication; import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint; -import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel; -import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies; import com.sap.sailing.gwt.ui.shared.EventDTO; @@ -45,7 +45,6 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { private String leaderboardName; private String leaderboardGroupName; private LeaderboardType leaderboardType; - private GlobalNavigationPanel globalNavigationPanel; private EventDTO event; @Override @@ -121,30 +120,15 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { private void createUI(boolean showRaceDetails, boolean embedded, boolean hideToolbar, EventDTO event) { DockLayoutPanel mainPanel = new DockLayoutPanel(Unit.PX); RootLayoutPanel.get().add(mainPanel); - LogoAndTitlePanel logoAndTitlePanel = null; if (!embedded) { // Hack to shorten the leaderboardName in case of overall leaderboards String leaderboardDisplayName = Window.Location.getParameter("displayName"); if (leaderboardDisplayName == null || leaderboardDisplayName.isEmpty()) { leaderboardDisplayName = leaderboardName; } - globalNavigationPanel = new GlobalNavigationPanel(getStringMessages(), true, null, leaderboardGroupName, event, null); - logoAndTitlePanel = new LogoAndTitlePanel(leaderboardGroupName, leaderboardDisplayName, getStringMessages(), this, getUserService()) { - @Override - public void onResize() { - super.onResize(); - if (isSmallWidth()) { - remove(globalNavigationPanel); - } else { - add(globalNavigationPanel); - } - } - }; - logoAndTitlePanel.addStyleName("LogoAndTitlePanel"); - if (!isSmallWidth()) { - logoAndTitlePanel.add(globalNavigationPanel); - } - mainPanel.addNorth(logoAndTitlePanel, 68); + SAPSailingHeaderWithAuthentication header = new SAPSailingHeaderWithAuthentication(leaderboardDisplayName); + new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView()); + mainPanel.addNorth(header, 75); } ScrollPanel contentScrollPanel = new ScrollPanel(); long delayBetweenAutoAdvancesInMilliseconds = DEFAULT_REFRESH_INTERVAL_MILLIS; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index 04bd6b27d09..3d4056386e1 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -58,6 +58,7 @@ import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.sap.sailing.domain.common.DetailType; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.InvertibleComparator; import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.MaxPointsReason; @@ -97,6 +98,7 @@ import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil; import com.sap.sailing.gwt.ui.leaderboard.DetailTypeColumn.LegDetailField; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies; import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; +import com.sap.sse.common.Duration; import com.sap.sse.common.Util; import com.sap.sse.common.Util.Pair; import com.sap.sse.common.filter.BinaryOperator; @@ -901,15 +903,17 @@ public class LeaderboardPanel extends SimplePanel implements Component { @Override public Double get(LeaderboardRowDTO row) { - Double result = null; - LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); - if (fieldsForRace != null && fieldsForRace.legDetails != null) { - double distanceTraveledInMeters = 0; - long timeInMilliseconds = 0; - for (LegEntryDTO legDetail : fieldsForRace.legDetails) { - if (legDetail != null) { - if (legDetail.distanceTraveledInMeters != null && legDetail.timeInMilliseconds != null) { - distanceTraveledInMeters += legDetail.distanceTraveledInMeters; - timeInMilliseconds += legDetail.timeInMilliseconds; - } else { - distanceTraveledInMeters = 0; - timeInMilliseconds = 0; - break; - } - } - } - if (timeInMilliseconds != 0) { - result = distanceTraveledInMeters / (double) timeInMilliseconds * 1000 * 3600 / 1852; - } + final Distance distanceTraveledInMeters = row.getDistanceTraveledInMeters(getRaceColumnName()); + final Duration time = row.getTimeSailed(getRaceColumnName()); + final Double result; + if (distanceTraveledInMeters != null && time != null) { + result = distanceTraveledInMeters.inTime(time).getKnots(); + } else { + result = null; } return result; } @@ -1297,21 +1288,7 @@ public class LeaderboardPanel extends SimplePanel implements Component { @Override public Double get(LeaderboardRowDTO row) { - Double result = null; - LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); - if (fieldsForRace != null && fieldsForRace.legDetails != null) { - for (LegEntryDTO legDetail : fieldsForRace.legDetails) { - if (legDetail != null) { - if (legDetail.distanceTraveledInMeters != null) { - if (result == null) { - result = 0.0; - } - result += legDetail.distanceTraveledInMeters; - } - } - } - } - return result; + return row.getDistanceTraveledInMeters(getRaceColumnName()).getMeters(); } } @@ -1444,27 +1421,48 @@ public class LeaderboardPanel extends SimplePanel implements Component { + private class RaceCurrentSpeedOverGroundInKnots extends AbstractLastLegDetailField { @Override - protected Double get(LegEntryDTO lastLegDetail) { - return lastLegDetail.currentSpeedOverGroundInKnots; + protected Double getBeforeLastLegFinished(LegEntryDTO currentLegDetail) { + return currentLegDetail.currentSpeedOverGroundInKnots; + } + + @Override + protected Double getAfterLastLegFinished(LeaderboardRowDTO row) { + return new RaceAverageSpeedInKnots().get(row); } } - private class CurrentRideHeightInMeters extends AbstractLastLegDetailField { + private class RaceCurrentRideHeightInMeters extends AbstractLastLegDetailField { @Override - protected Double get(LegEntryDTO lastLegDetail) { - return lastLegDetail.currentRideHeightInMeters; + protected Double getBeforeLastLegFinished(LegEntryDTO currentLegDetail) { + return currentLegDetail.currentRideHeightInMeters; + } + + @Override + protected Double getAfterLastLegFinished(LeaderboardRowDTO row) { + Double result = null; + LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); + if (fieldsForRace != null) { + result = fieldsForRace.averageRideHeightInMeters; + } + return result; } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java index 25b838bd3cb..8053321aa88 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardSettingsDialogComponent.java @@ -70,6 +70,7 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone dialogPanel.add(createSelectedRacesPanel(dialog)); dialogPanel.add(createOverallDetailPanel(dialog)); dialogPanel.add(createRaceDetailPanel(dialog)); + dialogPanel.add(createRaceStartAnalysisPanel(dialog)); dialogPanel.add(createLegDetailsPanel(dialog)); dialogPanel.add(createManeuverDetailsPanel(dialog)); dialogPanel.add(createTimingDetailsPanel(dialog)); @@ -149,6 +150,28 @@ public class LeaderboardSettingsDialogComponent implements SettingsDialogCompone return raceDetailDialog; } + private FlowPanel createRaceStartAnalysisPanel(DataEntryDialog dialog) { + FlowPanel raceStartAnalysisDialog = new FlowPanel(); + raceStartAnalysisDialog.ensureDebugId("RaceStartAnalysisDialog"); + raceStartAnalysisDialog.add(dialog.createHeadline(stringMessages.raceStartAnalysis(), true)); + raceStartAnalysisDialog.addStyleName("SettingsDialogComponent"); + int detailCountInCurrentFlowPanel = 0; + List currentRaceDetailSelection = initialSettings.getRaceDetailsToShow(); + FlowPanel raceStartAnalysisDialogContent = null; + for (DetailType type : LeaderboardPanel.getAvailableRaceStartAnalysisColumnTypes()) { + if (detailCountInCurrentFlowPanel % 8 == 0) { + raceStartAnalysisDialogContent = new FlowPanel(); + raceStartAnalysisDialogContent.addStyleName("dialogInnerContent"); + raceStartAnalysisDialog.add(raceStartAnalysisDialogContent); + } + CheckBox checkbox = createAndRegisterCheckbox(dialog, type, currentRaceDetailSelection.contains(type), + raceDetailCheckboxes); + raceStartAnalysisDialogContent.add(checkbox); + detailCountInCurrentFlowPanel++; + } + return raceStartAnalysisDialog; + } + private FlowPanel createOverallDetailPanel(DataEntryDialog dialog) { FlowPanel overallDetailDialog = new FlowPanel(); overallDetailDialog.ensureDebugId("OverallDetailsSettingsPanel"); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ViewerToolbar.css b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ViewerToolbar.css index d55a8630c74..769bf019f09 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ViewerToolbar.css +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/ViewerToolbar.css @@ -76,7 +76,7 @@ font-size: 14px; margin: 0 0 0 0; padding: 2px 7px 2px 3px; - height: 18px; + height: 22px; display: inline-block; text-decoration: none; -moz-border-radius: 5px 0 0 5px; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java index a06944fbf59..efb8d3a97c3 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/EmbeddedMapAndWindChartEntryPoint.java @@ -110,7 +110,7 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint final RaceMapSettings raceMapSettings = new RaceMapSettings(raceMapZoomSettings, raceMapHelpLinesSettings, defaultRaceMapSettings.getTransparentHoverlines(), defaultRaceMapSettings.getHoverlineStrokeWeight(), defaultRaceMapSettings.getTailLengthInMilliseconds(), windUp, - defaultRaceMapSettings.getBuoyZoneRadiusInMeters(), defaultRaceMapSettings.isShowOnlySelectedCompetitors(), + defaultRaceMapSettings.getBuoyZoneRadius(), defaultRaceMapSettings.isShowOnlySelectedCompetitors(), defaultRaceMapSettings.isShowSelectedCompetitorsInfo(), defaultRaceMapSettings.isShowWindStreamletColors(), defaultRaceMapSettings.isShowWindStreamletOverlay(), defaultRaceMapSettings.isShowSimulationOverlay(), defaultRaceMapSettings.isShowMapControls(), defaultRaceMapSettings.getManeuverTypesToShow(), diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java index daad9911bf1..71951d4a498 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java @@ -17,14 +17,17 @@ import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.i18n.client.DateTimeFormat; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.MenuBar; +import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.Widget; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; @@ -34,6 +37,7 @@ import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.LeaderboardDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.gwt.common.authentication.SailingAuthenticationEntryPointLinkFactory; import com.sap.sailing.gwt.ui.client.CompetitorColorProvider; import com.sap.sailing.gwt.ui.client.CompetitorColorProviderImpl; @@ -73,6 +77,8 @@ import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory; import com.sap.sailing.gwt.ui.raceboard.RaceBoardResources.RaceBoardMainCss; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sse.common.Util; import com.sap.sse.common.filter.FilterSet; import com.sap.sse.common.settings.AbstractSettings; import com.sap.sse.gwt.client.ErrorReporter; @@ -86,6 +92,7 @@ import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent; import com.sap.sse.gwt.client.shared.perspective.AbstractPerspectiveComposite; import com.sap.sse.gwt.client.shared.perspective.PerspectiveLifecycleWithAllSettings; import com.sap.sse.gwt.client.useragent.UserAgentDetails; +import com.sap.sse.gwt.shared.GwtHttpRequestUtils; import com.sap.sse.security.ui.authentication.generic.GenericAuthentication; import com.sap.sse.security.ui.authentication.view.AuthenticationMenuView; import com.sap.sse.security.ui.authentication.view.AuthenticationMenuViewImpl; @@ -101,7 +108,7 @@ import com.sap.sse.security.ui.client.UserService; * @author Frank Mittag, Axel Uhl (d043530) * */ -public class RaceBoardPanel extends AbstractPerspectiveComposite implements LeaderboardUpdateListener, PopupPositionProvider +public class RaceBoardPanel extends AbstractPerspectiveComposite implements LeaderboardUpdateListener, PopupPositionProvider,RequiresResize { private final SailingServiceAsync sailingService; private final MediaServiceAsync mediaService; @@ -138,7 +145,7 @@ public class RaceBoardPanel extends AbstractPerspectiveComposite() { + @Override + public void onSuccess(RegattaDTO regattaDTO) { + Distance buoyZoneRadius = regattaDTO.getCalculatedBuoyZoneRadius(); + RaceMapSettings existingMapSettings = raceMap.getSettings(); + if (!Util.equalsWithNull(buoyZoneRadius, existingMapSettings.getBuoyZoneRadius())) { + final RaceMapSettings newRaceMapSettings = new RaceMapSettings( + existingMapSettings.getZoomSettings(), existingMapSettings.getHelpLinesSettings(), + existingMapSettings.getTransparentHoverlines(), + existingMapSettings.getHoverlineStrokeWeight(), + existingMapSettings.getTailLengthInMilliseconds(), existingMapSettings.isWindUp(), + buoyZoneRadius, existingMapSettings.isShowOnlySelectedCompetitors(), + existingMapSettings.isShowSelectedCompetitorsInfo(), + existingMapSettings.isShowWindStreamletColors(), + existingMapSettings.isShowWindStreamletOverlay(), + existingMapSettings.isShowSimulationOverlay(), existingMapSettings.isShowMapControls(), + existingMapSettings.getManeuverTypesToShow(), + existingMapSettings.isShowDouglasPeuckerPoints()); + raceMap.updateSettings(newRaceMapSettings); + } + } + + @Override + public void onFailure(Throwable caught) { + } + }); + } + CompetitorFilterPanel competitorSearchTextBox = new CompetitorFilterPanel(competitorSelectionProvider, stringMessages, raceMap, new LeaderboardFetcher() { @Override @@ -639,5 +690,10 @@ public class RaceBoardPanel extends AbstractPerspectiveComposite(), getService())), false, - baseDomainFactory.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, + baseDomainFactory.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, RankingMetricsFactory.getRankingMetricConstructor(RankingMetrics.ONE_DESIGN)); // TODO: is course area relevant for swiss timing replay? } else { @@ -2939,7 +2941,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S final BravoFixTrack bravoFixTrack = trackedRace .> getSensorTrack(competitor, BravoFixTrack.TRACK_NAME); if (bravoFixTrack != null) { - result = bravoFixTrack.getRideHeight(timePoint); + final Distance rideHeight = bravoFixTrack.getRideHeight(timePoint); + result = rideHeight == null ? null : rideHeight.getMeters(); } break; default: @@ -3912,7 +3915,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public void updateRegatta(RegattaIdentifier regattaName, Date startDate, Date endDate, UUID defaultCourseAreaUuid, - RegattaConfigurationDTO configurationDTO, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes) { + RegattaConfigurationDTO configurationDTO, Double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes) { Regatta regatta = getService().getRegatta(regattaName); if (regatta != null) { SecurityUtils.getSubject().checkPermission(Permission.REGATTA.getStringPermissionForObjects(Mode.UPDATE, regatta.getName())); @@ -3920,7 +3923,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S TimePoint startTimePoint = startDate != null ? new MillisecondsTimePoint(startDate) : null; TimePoint endTimePoint = endDate != null ? new MillisecondsTimePoint(endDate) : null; getService().apply(new UpdateSpecificRegatta(regattaName, startTimePoint, endTimePoint, - defaultCourseAreaUuid, convertToRegattaConfiguration(configurationDTO), useStartTimeInference, controlTrackingFromStartAndFinishTimes)); + defaultCourseAreaUuid, convertToRegattaConfiguration(configurationDTO), buoyZoneRadiusInHullLengths, useStartTimeInference, controlTrackingFromStartAndFinishTimes)); } @Override @@ -4011,7 +4014,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public RegattaDTO createRegatta(String regattaName, String boatClassName, Date startDate, Date endDate, RegattaCreationParametersDTO seriesNamesWithFleetNamesAndFleetOrderingAndMedal, - boolean persistent, ScoringSchemeType scoringSchemeType, UUID defaultCourseAreaId, boolean useStartTimeInference, + boolean persistent, ScoringSchemeType scoringSchemeType, UUID defaultCourseAreaId, Double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes, RankingMetrics rankingMetricType) { SecurityUtils.getSubject().checkPermission(Permission.REGATTA.getStringPermissionForObjects(Mode.CREATE, regattaName)); TimePoint startTimePoint = startDate != null ? new MillisecondsTimePoint(startDate) : null; @@ -4020,7 +4023,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S new AddSpecificRegatta( regattaName, boatClassName, startTimePoint, endTimePoint, UUID.randomUUID(), seriesNamesWithFleetNamesAndFleetOrderingAndMedal, - persistent, baseDomainFactory.createScoringScheme(scoringSchemeType), defaultCourseAreaId, useStartTimeInference, + persistent, baseDomainFactory.createScoringScheme(scoringSchemeType), defaultCourseAreaId, buoyZoneRadiusInHullLengths, useStartTimeInference, controlTrackingFromStartAndFinishTimes, rankingMetricType)); return convertToRegattaDTO(regatta); } @@ -4228,16 +4231,18 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S if (leaderboard != null) { if (visibleRegattas != null && !visibleRegattas.contains(leaderboard.getName())) { return result; - } + } String regattaName = getRegattaNameFromLeaderboard(leaderboard); if (leaderboard instanceof RegattaLeaderboard) { RegattaLeaderboard regattaLeaderboard = (RegattaLeaderboard) leaderboard; - BoatClass boatClass = regattaLeaderboard.getRegatta().getBoatClass(); - for (Series series : regattaLeaderboard.getRegatta().getSeries()) { + Regatta regatta = regattaLeaderboard.getRegatta(); + BoatClass boatClass = regatta.getBoatClass(); + Distance buyZoneRadius = RegattaUtil.getCalculatedRegattaBuoyZoneRadius(regatta, boatClass); + for (Series series : regatta.getSeries()) { Map> entriesPerFleet = new HashMap>(); for (RaceColumn raceColumn : series.getRaceColumns()) { getRegattaOverviewEntries(showOnlyRacesOfSameDay, dayToCheck, - usedCourseArea, leaderboard, boatClass.getName(), regattaName, series.getName(), raceColumn, entriesPerFleet); + usedCourseArea, leaderboard, boatClass.getName(), regattaName, buyZoneRadius, series.getName(), raceColumn, entriesPerFleet); } result.addAll(getRegattaOverviewEntriesToBeShown(showOnlyCurrentlyRunningRaces, entriesPerFleet)); } @@ -4248,10 +4253,11 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S boatClass = trackedRace.getRace().getBoatClass(); break; } + Distance buyZoneRadius = RegattaUtil.getCalculatedRegattaBuoyZoneRadius(null, boatClass); Map> entriesPerFleet = new HashMap>(); for (RaceColumn raceColumn : leaderboard.getRaceColumns()) { getRegattaOverviewEntries(showOnlyRacesOfSameDay, dayToCheck, usedCourseArea, - leaderboard, boatClass == null ? "" : boatClass.getName(), regattaName, LeaderboardNameConstants.DEFAULT_SERIES_NAME, raceColumn, entriesPerFleet); + leaderboard, boatClass == null ? "" : boatClass.getName(), regattaName, buyZoneRadius, LeaderboardNameConstants.DEFAULT_SERIES_NAME, raceColumn, entriesPerFleet); } result.addAll(getRegattaOverviewEntriesToBeShown(showOnlyCurrentlyRunningRaces, entriesPerFleet)); } @@ -4263,7 +4269,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S SecurityUtils.getSubject().checkPermission(Permission.REGATTA.getStringPermissionForObjects(Mode.CREATE, regatta.getName())); this.createRegatta(regatta.getName(), regatta.boatClass.getName(), regatta.startDate, regatta.endDate, new RegattaCreationParametersDTO(getSeriesCreationParameters(regatta)), - true, regatta.scoringScheme, regatta.defaultCourseAreaUuid, regatta.useStartTimeInference, + true, regatta.scoringScheme, regatta.defaultCourseAreaUuid, regatta.buoyZoneRadiusInHullLengths, regatta.useStartTimeInference, regatta.controlTrackingFromStartAndFinishTimes, regatta.rankingMetricType); } @@ -4392,12 +4398,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } private void getRegattaOverviewEntries(boolean showOnlyRacesOfSameDay, Calendar dayToCheck, - CourseArea courseArea, Leaderboard leaderboard, String boatClassName, String regattaName, String seriesName, RaceColumn raceColumn, + CourseArea courseArea, Leaderboard leaderboard, String boatClassName, String regattaName, Distance buyZoneRadius, String seriesName, RaceColumn raceColumn, Map> entriesPerFleet) { if (!raceColumn.isCarryForward()) { for (Fleet fleet : raceColumn.getFleets()) { RegattaOverviewEntryDTO entry = createRegattaOverviewEntryDTO(courseArea, - leaderboard, boatClassName, regattaName, seriesName, raceColumn, fleet, + leaderboard, boatClassName, regattaName, buyZoneRadius, seriesName, raceColumn, fleet, showOnlyRacesOfSameDay, dayToCheck); if (entry != null) { addRegattaOverviewEntryToEntriesPerFleet(entriesPerFleet, fleet, entry); @@ -4448,7 +4454,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } private RegattaOverviewEntryDTO createRegattaOverviewEntryDTO(CourseArea courseArea, Leaderboard leaderboard, String boatClassName, - String regattaName, String seriesName, RaceColumn raceColumn, Fleet fleet, boolean showOnlyRacesOfSameDay, Calendar dayToCheck) { + String regattaName, Distance buyZoneRadius, String seriesName, RaceColumn raceColumn, Fleet fleet, boolean showOnlyRacesOfSameDay, Calendar dayToCheck) { RegattaOverviewEntryDTO entry = new RegattaOverviewEntryDTO(); if (courseArea != null) { entry.courseAreaName = courseArea.getName(); @@ -4462,7 +4468,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S entry.regattaName = leaderboard.getName(); entry.raceInfo = createRaceInfoDTO(seriesName, raceColumn, fleet); entry.currentServerTime = new Date(); - + entry.buyZoneRadius = buyZoneRadius; if (showOnlyRacesOfSameDay) { if (!RaceStateOfSameDayHelper.isRaceStateOfSameDay(entry.raceInfo.startTime, entry.raceInfo.finishedTime, entry.raceInfo.abortingTimeInPassBefore, dayToCheck)) { entry = null; @@ -5799,12 +5805,13 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public Map getCompetitorMarkPassings(RegattaAndRaceIdentifier race, CompetitorDTO competitorDTO) { + public Map getCompetitorMarkPassings(RegattaAndRaceIdentifier race, CompetitorDTO competitorDTO, boolean waitForCalculations) { Map result = new HashMap<>(); final TrackedRace trackedRace = getExistingTrackedRace(race); if (trackedRace != null) { Competitor competitor = getCompetitorByIdAsString(trackedRace.getRace().getCompetitors(), competitorDTO.getIdAsString()); - Set competitorMarkPassings = trackedRace.getMarkPassings(competitor); + Set competitorMarkPassings; + competitorMarkPassings = trackedRace.getMarkPassings(competitor, waitForCalculations); Iterable waypoints = trackedRace.getRace().getCourse().getWaypoints(); if (competitorMarkPassings != null) { for (MarkPassing markPassing : competitorMarkPassings) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RegattaDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RegattaDTO.java index 172ceb47351..2b019f95b24 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RegattaDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RegattaDTO.java @@ -5,6 +5,8 @@ import java.util.Date; import java.util.List; import java.util.UUID; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.RankingMetrics; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaIdentifier; @@ -15,6 +17,7 @@ import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.NamedDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapSettings; import com.sap.sse.common.Util.Pair; public class RegattaDTO extends NamedDTO { @@ -34,7 +37,8 @@ public class RegattaDTO extends NamedDTO { public boolean useStartTimeInference = true; public boolean controlTrackingFromStartAndFinishTimes = false; public RankingMetrics rankingMetricType; - + public Double buoyZoneRadiusInHullLengths; + public RegattaDTO() {} public RegattaDTO(String name, ScoringSchemeType scoringScheme) { @@ -64,6 +68,7 @@ public class RegattaDTO extends NamedDTO { this.configuration = other.configuration; this.useStartTimeInference = other.useStartTimeInference; this.controlTrackingFromStartAndFinishTimes = other.controlTrackingFromStartAndFinishTimes; + this.buoyZoneRadiusInHullLengths = other.buoyZoneRadiusInHullLengths; } public Pair getSeriesAndFleet(RegattaAndRaceIdentifier raceIdentifier) { @@ -120,6 +125,12 @@ public class RegattaDTO extends NamedDTO { return false; } + public Distance getCalculatedBuoyZoneRadius() { + Distance boatHullLength = boatClass == null ? null : boatClass.getHullLength(); + double hullLengthFactor = this.buoyZoneRadiusInHullLengths == null ? Regatta.DEFAULT_BUOY_ZONE_RADIUS_IN_HULL_LENGTHS : this.buoyZoneRadiusInHullLengths; + return boatHullLength == null ? RaceMapSettings.DEFAULT_BUOY_ZONE_RADIUS : boatHullLength.scale(hullLengthFactor); + } + @Override public int hashCode() { final int prime = 31; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RegattaOverviewEntryDTO.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RegattaOverviewEntryDTO.java index 8b589bd8207..8f2f4b20f04 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RegattaOverviewEntryDTO.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/RegattaOverviewEntryDTO.java @@ -3,6 +3,7 @@ package com.sap.sailing.gwt.ui.shared; import java.util.Date; import com.google.gwt.user.client.rpc.IsSerializable; +import com.sap.sailing.domain.common.Distance; public class RegattaOverviewEntryDTO implements IsSerializable { public String courseAreaIdAsString; @@ -12,7 +13,8 @@ public class RegattaOverviewEntryDTO implements IsSerializable { public String regattaName; public String boatClassName; public Date currentServerTime; - + public Distance buyZoneRadius; + // for GWT serialization public RegattaOverviewEntryDTO() { } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/CanvasOverlayV3.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/CanvasOverlayV3.java index 04af7ede37e..8516195d134 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/CanvasOverlayV3.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/CanvasOverlayV3.java @@ -39,9 +39,9 @@ import com.google.gwt.maps.client.overlays.overlayhandlers.OverlayViewMethods; import com.google.gwt.maps.client.overlays.overlayhandlers.OverlayViewOnAddHandler; import com.google.gwt.maps.client.overlays.overlayhandlers.OverlayViewOnDrawHandler; import com.google.gwt.maps.client.overlays.overlayhandlers.OverlayViewOnRemoveHandler; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.gwt.ui.client.shared.racemap.CoordinateSystem; /** @@ -364,9 +364,9 @@ public abstract class CanvasOverlayV3 { setProperty(canvas.getElement().getStyle(), "transform", "translateZ(0) rotate("+Math.round(rotationInDegrees)+"deg)"); } - protected double calculateRadiusOfBoundingBoxInPixels(MapCanvasProjection projection, Position centerPosition, double lengthInMeter) { - Position translateRhumbX = centerPosition.translateRhumb(new DegreeBearingImpl(90), new MeterDistance(lengthInMeter)); - Position translateRhumbY = centerPosition.translateRhumb(new DegreeBearingImpl(0), new MeterDistance(lengthInMeter)); + protected double calculateRadiusOfBoundingBoxInPixels(MapCanvasProjection projection, Position centerPosition, Distance length) { + Position translateRhumbX = centerPosition.translateRhumb(new DegreeBearingImpl(90), length); + Position translateRhumbY = centerPosition.translateRhumb(new DegreeBearingImpl(0), length); LatLng posWithDistanceX = coordinateSystem.toLatLng(translateRhumbX); LatLng posWithDistanceY = coordinateSystem.toLatLng(translateRhumbY); Point pointCenter = projection.fromLatLngToDivPixel(coordinateSystem.toLatLng(centerPosition)); @@ -377,17 +377,17 @@ public abstract class CanvasOverlayV3 { return Math.min(diffX, diffY); } - protected double calculateDistanceAlongX(MapCanvasProjection projection, Position pos, double distanceXInMeter) { - Position translateRhumbX = pos.translateRhumb(new DegreeBearingImpl(90), new MeterDistance(distanceXInMeter)); + protected double calculateDistanceAlongX(MapCanvasProjection projection, Position pos, Distance distanceX) { + Position translateRhumbX = pos.translateRhumb(new DegreeBearingImpl(90), distanceX); LatLng posWithDistanceX = coordinateSystem.toLatLng(translateRhumbX); Point point = projection.fromLatLngToDivPixel(coordinateSystem.toLatLng(pos)); Point pointX = projection.fromLatLngToDivPixel(posWithDistanceX); return Math.abs(pointX.getX() - point.getX()); } - protected Size calculateBoundingBox(MapCanvasProjection projection, Position pos, double distanceXInMeter, double distanceYInMeter) { - Position translateRhumbX = pos.translateRhumb(new DegreeBearingImpl(90), new MeterDistance(distanceXInMeter)); - Position translateRhumbY = pos.translateRhumb(new DegreeBearingImpl(0), new MeterDistance(distanceYInMeter)); + protected Size calculateBoundingBox(MapCanvasProjection projection, Position pos, Distance distanceX, Distance distanceY) { + Position translateRhumbX = pos.translateRhumb(new DegreeBearingImpl(90), distanceX); + Position translateRhumbY = pos.translateRhumb(new DegreeBearingImpl(0), distanceY); LatLng posWithDistanceX = coordinateSystem.toLatLng(translateRhumbX); LatLng posWithDistanceY = coordinateSystem.toLatLng(translateRhumbY); Point pointCenter = projection.fromLatLngToDivPixel(coordinateSystem.toLatLng(pos)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/FixVectorGraphics.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/FixVectorGraphics.java index 4287ca9d7f7..33674784a38 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/FixVectorGraphics.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/FixVectorGraphics.java @@ -1,7 +1,9 @@ package com.sap.sailing.gwt.ui.shared.racemap; import com.google.gwt.canvas.dom.client.Context2d; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.FixType; +import com.sap.sailing.domain.common.impl.MeterDistance; /** * A class for course fix graphics based on SVG graphics drawn to a HTML5 canvas @@ -11,8 +13,8 @@ import com.sap.sailing.domain.common.FixType; */ public class FixVectorGraphics { - protected double fixHeightInMeters; - protected double fixWidthInMeters; + protected Distance fixHeight; + protected Distance fixWidth; private final String color; private final FixType type; @@ -28,8 +30,8 @@ public class FixVectorGraphics { public FixVectorGraphics(FixType type, String color) { this.type = type; this.color = color; - this.fixHeightInMeters = 2.1; - this.fixWidthInMeters = 1.5; + this.fixHeight = new MeterDistance(2.1); + this.fixWidth = new MeterDistance(1.5); } public void drawMarkToCanvas(Context2d ctx, double width, double height, double scaleFactor) { @@ -101,12 +103,12 @@ public class FixVectorGraphics { ctx.restore(); } - public double getFixHeightInMeters() { - return fixHeightInMeters; + public Distance getFixHeight() { + return fixHeight; } - public double getFixWidthInMeters() { - return fixWidthInMeters; + public Distance getFixWidth() { + return fixWidth; } public FixType getType() { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/MarkVectorGraphics.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/MarkVectorGraphics.java index dc9ea7cb03f..5d61b9799f5 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/MarkVectorGraphics.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/shared/racemap/MarkVectorGraphics.java @@ -2,7 +2,9 @@ package com.sap.sailing.gwt.ui.shared.racemap; import com.google.gwt.canvas.dom.client.CanvasGradient; import com.google.gwt.canvas.dom.client.Context2d; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.MarkType; +import com.sap.sailing.domain.common.impl.MeterDistance; /** * A class for course mark graphics based on SVG graphics drawn to a HTML5 canvas @@ -12,8 +14,8 @@ import com.sap.sailing.domain.common.MarkType; * */ public class MarkVectorGraphics { - protected double markHeightInMeters; - protected double markWidthInMeters; + protected Distance markHeightInMeters; + protected Distance markWidthInMeters; private final String color; private final String shape; @@ -33,8 +35,8 @@ public class MarkVectorGraphics { this.color = color; this.shape = shape; this.pattern = pattern; - this.markHeightInMeters = 2.1; - this.markWidthInMeters = 1.5; + this.markHeightInMeters = new MeterDistance(2.1); + this.markWidthInMeters = new MeterDistance(1.5); } public void drawMarkToCanvas(Context2d ctx, boolean isSelected, double width, double height, double scaleFactor) { @@ -755,11 +757,11 @@ public class MarkVectorGraphics { ctx.restore(); } - public double getMarkHeightInMeters() { + public Distance getMarkHeight() { return markHeightInMeters; } - public double getMarkWidthInMeters() { + public Distance getMarkWidth() { return markWidthInMeters; } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java index 5b909282106..c20ec8fb25d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/spectator/LeaderboardGroupPanel.java @@ -439,7 +439,7 @@ public class LeaderboardGroupPanel extends SimplePanel implements HasWelcomeWidg } return linkParams; } - + private SafeHtml getAnchor(String link, String linkText, String style) { if (isEmbedded) { return ANCHORTEMPLATE.anchorWithTarget(link, linkText, style, "_blank"); diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/BuoyPingerAppNotes.html b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/BuoyPingerAppNotes.html index 5b611d43983..313d1dc8d3d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/BuoyPingerAppNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/BuoyPingerAppNotes.html @@ -4,7 +4,16 @@

What's New - SAP Sailing Buoy Pinger

+
October 2016
+
    +
  • Improved pinging accuracy by using a more controllable API: + The app will now only use true GPS signals instead of accepting + positions acquired through network triangulation.
  • +
  • In some rare situations the app crashed, if you want to scan a QR-Code.
  • +
+
June 2016
+
  • First PlayStore release
diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/InSightAppNotes.html b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/InSightAppNotes.html index fac557514d7..a61686eb475 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/InSightAppNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/InSightAppNotes.html @@ -2,9 +2,21 @@
-

What's New - SAP Sail InSight

+

What's New - SAP Sail InSight (Android)

+
October 2016
+
    +
  • Improved tracking reliability and accuracy, + especially when the app is in background by using a more reliable and controllable API: + The app will now only use true GPS signals instead of accepting + positions acquired through network triangulation.
  • +
  • There now is a more detailed energy saving option, + and the automatic overriding of user preferences has been removed.
  • +
  • In some rare situations the app crashed, if you want to scan a QR-Code.
  • +
+
August 2016
+
  • The GPS fix sending was optimize to send all data in a bulk. Instead of sending every fix separately. This will reduce the battery consumption and minimize the @@ -15,11 +27,23 @@
July 2016
+
  • It is possible to track buoys for multiple regattas. Scan all buoy QR-codes and start tracking for one buoy. It will track all buoys.
+

What's New - SAP Sail InSight (iOS)

+
+
October 2016
+
    +
  • This release introduces an entirely new design.
  • +
  • The landscape mode is optimized for small devices.
  • +
  • Unsent GPS-fixes will be sent automatically after a sudden reboot of the device.
  • +
  • Fixed an issue where a login could be done twice.
  • +
  • Fixed an issue where an invalid login is handled as a successful one.
  • +
+
\ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/RaceCommitteeAppNotes.html b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/RaceCommitteeAppNotes.html index 4a7c5113a7f..b2cfb785b4e 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/RaceCommitteeAppNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/RaceCommitteeAppNotes.html @@ -4,6 +4,12 @@

What's New - SAP Sailing Race Manager

+
October 2016
+ +
    +
  • Fixed missing German translations for some buttons and labels
  • +
+
September 2016
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html index 679027320bc..1f60f4c7fd4 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html @@ -4,11 +4,31 @@

    What's New - SAP Sailing Analytics

    +
    October 2016
    +
      +
    • An issue with live re-calculation of the maximum speed over ground (SOG) values has been fixed. + In some circumstances it was possible that new live data did not properly update the MaxSOG figures. + Since this depended on the timing of read and write access it could have led to different values + for different server instances, giving confusing results. The fix provided now ensures that + the MaxSOG values are properly evaluated when new data has arrived. +
    • +
    • The buoy zone radius is now determined by a configurable regatta property. By default, it is + set to two hull lengths, and a regatta administrator may choose to change it to any other factor + such as 3 or 2.5. This determines the default used in the race viewer (RaceBoard.html). Users can + still change the radius in the map settings to any arbitrary number, deviating from what has + "officially" been set for the regatta. +
    • +
    +
    August 2016
      -
    • In order to prepare user-specific configuration support in the /gwt/Simulator.html, this - module now requires a user account to be used. Please sign up and log in to use the full-featured - simulator as usual. +
    • In order to prepare user-specific configuration support in additional parts of the application, + various modules now require a user account to be accessed. Please sign up and log in to use their + full functionality as usual. The following modules are affected: +
        +
      • /gwt/Simulator.html
      • +
      • /gwt/DataMining.html
      • +
    diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/DataMining.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/DataMining.gwt.xml index c27792efff1..2cb614b9326 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/DataMining.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/DataMining.gwt.xml @@ -1,48 +1,48 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Leaderboard.gwt.xml b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Leaderboard.gwt.xml index dc3971ece1c..392a679e575 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Leaderboard.gwt.xml +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/ui/Leaderboard.gwt.xml @@ -37,4 +37,5 @@ + diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/AbstractTestStoringAndRetrievingRaceLogInRegatta.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/AbstractTestStoringAndRetrievingRaceLogInRegatta.java index 01e5911f9e0..c1650c821cc 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/AbstractTestStoringAndRetrievingRaceLogInRegatta.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/AbstractTestStoringAndRetrievingRaceLogInRegatta.java @@ -62,7 +62,7 @@ public abstract class AbstractTestStoringAndRetrievingRaceLogInRegatta extends R List series = createSeriesForTestRegatta(); Regatta regatta = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), RegattaImpl.getDefaultName(regattaBaseName, boatClass == null ? null : boatClass.getName()), boatClass, /*startDate*/ null, /*endDate*/ null, series, persistent, scoringScheme, "123", - courseArea, /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + courseArea, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); return regatta; } diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java index a6d36025a81..3d39b3d70b6 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndLoadingEventsAndRegattas.java @@ -405,7 +405,7 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new); final String regattaName = regattaProxy.getName(); Regatta regatta = res.createRegatta(regattaName, regattaProxy.getBoatClass().getName(), regattaStartDate, regattaEndDate, - "123", regattaProxy.getSeries(), regattaProxy.isPersistent(), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + "123", regattaProxy.getSeries(), regattaProxy.isPersistent(), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); addRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regatta); res.addRegattaLeaderboard(regatta.getRegattaIdentifier(), null, new int[] { 3, 5 }); DomainObjectFactory dof = PersistenceFactory.INSTANCE.getDomainObjectFactory(getMongoService(), DomainFactory.INSTANCE); @@ -447,7 +447,7 @@ public class TestStoringAndLoadingEventsAndRegattas extends AbstractMongoDBTest Regatta regattaProxy = createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClass.getName()), boatClass, regattaStartDate, regattaEndDate, /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, OneDesignRankingMetric::new); Regatta regatta = res.createRegatta(regattaProxy.getName(), regattaProxy.getBoatClass().getName(), regattaProxy.getStartDate(), regattaProxy.getEndDate(), - "123", regattaProxy.getSeries(), regattaProxy.isPersistent(), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + "123", regattaProxy.getSeries(), regattaProxy.isPersistent(), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); trackedRegatta[0] = new DynamicTrackedRegattaImpl(regatta); addRaceColumns(numberOfQualifyingRaces, numberOfFinalRaces, regatta); logColumnsInRegatta(regatta); diff --git a/java/com.sap.sailing.polars.datamining/META-INF/MANIFEST.MF b/java/com.sap.sailing.polars.datamining/META-INF/MANIFEST.MF index 88c0c345dd9..30ee0d23218 100644 --- a/java/com.sap.sailing.polars.datamining/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.polars.datamining/META-INF/MANIFEST.MF @@ -10,14 +10,14 @@ Require-Bundle: org.eclipse.osgi;bundle-version="3.10.1", com.sap.sse.datamining;bundle-version="1.0.0", com.sap.sse.datamining.annotations;bundle-version="1.0.0", com.sap.sse.datamining.shared;bundle-version="1.0.0", - com.sap.sailing.datamining, com.sap.sailing.server;bundle-version="1.0.0", com.sap.sse.common;bundle-version="1.0.0", com.sap.sailing.domain;bundle-version="1.0.0", com.sap.sailing.domain.shared.android, com.sap.sailing.domain.common;bundle-version="1.0.0", com.sap.sailing.polars.datamining.shared, - org.apache.commons.math + org.apache.commons.math, + com.sap.sailing.datamining Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ActivationPolicy: lazy Export-Package: com.sap.sailing.polars.datamining.data, diff --git a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java index 8a0b9d74bcc..72902780367 100755 --- a/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java +++ b/java/com.sap.sailing.polars/src/com/sap/sailing/polars/impl/PolarDataServiceImpl.java @@ -35,6 +35,7 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException; import com.sap.sailing.domain.polars.PolarDataService; import com.sap.sailing.domain.polars.PolarsChangedListener; +import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.polars.PolarDataOperation; import com.sap.sailing.polars.mining.BearingClusterGroup; @@ -236,8 +237,14 @@ public class PolarDataServiceImpl implements PolarDataService, @Override public void insertExistingFixes(TrackedRace trackedRace) { for (Competitor competitor : trackedRace.getRace().getCompetitors()) { - for (GPSFixMoving fix : trackedRace.getTrack(competitor).getFixes()) { - competitorPositionChanged(fix, competitor, trackedRace); + GPSFixTrack track = trackedRace.getTrack(competitor); + track.lockForRead(); + try { + for (GPSFixMoving fix : track.getFixes()) { + competitorPositionChanged(fix, competitor, trackedRace); + } + } finally { + track.unlockAfterRead(); } } } diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/FlatGPSFixJsonSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/FlatGPSFixJsonSerializer.java index 689a03fe977..cf66b64e114 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/FlatGPSFixJsonSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/impl/FlatGPSFixJsonSerializer.java @@ -10,6 +10,7 @@ public class FlatGPSFixJsonSerializer implements JsonSerializer { public static final String FIELD_LAT_DEG = "latitude"; public static final String FIELD_TIME_MILLIS = "timestamp"; public static final String FIELD_ACCURACY = "accuracy"; + public static final double NOT_AVAILABLE_THROUGH_SERVER = -1; @Override public JSONObject serialize(GPSFix object) { @@ -17,12 +18,7 @@ public class FlatGPSFixJsonSerializer implements JsonSerializer { result.put(FIELD_LON_DEG, object.getPosition().getLngDeg()); result.put(FIELD_LAT_DEG, object.getPosition().getLatDeg()); result.put(FIELD_TIME_MILLIS, object.getTimePoint().asMillis()); - // TODO Insert Accuracy if someday available from the server - // Buoy Pinger app will interpret no accuracy in JSON message as MarkPingInfo.NOT_SET_BY_USER (-1) and store that in DB - // For the user these values are displayed as R.string.unknown ("n/a"); - // (CheckinManager in Buoy Pinger can accept messages with and without accuracy) - - // result.put(FIELD_ACCURACY, XXXXXXXXX); + result.put(FIELD_ACCURACY, NOT_AVAILABLE_THROUGH_SERVER); return result; } } diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java index 451a2c62ba1..c0c5132937a 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardGroupResourceTest.java @@ -51,7 +51,8 @@ public class LeaderboardGroupResourceTest extends AbstractJaxRsApiTest { series.add(testSeries); regatta = racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), series, /*persistent*/ true, - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /*buoyZoneRadiusInHullLengths*/2.0, + /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); List competitors = createCompetitors(4); diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java index aac730fbe7e..58f4d3e582c 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/LeaderboardsResourceTest.java @@ -58,7 +58,8 @@ public class LeaderboardsResourceTest extends AbstractJaxRsApiTest { series.add(testSeries); regatta = racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaBaseName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), series, /*persistent*/ true, - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, + /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); List competitors = createCompetitors(4); diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java index 3def955ee84..99f46567a63 100644 --- a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/test/jaxrs/RegattasResourceTest.java @@ -49,7 +49,7 @@ public class RegattasResourceTest extends AbstractJaxRsApiTest { series.add(testSeries); racingEventService.createRegatta(RegattaImpl.getDefaultName(regattaName, boatClassName), boatClassName, startDate, endDate, UUID.randomUUID(), series, /*persistent*/ true, - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); testSeries.addRaceColumn("R1", /* trackedRegattaRegistry */ null); testSeries.addRaceColumn("R2", /* trackedRegattaRegistry */ null); } diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java index 72cd10d3c1f..caf58194ef7 100755 --- a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/impl/FileUploadServlet.java @@ -9,8 +9,6 @@ import java.util.logging.Logger; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import org.apache.commons.fileupload.FileItem; @@ -40,7 +38,7 @@ public class FileUploadServlet extends AbstractFileUploadServlet { /** * The maximum size of an image uploaded by a user as a team image, in megabytes (1024*1024 bytes) */ - private static final int MAX_SIZE_IN_MB = 5; + private static final int MAX_SIZE_IN_MB = 500; @Override protected void process(List fileItems, HttpServletRequest req, HttpServletResponse resp) throws UnsupportedEncodingException, IOException { @@ -61,13 +59,16 @@ public class FileUploadServlet extends AbstractFileUploadServlet { } try { if (fileItem.getSize() > 1024 * 1024 * MAX_SIZE_IN_MB) { - throw new WebApplicationException(Response.status(Status.BAD_REQUEST) - .entity("Image is larger than " + MAX_SIZE_IN_MB + "MB").build()); + final String errorMessage = "Image is larger than " + MAX_SIZE_IN_MB + "MB"; + logger.warning("Ignoring file storage request because file "+fileItem.getName()+" is larger than "+MAX_SIZE_IN_MB+"MB"); + result.put("status", Status.INTERNAL_SERVER_ERROR.name()); + result.put("message", errorMessage); + } else { + final URI fileUri = getService().getFileStorageManagementService().getActiveFileStorageService() + .storeFile(fileItem.getInputStream(), fileExtension, fileItem.getSize()); + result.put(JSON_FILE_NAME, fileItem.getName()); + result.put(JSON_FILE_URI, fileUri.toString()); } - final URI fileUri = getService().getFileStorageManagementService().getActiveFileStorageService() - .storeFile(fileItem.getInputStream(), fileExtension, fileItem.getSize()); - result.put(JSON_FILE_NAME, fileItem.getName()); - result.put(JSON_FILE_URI, fileUri.toString()); } catch (IOException | OperationFailedException | InvalidPropertiesException | NoCorrespondingServiceRegisteredException e) { final String errorMessage = "Could not store file"+ (e.getMessage()==null?"":(": " + e.getMessage())); logger.log(Level.WARNING, "Could not store file", e); diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java index e2bc42b0336..fc6dd8c1197 100644 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/AbstractLogReplicationTest.java @@ -104,7 +104,7 @@ public abstract class AbstractLogReplicationTest series = Collections.emptyList(); Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(baseEventName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, regattaId, series, - /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, + /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); Iterable emptyWaypointList = Collections.emptyList(); final String competitorName = "Der mit dem Kiel zieht"; diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java index 02f55614501..07fb1eba53d 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java @@ -123,7 +123,7 @@ public class InitialLoadReplicationObjectIdentityTest extends AbstractServerRepl final Iterable series = Collections.emptyList(); Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(baseEventName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), series, - /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, + /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric.CONSTRUCTOR); assertNotNull(master.getRegatta(masterRegatta.getRegattaIdentifier())); assertTrue(master.getAllRegattas().iterator().hasNext()); diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java index ea4382fd063..02bb785a886 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/RegattaReplicationTest.java @@ -46,7 +46,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { final UUID regattaId = UUID.randomUUID(); Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(baseEventName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, regattaId, series, - /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); Thread.sleep(1000); @@ -75,12 +75,12 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { UUID currentCourseAreaId = null; Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName("Kiel Week 2012", "49er"), "49er", /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), Collections.emptyList(), - /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), currentCourseAreaId, /* useStartTimeInference */ true, - /* /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); - + /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), currentCourseAreaId, + /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + // Test for 'null' master.apply(new UpdateSpecificRegatta(masterRegatta.getRegattaIdentifier(), /*startDate*/ null, /*endDate*/ null, - currentCourseAreaId, null, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false)); + currentCourseAreaId, null, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false)); Thread.sleep(1000); replicatedRegatta = replica.getRegatta(new RegattaName(masterRegatta.getName())); assertNotNull(replicatedRegatta); @@ -89,7 +89,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { // Test for 'alpha' currentCourseAreaId = alphaCourseAreaId; master.apply(new UpdateSpecificRegatta(masterRegatta.getRegattaIdentifier(), /*startDate*/ null, /*endDate*/ null, - currentCourseAreaId, null, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false)); + currentCourseAreaId, null, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false)); Thread.sleep(1000); replicatedRegatta = replica.getRegatta(new RegattaName(masterRegatta.getName())); assertNotNull(replicatedRegatta); @@ -98,7 +98,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { // Test for 'tv' currentCourseAreaId = tvCourseAreaId; master.apply(new UpdateSpecificRegatta(masterRegatta.getRegattaIdentifier(), /*startDate*/ null, /*endDate*/ null, - currentCourseAreaId, null, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false)); + currentCourseAreaId, null, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false)); Thread.sleep(1000); replicatedRegatta = replica.getRegatta(new RegattaName(masterRegatta.getName())); assertNotNull(replicatedRegatta); @@ -106,7 +106,8 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { // Test back to 'null' currentCourseAreaId = null; - master.apply(new UpdateSpecificRegatta(masterRegatta.getRegattaIdentifier(), /*startDate*/ null, /*endDate*/ null, currentCourseAreaId, null, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false)); + master.apply(new UpdateSpecificRegatta(masterRegatta.getRegattaIdentifier(), /*startDate*/ null, /*endDate*/ null, currentCourseAreaId, null, + /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false)); Thread.sleep(1000); replicatedRegatta = replica.getRegatta(new RegattaName(masterRegatta.getName())); assertNotNull(replicatedRegatta); @@ -124,7 +125,8 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { master.addCourseAreas(event.getId(), new String[] {"Golf"}, new UUID[] {golfCourseAreaId}); final String regattaName = RegattaImpl.getDefaultName("Kiel Week 2012", "49er"); Regatta masterRegatta = master.createRegatta(regattaName, "49er", /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), Collections.emptyList(), - /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), tvCourseAreaId, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), tvCourseAreaId, + /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); event = master.getEvent(event.getId()); assertTrue(event.getRegattas().iterator().hasNext()); assertEquals(regattaName, event.getRegattas().iterator().next().getName()); @@ -133,7 +135,8 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { Iterator regattasInReplicatedEvent = replicatedEvent.getRegattas().iterator(); assertTrue(regattasInReplicatedEvent.hasNext()); assertEquals(regattaName, regattasInReplicatedEvent.next().getName()); - master.apply(new UpdateSpecificRegatta(masterRegatta.getRegattaIdentifier(), /*startDate*/ null, /*endDate*/ null, golfCourseAreaId, null, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false)); + master.apply(new UpdateSpecificRegatta(masterRegatta.getRegattaIdentifier(), /*startDate*/ null, /*endDate*/ null, golfCourseAreaId, null, + /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false)); } @Test @@ -149,10 +152,12 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { UUID currentCourseAreaId = null; Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName("RR", "49er"), "49er", /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), Collections.emptyList(), - true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), currentCourseAreaId, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), currentCourseAreaId, + /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); // Test for 'null' - master.apply(new UpdateSpecificRegatta(masterRegatta.getRegattaIdentifier(), /*startDate*/ null, /*endDate*/ null, currentCourseAreaId, null, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false)); + master.apply(new UpdateSpecificRegatta(masterRegatta.getRegattaIdentifier(), /*startDate*/ null, /*endDate*/ null, currentCourseAreaId, null, + /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false)); Thread.sleep(1000); replicatedRegatta = replica.getRegatta(new RegattaName(masterRegatta.getName())); assertNotNull(replicatedRegatta); @@ -161,7 +166,8 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { // Test for values RegattaConfigurationImpl config = new RegattaConfigurationImpl(); config.setDefaultCourseDesignerMode(CourseDesignerMode.BY_MARKS); - master.apply(new UpdateSpecificRegatta(masterRegatta.getRegattaIdentifier(), /*startDate*/ null, /*endDate*/ null, currentCourseAreaId, config, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false)); + master.apply(new UpdateSpecificRegatta(masterRegatta.getRegattaIdentifier(), /*startDate*/ null, /*endDate*/ null, currentCourseAreaId, config, + /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false)); Thread.sleep(1000); replicatedRegatta = replica.getRegatta(new RegattaName(masterRegatta.getName())); assertNotNull(replicatedRegatta); @@ -193,7 +199,8 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { Series medal = new SeriesImpl("Medal", /* isMedal */ true, /* isFleetsCanRunInParallel */ true, Arrays.asList(new Fleet[] { new FleetImpl("Medal") }), emptyRaceColumnNamesList, /* trackedRegattaRegistry */ null); Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(baseEventName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, - UUID.randomUUID(), Arrays.asList(new Series[] { qualification, finals, medal }), /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + UUID.randomUUID(), Arrays.asList(new Series[] { qualification, finals, medal }), /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, + /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); Thread.sleep(1000); Regatta replicatedRegatta = replica.getRegatta(new RegattaName(masterRegatta.getName())); assertNotNull(replicatedRegatta); @@ -228,7 +235,8 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { Series qualification = new SeriesImpl("Qualification", /* isMedal */ false, /* isFleetsCanRunInParallel */ true, Arrays.asList(new Fleet[] { new FleetImpl("Yellow"), new FleetImpl("Blue") }), emptyRaceColumnNamesList, /* trackedRegattaRegistry */ null); Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(baseEventName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, - UUID.randomUUID(), Arrays.asList(new Series[] { qualification }), /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + UUID.randomUUID(), Arrays.asList(new Series[] { qualification }), /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, + /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); Thread.sleep(1000); Regatta replicatedRegatta = replica.getRegatta(new RegattaName(masterRegatta.getName())); assertNotNull(replicatedRegatta); @@ -273,7 +281,7 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(baseEventName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), Arrays.asList(new Series[] { qualification }), /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), null, - /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, + /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); Thread.sleep(1000); Regatta replicatedRegatta = replica.getRegatta(new RegattaName(masterRegatta.getName())); @@ -309,7 +317,8 @@ public class RegattaReplicationTest extends AbstractServerReplicationTest { CourseArea masterCourseArea = master.addCourseAreas(masterEvent.getId(), new String[] {courseArea}, new UUID[] {UUID.randomUUID()})[0]; Regatta masterRegatta = master.createRegatta(RegattaImpl.getDefaultName(eventName, boatClassName), boatClassName, /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), series, - /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), masterCourseArea.getId(), /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + /* persistent */ true, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), masterCourseArea.getId(), + /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); Thread.sleep(1000); Event replicatedEvent = replica.getEvent(masterEvent.getId()); assertNotNull(replicatedEvent); diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/ApplyScoresFromRaceLogTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/ApplyScoresFromRaceLogTest.java index 8267c1c0f3d..96e8418a984 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/ApplyScoresFromRaceLogTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/ApplyScoresFromRaceLogTest.java @@ -1,6 +1,7 @@ package com.sap.sailing.server.test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Collections; @@ -8,6 +9,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.function.Function; import org.junit.Test; @@ -96,16 +98,99 @@ public class ApplyScoresFromRaceLogTest extends LeaderboardScoringAndRankingTest final List rankedCompetitorsBeforeApplying = leaderboard.getCompetitorsFromBestToWorst(later); assertEquals(competitors, rankedCompetitorsBeforeApplying); // no effects of preliminary results list yet f1RaceState.setFinishPositioningConfirmed(now); + final Function expectedPoints = (c)->scores.get(c)==null?(mprs.get(c) == null || mprs.get(c) == MaxPointsReason.NONE ? competitors.indexOf(c)+1 : competitors.size()+1):scores.get(c); for (final Competitor c : competitors) { - assertEquals(scores.get(c)==null?competitors.indexOf(c)+1:scores.get(c), leaderboard.getTotalPoints(c, f1Column, now), 0.00000001); + assertEquals(expectedPoints.apply(c), leaderboard.getTotalPoints(c, f1Column, now), 0.00000001); } final List expectedNewOrder = new ArrayList<>(competitors); - expectedNewOrder.sort((c1, c2)-> - new Double(scores.get(c1)==null?competitors.indexOf(c1)+1:scores.get(c1)).compareTo( - new Double(scores.get(c2)==null?competitors.indexOf(c2)+1:scores.get(c2)))); + expectedNewOrder.sort((c1, c2)->new Double(expectedPoints.apply(c1)).compareTo(new Double(expectedPoints.apply(c1)))); final List rankedCompetitorsAfterApplying = leaderboard.getCompetitorsFromBestToWorst(later); - assertEquals(expectedNewOrder, rankedCompetitorsAfterApplying); + double lastScore = 0; + for (final Competitor c : rankedCompetitorsAfterApplying) { + assertEquals(expectedPoints.apply(c), leaderboard.getTotalPoints(c, f1Column, later)); + assertTrue(leaderboard.getTotalPoints(c, f1Column, later) >= lastScore); + lastScore = leaderboard.getTotalPoints(c, f1Column, later); + } } + /** + * See also bug 3955: when for a competitor no score is explicitly provided but a {@link MaxPointsReason} has been set, don't + * correct the score in the leaderboard's score correction but only set the {@link MaxPointsReason}. This will let the scoring + * scheme select the score, as it used to be before the fix for bug 3794, commit 830e64842a39fb137446887c5177f7de34bd5b5a. + */ + @Test + public void testScoresFromRaceLogOnlyAppliedIfExplicitOrNoMaxPointsReason() throws NoWindException { + final RacingEventService service = new RacingEventServiceImpl(); + final List competitors = new ArrayList<>(); + for (int i=0; i<8; i++) { + final String competitorName = "C"+i; + competitors.add(service.getBaseDomainFactory().getCompetitorStore().getOrCreateCompetitor(UUID.randomUUID(), + competitorName, /* displayColor */ Color.RED, /* email */ null, /* flagImageURI */ null, + new TeamImpl("STG", Collections.singleton( + new PersonImpl(competitorName, new NationalityImpl("GER"), + /* dateOfBirth */ null, "This is famous "+competitorName)), + new PersonImpl("Rigo van Maas", new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl(competitorName + "'s boat", + new BoatClassImpl("505", /* typicallyStartsUpwind */ true), /* sailID */ null), + /* timeOnTimeFactor */ null, /* timeOnDistanceAllowancePerNauticalMile */ null, null)); + } + final Regatta regatta = createRegatta(/* qualifying */0, new String[] { "Default" }, /* final */1, + new String[] { "Default" }, + /* medal */false, /* medal */ 0, "testOneStartedRaceWithDifferentScores", + DomainFactory.INSTANCE.getOrCreateBoatClass("49er", /* typicallyStartsUpwind */true), DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.HIGH_POINT_FIRST_GETS_TEN_OR_EIGHT)); + TimePoint now = MillisecondsTimePoint.now(); + TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000); + TrackedRace f1 = new MockedTrackedRaceWithStartTimeAndRanks(now, competitors); + RaceColumn f1Column = series.get(1).getRaceColumnByName("F1"); + f1Column.setTrackedRace(f1Column.getFleets().iterator().next(), f1); + final Leaderboard leaderboard = createLeaderboard(regatta, /* discarding thresholds */ new int[0]); + + service.addLeaderboard(leaderboard); // should add a RaceLogScoringReplicator as listener to the leaderboard + service.addRegattaWithoutReplication(regatta); + int oneBasedRank = 1; + final CompetitorResults results = new CompetitorResultsImpl(); + + setResultForCompetitor(competitors.get(0), oneBasedRank++, results, MaxPointsReason.DNF, /* explicit score */ null); + setResultForCompetitor(competitors.get(1), oneBasedRank++, results, MaxPointsReason.OCS, /* explicit score */ null); + setResultForCompetitor(competitors.get(2), oneBasedRank++, results, MaxPointsReason.DNF, /* explicit score */ 0.1); + setResultForCompetitor(competitors.get(3), oneBasedRank++, results, MaxPointsReason.OCS, /* explicit score */ 0.2); + setResultForCompetitor(competitors.get(4), oneBasedRank++, results, /* maxPointsReason */ null, /* explicit score */ null); + setResultForCompetitor(competitors.get(5), oneBasedRank++, results, /* maxPointsReason */ null, /* explicit score */ 2.4); + setResultForCompetitor(competitors.get(6), oneBasedRank++, results, MaxPointsReason.NONE, /* explicit score */ null); + setResultForCompetitor(competitors.get(7), oneBasedRank++, results, MaxPointsReason.NONE, /* explicit score */ 3.3); + + final RaceLog f1RaceLog = f1Column.getRaceLog(f1Column.getFleets().iterator().next()); + final LogEventAuthorImpl author = new LogEventAuthorImpl("Axel", 0); + final RaceState f1RaceState = new RaceStateImpl(service, f1RaceLog, author, + new RacingProcedureFactoryImpl(author, new EmptyRegattaConfiguration())); + f1RaceState.setFinishPositioningListChanged(now, results); + final List rankedCompetitorsBeforeApplying = leaderboard.getCompetitorsFromBestToWorst(later); + assertEquals(competitors, rankedCompetitorsBeforeApplying); // no effects of preliminary results list yet + f1RaceState.setFinishPositioningConfirmed(now); + + assertScoreCorrections(leaderboard, f1Column, competitors.get(0), MaxPointsReason.DNF, 0, /* score is corrected */ false, later); + assertScoreCorrections(leaderboard, f1Column, competitors.get(1), MaxPointsReason.OCS, 0, /* score is corrected */ false, later); + assertScoreCorrections(leaderboard, f1Column, competitors.get(2), MaxPointsReason.DNF, 0.1, /* score is corrected */ true, later); + assertScoreCorrections(leaderboard, f1Column, competitors.get(3), MaxPointsReason.OCS, 0.2, /* score is corrected */ true, later); + assertScoreCorrections(leaderboard, f1Column, competitors.get(4), MaxPointsReason.NONE, 6, /* score is corrected */ true, later); // score corrected based on rank because no MaxPointsReason set + assertScoreCorrections(leaderboard, f1Column, competitors.get(5), MaxPointsReason.NONE, 2.4, /* score is corrected */ true, later); // score corrected based on explicit score + assertScoreCorrections(leaderboard, f1Column, competitors.get(6), MaxPointsReason.NONE, 4, /* score is corrected */ true, later); // score corrected based on rank because MaxPointsReason.NONE set + assertScoreCorrections(leaderboard, f1Column, competitors.get(7), MaxPointsReason.NONE, 3.3, /* score is corrected */ true, later); // score corrected based on explicit score + } + + private void assertScoreCorrections(Leaderboard leaderboard, RaceColumn raceColumn, Competitor competitor, + MaxPointsReason expectedMaxPointsReason, double expectedScore, boolean expectedIsScoreCorrected, + TimePoint timePoint) { + assertEquals(expectedIsScoreCorrected, leaderboard.getScoreCorrection().getExplicitScoreCorrection(competitor, raceColumn) != null); + assertEquals(expectedMaxPointsReason, leaderboard.getScoreCorrection().getMaxPointsReason(competitor, raceColumn, timePoint)); + assertEquals(expectedScore, leaderboard.getTotalPoints(competitor, raceColumn, timePoint), 0.000001); + } + + private void setResultForCompetitor(final Competitor competitor, int oneBasedRank, + final CompetitorResults results, MaxPointsReason maxPointsReason, Double explicitScore) { + results.add(new CompetitorResultImpl(competitor.getId(), competitor.getName(), oneBasedRank++, maxPointsReason, explicitScore, /* finishingTime */ null, /* comment */ null)); + } + + } diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java index ce3116f033e..3880fcd120a 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/MasterDataImportTest.java @@ -216,8 +216,8 @@ public class MasterDataImportTest { UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, - /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */ - true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /*buoyZoneRadiusInHullLengths*/2.0, + /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); event.addRegatta(regatta); for (String name : raceColumnNames) { series.get(0).addRaceColumn(name, sourceService); @@ -508,8 +508,8 @@ public class MasterDataImportTest { UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, - /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */ - true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /*buoyZoneRadiusInHullLengths*/2.0, + /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); event.addRegatta(regatta); for (String name : raceColumnNames) { series.get(0).addRaceColumn(name, sourceService); @@ -666,8 +666,8 @@ public class MasterDataImportTest { series.add(new SeriesImpl("testSeries", false, /* isFleetsCanRunInParallel */ true, fleets, emptyRaceColumnNamesList, sourceService)); UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta(RegattaImpl.getDefaultName("testRegatta", "29er"), "29er", - /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */ - true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /*buoyZoneRadiusInHullLengths*/2.0, + /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); event.addRegatta(regatta); for (String name : raceColumnNames) { series.get(0).addRaceColumn(name, sourceService); @@ -830,8 +830,8 @@ public class MasterDataImportTest { UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, - /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */ - true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /*buoyZoneRadiusInHullLengths*/2.0, + /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); event.addRegatta(regatta); for (String name : raceColumnNames) { series.get(0).addRaceColumn(name, sourceService); @@ -990,8 +990,8 @@ public class MasterDataImportTest { UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, - /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */ - true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /*buoyZoneRadiusInHullLengths*/2.0, + /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); event.addRegatta(regatta); for (String name : raceColumnNames) { series.get(0).addRaceColumn(name, sourceService); @@ -1104,8 +1104,8 @@ public class MasterDataImportTest { destService)); Regatta regattaNotToOverride = destService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, /* startDate */ - null, /* endDate */null, regattaUUID, seriesNotToOverride, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */ - true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + null, /* endDate */null, regattaUUID, seriesNotToOverride, true, new LowPoint(), courseAreaUUID, /*buoyZoneRadiusInHullLengths*/2.0, + /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); event.addRegatta(regattaNotToOverride); for (String name : raceColumnNamesNotToOverride) { seriesNotToOverride.get(0).addRaceColumn(name, destService); @@ -1192,8 +1192,8 @@ public class MasterDataImportTest { UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, - /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */ - true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /*buoyZoneRadiusInHullLengths*/2.0, + /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); event.addRegatta(regatta); for (String name : raceColumnNames) { series.get(0).addRaceColumn(name, sourceService); @@ -1302,8 +1302,8 @@ public class MasterDataImportTest { destService)); Regatta regattaToOverride = destService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, /* startDate */ - null, /* endDate */null, regattaUUID, seriesToOverride, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */ - true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + null, /* endDate */null, regattaUUID, seriesToOverride, true, new LowPoint(), courseAreaUUID, /*buoyZoneRadiusInHullLengths*/2.0, + /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); event.addRegatta(regattaToOverride); for (String name : raceColumnNamesToOverride) { seriesToOverride.get(0).addRaceColumn(name, destService); @@ -1413,7 +1413,7 @@ public class MasterDataImportTest { Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, /* startDate */ null, /* endDate */null, UUID.randomUUID(), new ArrayList(), true, new LowPoint(), - defaultCourseArea.getId(), /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + defaultCourseArea.getId(), /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); // Let's use the setters directly because we are not testing replication RegattaConfigurationImpl configuration = new RegattaConfigurationImpl(); configuration.setDefaultCourseDesignerMode(CourseDesignerMode.BY_MAP); @@ -1495,7 +1495,7 @@ public class MasterDataImportTest { Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), - defaultCourseArea.getId(), /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + defaultCourseArea.getId(), /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); for (String name : raceColumnNames) { series.get(0).addRaceColumn(name, sourceService); } @@ -1628,8 +1628,8 @@ public class MasterDataImportTest { UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, - /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */ - true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /*buoyZoneRadiusInHullLengths*/2.0, + /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); event.addRegatta(regatta); for (String name : raceColumnNames) { series.get(0).addRaceColumn(name, sourceService); @@ -1872,8 +1872,8 @@ public class MasterDataImportTest { UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, - /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */ - true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + /* startDate */null, /* endDate */null, regattaUUID, series, true, new LowPoint(), courseAreaUUID, /*buoyZoneRadiusInHullLengths*/2.0, + /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); event.addRegatta(regatta); for (String name : raceColumnNames) { series.get(0).addRaceColumn(name, sourceService); @@ -2103,8 +2103,8 @@ public class MasterDataImportTest { UUID regattaUUID = UUID.randomUUID(); Regatta regatta = sourceService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, /* startDate */ - null, /* endDate */null, regattaUUID, seriesOnSource, true, new LowPoint(), courseAreaUUID, /* useStartTimeInference */ - true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + null, /* endDate */null, regattaUUID, seriesOnSource, true, new LowPoint(), courseAreaUUID, /*buoyZoneRadiusInHullLengths*/2.0, + /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); for (String name : raceColumnNames) { seriesOnSource.get(0).addRaceColumn(name, sourceService); } @@ -2232,7 +2232,7 @@ public class MasterDataImportTest { Regatta regattaOnTarget = destService.createRegatta( RegattaImpl.getDefaultName(TEST_REGATTA_NAME, TEST_BOAT_CLASS_NAME), TEST_BOAT_CLASS_NAME, /* startDate */null, /* endDate */null, regattaUUID, seriesOnTarget, true, new LowPoint(), - courseAreaUUID, /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); + courseAreaUUID, /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */true, /* controlTrackingFromStartAndFinishTimes */ false, OneDesignRankingMetric::new); raceColumnOnTarget.setTrackedRace(defaultFleetOnTarget, new DummyTrackedRace(raceId, competitors, regattaOnTarget, null, sourceService.getWindStore())); diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java index 2ad726747b6..af49b323e3c 100755 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/SearchServiceTest.java @@ -141,7 +141,7 @@ public class SearchServiceTest { pfingstbusch29er = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Pfingstbusch", "29er"), "29er", /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ - true, new LowPoint(), kielAlpha.getId(), /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, RankingMetrics.ONE_DESIGN)); + true, new LowPoint(), kielAlpha.getId(), /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, RankingMetrics.ONE_DESIGN)); server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R1")); server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R2")); server.apply(new AddColumnToSeries(pfingstbusch29er.getRegattaIdentifier(), "Default", "R3")); @@ -149,7 +149,7 @@ public class SearchServiceTest { /* leaderboardDisplayName */ null, /* discardThresholds */ new int[0])); pfingstbusch470 = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Pfingstbusch", "470"), "470", /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ - true, new LowPoint(), kielBravo.getId(), /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, RankingMetrics.ONE_DESIGN)); + true, new LowPoint(), kielBravo.getId(), /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, RankingMetrics.ONE_DESIGN)); server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R1")); server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R2")); server.apply(new AddColumnToSeries(pfingstbusch470.getRegattaIdentifier(), "Default", "R3")); @@ -172,7 +172,7 @@ public class SearchServiceTest { aalRegatta = server.apply(new AddSpecificRegatta(RegattaImpl.getDefaultName("Aalregatta", "ORC"), "ORC", /*startDate*/ null, /*endDate*/ null, UUID.randomUUID(), new RegattaCreationParametersDTO(seriesCreationParams), /* persistent */ - true, new LowPoint(), flensburgStandard.getId(), /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, RankingMetrics.ONE_DESIGN)); + true, new LowPoint(), flensburgStandard.getId(), /*buoyZoneRadiusInHullLengths*/2.0, /* useStartTimeInference */ true, /* controlTrackingFromStartAndFinishTimes */ false, RankingMetrics.ONE_DESIGN)); server.apply(new AddColumnToSeries(aalRegatta.getRegattaIdentifier(), "Default", "R1")); server.apply(new AddColumnToSeries(aalRegatta.getRegattaIdentifier(), "Default", "R2")); RegattaLeaderboard aalRegattaLeaderboard = server.apply(new CreateRegattaLeaderboard(aalRegatta.getRegattaIdentifier(), diff --git a/java/com.sap.sailing.server.trackfiles.test/src/com/sap/sailing/server/trackfiles/test/BravoDataImportTest.java b/java/com.sap.sailing.server.trackfiles.test/src/com/sap/sailing/server/trackfiles/test/BravoDataImportTest.java index 5fe5f150b5f..df3e94c8a5d 100644 --- a/java/com.sap.sailing.server.trackfiles.test/src/com/sap/sailing/server/trackfiles/test/BravoDataImportTest.java +++ b/java/com.sap.sailing.server.trackfiles.test/src/com/sap/sailing/server/trackfiles/test/BravoDataImportTest.java @@ -19,12 +19,12 @@ public class BravoDataImportTest { private final DoubleVectorFixImporter bravoDataImporter = new BravoDataImporterImpl(); private int callbackCallCount = 0; - private double sumRideHeight = 0.0; + private double sumRideHeightInMeters = 0.0; @Before public void setUp() { this.callbackCallCount = 0; - this.sumRideHeight = 0.0; + this.sumRideHeightInMeters = 0.0; } @Test @@ -51,12 +51,12 @@ public class BravoDataImportTest { bravoDataImporter.importFixes(importData.getInputStream(), (fixes, device) -> { for (DoubleVectorFix fix : fixes) { callbackCallCount++; - sumRideHeight += new BravoFixImpl(fix).getRideHeight(); + sumRideHeightInMeters += new BravoFixImpl(fix).getRideHeight().getMeters(); } }, "filename", "source"); Assert.assertEquals(importData.expectedFixesCount, callbackCallCount); Assert.assertEquals(importData.expectedAverageRideHeight, - callbackCallCount == 0 ? sumRideHeight : sumRideHeight / callbackCallCount, 0.00001); + callbackCallCount == 0 ? sumRideHeightInMeters : sumRideHeightInMeters / callbackCallCount, 0.00001); } private enum ImportData { diff --git a/java/com.sap.sailing.server.trackfiles/src/com/sap/sailing/server/trackfiles/impl/BravoDataImporterImpl.java b/java/com.sap.sailing.server.trackfiles/src/com/sap/sailing/server/trackfiles/impl/BravoDataImporterImpl.java index 9d7f8045b14..d9c9a7135f7 100644 --- a/java/com.sap.sailing.server.trackfiles/src/com/sap/sailing/server/trackfiles/impl/BravoDataImporterImpl.java +++ b/java/com.sap.sailing.server.trackfiles/src/com/sap/sailing/server/trackfiles/impl/BravoDataImporterImpl.java @@ -15,6 +15,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.IntStream; @@ -46,7 +47,7 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter { private final String BOF = "jjlDATE\tjjlTIME\tEpoch"; private static final int BATCH_SIZE = 5000; - public void importFixes(InputStream inputStream, Callback callback, String filename, String sourceName) + public void importFixes(InputStream inputStream, Callback callback, final String filename, String sourceName) throws FormatNotSupportedException, IOException { final TrackFileImportDeviceIdentifier trackIdentifier = new TrackFileImportDeviceIdentifierImpl( UUID.randomUUID(), filename, sourceName, MillisecondsTimePoint.now()); @@ -59,12 +60,14 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter { } else { isr = new InputStreamReader(inputStream); } - LOG.fine("Start parsing imu file"); + LOG.fine("Start parsing bravo file"); + AtomicLong lineNr = new AtomicLong(); try (BufferedReader buffer = new BufferedReader(isr)) { String headerLine = null; headerSearch: while (headerLine == null) { LOG.fine("Searching for header in bravo file"); String headerCandidate = buffer.readLine(); + lineNr.incrementAndGet(); if (headerCandidate == null) { throw new RuntimeException("Missing required header in file " + filename); } @@ -79,10 +82,14 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter { LOG.fine("Parse and store data in batches of " + BATCH_SIZE + " items"); final ArrayList collectedFixes = new ArrayList<>(BATCH_SIZE); buffer.lines().forEach(line -> { - collectedFixes.add(parseLine(line, colIndices)); - if (collectedFixes.size() == BATCH_SIZE) { - callback.addFixes(collectedFixes, trackIdentifier); - collectedFixes.clear(); + lineNr.incrementAndGet(); + DoubleVectorFixImpl vectorFix = parseLine(lineNr.get(), filename, line, colIndices); + if (vectorFix != null) { + collectedFixes.add(vectorFix); + if (collectedFixes.size() == BATCH_SIZE) { + callback.addFixes(collectedFixes, trackIdentifier); + collectedFixes.clear(); + } } }); if (!collectedFixes.isEmpty()) { @@ -102,36 +109,42 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter { * @param colIndices * @return */ - private DoubleVectorFixImpl parseLine(String line, Map colIndices) { - String[] contentTokens = split(line); - TimePoint fixTp; - String epochColValue = contentTokens[2]; - if (epochColValue != null && epochColValue.length() > 0) { - epochColValue = epochColValue.substring(0, epochColValue.indexOf(".")); - long epoch = Long.parseLong(epochColValue); - fixTp = new MillisecondsTimePoint(epoch); - } else { - String jjLDATE = contentTokens[0].substring(0, 9); - StringBuilder dtb = new StringBuilder(jjLDATE); - String jjlTIME = contentTokens[1]; - int offset = 6 - jjlTIME.indexOf("."); - IntStream.range(0, offset).forEach(n -> dtb.append("0")); - dtb.append(jjlTIME); - LocalDateTime day = DATE_FMT.parse(dtb.toString(), LocalDateTime::from); - Instant instant = day.toInstant(ZoneOffset.UTC); - fixTp = new MillisecondsTimePoint(Date.from(instant)); + private DoubleVectorFixImpl parseLine(long lineNr, String filename, String line, Map colIndices) { + try { + String[] contentTokens = split(line); + TimePoint fixTp; + String epochColValue = contentTokens[2]; + if (epochColValue != null && epochColValue.length() > 0) { + epochColValue = epochColValue.substring(0, epochColValue.indexOf(".")); + long epoch = Long.parseLong(epochColValue); + fixTp = new MillisecondsTimePoint(epoch); + } else { + String jjLDATE = contentTokens[0].substring(0, 9); + StringBuilder dtb = new StringBuilder(jjLDATE); + String jjlTIME = contentTokens[1]; + int offset = 6 - jjlTIME.indexOf("."); + IntStream.range(0, offset).forEach(n -> dtb.append("0")); + dtb.append(jjlTIME); + LocalDateTime day = DATE_FMT.parse(dtb.toString(), LocalDateTime::from); + Instant instant = day.toInstant(ZoneOffset.UTC); + fixTp = new MillisecondsTimePoint(Date.from(instant)); + } + // code for time adjustment for foiling test data + // long r16TP = ZonedDateTime.of(LocalDateTime.of(2016, 3, 19, 11, 55), ZoneId.of("Europe/Berlin")) + // .toEpochSecond() * 1000; + // fixTp = fixTp.plus(r16TP - 1461828459589l); + double[] fixData = new double[metadata.columnCount]; + for (int columnIndexInFix = 0; columnIndexInFix < fixData.length; columnIndexInFix++) { + String columnName = metadata.getColumns().get(columnIndexInFix); + Integer columnIndexInFile = colIndices.get(columnName); + fixData[columnIndexInFix] = Double.parseDouble(contentTokens[columnIndexInFile]); + } + return new DoubleVectorFixImpl(fixTp, fixData); + } catch (Exception e) { + LOG.warning( + "Error parsing line nr " + lineNr + " in file " + filename + "with exception: " + e.getMessage()); + return null; } - // code for time adjustment for foiling test data - // long r16TP = ZonedDateTime.of(LocalDateTime.of(2016, 3, 19, 11, 55), ZoneId.of("Europe/Berlin")) - // .toEpochSecond() * 1000; - // fixTp = fixTp.plus(r16TP - 1461828459589l); - double[] fixData = new double[metadata.columnCount]; - for (int columnIndexInFix = 0; columnIndexInFix < fixData.length; columnIndexInFix++) { - String columnName = metadata.getColumns().get(columnIndexInFix); - Integer columnIndexInFile = colIndices.get(columnName); - fixData[columnIndexInFix] = Double.parseDouble(contentTokens[columnIndexInFile]); - } - return new DoubleVectorFixImpl(fixTp, fixData); } private Map validateAndParseHeader(String headerLine) { diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java index be93bfbe602..8e8a5c4965c 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java @@ -314,12 +314,12 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch * @param controlTrackingFromStartAndFinishTimes TODO */ Regatta createRegatta(String regattaName, String boatClassName, TimePoint startDate, TimePoint endDate, Serializable id, Iterable series, - boolean persistent, ScoringScheme scoringScheme, Serializable defaultCourseAreaId, + boolean persistent, ScoringScheme scoringScheme, Serializable defaultCourseAreaId, Double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes, RankingMetricConstructor rankingMetricConstructor); Regatta updateRegatta(RegattaIdentifier regattaIdentifier, TimePoint startDate, TimePoint endDate, Serializable newDefaultCourseAreaId, RegattaConfiguration regattaConfiguration, - Iterable series, boolean useStartTimeInference, + Iterable series, Double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes); /** @@ -465,7 +465,8 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch Util.Pair getOrCreateRegattaWithoutReplication(String fullRegattaName, String boatClassName, TimePoint startDate, TimePoint endDate, Serializable id, Iterable series, boolean persistent, ScoringScheme scoringScheme, - Serializable defaultCourseAreaId, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes, RankingMetricConstructor rankingMetricConstructor); + Serializable defaultCourseAreaId, double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, + boolean controlTrackingFromStartAndFinishTimes, RankingMetricConstructor rankingMetricConstructor); /** * @return map where keys are the toString() representation of the {@link RaceDefinition#getId() IDs} of races passed to diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java index dc88222e500..cd71012d9e0 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RaceLogScoringReplicator.java @@ -143,9 +143,17 @@ public class RaceLogScoringReplicator implements RaceColumnListener { if (positioningList != null) { for (CompetitorResult positionedCompetitor : positioningList) { Competitor competitor = service.getBaseDomainFactory().getExistingCompetitorById(positionedCompetitor.getCompetitorId()); - int rankByRaceCommittee = getRankInPositioningListByRaceCommittee(positionedCompetitor); - correctScoreInLeaderboard(leaderboard, raceColumn, timePoint, numberOfCompetitorsInRace, - competitor, rankByRaceCommittee, positionedCompetitor.getScore()); + // The score is updated when explicitly provided or when no penalty was set; + // in turn, this means that when a penalty is set and no score is explicitly provided, + // it is up to the scoring scheme to infer a penalty score for the MaxPointsReason. + // See also bug 3955. + if (positionedCompetitor.getScore() != null + || positionedCompetitor.getMaxPointsReason() == null + || positionedCompetitor.getMaxPointsReason().equals(MaxPointsReason.NONE)) { + int rankByRaceCommittee = getRankInPositioningListByRaceCommittee(positionedCompetitor); + correctScoreInLeaderboard(leaderboard, raceColumn, timePoint, numberOfCompetitorsInRace, competitor, + rankByRaceCommittee, positionedCompetitor.getScore()); + } setMaxPointsReasonInLeaderboardIfNecessary(leaderboard, raceColumn, timePoint, positionedCompetitor.getMaxPointsReason(), competitor); } // Since the metadata update is used by the Sailing suite to determine the final state of a race, it has to diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index 2605ac6ddb2..06c93aeca4a 100644 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -1238,11 +1238,11 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes @Override public Regatta createRegatta(String fullRegattaName, String boatClassName, TimePoint startDate, TimePoint endDate, Serializable id, Iterable series, boolean persistent, ScoringScheme scoringScheme, - Serializable defaultCourseAreaId, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes, + Serializable defaultCourseAreaId, Double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes, RankingMetricConstructor rankingMetricConstructor) { com.sap.sse.common.Util.Pair regattaWithCreatedFlag = getOrCreateRegattaWithoutReplication( fullRegattaName, boatClassName, startDate, endDate, id, series, persistent, scoringScheme, - defaultCourseAreaId, useStartTimeInference, controlTrackingFromStartAndFinishTimes, rankingMetricConstructor); + defaultCourseAreaId, buoyZoneRadiusInHullLengths, useStartTimeInference, controlTrackingFromStartAndFinishTimes, rankingMetricConstructor); Regatta regatta = regattaWithCreatedFlag.getA(); if (regattaWithCreatedFlag.getB()) { onRegattaLikeAdded(regatta); @@ -1275,12 +1275,12 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes public com.sap.sse.common.Util.Pair getOrCreateRegattaWithoutReplication(String fullRegattaName, String boatClassName, TimePoint startDate, TimePoint endDate, Serializable id, Iterable series, boolean persistent, ScoringScheme scoringScheme, - Serializable defaultCourseAreaId, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes, + Serializable defaultCourseAreaId, double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes, RankingMetricConstructor rankingMetricConstructor) { CourseArea courseArea = getCourseArea(defaultCourseAreaId); Regatta regatta = new RegattaImpl(getRaceLogStore(), getRegattaLogStore(), fullRegattaName, getBaseDomainFactory().getOrCreateBoatClass(boatClassName), startDate, endDate, series, persistent, - scoringScheme, id, courseArea, useStartTimeInference, controlTrackingFromStartAndFinishTimes, rankingMetricConstructor); + scoringScheme, id, courseArea, buoyZoneRadiusInHullLengths, useStartTimeInference, controlTrackingFromStartAndFinishTimes, rankingMetricConstructor); boolean wasCreated = addAndConnectRegatta(persistent, defaultCourseAreaId, regatta); if (wasCreated) { logger.info("Created regatta " + regatta.getName() + " (" + hashCode() + ") on " + this); @@ -1461,7 +1461,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes replicate(new AddSpecificRegatta(regatta.getName(), regatta.getBoatClass() == null ? null : regatta .getBoatClass().getName(), regatta.getStartDate(), regatta.getEndDate(), regatta.getId(), getSeriesWithoutRaceColumnsConstructionParametersAsMap(regatta), regatta.isPersistent(), - regatta.getScoringScheme(), courseAreaId, regatta.useStartTimeInference(), regatta.isControlTrackingFromStartAndFinishTimes(), + regatta.getScoringScheme(), courseAreaId, regatta.getBuoyZoneRadiusInHullLengths(), regatta.useStartTimeInference(), regatta.isControlTrackingFromStartAndFinishTimes(), regatta.getRankingMetricType())); RegattaIdentifier regattaIdentifier = regatta.getRegattaIdentifier(); for (RaceDefinition race : regatta.getAllRaces()) { @@ -1774,11 +1774,12 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes public void finishedTimeChanged(TimePoint oldFinishedTime, TimePoint newFinishedTime) { // no action required; the update signaled by this call is implicit; the race log // updates that led to this change are replicated separately - - scheduler.execute(()-> - // Notify interested users: - notificationService.notifyUserOnBoatClassRaceChangesStateToFinished(trackedRace.getRace().getBoatClass(), trackedRace, - getMostAppropriateLeaderboard(), getMostAppropriateRaceColumn(), getMostAppropriateFleet())); + if (newFinishedTime.after(MillisecondsTimePoint.now().minus(Duration.ONE_HOUR))) { + scheduler.execute(()-> + // Notify interested users: + notificationService.notifyUserOnBoatClassRaceChangesStateToFinished(trackedRace.getRace().getBoatClass(), trackedRace, + getMostAppropriateLeaderboard(), getMostAppropriateRaceColumn(), getMostAppropriateFleet())); + } } @Override @@ -1838,7 +1839,8 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes replicate(new UpdateMarkPassings(getRaceIdentifier(), competitor, markPassings)); final MarkPassing last = Util.last(markPassings); if (last != null && last.getWaypoint() == trackedRace.getRace().getCourse().getLastWaypoint() && - trackedRace.getStatus().getStatus() != TrackedRaceStatusEnum.LOADING) { + trackedRace.getStatus().getStatus() != TrackedRaceStatusEnum.LOADING && + last.getTimePoint().after(MillisecondsTimePoint.now().minus(Duration.ONE_HOUR))) { scheduler.execute(() -> // Notify interested users: notificationService.notifyUserOnCompetitorPassesFinish(competitor, trackedRace, @@ -2143,7 +2145,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes @Override public Regatta updateRegatta(RegattaIdentifier regattaIdentifier, TimePoint startDate, TimePoint endDate, Serializable newDefaultCourseAreaId, RegattaConfiguration newRegattaConfiguration, - Iterable series, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes) { + Iterable series, Double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes) { // We're not doing any renaming of the regatta itself, therefore we don't have to sync on the maps. Regatta regatta = getRegatta(regattaIdentifier); CourseArea newCourseArea = getCourseArea(newDefaultCourseAreaId); @@ -2152,6 +2154,7 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes } regatta.setStartDate(startDate); regatta.setEndDate(endDate); + regatta.setBuoyZoneRadiusInHullLengths(buoyZoneRadiusInHullLengths); regatta.setControlTrackingFromStartAndFinishTimes(controlTrackingFromStartAndFinishTimes); if (regatta.useStartTimeInference() != useStartTimeInference) { regatta.setUseStartTimeInference(useStartTimeInference); @@ -3481,6 +3484,8 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes trackedRace.setPolarDataService(service); service.insertExistingFixes(trackedRace); } + } catch (Throwable e) { + logger.log(Level.SEVERE, "Error reconstructing the polars for tracked races", e); } finally { trackedRegatta.unlockTrackedRacesAfterRead(); } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RemoteSailingServerSet.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RemoteSailingServerSet.java index f9993be852e..13275a35242 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RemoteSailingServerSet.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RemoteSailingServerSet.java @@ -33,6 +33,8 @@ import com.sap.sailing.server.gateway.deserialization.impl.LeaderboardGroupBaseJ import com.sap.sailing.server.gateway.deserialization.impl.VenueJsonDeserializer; 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.HttpUrlConnectionHelper; /** @@ -44,15 +46,16 @@ import com.sap.sse.util.HttpUrlConnectionHelper; */ public class RemoteSailingServerSet { private static final int POLLING_INTERVAL_IN_SECONDS = 60; + private NamedReentrantReadWriteLock lock = new NamedReentrantReadWriteLock("lock for RemoteSailingServerSet", true); private static final Logger logger = Logger.getLogger(RemoteSailingServerSet.class.getName()); - + /** * Holds the remote server references managed by this set. Keys are the * {@link RemoteSailingServerReference#getName() names} of the server references. */ private final ConcurrentMap remoteSailingServers; - + private final ConcurrentMap, Exception>> cachedEventsForRemoteSailingServers; /** @@ -62,53 +65,63 @@ public class RemoteSailingServerSet { public RemoteSailingServerSet(ScheduledExecutorService scheduler) { remoteSailingServers = new ConcurrentHashMap<>(); cachedEventsForRemoteSailingServers = new ConcurrentHashMap<>(); - scheduler.scheduleAtFixedRate(new Runnable() { @Override public void run() { updateRemoteSailingServerReferenceEventCaches(); } }, - /* initialDelay */ 0, POLLING_INTERVAL_IN_SECONDS, TimeUnit.SECONDS); + scheduler.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + updateRemoteSailingServerReferenceEventCaches(); + } + }, /* initialDelay */ 0, POLLING_INTERVAL_IN_SECONDS, TimeUnit.SECONDS); } public void clear() { - remoteSailingServers.clear(); - cachedEventsForRemoteSailingServers.clear(); + LockUtil.executeWithWriteLock(lock, () -> { + remoteSailingServers.clear(); + cachedEventsForRemoteSailingServers.clear(); + }); } public void add(RemoteSailingServerReference remoteSailingServerReference) { - remoteSailingServers.put(remoteSailingServerReference.getName(), remoteSailingServerReference); - triggerAsynchronousEventCacheUpdate(remoteSailingServerReference); + LockUtil.executeWithWriteLock(lock, () -> { + remoteSailingServers.put(remoteSailingServerReference.getName(), remoteSailingServerReference); + triggerAsynchronousEventCacheUpdate(remoteSailingServerReference); + }); } - + private void updateRemoteSailingServerReferenceEventCaches() { - for (RemoteSailingServerReference ref : remoteSailingServers.values()) { - triggerAsynchronousEventCacheUpdate(ref); - } + LockUtil.executeWithReadLock(lock, () -> { + for (RemoteSailingServerReference ref : remoteSailingServers.values()) { + triggerAsynchronousEventCacheUpdate(ref); + } + }); } - + /** - * If this set has a {@link RemoteSailingServerReference} whose {@link RemoteSailingServerReference#getName() name} equals - * name, it is returned. Otherwise, null is returned. + * If this set has a {@link RemoteSailingServerReference} whose {@link RemoteSailingServerReference#getName() name} + * equals name, it is returned. Otherwise, null is returned. */ public RemoteSailingServerReference getServerReferenceByName(String name) { return remoteSailingServers.get(name); } private void triggerAsynchronousEventCacheUpdate(final RemoteSailingServerReference ref) { - new Thread("Event Cache Updater for remote server "+ref) { - @Override public void run() { updateRemoteServerEventCacheSynchronously(ref); } - }.start(); + new Thread(() -> updateRemoteServerEventCacheSynchronously(ref), "Event Cache Updater for remote server " + ref).start(); } - private Util.Pair, Exception> updateRemoteServerEventCacheSynchronously(RemoteSailingServerReference ref) { + private Util.Pair, Exception> updateRemoteServerEventCacheSynchronously( + RemoteSailingServerReference ref) { BufferedReader bufferedReader = null; Util.Pair, Exception> result; try { try { final URL eventsURL = getEventsURL(ref.getURL()); - logger.fine("Updating events for remote server "+ref+" from URL "+eventsURL); + logger.fine("Updating events for remote server " + ref + " from URL " + eventsURL); URLConnection urlConnection = HttpUrlConnectionHelper.redirectConnection(eventsURL); bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8")); JSONParser parser = new JSONParser(); Object eventsAsObject = parser.parse(bufferedReader); - EventBaseJsonDeserializer deserializer = new EventBaseJsonDeserializer(new VenueJsonDeserializer( - new CourseAreaJsonDeserializer(DomainFactory.INSTANCE)), new LeaderboardGroupBaseJsonDeserializer()); + EventBaseJsonDeserializer deserializer = new EventBaseJsonDeserializer( + new VenueJsonDeserializer(new CourseAreaJsonDeserializer(DomainFactory.INSTANCE)), + new LeaderboardGroupBaseJsonDeserializer()); JSONArray eventsAsJsonArray = (JSONArray) eventsAsObject; final Set events = new HashSet<>(); for (Object eventAsObject : eventsAsJsonArray) { @@ -123,13 +136,22 @@ public class RemoteSailingServerSet { } } } catch (IOException | ParseException e) { - logger.log(Level.INFO, "Exception trying to fetch events from remote server "+ref+": "+e.getMessage(), e); + logger.log(Level.INFO, "Exception trying to fetch events from remote server " + ref + ": " + e.getMessage(), + e); result = new Util.Pair, Exception>(/* events */ null, e); } - cachedEventsForRemoteSailingServers.put(ref, result); + final Pair, Exception> finalResult = result; + LockUtil.executeWithWriteLock(lock, () -> { + // check that the server was not removed while no lock was held + if (remoteSailingServers.containsValue(ref)) { + cachedEventsForRemoteSailingServers.put(ref, finalResult); + } else { + logger.fine("Omitted update for " + ref + " as it was removed"); + } + }); return result; } - + private URL getEventsURL(URL remoteServerBaseURL) throws MalformedURLException { String getEventsUrl = remoteServerBaseURL.toExternalForm(); if (!getEventsUrl.endsWith("/")) { @@ -140,36 +162,55 @@ public class RemoteSailingServerSet { } public Map, Exception>> getCachedEventsForRemoteSailingServers() { - return Collections.unmodifiableMap(cachedEventsForRemoteSailingServers); + LockUtil.lockForRead(lock); + try { + return Collections.unmodifiableMap(cachedEventsForRemoteSailingServers); + } finally { + LockUtil.unlockAfterRead(lock); + } } public RemoteSailingServerReference remove(String name) { - RemoteSailingServerReference ref = remoteSailingServers.remove(name); - if (ref != null) { - cachedEventsForRemoteSailingServers.remove(ref); + LockUtil.lockForWrite(lock); + try{ + RemoteSailingServerReference ref = remoteSailingServers.remove(name); + if (ref != null) { + cachedEventsForRemoteSailingServers.remove(ref); + } + return ref; + }finally{ + LockUtil.unlockAfterWrite(lock); } - return ref; } /** - * Synchronously fetches the latest events list for the remote server reference specified. The - * result is cached. If ref was not yet part of this remote sailing server reference set, - * it is automatically added. + * Synchronously fetches the latest events list for the remote server reference specified. The result is cached. If + * ref was not yet part of this remote sailing server reference set, it is automatically added. */ public Util.Pair, Exception> getEventsOrException(RemoteSailingServerReference ref) { - if (!remoteSailingServers.containsKey(ref.getName())) { - remoteSailingServers.put(ref.getName(), ref); + LockUtil.lockForWrite(lock); + try { + if (!remoteSailingServers.containsKey(ref.getName())) { + remoteSailingServers.put(ref.getName(), ref); + } + } finally { + LockUtil.unlockAfterWrite(lock); } return updateRemoteServerEventCacheSynchronously(ref); } public Iterable getLiveRemoteServerReferences() { List result = new ArrayList<>(); - for (Map.Entry, Exception>> e : cachedEventsForRemoteSailingServers.entrySet()) { - if (e.getValue().getB() == null) { - // no exception; reference considered live - result.add(e.getKey()); + LockUtil.lockForRead(lock); + try { + for (Map.Entry, Exception>> e : cachedEventsForRemoteSailingServers.entrySet()) { + if (e.getValue().getB() == null) { + // no exception; reference considered live + result.add(e.getKey()); + } } + } finally { + LockUtil.unlockAfterRead(lock); } return result; } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java index c5be8205189..4bed05fb47a 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java @@ -67,22 +67,26 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { private static final long serialVersionUID = -11522605089325440L; private Regatta regatta; - public DummyTrackedRace(final Serializable raceId, final Iterable competitors, final Regatta regatta, final TrackedRegatta trackedRegatta, final WindStore windStore) { - this(competitors, regatta, trackedRegatta, new RaceDefinitionImpl("DummyRace", new CourseImpl("Dummy Course", Collections.emptyList()), - regatta.getBoatClass(), competitors, raceId), windStore); + public DummyTrackedRace(final Serializable raceId, final Iterable competitors, + final Regatta regatta, final TrackedRegatta trackedRegatta, final WindStore windStore) { + this(competitors, regatta, trackedRegatta, + new RaceDefinitionImpl("DummyRace", new CourseImpl("Dummy Course", Collections. emptyList()), + regatta.getBoatClass(), competitors, raceId), + windStore); } - - public DummyTrackedRace(final Iterable competitors, final Regatta regatta, final TrackedRegatta trackedRegatta, - RaceDefinition race, WindStore windStore) { + + public DummyTrackedRace(final Iterable competitors, final Regatta regatta, + final TrackedRegatta trackedRegatta, RaceDefinition race, WindStore windStore) { super(race, trackedRegatta, windStore, -1); this.regatta = regatta; } - public DummyTrackedRace(final Iterable competitors, final Regatta regatta, final TrackedRegatta trackedRegatta, - RaceDefinition race) { - this(competitors, regatta, trackedRegatta, race, EmptyWindStore.INSTANCE); - + + public DummyTrackedRace(final Iterable competitors, final Regatta regatta, + final TrackedRegatta trackedRegatta, RaceDefinition race) { + this(competitors, regatta, trackedRegatta, race, EmptyWindStore.INSTANCE); + } - + public DummyTrackedRace(final String raceName, final Serializable raceId) { super(new RaceDefinitionImpl(raceName, new CourseImpl("Dummy Course", Collections. emptyList()), /* boatClass */ null, new HashSet(), raceId), null, EmptyWindStore.INSTANCE, -1); @@ -214,7 +218,8 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { } @Override - public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) { + public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, + MarkPositionAtTimePointCache markPositionCache) { return null; } @@ -294,7 +299,7 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { public Tack getTack(Competitor competitor, TimePoint timePoint) throws NoWindException { return null; } - + @Override public Tack getTack(SpeedWithBearing speedWithBearing, Wind wind, TimePoint timePoint) { return null; @@ -346,7 +351,7 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { @Override public void removeListener(RaceChangeListener listener) { - + } @Override @@ -355,7 +360,8 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { } @Override - public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint, WindPositionMode windPositionMode) { + public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint, + WindPositionMode windPositionMode) { return null; } @@ -382,12 +388,12 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { @Override public void setWindSourcesToExclude(Iterable windSourcesToExclude) { - + } @Override - public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) - throws NoWindException { + public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint, + boolean waitForLatestAnalysis) throws NoWindException { return null; } @@ -431,8 +437,8 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { } @Override - public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, - boolean waitForLatestAnalyses) throws NoWindException { + public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, + boolean upwindOnly, boolean waitForLatestAnalyses) throws NoWindException { return null; } @@ -465,7 +471,7 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { @Override public void addCourseDesignChangedListener(CourseDesignChangedListener listener) { - + } @Override @@ -596,13 +602,13 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { @Override public void setPolarDataService(PolarDataService polarDataService) { } - + @Override - public Duration getEstimatedTimeToComplete(TimePoint timepoint) throws NotEnoughDataHasBeenAddedException, - NoWindException { + public Duration getEstimatedTimeToComplete(TimePoint timepoint) + throws NotEnoughDataHasBeenAddedException, NoWindException { return null; } - + @Override public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint, WindPositionMode windPositionMode, RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { @@ -640,7 +646,7 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { @Override public void updateStartAndEndOfTracking(boolean waitForGPSFixesToLoad) { } - + @Override public > TrackT getSensorTrack( Competitor competitor, String trackName) { @@ -656,4 +662,14 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { public Iterable getAttachedRaceLogs() { return Collections.emptySet(); } + + @Override + public NavigableSet getMarkPassings(Competitor competitor, boolean waitForLatestUpdates) { + return null; + } + + @Override + public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint) { + return null; + } } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/AddSpecificRegatta.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/AddSpecificRegatta.java index 4644545e764..a1f9a123357 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/AddSpecificRegatta.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/AddSpecificRegatta.java @@ -29,13 +29,14 @@ public class AddSpecificRegatta extends AbstractAddRegattaOperation { private final boolean persistent; private final ScoringScheme scoringScheme; private final Serializable defaultCourseAreaId; + private final Double buoyZoneRadiusInHullLengths; private final boolean useStartTimeInference; private final boolean controlTrackingFromStartAndFinishTimes; private final RankingMetrics rankingMetricType; public AddSpecificRegatta(String regattaName, String boatClassName, TimePoint startDate, TimePoint endDate, Serializable id, RegattaCreationParametersDTO seriesNamesWithFleetNamesAndFleetOrderingAndMedalAndDiscardingThresholds, - boolean persistent, ScoringScheme scoringScheme, Serializable defaultCourseAreaId, boolean useStartTimeInference, + boolean persistent, ScoringScheme scoringScheme, Serializable defaultCourseAreaId, Double buoyZoneRadiusInHullLengths, boolean useStartTimeInference, boolean controlTrackingFromStartAndFinishTimes, RankingMetrics rankingMetricType) { super(regattaName, boatClassName, startDate, endDate, id); this.seriesNamesWithFleetNamesAndFleetOrderingAndMedalAndStartsWithZeroScoreAndDiscardingThresholds = seriesNamesWithFleetNamesAndFleetOrderingAndMedalAndDiscardingThresholds; @@ -45,12 +46,13 @@ public class AddSpecificRegatta extends AbstractAddRegattaOperation { this.useStartTimeInference = useStartTimeInference; this.controlTrackingFromStartAndFinishTimes = controlTrackingFromStartAndFinishTimes; this.rankingMetricType = rankingMetricType; + this.buoyZoneRadiusInHullLengths = buoyZoneRadiusInHullLengths; } @Override public Regatta internalApplyTo(RacingEventService toState) throws Exception { Regatta regatta = toState.createRegatta(getRegattaName(), getBoatClassName(), getStartDate(), getEndDate(), getId(), createSeries(toState), - persistent, scoringScheme, defaultCourseAreaId, useStartTimeInference, controlTrackingFromStartAndFinishTimes, RankingMetricsFactory.getRankingMetricConstructor(rankingMetricType)); + persistent, scoringScheme, defaultCourseAreaId, buoyZoneRadiusInHullLengths, useStartTimeInference, controlTrackingFromStartAndFinishTimes, RankingMetricsFactory.getRankingMetricConstructor(rankingMetricType)); return regatta; } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateSpecificRegatta.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateSpecificRegatta.java index b6ca415e563..a59a133dd2f 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateSpecificRegatta.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/UpdateSpecificRegatta.java @@ -15,13 +15,14 @@ public class UpdateSpecificRegatta extends AbstractRacingEventServiceOperation _boatClassesInfo = new ArrayList(); @@ -21,11 +22,11 @@ public class ConfigurationManagerTest { @Before public void initialize() { - this._boatClassesInfo.add(new BoatClassPropertiesImpl("49er", 4.995, "PolarDiagram49.csv", 0)); - this._boatClassesInfo.add(new BoatClassPropertiesImpl("49er Bethwaite", 4.876, "PolarDiagram49Bethwaite.csv", 1)); - this._boatClassesInfo.add(new BoatClassPropertiesImpl("49er ORC", 4.995, "PolarDiagram49ORC.csv", 2)); - this._boatClassesInfo.add(new BoatClassPropertiesImpl("49er STG", 4.876, "PolarDiagram49STG.csv", 3)); - this._boatClassesInfo.add(new BoatClassPropertiesImpl("505 STG", 5.05, "PolarDiagram505STG.csv", 4)); + this._boatClassesInfo.add(new BoatClassPropertiesImpl("49er", new MeterDistance(4.995), "PolarDiagram49.csv", 0)); + this._boatClassesInfo.add(new BoatClassPropertiesImpl("49er Bethwaite", new MeterDistance(4.876), "PolarDiagram49Bethwaite.csv", 1)); + this._boatClassesInfo.add(new BoatClassPropertiesImpl("49er ORC", new MeterDistance(4.995), "PolarDiagram49ORC.csv", 2)); + this._boatClassesInfo.add(new BoatClassPropertiesImpl("49er STG", new MeterDistance(4.876), "PolarDiagram49STG.csv", 3)); + this._boatClassesInfo.add(new BoatClassPropertiesImpl("505 STG", new MeterDistance(5.05), "PolarDiagram505STG.csv", 4)); this._racesInfo .add(new RacePropertiesImpl( diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/BoatClassProperties.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/BoatClassProperties.java index 931fb1067a9..2af20e3f3ee 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/BoatClassProperties.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/BoatClassProperties.java @@ -1,10 +1,12 @@ package com.sap.sailing.simulator; +import com.sap.sailing.domain.common.Distance; + public interface BoatClassProperties { String getName(); - Double getLength(); + Distance getLength(); String getPolar(); diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/BoatClassPropertiesImpl.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/BoatClassPropertiesImpl.java index 3c756c63bae..6147ad18aeb 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/BoatClassPropertiesImpl.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/BoatClassPropertiesImpl.java @@ -1,15 +1,16 @@ package com.sap.sailing.simulator.impl; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.simulator.BoatClassProperties; public class BoatClassPropertiesImpl implements BoatClassProperties { String name; - Double length; + Distance length; String polar; Integer index; - public BoatClassPropertiesImpl(String name, Double length, String polar, Integer index) { + public BoatClassPropertiesImpl(String name, Distance length, String polar, Integer index) { this.name = name; this.length = length; this.polar = polar; @@ -20,7 +21,7 @@ public class BoatClassPropertiesImpl implements BoatClassProperties { return this.name; } - public Double getLength() { + public Distance getLength() { return this.length; } diff --git a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/ConfigurationManager.java b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/ConfigurationManager.java index bd55260c5bd..9615df9d11b 100644 --- a/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/ConfigurationManager.java +++ b/java/com.sap.sailing.simulator/src/com/sap/sailing/simulator/impl/ConfigurationManager.java @@ -9,6 +9,7 @@ import java.net.URL; import java.util.ArrayList; import java.util.List; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.simulator.BoatClassProperties; import com.sap.sailing.simulator.RaceProperties; @@ -71,7 +72,7 @@ public enum ConfigurationManager { if (polarDiagramConfig) { this._boatClassesInfo - .add(new BoatClassPropertiesImpl(elements[0], Double.parseDouble(elements[1]), elements[2], index++)); + .add(new BoatClassPropertiesImpl(elements[0], new MeterDistance(Double.parseDouble(elements[1])), elements[2], index++)); } else { this._racesInfo.add(new RacePropertiesImpl(elements[0], elements[1], elements[2], index++)); } diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index b8694747ed8..dfe10f632db 100644 --- a/java/com.sap.sailing.www/release_notes_admin.html +++ b/java/com.sap.sailing.www/release_notes_admin.html @@ -23,6 +23,24 @@

    Release Notes - Administration Console

    +

    October 2016

    +
      +
    • A regatta now has an additional property: "Buoy zone radius in hull lengths." It + can be used to determine the radius of the zone around the marks in terms of + hull lengths. This sets the default for the race viewer (RaceBoard.html entry point) + based on the hull length as known from the boat class.
    • +
    • When producing the QR code for the Race Management App's device configurations, + the access token parameter was not properly URL-encoded. In case the access token + contains a '+' character it is incorrectly decoded by the app, letting the authentication + fail. This issue has now been fixed in the AdminConsole, and the access token parameter is + now properly URL-encoded.
    • +
    • Tracked races can now also be filtered by the regatta to which they belong. + When selecting a regatta leaderboard, by default the table of tracked races will be + filtered by the regatta whose leaderboard was selected. The regatta filter can be + used in conjunction with the regular text filter field. +
    • +
    +

    September 2016

    • When denoting a regatta for smartphone tracking, the "By Marks" course designer is diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/TimeRange.java b/java/com.sap.sse.common/src/com/sap/sse/common/TimeRange.java index e32777f9172..ba15ebc1c6d 100644 --- a/java/com.sap.sse.common/src/com/sap/sse/common/TimeRange.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/TimeRange.java @@ -30,8 +30,12 @@ public interface TimeRange extends Comparable, Serializable { boolean startsBefore(TimeRange other); + boolean startsAfter(TimePoint timePoint); + boolean endsAfter(TimeRange other); + boolean endsBefore(TimePoint timePoint); + /** * Is the time range open ended towards older timepoints? */ diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java index 3e877783873..ff8bd30a4cb 100644 --- a/java/com.sap.sse.common/src/com/sap/sse/common/Util.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/Util.java @@ -288,6 +288,15 @@ public class Util { } return result; } + + public static List createList(Iterable iterable) { + List list = new ArrayList<>(); + Iterator iterator = iterable.iterator(); + while (iterator.hasNext()) { + list.add(iterator.next()); + } + return list; + } /** * A null-safe check whether t is contained in ts. For ts==null the method diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/filter/AbstractKeywordFilter.java b/java/com.sap.sse.common/src/com/sap/sse/common/filter/AbstractKeywordFilter.java new file mode 100644 index 00000000000..a18dc94d3fe --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/filter/AbstractKeywordFilter.java @@ -0,0 +1,47 @@ +package com.sap.sse.common.filter; + +import java.util.HashSet; +import java.util.Set; + +import com.sap.sse.common.Util; +import com.sap.sse.common.filter.impl.KeywordMatcher; + +/** + * {@link Filter} implementation that matches the given objects using a {@link KeywordMatcher}. + * + * @param + */ +public abstract class AbstractKeywordFilter implements Filter { + + private final KeywordMatcher matcher = new KeywordMatcher() { + @Override + public Iterable getStrings(T t) { + return AbstractKeywordFilter.this.getStrings(t); + } + }; + + private final Set keywords = new HashSet<>(); + + /** + * Sets the keywords to use for matching of domain objects. + */ + public void setKeywords(Iterable keywords) { + this.keywords.clear(); + Util.addAll(keywords, this.keywords); + } + + /** + * @see KeywordMatcher#getStrings(Object) + */ + public abstract Iterable getStrings(T t); + + @Override + public boolean matches(T t) { + return matcher.matches(keywords, t); + } + + @Override + public String getName() { + return getClass().getSimpleName(); + } +} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/filter/AbstractListFilter.java b/java/com.sap.sse.common/src/com/sap/sse/common/filter/AbstractListFilter.java index a1308ddf82c..58c63d455c7 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/filter/AbstractListFilter.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/filter/AbstractListFilter.java @@ -3,7 +3,7 @@ package com.sap.sse.common.filter; import java.util.ArrayList; import java.util.List; -import com.sap.sse.common.Util; +import com.sap.sse.common.filter.impl.KeywordMatcher; /** * Matches a list of keywords in a list of strings. A match is recognized if each of the keywords is contained in at least @@ -17,14 +17,16 @@ import com.sap.sse.common.Util; * @param */ public abstract class AbstractListFilter { + + private final KeywordMatcher matcher = new KeywordMatcher() { + @Override + public Iterable getStrings(T t) { + return AbstractListFilter.this.getStrings(t); + } + }; /** - * Subclasses must implement this to extract the strings from an object of type T based on which the - * filter performs its filtering - * - * @param t - * the object from which to extract the searchable strings - * @return the searchable strings + * @see KeywordMatcher#getStrings(Object) */ public abstract Iterable getStrings(T t); @@ -34,46 +36,13 @@ public abstract class AbstractListFilter { public Iterable applyFilter(Iterable keywords, Iterable all) { List sortedList = new ArrayList(); if (all != null) { - if (keywords != null && !Util.isEmpty(keywords)) { - for (T t : all) { - if (containsText(t, keywords)) { - sortedList.add(t); - } - } - } else { - for (T t : all) { + for (T t : all) { + if (matcher.matches(keywords, t)) { sortedList.add(t); } } } return sortedList; } - - /** - * Returns true if for each of the keywords any of the {@link #getStrings(Object) values - * to check} contains that keyword. - * - * @param keywords - * the words to filter on - * @return true if the valuesToCheck contains all wordsToFilter, - * false if not - */ - private boolean containsText(T obj, Iterable keywords) { - boolean failed = false; - for (String word : keywords) { - String textAsUppercase = word.toUpperCase().trim(); - failed = true; - for (String s : getStrings(obj)) { - if (s != null && s.toUpperCase().contains(textAsUppercase)) { - failed = false; - break; - } - } - if (failed) { - return false; - } - } - return true; - } } diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/filter/impl/KeywordMatcher.java b/java/com.sap.sse.common/src/com/sap/sse/common/filter/impl/KeywordMatcher.java new file mode 100644 index 00000000000..a17781eb2f8 --- /dev/null +++ b/java/com.sap.sse.common/src/com/sap/sse/common/filter/impl/KeywordMatcher.java @@ -0,0 +1,56 @@ +package com.sap.sse.common.filter.impl; + +import com.sap.sse.common.Util; + +/** + * Matches domain objects with a set of keywords. A match is recognized if each of the keywords is contained in at + * least one of the {@link #getStrings(Object) strings}. The typical use for this class is building a disjuctive + * ("AND"-like) keyword matcher where {@link #getStrings(Object)} provides one or more strings extracted from the + * searchable object and its context. + * + * @param + */ +public abstract class KeywordMatcher { + + /** + * Subclasses must implement this to extract the strings from an object of type T based on which the + * filter performs its filtering + * + * @param t + * the object from which to extract the searchable strings + * @return the searchable strings + */ + public abstract Iterable getStrings(T t); + + public boolean matches(Iterable keywords, T t) { + boolean matchesOrNoKeywords = keywords == null || Util.isEmpty(keywords) || containsText(t, keywords); + return matchesOrNoKeywords; + } + + /** + * Returns true if for each of the keywords any of the {@link #getStrings(Object) values + * to check} contains that keyword. + * + * @param keywords + * the words to filter on + * @return true if the valuesToCheck contains all wordsToFilter, + * false if not + */ + private boolean containsText(T obj, Iterable keywords) { + boolean failed = false; + for (String word : keywords) { + String textAsUppercase = word.toUpperCase().trim(); + failed = true; + for (String s : getStrings(obj)) { + if (s != null && s.toUpperCase().contains(textAsUppercase)) { + failed = false; + break; + } + } + if (failed) { + return false; + } + } + return true; + } +} diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/impl/TimeRangeImpl.java b/java/com.sap.sse.common/src/com/sap/sse/common/impl/TimeRangeImpl.java index 99ee927d7be..eb4d020fcc0 100644 --- a/java/com.sap.sse.common/src/com/sap/sse/common/impl/TimeRangeImpl.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/impl/TimeRangeImpl.java @@ -75,11 +75,21 @@ public class TimeRangeImpl extends Util.Pair implements Ti return from().before(other.from()); } + @Override + public boolean startsAfter(TimePoint timePoint) { + return from().after(timePoint); + } + @Override public boolean endsAfter(TimeRange other) { return to().after(other.to()); } + @Override + public boolean endsBefore(TimePoint timePoint) { + return to().before(timePoint); + } + @Override public Duration timeDifference(TimePoint timePoint) { final Duration result; diff --git a/java/com.sap.sse.common/src/com/sap/sse/common/util/NaturalComparator.java b/java/com.sap.sse.common/src/com/sap/sse/common/util/NaturalComparator.java index d7c78e4e239..9dcfe60ac59 100755 --- a/java/com.sap.sse.common/src/com/sap/sse/common/util/NaturalComparator.java +++ b/java/com.sap.sse.common/src/com/sap/sse/common/util/NaturalComparator.java @@ -47,7 +47,7 @@ public class NaturalComparator implements Comparator { // Compare all-digit blocks as numbers - all-char blocks as strings if (Character.isDigit(aBlock.charAt(0)) && Character.isDigit(bBlock.charAt(0))) { - result = Integer.valueOf(aBlock).compareTo(Integer.valueOf(bBlock)); + result = Long.valueOf(aBlock).compareTo(Long.valueOf(bBlock)); } else { result = aBlock.compareTo(bBlock); } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeTicksCalculator.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeTicksCalculator.java index 735989a2776..4b4a557ba08 100644 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeTicksCalculator.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/TimeTicksCalculator.java @@ -158,10 +158,24 @@ public class TimeTicksCalculator { // get tick positions i = 1; + /* + * There are the Competitor and the Wind highcharts which + * set time ticks depending on a locale. So we have to + * move the ticks in this time slider on the time zone offset + * to match the time ticks in time slider with ticks in highcharts + */ + minDate = new Date(minDate.getTime() - minDate.getTimezoneOffset() * TimeUnits.MINUTE.unitInMs); minYear = minDate.getYear() + 1900; int minMonth = minDate.getMonth() + 1; int minDateDate = minDate.getDate(); long time = minDate.getTime(); + /* + * After the moving time on time zone offset, + * we should make some steps back to find the race start time + */ + while (time > min) { + time -= interval * count; + } // iterate and add tick positions at appropriate values while (time < max) { diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/AbstractFilterablePanel.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/AbstractFilterablePanel.java old mode 100755 new mode 100644 index 7dd53493eca..48c9da24ca4 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/AbstractFilterablePanel.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/AbstractFilterablePanel.java @@ -1,6 +1,10 @@ package com.sap.sse.gwt.client.panels; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import com.google.gwt.event.dom.client.KeyUpEvent; import com.google.gwt.event.dom.client.KeyUpHandler; @@ -12,25 +16,28 @@ import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.view.client.ListDataProvider; import com.sap.sse.common.Util; +import com.sap.sse.common.filter.AbstractKeywordFilter; import com.sap.sse.common.filter.AbstractListFilter; +import com.sap.sse.common.filter.Filter; import com.sap.sse.gwt.client.celltable.EntityIdentityComparator; import com.sap.sse.gwt.client.celltable.RefreshableMultiSelectionModel; import com.sap.sse.gwt.client.celltable.RefreshableSelectionModel; import com.sap.sse.gwt.client.celltable.RefreshableSingleSelectionModel; /** - * This Panel contains a text box. Text entered into the text box filters the {@link CellTable} passed to - * the constructor by adjusting the cell table's {@link ListDataProvider}'s contents using the {@link - * applyFilter(String, List)} of the {@link AbstractListFilter} and then sorting the table again according the the - * sorting criteria currently active (the sorting is the only reason why the {@link CellTable} actually needs to be - * known to an instance of this class). To be initiated the method {@link #getSearchableStrings(Object)} has to be - * defined, which gets those Strings from a T that should be considered when filtering, e.g. name or - * boatClass. The cell table can be sorted independently from the text box (e.g., after adding new objects) by calling - * the method {@link #updateAll(Iterable)} which then runs the filter over the new selection.

      + * This Panel contains a text box. Text entered into the text box filters the {@link CellTable} passed to the + * constructor by adjusting the cell table's {@link ListDataProvider}'s contents using the {@link applyFilter(String, + * List)} of the {@link AbstractListFilter} and then sorting the table again according the the sorting criteria + * currently active (the sorting is the only reason why the {@link CellTable} actually needs to be known to an instance + * of this class). To be initiated the method {@link #getSearchableStrings(Object)} has to be defined, which gets those + * Strings from a T that should be considered when filtering, e.g. name or boatClass. The cell table can be + * sorted independently from the text box (e.g., after adding new objects) by calling the method + * {@link #updateAll(Iterable)} which then runs the filter over the new selection. + *

      * - * Note that this panel does not contain the table that it filters. With this, this class's clients are free - * to position the table wherever they want, not necessarily related to the text box provided by this panel in any - * specific way. + * Note that this panel does not contain the table that it filters. With this, this class's clients are free to + * position the table wherever they want, not necessarily related to the text box provided by this panel in any specific + * way. * * @param * @author Nicolas Klose, Axel Uhl @@ -42,20 +49,31 @@ public abstract class AbstractFilterablePanel extends HorizontalPanel { protected final ListDataProvider filtered; protected final TextBox textBox; - private final AbstractListFilter filterer = new AbstractListFilter() { + private final Set> filters = new HashSet<>(); + + protected final AbstractKeywordFilter filterer = new AbstractKeywordFilter() { @Override public Iterable getStrings(T t) { return getSearchableStrings(t); } }; - + /** * @param all * the sequence of all objects that may be displayed in the table and from which the filter may choose. * This panel keeps a copy, so modifications to the all object do not reflect in the table * contents. Use {@link #updateAll(Iterable)} instead to update the sequence of available objects. + * @param drawTextBox + * if {@code true}, the default text box will be shown that can be used to provide the filter text; if + * {@code false}, the box may optionally be added later using the {@link #addDefaultTextBox()} method. + * This way, subclasses may choose to add other filter elements before the default text filter box. + * Filtering will still use the text box contents, even if the box is ultimately not shown; but under + * normal circumstances the text box will be empty in this case, not making filtering any stricter. */ - public AbstractFilterablePanel(Iterable all, AbstractCellTable display, final ListDataProvider filtered) { + public AbstractFilterablePanel(Iterable all, AbstractCellTable display, final ListDataProvider filtered, + boolean drawTextBox) { + filters.add(filterer); + setSpacing(5); this.all = new ListDataProvider<>(); this.display = display; @@ -64,15 +82,15 @@ public abstract class AbstractFilterablePanel extends HorizontalPanel { this.textBox.ensureDebugId("FilterTextBox"); this.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE); setAll(all); - add(getTextBox()); - getTextBox().addKeyUpHandler(new KeyUpHandler() { - @Override - public void onKeyUp(KeyUpEvent event) { - filter(); - } - }); + if (drawTextBox) { + addDefaultTextBox(); + } } - + + public AbstractFilterablePanel(Iterable all, AbstractCellTable display, final ListDataProvider filtered) { + this(all, display, filtered, /* show default filter text box */ true); + } + private void setAll(Iterable all) { this.all.getList().clear(); if (all != null) { @@ -81,6 +99,10 @@ public abstract class AbstractFilterablePanel extends HorizontalPanel { } } } + + public void addFilter(Filter filterToAdd) { + filters.add(filterToAdd); + } /** * Subclasses must implement this to extract the strings from an object of type T based on which the @@ -93,14 +115,13 @@ public abstract class AbstractFilterablePanel extends HorizontalPanel { public abstract Iterable getSearchableStrings(T t); /** - * Updates the set of all objects to be shown in the table and applies the search filter to update the - * table view. + * Updates the set of all objects to be shown in the table and applies the search filter to update the table view. */ - public void updateAll(Iterable all){ + public void updateAll(Iterable all) { setAll(all); filter(); } - + /** * Adds an object and applies the search filter. */ @@ -116,14 +137,15 @@ public abstract class AbstractFilterablePanel extends HorizontalPanel { all.getList().add(index, object); filter(); } - + /** - * Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element. + * Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not + * contain the element. */ public int indexOf(T object) { return all.getList().indexOf(object); } - + /** * Removes an object and applies the search filter. */ @@ -131,12 +153,12 @@ public abstract class AbstractFilterablePanel extends HorizontalPanel { all.getList().remove(object); filter(); } - + public void addAll(Iterable objects) { Util.addAll(objects, all.getList()); filter(); } - + /** * Would better be called {@code clear()}, but {@link #clear} is already the method inherited from {@link Panel}... * This method removes all entries from this filterable panel. The effect is the same as invoking @@ -146,45 +168,74 @@ public abstract class AbstractFilterablePanel extends HorizontalPanel { all.getList().clear(); filter(); } - + public void removeAll(Iterable objects) { Util.removeAll(objects, all.getList()); filter(); } - + public void filter() { filtered.getList().clear(); - Util.addAll(getFilterer().applyFilter(Arrays.asList(getTextBox().getText().split(" ")), all.getList()), - filtered.getList()); + retainElementsInFilteredThatPassFilter(); filtered.refresh(); sort(); } - - private void sort() { + + protected void retainElementsInFilteredThatPassFilter() { + List filteredElements = new ArrayList<>(); + for (T t : all.getList()) { + if (matches(t)) { + filteredElements.add(t); + } + } + Util.addAll(filteredElements, filtered.getList()); + } + + private boolean matches(T t) { + for (Filter filter : filters) { + if (!filter.matches(t)) { + return false; + } + } + return true; + } + + protected void sort() { if (display != null) { ColumnSortEvent.fire(display, display.getColumnSortList()); } } + public void addDefaultTextBox() { + add(getTextBox()); + getTextBox().addKeyUpHandler(new KeyUpHandler() { + @Override + public void onKeyUp(KeyUpEvent event) { + filterer.setKeywords(Arrays.asList(getTextBox().getText().split(" "))); + filter(); + } + }); + } + public TextBox getTextBox() { return textBox; } - + public Iterable getAll() { return all.getList(); } - + /** * Registers a {@link RefreshableSelectionModel} on the all data structure. So the selection can be maintained when - * the {@link CellTable} is filtered. You can use the {@link RefreshableSelectionModel} returned by this method - * e.g. for a {@link CellTable} using {@link CellTable#setSelectionModel(com.google.gwt.view.client.SelectionModel)} + * the {@link CellTable} is filtered. You can use the {@link RefreshableSelectionModel} returned by this method e.g. + * for a {@link CellTable} using {@link CellTable#setSelectionModel(com.google.gwt.view.client.SelectionModel)} * * @param comp * {@link EntityIdentityComparator Comperator} to create the {@link RefreshableSelectionModel selection * model} * @param multiselection * set true when the {@link RefreshableSelectionModel selection model} should be a - * {@link RefreshableMultiSelectionModel}. When it´s set false the + * {@link RefreshableMultiSelectionModel}. When it's set false the * {@link RefreshableSelectionModel selection model} will be a {@link RefreshableSingleSelectionModel}. */ public RefreshableSelectionModel registerSelectionModelOnAllElements(EntityIdentityComparator comp, @@ -206,19 +257,16 @@ public abstract class AbstractFilterablePanel extends HorizontalPanel { public ListDataProvider getAllListDataProvider() { return all; } - + /** * This method can be used to set the CellTable, when the {@link CellTable} on which this Panel should work is * created after this Panel. When the table isn't set correctly the order of elements, could be wrong after * filtering the data. * - * @param table {@link AbstractCellTable} on which this panel works. + * @param table + * {@link AbstractCellTable} on which this panel works. */ public void setTable(AbstractCellTable table) { display = table; } - - protected AbstractListFilter getFilterer() { - return filterer; - } } \ No newline at end of file diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/CustomizableFilterablePanel.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/CustomizableFilterablePanel.java new file mode 100644 index 00000000000..0b1b2c310bc --- /dev/null +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/panels/CustomizableFilterablePanel.java @@ -0,0 +1,30 @@ +package com.sap.sse.gwt.client.panels; + +import com.google.gwt.user.cellview.client.AbstractCellTable; +import com.google.gwt.user.client.ui.HasVerticalAlignment; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.view.client.ListDataProvider; +import com.sap.sse.common.filter.Filter; + +/** + * Customizable filter panel which allows to create a flexible filter panel with various number of filter inputs (e.g. + * combo box and text box). Need to set filter value on each input field change handler. + * + * @param + * @author Oleg Zheleznov + * + */ +public abstract class CustomizableFilterablePanel extends AbstractFilterablePanel { + + public CustomizableFilterablePanel(Iterable all, AbstractCellTable display, ListDataProvider filtered) { + super(all, display, filtered, /* show default filter text box */ false); + } + + public void add(Label label, Widget widget, Filter filter) { + add(label); + add(widget); + addFilter(filter); + setCellVerticalAlignment(label, HasVerticalAlignment.ALIGN_MIDDLE); + } +} diff --git a/java/com.sap.sse/src/com/sap/sse/util/impl/HasTracingGetImpl.java b/java/com.sap.sse/src/com/sap/sse/util/impl/HasTracingGetImpl.java index f0d0b1aedd7..f87ba32bce9 100644 --- a/java/com.sap.sse/src/com/sap/sse/util/impl/HasTracingGetImpl.java +++ b/java/com.sap.sse/src/com/sap/sse/util/impl/HasTracingGetImpl.java @@ -27,7 +27,7 @@ public abstract class HasTracingGetImpl implements HasTracingGet { try { return future.get(MILLIS_AFTER_WHICH_TO_TRACE_NON_RETURNING_GET, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { - logger.log(Level.INFO, "Timeout waiting for future task "+future+ + logger.log(Level.INFO, ""+Thread.currentThread()+": Timeout waiting for future task "+future+ " (retrying); "+ getAdditionalTraceInfo(), e); } } diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceCompetitorStatusType.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceCompetitorStatusType.html new file mode 100755 index 00000000000..e906c147cae --- /dev/null +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceCompetitorStatusType.html @@ -0,0 +1,439 @@ + + + + + +RaceCompetitorStatusType + + + + + + +

      JavaScript is disabled on your browser.
      + + + + + + +
      +
      com.tractrac.model.lib.api.event
      +

      Enum RaceCompetitorStatusType

      +
      +
      +
        +
      • java.lang.Object
      • +
      • + +
      • +
      +
      +
        +
      • +
        +
        All Implemented Interfaces:
        +
        java.io.Serializable, java.lang.Comparable<RaceCompetitorStatusType>
        +
        +
        +
        +
        public enum RaceCompetitorStatusType
        +extends java.lang.Enum<RaceCompetitorStatusType>
        +
        Enumeration representing all the possible values for the competitor status.
        +
      • +
      +
      +
      + +
      +
      +
        +
      • + + + +
          +
        • + + +

          Method Detail

          + + + +
            +
          • +

            values

            +
            public static RaceCompetitorStatusType[] values()
            +
            Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
            +for (RaceCompetitorStatusType c : RaceCompetitorStatusType.values())
            +    System.out.println(c);
            +
            +
            +
            Returns:
            +
            an array containing the constants of this enum type, in the order they are declared
            +
            +
          • +
          + + + +
            +
          • +

            valueOf

            +
            public static RaceCompetitorStatusType valueOf(java.lang.String name)
            +
            Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
            +
            +
            Parameters:
            +
            name - the name of the enum constant to be returned.
            +
            Returns:
            +
            the enum constant with the specified name
            +
            Throws:
            +
            java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
            +
            java.lang.NullPointerException - if the argument is null
            +
            +
          • +
          + + + +
            +
          • +

            getValue

            +
            public int getValue()
            +
          • +
          + + + + + + + + +
        • +
        +
      • +
      +
      +
      + + + + + + + diff --git a/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceVisibilityType.html b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceVisibilityType.html new file mode 100755 index 00000000000..c6a263652b9 --- /dev/null +++ b/java/com.tractrac.clientmodule/javadoc/com/tractrac/model/lib/api/event/RaceVisibilityType.html @@ -0,0 +1,523 @@ + + + + + +RaceVisibilityType + + + + + + + + + + + + +
      +
      com.tractrac.model.lib.api.event
      +

      Enum RaceVisibilityType

      +
      +
      +
        +
      • java.lang.Object
      • +
      • +
          +
        • java.lang.Enum<RaceVisibilityType>
        • +
        • +
            +
          • com.tractrac.model.lib.api.event.RaceVisibilityType
          • +
          +
        • +
        +
      • +
      +
      +
        +
      • +
        +
        All Implemented Interfaces:
        +
        java.io.Serializable, java.lang.Comparable<RaceVisibilityType>
        +
        +
        +
        +
        public enum RaceVisibilityType
        +extends java.lang.Enum<RaceVisibilityType>
        +
        Values for race visitility.
        +
      • +
      +
      +
      + +
      +
      +
        +
      • + + + + + +
          +
        • + + +

          Method Detail

          + + + +
            +
          • +

            values

            +
            public static RaceVisibilityType[] values()
            +
            Returns an array containing the constants of this enum type, in +the order they are declared. This method may be used to iterate +over the constants as follows: +
            +for (RaceVisibilityType c : RaceVisibilityType.values())
            +    System.out.println(c);
            +
            +
            +
            Returns:
            +
            an array containing the constants of this enum type, in the order they are declared
            +
            +
          • +
          + + + +
            +
          • +

            valueOf

            +
            public static RaceVisibilityType valueOf(java.lang.String name)
            +
            Returns the enum constant of this type with the specified name. +The string must match exactly an identifier used to declare an +enum constant in this type. (Extraneous whitespace characters are +not permitted.)
            +
            +
            Parameters:
            +
            name - the name of the enum constant to be returned.
            +
            Returns:
            +
            the enum constant with the specified name
            +
            Throws:
            +
            java.lang.IllegalArgumentException - if this enum type has no constant with the specified name
            +
            java.lang.NullPointerException - if the argument is null
            +
            +
          • +
          + + + +
            +
          • +

            getValue

            +
            public int getValue()
            +
          • +
          + + + + + + + +
            +
          • +

            fromString

            +
            public static RaceVisibilityType fromString(java.lang.String status)
            +
          • +
          +
        • +
        +
      • +
      +
      +
      + + + + + + + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-17.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-17.html new file mode 100755 index 00000000000..d312d292644 --- /dev/null +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-17.html @@ -0,0 +1,238 @@ + + + + + +U-Index + + + + + + + + +
      + + + + + + + +
      + + +
      A B C D E F G H I L M N O R S T U V  + + +

      U

      +
      +
      unsubscribeCompetitors(ICompetitorsListener) - Method in interface com.tractrac.subscription.lib.api.IEventSubscriber
      +
      +
      + Unsubscribes the competitors subscribers +
      +
      +
      unsubscribeConnectionStatus(IConnectionStatusListener) - Method in interface com.tractrac.subscription.lib.api.ISubscriber
      +
      +
      Unsubscribes for connection status
      +
      +
      unsubscribeControlPassings(IControlPassingsListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
      +
      +
      Unsubscribes to control passing data for the specified race.
      +
      +
      unsubscribeControlPositions(IControlPointPositionListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
      +
      +
      Unsubscribes to control positions.
      +
      +
      unsubscribeControls(IControlsListener) - Method in interface com.tractrac.subscription.lib.api.IEventSubscriber
      +
      +
      + Unsubscribes the controls subscribers +
      +
      +
      unsubscribeEventMessages(IEventMessageListener) - Method in interface com.tractrac.subscription.lib.api.IEventSubscriber
      +
      +
      Unsubscribes to event messages.
      +
      +
      unsubscribeEventTimesChanges(IStartStopTimesChangeListener) - Method in interface com.tractrac.subscription.lib.api.IEventSubscriber
      +
      +
      Unsubscribes to changes in event start and stop times.
      +
      +
      unsubscribePositions(IPositionListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
      +
      +
      Unsubscribes to competitor positions for a given Race.
      +
      +
      unsubscribePositionsOffset(IPositionOffsetListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
      +
      +
      Unsubscribes to competitor positions for a given Race.
      +
      +
      unsubscribePositionsSnapped(IPositionSnappedListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
      +
      +
      Unsubscribes to competitor positions for a given Race.
      +
      +
      unsubscribeRaceCompetitor(IRaceCompetitorListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
      +
      +
      Unsubscribes to race competitor
      +
      +
      unsubscribeRaceMessages(IRaceMessageListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
      +
      +
      Unsubscribes to race messages
      +
      +
      unsubscribeRaces(IRacesListener) - Method in interface com.tractrac.subscription.lib.api.IEventSubscriber
      +
      +
      + Unsubscribes the races subscribers +
      +
      +
      unsubscribeRaceTimesChanges(IRaceStartStopTimesChangeListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
      +
      +
      Unsubscribes to changes in race start and stop times.
      +
      +
      unsubscribeRouteChanges(IControlRouteChangeListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
      +
      +
      Unsubscribes to route changes for a given Race.
      +
      +
      unsubscribeRoutes(IRoutesListener) - Method in interface com.tractrac.subscription.lib.api.IRaceSubscriber
      +
      +
      + Unsubscribes to route changes for a given Race.
      +
      +
      unsubscribeServerTime(IServerTimeListener) - Method in interface com.tractrac.subscription.lib.api.IEventSubscriber
      +
      +
      Unsubscribes to messages for the server time.
      +
      +
      UPCOMING_STR - Static variable in enum com.tractrac.model.lib.api.event.RaceVisibilityType
      +
       
      +
      updateCompetitor(ICompetitor) - Method in interface com.tractrac.model.lib.api.event.IEvent
      +
      +
      Update a competitor
      +
      +
      updateCompetitor(ICompetitor) - Method in interface com.tractrac.subscription.lib.api.competitor.ICompetitorsListener
      +
      +
      This event is thrown when a competitor is updated.
      +
      +
      updateControl(IControl) - Method in interface com.tractrac.model.lib.api.event.IEvent
      +
      +
      Update a control
      +
      +
      updateControl(IControl) - Method in interface com.tractrac.subscription.lib.api.control.IControlsListener
      +
      +
      This event is thrown when a control is updated.
      +
      +
      updateRace(IRace) - Method in interface com.tractrac.model.lib.api.event.IEvent
      +
      +
      Update a race
      +
      +
      updateRace(IRace) - Method in interface com.tractrac.subscription.lib.api.race.IRacesListener
      +
      +
      This event is thrown when a race is updated.
      +
      +
      updateRaceCompetitor(IRaceCompetitor) - Method in interface com.tractrac.model.lib.api.event.IRace
      +
      +
      Updates a race competitor
      +
      +
      updateRaceCompetitor(IRaceCompetitor) - Method in interface com.tractrac.subscription.lib.api.race.IRaceCompetitorListener
      +
      +
      This event is thrown when a race competitor is updated
      +
      +
      updateRoute(IRoute) - Method in interface com.tractrac.subscription.lib.api.route.IRoutesListener
      +
      +
      This event is thrown when a route is updated.
      +
      +
      +A B C D E F G H I L M N O R S T U V 
      + +
      + + + + + + + +
      + + + + diff --git a/java/com.tractrac.clientmodule/javadoc/index-files/index-18.html b/java/com.tractrac.clientmodule/javadoc/index-files/index-18.html new file mode 100755 index 00000000000..ecf8f983fbd --- /dev/null +++ b/java/com.tractrac.clientmodule/javadoc/index-files/index-18.html @@ -0,0 +1,188 @@ + + + + + +V-Index + + + + + + + + +
      + + + + + + + +
      + + +
      A B C D E F G H I L M N O R S T U V  + + +

      V

      +
      +
      valueOf(String) - Static method in enum com.tractrac.model.lib.api.event.EventType
      +
      +
      Returns the enum constant of this type with the specified name.
      +
      +
      valueOf(String) - Static method in enum com.tractrac.model.lib.api.event.RaceCompetitorStatusType
      +
      +
      Returns the enum constant of this type with the specified name.
      +
      +
      valueOf(String) - Static method in enum com.tractrac.model.lib.api.event.RaceStatusType
      +
      +
      Returns the enum constant of this type with the specified name.
      +
      +
      valueOf(String) - Static method in enum com.tractrac.model.lib.api.event.RaceVisibilityType
      +
      +
      Returns the enum constant of this type with the specified name.
      +
      +
      valueOf(String) - Static method in enum com.tractrac.model.lib.api.event.StartTimeType
      +
      +
      Returns the enum constant of this type with the specified name.
      +
      +
      valueOf(String) - Static method in enum com.tractrac.subscription.lib.api.event.ILiveDataEvent.StatusType
      +
      +
      Returns the enum constant of this type with the specified name.
      +
      +
      valueOf(String) - Static method in enum com.tractrac.subscription.lib.api.event.IStoredDataEvent.Type
      +
      +
      Returns the enum constant of this type with the specified name.
      +
      +
      values() - Static method in enum com.tractrac.model.lib.api.event.EventType
      +
      +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
      values() - Static method in enum com.tractrac.model.lib.api.event.RaceCompetitorStatusType
      +
      +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
      values() - Static method in enum com.tractrac.model.lib.api.event.RaceStatusType
      +
      +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
      values() - Static method in enum com.tractrac.model.lib.api.event.RaceVisibilityType
      +
      +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
      values() - Static method in enum com.tractrac.model.lib.api.event.StartTimeType
      +
      +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
      values() - Static method in enum com.tractrac.subscription.lib.api.event.ILiveDataEvent.StatusType
      +
      +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
      values() - Static method in enum com.tractrac.subscription.lib.api.event.IStoredDataEvent.Type
      +
      +
      Returns an array containing the constants of this enum type, in +the order they are declared.
      +
      +
      +A B C D E F G H I L M N O R S T U V 
      + +
      + + + + + + + +
      + + + + diff --git a/java/com.tractrac.clientmodule/javadoc/script.js b/java/com.tractrac.clientmodule/javadoc/script.js new file mode 100755 index 00000000000..b3463569314 --- /dev/null +++ b/java/com.tractrac.clientmodule/javadoc/script.js @@ -0,0 +1,30 @@ +function show(type) +{ + count = 0; + for (var key in methods) { + var row = document.getElementById(key); + if ((methods[key] & type) != 0) { + row.style.display = ''; + row.className = (count++ % 2) ? rowColor : altColor; + } + else + row.style.display = 'none'; + } + updateTabs(type); +} + +function updateTabs(type) +{ + for (var value in tabs) { + var sNode = document.getElementById(tabs[value][0]); + var spanNode = sNode.firstChild; + if (value == type) { + sNode.className = activeTableTab; + spanNode.innerHTML = tabs[value][1]; + } + else { + sNode.className = tableTab; + spanNode.innerHTML = "" + tabs[value][1] + ""; + } + } +} diff --git a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/ui/fragments/AbstractHomeFragment.java b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/ui/fragments/AbstractHomeFragment.java index 99b4d32c22b..f250089e8dd 100644 --- a/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/ui/fragments/AbstractHomeFragment.java +++ b/mobile/com.sap.sailing.android.shared/src/com/sap/sailing/android/ui/fragments/AbstractHomeFragment.java @@ -2,6 +2,13 @@ package com.sap.sailing.android.ui.fragments; import java.util.List; +import com.sap.sailing.android.shared.R; +import com.sap.sailing.android.shared.data.BaseCheckinData; +import com.sap.sailing.android.shared.logging.ExLog; +import com.sap.sailing.android.shared.ui.activities.AbstractStartActivity; +import com.sap.sailing.android.shared.ui.adapters.AbstractRegattaAdapter; +import com.sap.sailing.android.shared.util.BaseAppPreferences; + import android.annotation.SuppressLint; import android.app.Activity; import android.content.DialogInterface; @@ -18,13 +25,6 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.Toast; -import com.sap.sailing.android.shared.R; -import com.sap.sailing.android.shared.data.BaseCheckinData; -import com.sap.sailing.android.shared.logging.ExLog; -import com.sap.sailing.android.shared.ui.activities.AbstractStartActivity; -import com.sap.sailing.android.shared.ui.adapters.AbstractRegattaAdapter; -import com.sap.sailing.android.shared.util.BaseAppPreferences; - public abstract class AbstractHomeFragment extends BaseFragment { private final static String TAG = AbstractHomeFragment.class.getName(); protected AbstractRegattaAdapter adapter; @@ -58,26 +58,30 @@ public abstract class AbstractHomeFragment extends BaseFragment { } private boolean requestQRCodeScan() { + boolean result; + PackageManager manager = getActivity().getPackageManager(); Intent intent = new Intent("com.google.zxing.client.android.SCAN"); intent.putExtra("SCAN_MODE", "QR_CODE_MODE"); - - PackageManager manager = getActivity().getPackageManager(); List infos = manager.queryIntentActivities(intent, 0); if (infos.size() != 0) { - startActivityForResult(intent, requestCodeQRCode); - return true; - } else { - Uri marketUri = Uri.parse("market://details?id=com.google.zxing.client.android"); - Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri); - infos = manager.queryIntentActivities(marketIntent, 0); - if (infos.size() != 0) { - startActivity(marketIntent); - } else { - Toast.makeText(getActivity(), getString(R.string.error_play_store_and_scanning_not_available), - Toast.LENGTH_LONG).show(); + try { + startActivityForResult(intent, requestCodeQRCode); + result = true; + } catch (Exception ex) { + requestQRCodeScannerInstallation(); + result = false; } - return false; + } else { + requestQRCodeScannerInstallation(); + result = false; } + return result; + } + + private void requestQRCodeScannerInstallation() { + Uri marketUri = Uri.parse("market://details?id=com.google.zxing.client.android"); + Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri); + startActivity(marketIntent); } @Override diff --git a/mobile/com.sap.sailing.android.tracking.app/.classpath b/mobile/com.sap.sailing.android.tracking.app/.classpath index e6e526abe03..0f70b73f331 100644 --- a/mobile/com.sap.sailing.android.tracking.app/.classpath +++ b/mobile/com.sap.sailing.android.tracking.app/.classpath @@ -1,12 +1,12 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/mobile/com.sap.sailing.android.tracking.app/res/values-de/preferences.xml b/mobile/com.sap.sailing.android.tracking.app/res/values-de/preferences.xml index 190c72327e7..55c9fbd8018 100644 --- a/mobile/com.sap.sailing.android.tracking.app/res/values-de/preferences.xml +++ b/mobile/com.sap.sailing.android.tracking.app/res/values-de/preferences.xml @@ -4,8 +4,7 @@ Sendeintervall für GPS-Updates auswählen Wählen Sie den größtmöglichen Wert, der dennoch eine dem Anwendungsfall entsprechend hohe Aktualisierungsrate ermöglicht. - Der Energiesparmodus wurde automatisch aktiviert, GPS Updates werden alle 20 Sekunden an den Server gesendet. Diese Einstellung wird überschrieben, wenn Sie oben ein anderes Intervall auswählen. - + GPS Updates werden%1$s %2$s an den Server gesendet. alle diff --git a/mobile/com.sap.sailing.android.tracking.app/res/values/preferences.xml b/mobile/com.sap.sailing.android.tracking.app/res/values/preferences.xml index 20f2003a35d..a388bca7356 100644 --- a/mobile/com.sap.sailing.android.tracking.app/res/values/preferences.xml +++ b/mobile/com.sap.sailing.android.tracking.app/res/values/preferences.xml @@ -6,8 +6,6 @@ tracking - preferenceEnergySavingEnabled - preferenceEnergySavingOverride preferenceHeadingWithDeclinationSubtracted Battery Saving @@ -16,8 +14,7 @@ 0 choose GPS update sending interval Choose a value as high as possible to preserve battery life while still maintaining a reasonable live experience, depending on your use case. - Energy saving mode has been enabled automatically, GPS updates are being sent to the server every 20 seconds. You can change that by selecting an interval above. - + GPS updates will be sent to the server%1$s %2$s. msgSndIntMessage every @@ -37,9 +34,7 @@ gpsFixInterval - 500 - gpsFixFastestInterval - 100 + 1000 batteryIsCharging trackingTimerStarted trackerIsTracking diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/receiver/GcmBroadcastReceiver.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/receiver/GcmBroadcastReceiver.java deleted file mode 100644 index 3a8b5345599..00000000000 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/receiver/GcmBroadcastReceiver.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.sap.sailing.android.tracking.app.receiver; - -import android.app.Activity; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.support.v4.content.WakefulBroadcastReceiver; - -import com.sap.sailing.android.tracking.app.services.GcmIntentService; - -public class GcmBroadcastReceiver extends WakefulBroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - ComponentName componentName = new ComponentName(context.getPackageName(), GcmIntentService.class.getName()); - startWakefulService(context, (intent.setComponent(componentName))); - setResultCode(Activity.RESULT_OK); - } -} diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/GcmIntentService.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/GcmIntentService.java deleted file mode 100644 index 934b1e85f24..00000000000 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/GcmIntentService.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.sap.sailing.android.tracking.app.services; - -import android.app.IntentService; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.SystemClock; -import android.support.v4.app.NotificationCompat; - -import com.google.android.gms.gcm.GoogleCloudMessaging; -import com.sap.sailing.android.shared.logging.ExLog; -import com.sap.sailing.android.tracking.app.R; -import com.sap.sailing.android.tracking.app.receiver.GcmBroadcastReceiver; -import com.sap.sailing.android.tracking.app.ui.activities.StartActivity; - -public class GcmIntentService extends IntentService { - - private static final String TAG = GcmIntentService.class.getName(); - - public static final int NOTIFICATION_ID = 1; - private NotificationManager mNotificationManager; - NotificationCompat.Builder builder; - - public GcmIntentService() { - this("GcmIntentService"); - } - - public GcmIntentService(String name) { - super(name); - } - - @Override - protected void onHandleIntent(Intent intent) { - Bundle extras = intent.getExtras(); - GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this); - String messageType = gcm.getMessageType(intent); - - if (!extras.isEmpty()) { - /* - * Filter messages based on message type. Since it is likely that GCM will be extended in the future with - * new message types, just ignore any message types you're not interested in, or that you don't recognize. - */ - if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) { - sendNotification("Send error: " + extras.toString()); - } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) { - sendNotification("Deleted messages on server: " + extras.toString()); - // If it's a regular GCM message, do some work. - } else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) { - // This loop represents the service doing some work. - for (int i = 0; i < 5; i++) { - ExLog.i(this, TAG, "Working... " + (i + 1) + "/5 @ " + SystemClock.elapsedRealtime()); - try { - Thread.sleep(5000); - } catch (InterruptedException e) { - } - } - ExLog.i(this, TAG, "Completed work @ " + SystemClock.elapsedRealtime()); - // Post notification of received message. - sendNotification("Received: " + extras.toString()); - ExLog.i(this, TAG, "Received: " + extras.toString()); - } - } - GcmBroadcastReceiver.completeWakefulIntent(intent); - } - - // Put the message into a notification and post it. - // This is just one simple example of what you might choose to do with - // a GCM message. - private void sendNotification(String msg) { - mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE); - - PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, StartActivity.class), 0); - - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) - .setSmallIcon(R.drawable.ic_menu_share_sapsailing).setContentTitle("GCM Notification") - .setStyle(new NotificationCompat.BigTextStyle().bigText(msg)).setContentText(msg); - - mBuilder.setContentIntent(contentIntent); - mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build()); - } -} diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java old mode 100644 new mode 100755 index bd475c737c3..5cd6902a837 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/services/TrackingService.java @@ -10,13 +10,8 @@ import java.util.UUID; import org.json.JSONArray; import org.json.JSONException; -import org.json.JSONObject; +import org.json.JSONObject; -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.location.LocationListener; -import com.google.android.gms.location.LocationRequest; -import com.google.android.gms.location.LocationServices; import com.sap.sailing.android.shared.logging.ExLog; import com.sap.sailing.android.shared.services.sending.MessageSendingService; import com.sap.sailing.android.tracking.app.BuildConfig; @@ -37,11 +32,11 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; -import android.content.IntentFilter; +import android.content.Context; import android.graphics.BitmapFactory; import android.hardware.GeomagneticField; import android.location.Location; -import android.os.BatteryManager; +import android.location.LocationManager; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -49,13 +44,9 @@ import android.os.IBinder; import android.support.v4.app.NotificationCompat; import android.widget.Toast; -public class TrackingService extends Service implements GoogleApiClient.ConnectionCallbacks, - GoogleApiClient.OnConnectionFailedListener, LocationListener { +public class TrackingService extends Service implements android.location.LocationListener { - private GoogleApiClient googleApiClient; - private LocationRequest locationRequest; private NotificationManager notificationManager; - private boolean locationUpdateRequested = false; private AppPreferences prefs; private GPSQualityListener gpsQualityListener; @@ -68,13 +59,15 @@ public class TrackingService extends Service implements GoogleApiClient.Connecti private int NOTIFICATION_ID = R.string.tracker_started; public final static int UPDATE_INTERVAL_IN_MILLIS_DEFAULT = 1000; - private final static int UPDATE_INTERVAL_IN_MILLIS_POWERSAVE_MODE = 20000; - private final static float BATTERY_POWER_SAVE_THRESHOLD = 0.2f; + public final static String GPS_DISABLED_MESSAGE = "gpsDisabled"; + private float minLocationUpdateDistanceInMeters = 0f; private boolean initialLocation; private String checkinDigest; private EventInfo event; + protected LocationManager locationManager; + /** * Must be synchronized upon while modifying the {@link #timerForDelayingSendingMessages} field * and while modifying the {@link #locationsQueuedBasedOnSendingInterval} list. @@ -107,19 +100,23 @@ public class TrackingService extends Service implements GoogleApiClient.Connecti @Override public void onCreate() { super.onCreate(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + /** + * prior to JellyBean, the minimum time for location updates parameter MIGHT be ignored, + * so providing a minimum distance value greater than 0 is recommended + */ + minLocationUpdateDistanceInMeters = .5f; + } + locationsQueuedBasedOnSendingInterval = new LinkedHashMap<>(); prefs = new AppPreferences(this); initialLocation = true; - // http://developer.android.com/training/location/receive-location-updates.html - locationRequest = LocationRequest.create(); - locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); - locationRequest.setInterval(prefs.getGPSFixInterval()); - locationRequest.setFastestInterval(prefs.getGPSFixFastestInterval()); - - googleApiClient = new GoogleApiClient.Builder(this).addApi(LocationServices.API).addConnectionCallbacks(this) - .addOnConnectionFailedListener(this).build(); notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + + locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + + } @Override @@ -155,23 +152,17 @@ public class TrackingService extends Service implements GoogleApiClient.Connecti } private void startTracking() { - googleApiClient.connect(); - locationUpdateRequested = true; + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, prefs.getGPSFixInterval(), minLocationUpdateDistanceInMeters, this); ExLog.i(this, TAG, "Started Tracking"); showNotification(); prefs.setTrackerIsTracking(true); prefs.setTrackerIsTrackingCheckinDigest(checkinDigest); - prefs.setEnergySavingOverride(false); } private void stopTracking() { - if (googleApiClient.isConnected()) { - LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, this); - } - googleApiClient.disconnect(); - locationUpdateRequested = false; + locationManager.removeUpdates(this); prefs.setTrackerIsTracking(false); prefs.setTrackerIsTrackingCheckinDigest(null); @@ -180,23 +171,6 @@ public class TrackingService extends Service implements GoogleApiClient.Connecti ExLog.i(this, TAG, "Stopped Tracking"); } - @Override - public void onConnectionFailed(ConnectionResult arg0) { - ExLog.e(this, TAG, "Failed to connect to Google Play Services for location updates"); - } - - @Override - public void onConnected(Bundle arg0) { - if (locationUpdateRequested) { - LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, this); - } - } - - @Override - public void onConnectionSuspended(int i) { - // no-op - } - private void reportGPSQualityBearingAndSpeed(float gpsAccuracy, float bearing, float speed, double latitude, double longitude, double altitude) { Bearing bearingImpl = null; @@ -231,18 +205,6 @@ public class TrackingService extends Service implements GoogleApiClient.Connecti } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) - @Override - public void onLocationChanged(Location location) { - if (initialLocation) { - storeInitialTrackingTimestamp(); - } - updateResendIntervalSetting(); - reportGPSQualityBearingAndSpeed(location.getAccuracy(), location.getBearing(), location.getSpeed(), - location.getLatitude(), location.getLongitude(), location.getAltitude()); - final String postUrlStr = event.server + prefs.getServerGpsFixesPostPath(); - enqueueForSending(postUrlStr, location); - } - private JSONObject createJsonLocationFix(Location location) throws JSONException { JSONObject fixJson = new JSONObject(); fixJson.put(FlatSmartphoneUuidAndGPSFixMovingJsonSerializer.BEARING_DEG, location.getBearing()); @@ -348,48 +310,8 @@ public class TrackingService extends Service implements GoogleApiClient.Connecti initialLocation = false; } - /** - * Update the interval in which message sending service should retry - possibly enable power saving - */ - private void updateResendIntervalSetting() { - float batteryPct = getBatteryPercentage(); - boolean batteryIsCharging = prefs.getBatteryIsCharging(); - int updateInterval = prefs.getMessageSendingIntervalInMillis(); - if (batteryPct < BATTERY_POWER_SAVE_THRESHOLD && !batteryIsCharging && !prefs.getEnergySavingOverride()) { - if (BuildConfig.DEBUG) { - ExLog.i(this, "POWER-LEVELS", "in power saving mode"); - } - updateInterval = UPDATE_INTERVAL_IN_MILLIS_POWERSAVE_MODE; - prefs.setEnergySavingEnabledAutomatically(true); - } else { - if (BuildConfig.DEBUG) { - ExLog.i(this, "POWER-LEVELS", "in default power mode"); - } - } - prefs.setMessageResendIntervalInMillis(updateInterval); - } - - /** - * Get battery charging level - * - * @return battery charging level in interval [0,1] - */ - private float getBatteryPercentage() { - IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); - Intent batteryStatus = this.registerReceiver(null, ifilter); - - int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); - int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); - - float batteryPct = level / (float) scale; - - if (BuildConfig.DEBUG) { - ExLog.i(this, TAG, "Battery: " + (batteryPct * 100) + "%"); - } - - return batteryPct; - } - + + @TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1) @Override public IBinder onBind(Intent intent) { return trackingBinder; @@ -402,7 +324,37 @@ public class TrackingService extends Service implements GoogleApiClient.Connecti Toast.makeText(this, R.string.tracker_stopped, Toast.LENGTH_SHORT).show(); } - // Useful code for Bug 3048. Will stay commented for now. + /** + * Methods implemented through LocationManager + */ + @Override + public void onLocationChanged(Location location) { + if (initialLocation) { + storeInitialTrackingTimestamp(); + } + reportGPSQualityBearingAndSpeed(location.getAccuracy(), location.getBearing(), location.getSpeed(), + location.getLatitude(), location.getLongitude(), location.getAltitude()); + final String postUrlStr = event.server + prefs.getServerGpsFixesPostPath(); + enqueueForSending(postUrlStr, location); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + //Status Update by the provider (GPS) + } + + @Override + public void onProviderDisabled(String provider) { + //provider (GPS) disabled by the user while tracking + Intent local = new Intent(); + local.setAction(GPS_DISABLED_MESSAGE); + this.sendBroadcast(local); + } + + @Override + public void onProviderEnabled(String provider) { + //provider (GPS) (re)enabled by the user while tracking + } private void showNotification() { Intent intent = new Intent(this, TrackingActivity.class); diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java index 855eaaa6538..478fd24ffb2 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/activities/TrackingActivity.java @@ -6,7 +6,6 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.graphics.drawable.ColorDrawable; -import android.location.LocationManager; import android.os.Bundle; import android.os.IBinder; import android.support.v4.app.Fragment; @@ -239,11 +238,7 @@ public class TrackingActivity extends BaseActivity implements GPSQualityListener mPager.setAdapter(mPagerAdapter); mPager.setCurrentItem(lastViewPagerItem); - LocationManager service = (LocationManager) getSystemService(LOCATION_SERVICE); - boolean gpsEnabled = service.isProviderEnabled(LocationManager.GPS_PROVIDER); - if (!gpsEnabled) { - showErrorPopup(R.string.warning, R.string.gps_turned_off); - } + } @Override diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/RegattaFragment.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/RegattaFragment.java index 9ec69eae730..2c3c98f36bd 100755 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/RegattaFragment.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/RegattaFragment.java @@ -75,7 +75,7 @@ public class RegattaFragment extends BaseFragment implements OnClickListener { TextView addPhotoText = (TextView) view.findViewById(R.id.add_photo_text); addPhotoText.setOnClickListener(this); - + return view; } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/TrackingFragment.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/TrackingFragment.java index cb01fbdb4d8..e49bb5840a2 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/TrackingFragment.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/TrackingFragment.java @@ -2,7 +2,10 @@ package com.sap.sailing.android.tracking.app.ui.fragments; import android.annotation.TargetApi; import android.app.Activity; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.os.Build; import android.os.Bundle; import android.provider.Settings; @@ -16,8 +19,10 @@ import android.widget.Toast; import com.sap.sailing.android.shared.logging.ExLog; import com.sap.sailing.android.shared.services.sending.MessageSendingService.APIConnectivity; import com.sap.sailing.android.shared.ui.customviews.SignalQualityIndicatorView; +import com.sap.sailing.android.shared.util.LocationHelper; import com.sap.sailing.android.tracking.app.BuildConfig; import com.sap.sailing.android.tracking.app.R; +import com.sap.sailing.android.tracking.app.services.TrackingService; import com.sap.sailing.android.tracking.app.services.TrackingService.GPSQuality; import com.sap.sailing.android.tracking.app.ui.activities.TrackingActivity; import com.sap.sailing.android.tracking.app.utils.AppPreferences; @@ -30,8 +35,8 @@ public class TrackingFragment extends BaseFragment { static final String SIS_GPS_ACCURACY = "instanceStateGpsAccuracy"; static final String SIS_GPS_UNSENT_FIXES = "instanceStateGpsUnsentFixes"; - private AppPreferences prefs; private long lastGPSQualityUpdate; + private BroadcastReceiver gpsDisabledReceiver; private Toast gpsToast; private boolean gpsFound = true; @@ -40,11 +45,8 @@ public class TrackingFragment extends BaseFragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); - ViewGroup view = (ViewGroup) inflater.inflate(R.layout.fragment_tracking, container, false); - prefs = new AppPreferences(getActivity()); - return view; } @@ -53,7 +55,28 @@ public class TrackingFragment extends BaseFragment { super.onResume(); // so it initally updates to "battery-saving" etc. setAPIConnectivityStatus(APIConnectivity.noAttempt); + + //setup receiver to get message from tracking service if GPS is disabled while tracking + IntentFilter filter = new IntentFilter(); + filter.addAction(TrackingService.GPS_DISABLED_MESSAGE); + + gpsDisabledReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + LocationHelper.showNoGPSError(getActivity(), getString(R.string.enable_gps)); + } + }; + getActivity().registerReceiver(gpsDisabledReceiver,filter); + if (!isLocationEnabled(getActivity())) { + LocationHelper.showNoGPSError(getActivity(), getString(R.string.enable_gps)); + } } + + @Override + public void onStop() { + super.onStop(); + getActivity().unregisterReceiver(gpsDisabledReceiver); + }; @Override public void onActivityCreated(Bundle savedInstanceState) { @@ -116,7 +139,6 @@ public class TrackingFragment extends BaseFragment { public void updateTrackingStatus(GPSQuality quality) { if (isAdded()) { TextView textView = (TextView) getActivity().findViewById(R.id.tracking_status); - if (quality == GPSQuality.noSignal) { textView.setText(getString(R.string.tracking_status_no_gps_signal)); textView.setTextColor(getResources().getColor(R.color.sap_red)); @@ -129,7 +151,9 @@ public class TrackingFragment extends BaseFragment { } else { textView.setText(getString(R.string.tracking_status_tracking)); textView.setTextColor(getResources().getColor(R.color.fiori_text_color)); - gpsToast.cancel(); + if (gpsToast == null) { + gpsToast.cancel(); + } //GPS was lost before but is available again now --> show gpsFound toast if (!gpsFound) { gpsToast = Toast.makeText(getActivity(),getString(R.string.tracking_status_gps_found), Toast.LENGTH_SHORT); @@ -152,14 +176,8 @@ public class TrackingFragment extends BaseFragment { public void run() { TextView textView = (TextView) getActivity().findViewById(R.id.mode); if (apiConnectivity == APIConnectivity.transmissionSuccess) { - if (prefs.getEnergySavingEnabledAutomatically() && !prefs.getEnergySavingOverride()) { - textView.setText(getString(R.string.tracking_mode_battery_saving)); - textView.setTextColor(getResources().getColor(R.color.sap_red)); - } else { - textView.setText(getString(R.string.tracking_mode_live)); - textView.setTextColor(getResources().getColor(R.color.fiori_text_color)); - } - + textView.setText(getString(R.string.tracking_mode_live)); + textView.setTextColor(getResources().getColor(R.color.fiori_text_color)); } else if (apiConnectivity == APIConnectivity.noAttempt) { textView.setText(getString(R.string.tracking_mode_offline)); textView.setTextColor(getResources().getColor(R.color.fiori_text_color)); diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/preference/GeneralPreferenceFragment.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/preference/GeneralPreferenceFragment.java index ede9973e121..6a0143a4bbd 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/preference/GeneralPreferenceFragment.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/ui/fragments/preference/GeneralPreferenceFragment.java @@ -68,8 +68,6 @@ public class GeneralPreferenceFragment extends BasePreferenceFragment implements prefs.setMessageResendIntervalInMillis(msgSndInt * 1000); break; } - if(prefs.getEnergySavingEnabledAutomatically()) - prefs.setEnergySavingOverride(true); updateEnergySavingMessage(newValue.toString()); } return true; @@ -93,15 +91,10 @@ public class GeneralPreferenceFragment extends BasePreferenceFragment implements String fillEvery = (entry.equals(msgSndIntPreference.getEntries()[0])) ? "" : " " + getString(R.string.preference_energy_saving_sending_interval_message_every); //set Text - AppPreferences prefs = new AppPreferences(getActivity()); - if(prefs.getEnergySavingEnabledAutomatically() && !prefs.getEnergySavingOverride()){ - msgSndIntMessage.setSummary(getString(R.string.preference_energy_saving_enabled_automatically)); - } else { - msgSndIntMessage.setSummary(String.format( + msgSndIntMessage.setSummary(String.format( getString(R.string.preference_energy_saving_sending_interval_message), fillEvery, entry) ); - } } } diff --git a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/AppPreferences.java b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/AppPreferences.java index 51f76777b36..73ed94a3232 100644 --- a/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/AppPreferences.java +++ b/mobile/com.sap.sailing.android.tracking.app/src/com/sap/sailing/android/tracking/app/utils/AppPreferences.java @@ -80,13 +80,6 @@ public class AppPreferences extends BaseAppPreferences { return value == null ? -1 : Integer.valueOf(value); } - public int getGPSFixFastestInterval() { - // EditTextPreference saves value as string, even if android:inputType="number" is set - String value = PrefUtils.getString(context, R.string.preference_gps_fix_fastest_interval_ms_key, - R.string.preference_gps_fastest_fix_interval_ms_default); - return value == null ? -1 : Integer.valueOf(value); - } - public String getEventId() { return PrefUtils.getString(context, R.string.preference_eventid_key, R.string.preference_eventid_default); } @@ -112,24 +105,6 @@ public class AppPreferences extends BaseAppPreferences { preferences.edit().putString(context.getString(R.string.preference_competitor_key), id).commit(); } - public void setEnergySavingEnabledAutomatically(boolean newValue) { - preferences.edit().putBoolean(context.getString(R.string.preference_energy_saving_enabled_key), newValue) - .commit(); - } - - public boolean getEnergySavingEnabledAutomatically() { - return preferences.getBoolean(context.getString(R.string.preference_energy_saving_enabled_key), false); - } - - public void setEnergySavingOverride(boolean newValue) { - preferences.edit().putBoolean(context.getString(R.string.preference_energy_saving_override_key), newValue) - .commit(); - } - - public boolean getEnergySavingOverride() { - return preferences.getBoolean(context.getString(R.string.preference_energy_saving_override_key), false); - } - public void setDisplayHeadingWithSubtractedDeclination(boolean newValue) { preferences.edit() .putBoolean(context.getString(R.string.preference_heading_with_declination_subtracted_key), newValue) diff --git a/mobile/com.sap.sailing.buoy.positioning/AndroidManifest.xml b/mobile/com.sap.sailing.buoy.positioning/AndroidManifest.xml index 337768ec7a6..3527afd8d8a 100644 --- a/mobile/com.sap.sailing.buoy.positioning/AndroidManifest.xml +++ b/mobile/com.sap.sailing.buoy.positioning/AndroidManifest.xml @@ -10,7 +10,6 @@ - diff --git a/mobile/com.sap.sailing.buoy.positioning/project.properties b/mobile/com.sap.sailing.buoy.positioning/project.properties index 26a3887ea96..874e1879208 100644 --- a/mobile/com.sap.sailing.buoy.positioning/project.properties +++ b/mobile/com.sap.sailing.buoy.positioning/project.properties @@ -14,4 +14,4 @@ target=android-22 android.library.reference.1=../google-play-services_lib android.library.reference.2=../com.sap.sailing.android.shared -android.library.reference.3=../licensesdialog_lib/de.psdev.licensesdialog +android.library.reference.3=../licensesdialog_lib/de.psdev.licensesdialog \ No newline at end of file diff --git a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/adapter/MarkAdapter.java b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/adapter/MarkAdapter.java index 5729fa1fadf..d0bef9210d4 100644 --- a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/adapter/MarkAdapter.java +++ b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/adapter/MarkAdapter.java @@ -13,6 +13,7 @@ import com.sap.sailing.android.buoy.positioning.app.R; import com.sap.sailing.android.buoy.positioning.app.provider.AnalyticsContract; import com.sap.sailing.android.buoy.positioning.app.util.DatabaseHelper; import com.sap.sailing.android.buoy.positioning.app.valueobjects.MarkPingInfo; +import com.sap.sailing.server.gateway.serialization.impl.FlatGPSFixJsonSerializer; public class MarkAdapter extends ResourceCursorAdapter { @@ -33,12 +34,11 @@ public class MarkAdapter extends ResourceCursorAdapter { } else { final DecimalFormat df = new DecimalFormat("#.##"); final double accuracy = markPings.get(0).getAccuracy(); - String accuracyString= ""; - //accuracy-values that are stored as -1 will be displayed as "n/a" - if(accuracy == MarkPingInfo.NOT_SET_BY_USER){ - accuracyString = context.getString(R.string.unknown); - } - else { + final String accuracyString; + // accuracy-values that are stored as -1 (meaning unknown) will simply be displayed as "set" without an accuracy + if (accuracy == FlatGPSFixJsonSerializer.NOT_AVAILABLE_THROUGH_SERVER) { + accuracyString = context.getString(R.string.set); + } else { accuracyString = context.getString(R.string.set) + " " + String.format(context.getString(R.string.mark_list_accuracy), df.format(accuracy)); } diff --git a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/BuoyFragment.java b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/BuoyFragment.java index 766a4a3423e..19aa8d7dcae 100644 --- a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/BuoyFragment.java +++ b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/ui/fragments/BuoyFragment.java @@ -1,14 +1,14 @@ package com.sap.sailing.android.buoy.positioning.app.ui.fragments; -import android.app.Activity; import android.app.FragmentManager; import android.app.FragmentTransaction; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.IntentSender; import android.location.Location; +import android.location.LocationManager; +import android.os.Build; import android.os.Bundle; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; @@ -20,17 +20,6 @@ import android.widget.Button; import android.widget.TextView; import android.widget.Toast; -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.common.api.PendingResult; -import com.google.android.gms.common.api.ResultCallback; -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.LocationListener; -import com.google.android.gms.location.LocationRequest; -import com.google.android.gms.location.LocationServices; -import com.google.android.gms.location.LocationSettingsRequest; -import com.google.android.gms.location.LocationSettingsResult; -import com.google.android.gms.location.LocationSettingsStatusCodes; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.MapFragment; @@ -50,12 +39,12 @@ import com.sap.sailing.android.shared.util.ViewHelper; import com.sap.sailing.android.ui.fragments.BaseFragment; public class BuoyFragment extends BaseFragment - implements GoogleApiClient.ConnectionCallbacks, LocationListener, GoogleApiClient.OnConnectionFailedListener { - + implements android.location.LocationListener { + private static final String TAG = BuoyFragment.class.getName(); private static final int GPS_MIN_TIME = 1000; - private static final int REQUEST_CHECK_SETTINGS = 1 << 16; + private float minLocationUpdateDistanceInMeters = 0f; private TextView accuracyTextView; private TextView distanceTextView; @@ -70,9 +59,8 @@ public class BuoyFragment extends BaseFragment private PositioningActivity positioningActivity; private LocalBroadcastManager mBroadcastManager; - private GoogleApiClient mGoogleApiClient; - private LocationRequest mLocationRequest; - private LocationSettingsRequest mSettingsRequest; + protected LocationManager locationManager; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -92,16 +80,16 @@ public class BuoyFragment extends BaseFragment mBroadcastManager = LocalBroadcastManager.getInstance(inflater.getContext()); initMapFragment(); - mLocationRequest = LocationRequest.create(); - mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); - mLocationRequest.setInterval(GPS_MIN_TIME); - mLocationRequest.setFastestInterval(GPS_MIN_TIME); - - mSettingsRequest = new LocationSettingsRequest.Builder().addLocationRequest(mLocationRequest).build(); - - mGoogleApiClient = new GoogleApiClient.Builder(getActivity()).addApi(LocationServices.API).addConnectionCallbacks(this) - .addOnConnectionFailedListener(this).build(); - + locationManager = (LocationManager) getActivity().getSystemService(Context.LOCATION_SERVICE); + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + /** + * prior to JellyBean, the minimum time for location updates parameter MIGHT be ignored, + * so providing a minimum distance value greater than 0 is recommended + */ + minLocationUpdateDistanceInMeters = .5f; + } + return layout; } @@ -134,7 +122,7 @@ public class BuoyFragment extends BaseFragment transaction.replace(R.id.map, mapFragment); transaction.commit(); } - + private void setUpSetPositionButton(View layout, ClickListener clickListener) { setPositionButton = ViewHelper.get(layout, R.id.marker_set_position_button); disablePositionButton(); @@ -147,62 +135,6 @@ public class BuoyFragment extends BaseFragment setPositionButton.setText(resId); } - private void checkGPS() { - PendingResult result = LocationServices.SettingsApi.checkLocationSettings(mGoogleApiClient, mSettingsRequest); - result.setResultCallback(new ResultCallback() { - @Override - public void onResult(LocationSettingsResult settingsResult) { - final Status status = settingsResult.getStatus(); - switch (status.getStatusCode()) { - case LocationSettingsStatusCodes.SUCCESS: - // All location settings are satisfied. The client can initialize location - // requests here. - startLocationUpdates(); - break; - - case LocationSettingsStatusCodes.RESOLUTION_REQUIRED: - // Location settings are not satisfied. But could be fixed by showing the user - // a dialog. - try { - // Show the dialog by calling startResolutionForResult(), - // and check the result in onActivityResult(). - status.startResolutionForResult(getActivity(), REQUEST_CHECK_SETTINGS); - } catch (IntentSender.SendIntentException e) { - // Ignore the error. - } - break; - - case LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE: - // Location settings are not satisfied. However, we have no way to fix the - // settings so we won't show the dialog. - break; - } - } - }); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - int code = (requestCode + 1) << 16; - switch (code) { - case REQUEST_CHECK_SETTINGS: - switch (resultCode) { - case Activity.RESULT_OK: - // All required changes were successfully made - mGoogleApiClient.connect(); - break; - - case Activity.RESULT_CANCELED: - // The user was asked to change settings, but chose not to - break; - - default: - break; - } - break; - } - } - private void initMarkerReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction(getString(R.string.database_changed)); @@ -213,18 +145,15 @@ public class BuoyFragment extends BaseFragment @Override public void onStart() { super.onStart(); - - mGoogleApiClient.connect(); + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, GPS_MIN_TIME, minLocationUpdateDistanceInMeters , this); + lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); } @Override public void onPause() { super.onPause(); - if (mGoogleApiClient.isConnected()) { - LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this); - } - mGoogleApiClient.disconnect(); - + locationManager.removeUpdates(this); + mBroadcastManager.unregisterReceiver(mReceiver); } @@ -252,6 +181,10 @@ public class BuoyFragment extends BaseFragment distanceTextView.setText(distanceText); } + + /** + * Methods implemented through LocationManager + */ @Override public void onLocationChanged(Location location) { if (getActivity() instanceof PositioningActivity) { @@ -273,6 +206,28 @@ public class BuoyFragment extends BaseFragment initialLocationUpdate = false; } } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + //no-op + } + + @Override + public void onProviderDisabled(String provider) { + //provider (GPS) disabled by the user while tracking + disablePositionButton(); + setUpTextUI(null); + signalQualityIndicatorView.setSignalQuality(GPSQuality.noSignal.toInt()); + + LocationHelper.showNoGPSError(getActivity(), getString(R.string.enable_gps)); + } + + @Override + public void onProviderEnabled(String provider) { + //provider (GPS) (re)enabled by the user while tracking + //call disablePositionButton() so that its text says "searching for GPS" again + disablePositionButton(); + } private void configureMap(GoogleMap map) { if (map != null) { @@ -313,26 +268,6 @@ public class BuoyFragment extends BaseFragment pingListener = listener; } - @Override - public void onConnected(Bundle bundle) { - checkGPS(); - } - - private void startLocationUpdates() { - LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this); - lastKnownLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient); - } - - @Override - public void onConnectionSuspended(int i) { - // no-op - } - - @Override - public void onConnectionFailed(ConnectionResult connectionResult) { - ExLog.e(getActivity(), TAG, "Failed to connect to Google Play Services for location updates"); - } - private enum GPSQuality { noSignal(0), poor(2), good(3), great(4); diff --git a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/util/CheckinManager.java b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/util/CheckinManager.java index 6e41b915e4b..d15a59bcf86 100644 --- a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/util/CheckinManager.java +++ b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/util/CheckinManager.java @@ -189,16 +189,16 @@ public class CheckinManager { org.json.simple.JSONObject simplePosition; simplePosition = JsonHelper.convertToSimple(positionJson); GPSFix gpsFix = deserializer.deserialize(simplePosition); - //accepts JSON messages without accuracy and with, without will be displayed as "n/a" - MarkPingInfo ping; - if (positionJson.has(FlatGPSFixJsonSerializer.FIELD_ACCURACY)){ + //accepts JSON messages without accuracy and with, without will simply be displayed as "set" + final MarkPingInfo ping; + if (!positionJson.has(FlatGPSFixJsonSerializer.FIELD_ACCURACY) || + positionJson.getDouble(FlatGPSFixJsonSerializer.FIELD_ACCURACY) == FlatGPSFixJsonSerializer.NOT_AVAILABLE_THROUGH_SERVER) { + ping = new MarkPingInfo(mark.getId(), gpsFix, FlatGPSFixJsonSerializer.NOT_AVAILABLE_THROUGH_SERVER); + } else { ping = new MarkPingInfo(mark.getId(), gpsFix, positionJson.getDouble(FlatGPSFixJsonSerializer.FIELD_ACCURACY)); } - else { - ping = new MarkPingInfo(mark.getId(), gpsFix, MarkPingInfo.NOT_SET_BY_USER); - } - if (ping != null){ - pings.add(ping); + if (ping != null) { + pings.add(ping); } } } diff --git a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/valueobjects/MarkPingInfo.java b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/valueobjects/MarkPingInfo.java index 1ace4490e7d..5e495d0a0fd 100644 --- a/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/valueobjects/MarkPingInfo.java +++ b/mobile/com.sap.sailing.buoy.positioning/src/com/sap/sailing/android/buoy/positioning/app/valueobjects/MarkPingInfo.java @@ -9,8 +9,6 @@ public class MarkPingInfo { private final GPSFix gpsFix; private final double accuracy; - public static final int NOT_SET_BY_USER = -1; // this is used for accuracies that are displayed as "n/a" - public MarkPingInfo(Serializable markId, GPSFix gpsFix, double accuracy) { super(); this.markId = markId; diff --git a/mobile/com.sap.sailing.racecommittee.app/res/values-de/strings.xml b/mobile/com.sap.sailing.racecommittee.app/res/values-de/strings.xml index 269424e2d43..08b55413332 100644 --- a/mobile/com.sap.sailing.racecommittee.app/res/values-de/strings.xml +++ b/mobile/com.sap.sailing.racecommittee.app/res/values-de/strings.xml @@ -460,20 +460,20 @@ N/A (vor %s) Vorheriger Kurs - Kurs Layout + Kurs-Layout ° Wegpunkt hinzufügen - Unpublished course design + Unveröffentlichtes Kurs-Layout Neu Zurücksetzen Soll die Liste komplett gelöscht werden? Soll die Liste zum letzten bekannten Stand zurückgesetzt werden? - Scan QR-Code + QR-Code scannen Manuelle Eingabe - Group Race - Select Race + Gruppenwettfahrt + Wettfahrt auswählen \'%1$s\' zur Ergebnisliste hinzugefügt an Position %2$s - Getting Started + Anmelden Position Server erfolgreich geändert auf %1$s. Strafe diff --git a/wiki/howto/onboarding.md b/wiki/howto/onboarding.md index bbfb3762a81..972b940d5b0 100644 --- a/wiki/howto/onboarding.md +++ b/wiki/howto/onboarding.md @@ -66,11 +66,14 @@ Beside the installation script for Eclipse Mars, there is also one for the new E * Clone the repository to your local file system from `ssh://[SAP-User]@git.wdf.sap.corp:29418/SAPSail/sapsailingcapture.git` or `ssh://[user]@sapsailing.com/home/trac/git` User "trac" has all public ssh keys. 2. Check out the 'master' branch from the git repository. The 'master' branch is the main development branch. Please check that you start your work on this branch. 3. Setup and configure Eclipse - * Make absolutely sure to import CodeFormatter.xml (from $GIT_HOME/java) into your Eclipse preferences (Preferences->Java->Code Style->Fortmatter) + * Make absolutely sure to import CodeFormatter.xml (from $GIT_HOME/java) into your Eclipse preferences (Preferences->Java->Code Style->Formatter) * Install the Eclipse GWT-Plugin (now called Google Plugin for Eclipse) * Install the Google Android SDK from the same Google Plugin for Eclipse update site * In Eclipse go to "Window->Preferences->Java->Build Path->Classpath Variables" and create a new classpath variable called ``ANDROID_HOME``. Set its value to the install location of your Android SDK, e.g., ``c:\apps\android-sdk-windows`` or ``/usr/local/android-sdk-linux``. * Install GWT SDK and add the SDK in Eclipse (Preferences -> Google -> Web Toolkit -> Add...) + * In "Window->Preferences->General->Editors->TextEditors" check Insert Spaces for Tabs + * In "Window->Preferences->Web->HTML Files->Editor" indent using Spaces + * In "Window->Preferences->General->Content Types" select on the right side CSS, now add in the lower file association list *.gss to get limited syntax highlighting and content assist in GSS files * Install Eclipse debugger for GWT SuperDevMode * Install Eclipse eGit (optional) * Check that JDK 1.8 is available and has been set for compilation in Eclipse @@ -78,7 +81,7 @@ Beside the installation script for Eclipse Mars, there is also one for the new E * Check that JDK 1.7 has been matched to JavaSE-1.7 and that JDK 1.8 has been matched to JavaSE-1.8 (...>Installed JREs>Execution Environments) * It is also possible to match the SAPJVM 7 or 8 to JavaSE-1.7 / JavaSE-1.8 (for profiling purposes) * Go to Windows->Preferences->Google->Errors/Warnings and set "Missing SDK" to "Ignore" - * Import all Race Analysis projects from the `java/` subdirectory of the git main folder (make sure to import as a git project in eclipse) + * Import all Race Analysis projects from the `java/` subdirectory of the git main folder (make sure to import as a git project in eclipse) (make sure to scan for nested projects) * Import all projects from the `mobile/` subdirectory of the git main folder; this in particular contains the race committee app projects * Set the Eclipse target platform to race-analysis-p2-remote.target (located in com.sap.sailing.targetplatform/definitions) * Wait until the target platform has been resolved completely