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 7674dbcea9a..c05cc401159 100644 --- a/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties +++ b/java/com.sap.sailing.datamining/resources/stringmessages/Sailing_StringMessages.properties @@ -47,6 +47,8 @@ Bft10=Storm Bft11=Violent storm Bft12=Hurricane 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 @@ -76,4 +78,7 @@ PassingSide=Passing Side TackAtStart=Tack at Start SpeedTenSecondsAfterStart=Speed 10s After Start RankThirtySecondsAfterStart=Rank 30s After Start -StartAdvantage=Start Advantage \ No newline at end of file +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 \ 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 2bf8f9b7dcf..a12a8452ec2 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 @@ -47,6 +47,8 @@ Bft10=schwerer Sturm Bft11=orkanartiger Sturm Bft12=Orkan 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 @@ -76,4 +78,7 @@ PassingSide=Passierende Seite TackAtStart=Startbug SpeedTenSecondsAfterStart=Geschwindigkeit 10s nach dem Startzeitpunkt RankThirtySecondsAfterStart=Absolute Platzierung 30s nach dem Startzeitpunkt -StartAdvantage=Startvorteil \ No newline at end of file +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 \ 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 2e0f2f683e2..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 @@ -23,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; @@ -126,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/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/HasRaceOfCompetitorContext.java b/java/com.sap.sailing.datamining/src/com/sap/sailing/datamining/data/HasRaceOfCompetitorContext.java index d2441fd0e7b..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 @@ -8,6 +8,7 @@ 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 { @@ -23,43 +24,52 @@ public interface HasRaceOfCompetitorContext { @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(); -// @Statistic(messageKey="StartAdvantage", resultDecimals=2, ordinal=2) -// public Double getNormalizedDistanceToFavoredEndAtStart(); + @Statistic(messageKey="WindwardDistanceToAdvantageousEndOfLine", resultDecimals=2, ordinal=2) + public Distance getWindwardDistanceToAdvantageousLineEndAtStart(); - @Connector(messageKey="SpeedWhenStarting", ordinal=2) + @Connector(messageKey="SpeedWhenStarting", ordinal=3) public Speed getSpeedWhenStarting(); - @Connector(messageKey="SpeedTenSecondsBeforeStart", ordinal=3) + @Connector(messageKey="SpeedTenSecondsBeforeStart", ordinal=4) public Speed getSpeedTenSecondsBeforeStart(); - @Connector(messageKey="SpeedTenSecondsAfterStart", ordinal=4) + @Connector(messageKey="SpeedTenSecondsAfterStart", ordinal=5) public Speed getSpeedTenSecondsAfterStart(); - @Statistic(messageKey="RankThirtySecondsAfterStart", resultDecimals=2, ordinal=5) - public Double getAbsoluteRankThirtySecondsAfterStart(); + @Statistic(messageKey="RankThirtySecondsAfterStart", resultDecimals=2, ordinal=6) + public Double getRankThirtySecondsAfterStart(); - @Statistic(messageKey="RankAtFirstMark", resultDecimals=2, ordinal=6) + @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=7) + @Statistic(messageKey="RankGainsOrLossesBetweenFirstMarkAndFinish", resultDecimals=2, ordinal=9) public Double getRankGainsOrLossesBetweenFirstMarkAndFinish(); - @Statistic(messageKey="NumberOfManeuvers", resultDecimals=0, ordinal=8) + @Statistic(messageKey="NumberOfManeuvers", resultDecimals=0, ordinal=10) public int getNumberOfManeuvers(); - @Statistic(messageKey="NumberOfTacks", resultDecimals=2, ordinal=9) + @Statistic(messageKey="NumberOfTacks", resultDecimals=2, ordinal=11) public int getNumberOfTacks(); - @Statistic(messageKey="NumberOfJibes", resultDecimals=2, ordinal=10) + @Statistic(messageKey="NumberOfJibes", resultDecimals=2, ordinal=12) public int getNumberOfJibes(); - @Statistic(messageKey="NumberOfPenaltyCircles", resultDecimals=2, ordinal=11) + @Statistic(messageKey="NumberOfPenaltyCircles", resultDecimals=2, ordinal=13) public int getNumberOfPenaltyCircles(); } 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 16dc536da6d..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 @@ -6,6 +6,7 @@ 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 { @@ -16,6 +17,9 @@ public interface HasTrackedLegOfCompetitorContext { @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(); @@ -31,5 +35,8 @@ public interface HasTrackedLegOfCompetitorContext { @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..059a000e659 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,10 @@ 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="IsTracked", ordinal=7) public Boolean isTracked(); @Statistic(messageKey="NumberOfCompetitorFixes", resultDecimals=0, ordinal=0) @@ -44,4 +49,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/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/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 8ce5e4bf821..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,20 +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 { @@ -52,6 +64,18 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext { 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); @@ -71,6 +95,46 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext { 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()); @@ -91,7 +155,7 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext { } @Override - public Double getAbsoluteRankThirtySecondsAfterStart() { + public Double getRankThirtySecondsAfterStart() { TimePoint startOfRace = getTrackedRace().getStartOfRace(); if (startOfRace == null) { return null; @@ -101,6 +165,21 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext { 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(); @@ -113,15 +192,10 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext { @Override public Double getRankGainsOrLossesBetweenFirstMarkAndFinish() { Double rankAtFirstMark = getRankAtFirstMark(); - Double rankAtFinish = getRankAtFinish(); + Double rankAtFinish = getTrackedRaceContext().getRankAtFinishForCompetitor(getCompetitor()); return rankAtFirstMark != null && rankAtFinish != null ? rankAtFirstMark - rankAtFinish : null; } - private Double getRankAtFinish() { - int rank = getTrackedRace().getRank(getCompetitor(), getTrackedRace().getEndOfTracking()); - return rank == 0 ? null : Double.valueOf(rank); - } - @Override public int getNumberOfManeuvers() { return getNumberOfTacks() + getNumberOfJibes(); 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 261947cb303..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,5 +1,7 @@ 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; @@ -9,6 +11,8 @@ 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 { @@ -48,6 +52,18 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti 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() { TimePoint timePoint = getTrackedLegContext().getTrackedRaceContext().getTrackedRace().getEndOfTracking(); @@ -94,4 +110,15 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti 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..a7e4a0893c0 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,12 @@ 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 isTracked() { @@ -125,5 +133,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/dto/LeaderboardEntryDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java index 1eab1c2ace2..9d525058fea 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardEntryDTO.java @@ -3,10 +3,13 @@ package com.sap.sailing.domain.common.dto; import java.io.Serializable; import java.util.List; +import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.Tack; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sse.common.Duration; +import com.sap.sse.common.impl.MillisecondsDurationImpl; /** * Holds a single competitor's scoring details for a single race. It may optionally contain @@ -78,6 +81,8 @@ public class LeaderboardEntryDTO implements Serializable { */ public Duration averageSamplingInterval; + public Double averageRideHeightInMeters; + /** * The time gap to the competitor leading the race; for one-design races this is the time the competitor is expected * to need to reach the leader's (windward) position (if in the same leg) or the difference between the time at @@ -132,6 +137,44 @@ public class LeaderboardEntryDTO implements Serializable { public boolean hasScoreCorrection() { return totalPointsCorrected || (reasonForMaxPoints != null && reasonForMaxPoints != MaxPointsReason.NONE); } + + public Duration getTimeSailedInMillis() { + final Duration result; + if (legDetails != null) { + long timeInMilliseconds = 0; + for (LegEntryDTO legDetail : legDetails) { + if (legDetail != null) { + if (legDetail.distanceTraveledInMeters != null && legDetail.timeInMilliseconds != null) { + timeInMilliseconds += legDetail.timeInMilliseconds; + } else { + timeInMilliseconds = 0; + break; + } + } + } + result = new MillisecondsDurationImpl(timeInMilliseconds); + } else { + result = null; + } + return result; + } + + public Distance getDistanceTraveledInMeters() { + Distance result = null; + if (legDetails != null) { + for (LegEntryDTO legDetail : legDetails) { + if (legDetail != null) { + if (legDetail.distanceTraveledInMeters != null) { + if (result == null) { + result = Distance.NULL; + } + result = result.add(new MeterDistance(legDetail.distanceTraveledInMeters)); + } + } + } + } + return result; + } @Override public int hashCode() { @@ -174,6 +217,7 @@ public class LeaderboardEntryDTO implements Serializable { result = prime * result + ((speedOverGroundAtStartOfRaceInKnots == null) ? 0 : speedOverGroundAtStartOfRaceInKnots.hashCode()); result = prime * result + ((startTack == null) ? 0 : startTack.hashCode()); + result = prime * result + ((averageRideHeightInMeters == null) ? 0 : averageRideHeightInMeters.hashCode()); result = prime * result + ((averageSamplingInterval == null) ? 0 : averageSamplingInterval.hashCode()); result = prime * result + ((timeSinceLastPositionFixInSeconds == null) ? 0 : timeSinceLastPositionFixInSeconds.hashCode()); result = prime * result + ((netPoints == null) ? 0 : netPoints.hashCode()); @@ -282,6 +326,11 @@ public class LeaderboardEntryDTO implements Serializable { return false; if (startTack != other.startTack) return false; + if (averageRideHeightInMeters == null) { + if (other.averageRideHeightInMeters != null) + return false; + } else if (!averageRideHeightInMeters.equals(other.averageRideHeightInMeters)) + return false; if (averageSamplingInterval == null) { if (other.averageSamplingInterval != null) return false; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java index 1f5036c8083..a5db8b41249 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/dto/LeaderboardRowDTO.java @@ -4,6 +4,9 @@ import java.io.Serializable; import java.util.Date; import java.util.Map; +import com.sap.sailing.domain.common.Distance; +import com.sap.sse.common.Duration; + /** * Holds data about one competitor and all races represented by the owning {@link LeaderboardDTO leaderboard}. * @@ -117,6 +120,28 @@ public class LeaderboardRowDTO implements Serializable { return true; } + public Distance getDistanceTraveledInMeters(String raceColumnName) { + final Distance result; + LeaderboardEntryDTO fieldsForRace = fieldsByRaceColumnName.get(raceColumnName); + if (fieldsForRace != null) { + result = fieldsForRace.getDistanceTraveledInMeters(); + } else { + result = null; + } + return result; + } + + public Duration getTimeSailed(String raceColumnName) { + final Duration result; + LeaderboardEntryDTO fieldsForRace = fieldsByRaceColumnName.get(raceColumnName); + if (fieldsForRace != null) { + result = fieldsForRace.getTimeSailedInMillis(); + } else { + result = null; + } + return result; + } + @Override public String toString() { return "LeaderboardRowDTO for competitor "+competitor; diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/BravoFix.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/BravoFix.java index 73dd3504886..22564e68cc3 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/BravoFix.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/BravoFix.java @@ -1,5 +1,7 @@ package com.sap.sailing.domain.common.tracking; +import com.sap.sailing.domain.common.Distance; + /** * Specific {@link SensorFix} for bravo devices (http://www.bravosystems.es/). * @@ -12,5 +14,5 @@ public interface BravoFix extends SensorFix { * * @return the ride height */ - double getRideHeight(); + Distance getRideHeight(); } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoFixImpl.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoFixImpl.java index 99ba3015f75..c83402e7545 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoFixImpl.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/tracking/impl/BravoFixImpl.java @@ -1,5 +1,7 @@ package com.sap.sailing.domain.common.tracking.impl; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.impl.MeterDistance; import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata; import com.sap.sailing.domain.common.tracking.BravoFix; import com.sap.sailing.domain.common.tracking.DoubleVectorFix; @@ -32,8 +34,8 @@ public class BravoFixImpl implements BravoFix { } @Override - public double getRideHeight() { - return fix.get(BravoSensorDataMetadata.INSTANCE.rideHeightColumn); + public Distance getRideHeight() { + return new MeterDistance(fix.get(BravoSensorDataMetadata.INSTANCE.rideHeightColumn)); } } 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.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 49dfbf08d37..44f0fd3fde5 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 @@ -541,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 - } }; } @@ -1135,4 +1132,9 @@ public class MockedTrackedRace implements DynamicTrackedRace { public NavigableSet getUpdatedMarkPassings(Competitor competitor) { 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 90a7d215fcd..75ccf497f77 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 @@ -686,4 +686,9 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { public NavigableSet getUpdatedMarkPassings(Competitor competitor) { 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/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/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 650431ab494..b21d9c3d34d 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 @@ -47,6 +47,7 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.common.tracking.SensorFix; 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; @@ -691,6 +692,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/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 3c4264ef233..76b06a6698e 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 @@ -1548,6 +1548,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.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 70421632ba3..d4687fc0db7 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 @@ -865,4 +865,9 @@ public class MockedTrackedRace implements DynamicTrackedRace { public NavigableSet getUpdatedMarkPassings(Competitor competitor) { return null; } + + @Override + public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint) { + return null; + } } 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/desktop/places/event/regatta/TabletAndDesktopRegattaEventView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/TabletAndDesktopRegattaEventView.java index b3c95caf26f..383fd6e7887 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/TabletAndDesktopRegattaEventView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/desktop/places/event/regatta/TabletAndDesktopRegattaEventView.java @@ -55,7 +55,7 @@ public class TabletAndDesktopRegattaEventView extends Composite implements Event if(currentPresenter.getEventDTO().getType() == EventType.SERIES_EVENT) { final PlaceNavigation currentEventSeriesNavigation = currentPresenter.getCurrentEventSeriesNavigation(); - Anchor seriesAnchor = new Anchor(currentPresenter.getEventDTO().getSeriesName()); + Anchor seriesAnchor = new Anchor(i18n.overallLeaderboardSelection()); seriesAnchor.setHref(currentEventSeriesNavigation.getTargetUrl()); seriesAnchor.addClickHandler(new ClickHandler() { @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/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 2c601c3ac0c..bd587251b9c 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 @@ -1736,6 +1736,9 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, 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 99176f00fa3..6389f0e472d 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 @@ -1614,10 +1614,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 @@ -1710,4 +1712,4 @@ lastFix=Last fix 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 \ No newline at end of file +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 cdac7ebc2dc..469bba23aa1 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 @@ -1600,10 +1600,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 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 8ec136ff41b..3865f5b0233 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 @@ -1612,6 +1612,9 @@ raceAnalysis=レース分析 startAnalysis=スタート分析 winningLanes=勝利レーン maximumNumberOfDiscards=カットレースの最大数 +externalLinks=External links +urlForAllLanguages=URL for all languages +urlForLanguage=URL for language ''{0}'' notificationRegattaConfigurationUpdatedUsingByMarks="マーク別" コースデザイナーを使用して、有効なレガッタ設定が行われました。 warningOverrideRegattaConfigurationCourseDesignerToByMarks=有効なレガッタ設定がすでに登録されていますが、コースデザイナーの設定が適切でありません。\n"マーク別" へのコースデザイナー設定を上書きしますか。 allRacesHaveBeenDenoted=すべてのレースがレースログ追跡に表示されています。 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 94466bff2c6..4beeff7e33f 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 @@ -1612,6 +1612,9 @@ raceAnalysis=Анализ гонки startAnalysis=Начать анализ winningLanes=Выигрышные маршруты maximumNumberOfDiscards=Максимальное число исключений +externalLinks=External links +urlForAllLanguages=URL for all languages +urlForLanguage=URL for language ''{0}'' notificationRegattaConfigurationUpdatedUsingByMarks=Активная конфигурация регаты была настроена с использованием дизайнера курса "По отметкам". warningOverrideRegattaConfigurationCourseDesignerToByMarks=Активная конфигурация регаты уже существует, но с неверно выбранным дизайнером курса.\nПереопределить выбор дизайнера курса на "По отметкам"? allRacesHaveBeenDenoted=Отслеживание журнала всех гонок отменено. 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 1e00ca7d20d..e648ab55be3 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 @@ -1610,7 +1610,9 @@ raceAnalysis=比赛轮次分析 startAnalysis=起航分析 winningLanes=获胜的船道 maximumNumberOfDiscards=最大放弃次数 - +externalLinks=External links +urlForAllLanguages=URL for all languages +urlForLanguage=URL for language ''{0}'' chartSelectionHint=提示:您还可以使用积分榜(在 LEADERBOARD 选项卡中)选择在记录中显示的参赛队。 chooseALanguage=选择一种语言 solutions=解决方案 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 a74bb40da7e..adb4f208fb6 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 @@ -1612,6 +1612,9 @@ raceAnalysis=比赛轮次分析 startAnalysis=起航分析 winningLanes=获胜的船道 maximumNumberOfDiscards=最大放弃次数 +externalLinks=External links +urlForAllLanguages=URL for all languages +urlForLanguage=URL for language ''{0}'' notificationRegattaConfigurationUpdatedUsingByMarks=已使用“按标志”场地设计器设置有效的比赛配置。 warningOverrideRegattaConfigurationCourseDesignerToByMarks=有效的比赛配置已存在,但是场地设计器设置错误。\n是否将场地设计器设置覆盖为“按标志”? allRacesHaveBeenDenoted=已为比赛轮次日志跟踪描述所有比赛轮次。 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 a6ab0f59476..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; @@ -972,10 +974,10 @@ 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; } @@ -1299,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(); } } @@ -1446,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/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/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 5d463d35864..365a85b5ab7 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -2941,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: 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/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.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/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.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/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index 3f36f29fd26..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 @@ -3484,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/masterdata/DummyTrackedRace.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java index 382602687ce..c29bfd74f27 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) { @@ -661,4 +667,9 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { public NavigableSet getUpdatedMarkPassings(Competitor competitor) { return null; } + + @Override + public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint) { + return null; + } } diff --git a/java/com.sap.sailing.www/release_notes_admin.html b/java/com.sap.sailing.www/release_notes_admin.html index 2c2431489df..c66d961a3ac 100644 --- a/java/com.sap.sailing.www/release_notes_admin.html +++ b/java/com.sap.sailing.www/release_notes_admin.html @@ -29,6 +29,11 @@ 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.

September 2016

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/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