Merge branch 'master' into bug3816_2

This commit is contained in:
Stsiapan_Tsybulski committed 2016-10-25 12:35:46 +03:00
commit d57652edf0
62 files changed
+1046 -239

No files matched your search

@@ -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<Double> cluster = hardPercentageClusterGroup.getClusterFor(-0.01);
assertThat(cluster, nullValue());
cluster = hardPercentageClusterGroup.getClusterFor(1.01);
assertThat(cluster, nullValue());
cluster = softLinearClusterGroup.getClusterFor(-0.1);
Cluster<Double> 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<Double> cluster = hardPercentageClusterGroup.getClusterFor(0.0);
Cluster<Double> 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<Double> cluster = clusterGroup.getClusterFor(value);
Cluster<Double> expectedCluster = createCluster(clusterGroup.getUpperGroupBound() / 2, ComparisonStrategy.GREATER_EQUALS_THAN,
(clusterGroup.getUpperGroupBound() / 2) + clusterGroup.getStepSize(), ComparisonStrategy.LOWER_THAN);
assertThat(cluster, is(expectedCluster));
}
private Cluster<Double> createCluster(double lowerBoundValue, ComparisonStrategy lowerComparisonStrategy, double upperBoundValue, ComparisonStrategy upperComparisonStrategy) {
ComparableClusterBoundary<Double> lowerBound = new ComparableClusterBoundary<>(lowerBoundValue, lowerComparisonStrategy);
ComparableClusterBoundary<Double> upperBound = new ComparableClusterBoundary<>(upperBoundValue, upperComparisonStrategy);
return new ClusterWithLowerAndUpperBoundaries<>(lowerBound, upperBound);
}
private Cluster<Double> createCluster(double boundValue, ComparisonStrategy comparisonStrategy) {
return new ClusterWithSingleBoundary<>(new ComparableClusterBoundary<>(boundValue, comparisonStrategy));
}
}
@@ -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<Double> 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<Double> cluster = percentageClusterGroup.getClusterFor(0.05);
String formattedCluster = formatter.format(cluster);
assertThat(formattedCluster, is("[0% - 10%["));
}
}
@@ -47,6 +47,8 @@ Bft10=Storm
Bft11=Violent storm Bft11=Violent storm
Bft12=Hurricane Bft12=Hurricane
RelativeScore=Relative Score (0 best; 1 worst) RelativeScore=Relative Score (0 best; 1 worst)
RelativeScoreInPercent=Relative Score in %
RelativeScoreInRaceInPercent=Relative Score in Race in %
AbsoluteRank=Absolute Rank AbsoluteRank=Absolute Rank
WindSpeedInBeaufort=Wind Speed in Bft WindSpeedInBeaufort=Wind Speed in Bft
DistanceAtStart=Distance to Start at Start DistanceAtStart=Distance to Start at Start
@@ -76,4 +78,7 @@ PassingSide=Passing Side
TackAtStart=Tack at Start TackAtStart=Tack at Start
SpeedTenSecondsAfterStart=Speed 10s After Start SpeedTenSecondsAfterStart=Speed 10s After Start
RankThirtySecondsAfterStart=Rank 30s After Start RankThirtySecondsAfterStart=Rank 30s After Start
StartAdvantage=Start Advantage 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
@@ -47,6 +47,8 @@ Bft10=schwerer Sturm
Bft11=orkanartiger Sturm Bft11=orkanartiger Sturm
Bft12=Orkan Bft12=Orkan
RelativeScore=Relative Wertung (0 am besten; 1 am schlechtesten) RelativeScore=Relative Wertung (0 am besten; 1 am schlechtesten)
RelativeScoreInPercent=Relative Wertung in %
RelativeScoreInRaceInPercent=Relative Wertung im Rennen in %
AbsoluteRank=Absolute Platzierung AbsoluteRank=Absolute Platzierung
WindSpeedInBeaufort=Windgeschwindigkeit in Bft. WindSpeedInBeaufort=Windgeschwindigkeit in Bft.
DistanceAtStart=Distanz zur Startlinie zum Startzeitpunkt DistanceAtStart=Distanz zur Startlinie zum Startzeitpunkt
@@ -76,4 +78,7 @@ PassingSide=Passierende Seite
TackAtStart=Startbug TackAtStart=Startbug
SpeedTenSecondsAfterStart=Geschwindigkeit 10s nach dem Startzeitpunkt SpeedTenSecondsAfterStart=Geschwindigkeit 10s nach dem Startzeitpunkt
RankThirtySecondsAfterStart=Absolute Platzierung 30s nach dem Startzeitpunkt RankThirtySecondsAfterStart=Absolute Platzierung 30s nach dem Startzeitpunkt
StartAdvantage=Startvorteil 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
@@ -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.ParallelDistanceMedianAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceMinAggregationProcessor; import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceMinAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceSumAggregationProcessor; 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.DataSourceProvider;
import com.sap.sse.datamining.components.AggregationProcessorDefinition; import com.sap.sse.datamining.components.AggregationProcessorDefinition;
import com.sap.sse.datamining.components.DataRetrieverChainDefinition; 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.impl.AbstractDataMiningActivatorWithPredefinedQueries;
import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO; import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO;
import com.sap.sse.datamining.shared.impl.PredefinedQueryIdentifier; import com.sap.sse.datamining.shared.impl.PredefinedQueryIdentifier;
@@ -126,9 +124,9 @@ public class Activator extends AbstractDataMiningActivatorWithPredefinedQueries
dataSourceProviders = new HashSet<>(); dataSourceProviders = new HashSet<>();
dataSourceProviders.add(new RacingEventServiceProvider(context)); dataSourceProviders.add(new RacingEventServiceProvider(context));
} }
public static ClusterGroup<Speed> getWindStrengthInBeaufortClusterGroup() { public static SailingClusterGroups getClusterGroups() {
return clusterGroups.getWindStrengthInBeaufortCluster(); return clusterGroups;
} }
public static Activator getDefault() { public static Activator getDefault() {
@@ -3,6 +3,9 @@ package com.sap.sailing.datamining;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; 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.Speed;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl; import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sse.datamining.data.Cluster; import com.sap.sse.datamining.data.Cluster;
@@ -17,7 +20,10 @@ import com.sap.sse.datamining.impl.data.LocalizedCluster;
public class SailingClusterGroups { public class SailingClusterGroups {
private final ClusterGroup<Speed> windStrengthInBeaufortCluster; private final ClusterGroup<Speed> windStrengthInBeaufortClusterGroup;
private final ClusterGroup<Double> percentageClusterGroup;
private final ClusterFormatter<Double> percentageClusterFormatter;
public SailingClusterGroups() { public SailingClusterGroups() {
Collection<Cluster<Speed>> clusters = new ArrayList<>(); Collection<Cluster<Speed>> clusters = new ArrayList<>();
@@ -98,11 +104,22 @@ public class SailingClusterGroups {
lowerBound = new ComparableClusterBoundary<Speed>(lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN); lowerBound = new ComparableClusterBoundary<Speed>(lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN);
clusters.add(new LocalizedCluster<Speed>("Bft12", new ClusterWithSingleBoundary<Speed>(lowerBound))); clusters.add(new LocalizedCluster<Speed>("Bft12", new ClusterWithSingleBoundary<Speed>(lowerBound)));
windStrengthInBeaufortCluster = new FixClusterGroup<Speed>(clusters); windStrengthInBeaufortClusterGroup = new FixClusterGroup<Speed>(clusters);
percentageClusterGroup = new LinearDoubleClusterGroup(0.0, 1.0, 0.1, true);
percentageClusterFormatter = new PercentageClusterFormatter();
} }
public ClusterGroup<Speed> getWindStrengthInBeaufortCluster() { public ClusterGroup<Speed> getWindStrengthInBeaufortClusterGroup() {
return windStrengthInBeaufortCluster; return windStrengthInBeaufortClusterGroup;
}
public ClusterGroup<Double> getPercentageClusterGroup() {
return percentageClusterGroup;
}
public ClusterFormatter<Double> getPercentageClusterFormatter() {
return percentageClusterFormatter;
} }
} }
@@ -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<T extends Serializable> {
String format(Cluster<T> cluster);
}
@@ -8,6 +8,7 @@ import com.sap.sailing.domain.common.Tack;
import com.sap.sse.datamining.annotations.Connector; import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension; import com.sap.sse.datamining.annotations.Dimension;
import com.sap.sse.datamining.annotations.Statistic; import com.sap.sse.datamining.annotations.Statistic;
import com.sap.sse.datamining.shared.impl.dto.ClusterDTO;
public interface HasRaceOfCompetitorContext { public interface HasRaceOfCompetitorContext {
@@ -23,43 +24,52 @@ public interface HasRaceOfCompetitorContext {
@Dimension(messageKey="TackAtStart", ordinal=12) @Dimension(messageKey="TackAtStart", ordinal=12)
public Tack getTackAtStart() throws NoWindException; 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) @Statistic(messageKey="DistanceAtStart", resultDecimals=2, ordinal=0)
public Distance getDistanceToStartLineAtStart(); public Distance getDistanceToStartLineAtStart();
@Statistic(messageKey="DistanceToStarboardSideAtStart", resultDecimals=2, ordinal=1) @Statistic(messageKey="DistanceToStarboardSideAtStart", resultDecimals=2, ordinal=1)
public Double getNormalizedDistanceToStarboardSideAtStart(); public Double getNormalizedDistanceToStarboardSideAtStart();
// @Statistic(messageKey="StartAdvantage", resultDecimals=2, ordinal=2) @Statistic(messageKey="WindwardDistanceToAdvantageousEndOfLine", resultDecimals=2, ordinal=2)
// public Double getNormalizedDistanceToFavoredEndAtStart(); public Distance getWindwardDistanceToAdvantageousLineEndAtStart();
@Connector(messageKey="SpeedWhenStarting", ordinal=2) @Connector(messageKey="SpeedWhenStarting", ordinal=3)
public Speed getSpeedWhenStarting(); public Speed getSpeedWhenStarting();
@Connector(messageKey="SpeedTenSecondsBeforeStart", ordinal=3) @Connector(messageKey="SpeedTenSecondsBeforeStart", ordinal=4)
public Speed getSpeedTenSecondsBeforeStart(); public Speed getSpeedTenSecondsBeforeStart();
@Connector(messageKey="SpeedTenSecondsAfterStart", ordinal=4) @Connector(messageKey="SpeedTenSecondsAfterStart", ordinal=5)
public Speed getSpeedTenSecondsAfterStart(); public Speed getSpeedTenSecondsAfterStart();
@Statistic(messageKey="RankThirtySecondsAfterStart", resultDecimals=2, ordinal=5) @Statistic(messageKey="RankThirtySecondsAfterStart", resultDecimals=2, ordinal=6)
public Double getAbsoluteRankThirtySecondsAfterStart(); 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(); public Double getRankAtFirstMark();
@Statistic(messageKey="RankGainsOrLossesBetweenFirstMarkAndFinish", resultDecimals=2, ordinal=7) @Statistic(messageKey="RankGainsOrLossesBetweenFirstMarkAndFinish", resultDecimals=2, ordinal=9)
public Double getRankGainsOrLossesBetweenFirstMarkAndFinish(); public Double getRankGainsOrLossesBetweenFirstMarkAndFinish();
@Statistic(messageKey="NumberOfManeuvers", resultDecimals=0, ordinal=8) @Statistic(messageKey="NumberOfManeuvers", resultDecimals=0, ordinal=10)
public int getNumberOfManeuvers(); public int getNumberOfManeuvers();
@Statistic(messageKey="NumberOfTacks", resultDecimals=2, ordinal=9) @Statistic(messageKey="NumberOfTacks", resultDecimals=2, ordinal=11)
public int getNumberOfTacks(); public int getNumberOfTacks();
@Statistic(messageKey="NumberOfJibes", resultDecimals=2, ordinal=10) @Statistic(messageKey="NumberOfJibes", resultDecimals=2, ordinal=12)
public int getNumberOfJibes(); public int getNumberOfJibes();
@Statistic(messageKey="NumberOfPenaltyCircles", resultDecimals=2, ordinal=11) @Statistic(messageKey="NumberOfPenaltyCircles", resultDecimals=2, ordinal=13)
public int getNumberOfPenaltyCircles(); public int getNumberOfPenaltyCircles();
} }
@@ -6,6 +6,7 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sse.datamining.annotations.Connector; import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension; import com.sap.sse.datamining.annotations.Dimension;
import com.sap.sse.datamining.annotations.Statistic; import com.sap.sse.datamining.annotations.Statistic;
import com.sap.sse.datamining.shared.impl.dto.ClusterDTO;
public interface HasTrackedLegOfCompetitorContext { public interface HasTrackedLegOfCompetitorContext {
@@ -16,6 +17,9 @@ public interface HasTrackedLegOfCompetitorContext {
@Dimension(messageKey="CompetitorSearchTag", ordinal=11) // TODO Clean me: Move Dimension to Competitor when possible @Dimension(messageKey="CompetitorSearchTag", ordinal=11) // TODO Clean me: Move Dimension to Competitor when possible
public String getCompetitorSearchTag(); public String getCompetitorSearchTag();
@Dimension(messageKey="RelativeScoreInRaceInPercent", ordinal=12)
public ClusterDTO getPercentageClusterForRelativeScoreInRace();
@Connector(messageKey="Competitor") @Connector(messageKey="Competitor")
public Competitor getCompetitor(); public Competitor getCompetitor();
@@ -31,5 +35,8 @@ public interface HasTrackedLegOfCompetitorContext {
@Statistic(messageKey="AbsoluteRank", resultDecimals=2, ordinal=3) @Statistic(messageKey="AbsoluteRank", resultDecimals=2, ordinal=3)
public Double getAbsoluteRank(); public Double getAbsoluteRank();
@Statistic(messageKey="TimeSpentInSeconds", resultDecimals=0, ordinal=4)
public Long getTimeTakenInSeconds();
} }
@@ -1,10 +1,12 @@
package com.sap.sailing.datamining.data; package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.base.BoatClass; 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.CourseArea;
import com.sap.sailing.domain.base.Fleet; import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.datamining.annotations.Connector; import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension; import com.sap.sse.datamining.annotations.Dimension;
@@ -35,7 +37,10 @@ public interface HasTrackedRaceContext {
@Dimension(messageKey="Year", ordinal=2) @Dimension(messageKey="Year", ordinal=2)
public Integer getYear(); public Integer getYear();
@Dimension(messageKey="IsTracked", ordinal=6) @Dimension(messageKey="AdvantageousEndOfLine", ordinal=6)
public NauticalSide getAdvantageousEndOfLine();
@Dimension(messageKey="IsTracked", ordinal=7)
public Boolean isTracked(); public Boolean isTracked();
@Statistic(messageKey="NumberOfCompetitorFixes", resultDecimals=0, ordinal=0) @Statistic(messageKey="NumberOfCompetitorFixes", resultDecimals=0, ordinal=0)
@@ -44,4 +49,9 @@ public interface HasTrackedRaceContext {
@Statistic(messageKey="NumberOfMarkFixes", resultDecimals=0, ordinal=1) @Statistic(messageKey="NumberOfMarkFixes", resultDecimals=0, ordinal=1)
public int getNumberOfMarkFixes(); public int getNumberOfMarkFixes();
// Convenience methods for race dependent calculation to avoid code duplication
public Double getRelativeScoreForCompetitor(Competitor competitor);
public Double getRankAtFinishForCompetitor(Competitor competitor);
} }
@@ -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<T extends Serializable> implements ClusterFormatter<T> {
private static final char INFINITE = '\u221e';
private final ClusterBoundariesAccessor boundariesAccessor = new ClusterBoundariesAccessor();
private final BoundaryValueAccessor valueAccessor = new BoundaryValueAccessor();
@Override
public String format(Cluster<T> cluster) {
if (cluster instanceof LocalizedCluster) {
// TODO recursive call format(localizedCluster.getInnerCluster()) after 49er analysis
}
if (cluster instanceof ClusterWithSingleBoundary) {
return formatClusterWithSingleBoundary((ClusterWithSingleBoundary<T>) cluster);
}
if (cluster instanceof ClusterWithLowerAndUpperBoundaries) {
return formatClusterWithLowerAndUpperBoundaries((ClusterWithLowerAndUpperBoundaries<T>) cluster);
}
throw new IllegalArgumentException("Can't format clusters of type " + cluster.getClass().getName());
}
protected String formatClusterWithSingleBoundary(ClusterWithSingleBoundary<T> cluster) {
List<ClusterBoundary<T>> boundaries = boundariesAccessor.getBoundaries(cluster);
if (boundaries == null) {
return "<Unformattable>";
}
ClusterBoundary<T> 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<T> cluster) {
List<ClusterBoundary<T>> boundaries = boundariesAccessor.getBoundaries(cluster);
if (boundaries == null) {
return "<Unformattable>";
}
return String.format("%s - %s", formatBoundary(boundaries.get(0)), formatBoundary(boundaries.get(1)));
}
protected String formatBoundary(ClusterBoundary<T> boundary) {
String value = formatValue(valueAccessor.getValue((AbstractClusterBoundary<T>) boundary));
if (value == null) {
return "<Unformattable>";
}
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<ClusterBoundary<T>> getBoundaries(AbstractCluster<T> cluster) {
List<ClusterBoundary<T>> boundaries = null;
Field boundariesField = null;
try {
boundariesField = AbstractCluster.class.getDeclaredField(boundariesFieldName);
boundariesField.setAccessible(true);
@SuppressWarnings("unchecked")
Collection<ClusterBoundary<T>> reflectedBoundaries = (Collection<ClusterBoundary<T>>) boundariesField.get(cluster);
boundaries = new ArrayList<>();
for (ClusterBoundary<T> 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<T> 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;
}
}
}
@@ -36,7 +36,7 @@ public class GPSFixWithContext implements HasGPSFixContext {
@Override @Override
public ClusterDTO getWindStrengthAsBeaufortCluster(Locale locale, ResourceBundleStringMessages stringMessages) { public ClusterDTO getWindStrengthAsBeaufortCluster(Locale locale, ResourceBundleStringMessages stringMessages) {
Wind wind = getWind(); Wind wind = getWind();
Cluster<?> cluster = Activator.getWindStrengthInBeaufortClusterGroup().getClusterFor(wind); Cluster<?> cluster = Activator.getClusterGroups().getWindStrengthInBeaufortClusterGroup().getClusterFor(wind);
return new ClusterDTO(cluster.asLocalizedString(locale, stringMessages)); return new ClusterDTO(cluster.asLocalizedString(locale, stringMessages));
} }
@@ -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<Double> {
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<Integer, Cluster<Double>> 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<Double> 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<Double> getCluster(int index) {
if (!clusterCache.containsKey(index)) {
clusterCache.put(index, createCluster(index));
}
return clusterCache.get(index);
}
private Cluster<Double> 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<Double> createLowerBound(int index, ComparisonStrategy comparisonStrategy) {
return new ComparableClusterBoundary<Double>(this.lowerBound + stepSize * index, comparisonStrategy);
}
private ComparableClusterBoundary<Double> createUpperBound(int index, ComparisonStrategy comparisonStrategy) {
return new ComparableClusterBoundary<Double>(this.lowerBound + stepSize * (index + 1.0), comparisonStrategy);
}
@Override
public Class<Double> getClusterElementsType() {
return Double.class;
}
public double getLowerGroupBound() {
return lowerBound;
}
public double getUpperGroupBound() {
return upperBound;
}
public double getStepSize() {
return stepSize;
}
public boolean isHardBounds() {
return hardBounds;
}
}
@@ -0,0 +1,10 @@
package com.sap.sailing.datamining.impl.data;
public class PercentageClusterFormatter extends AbstractClusterFormatter<Double> {
@Override
protected String formatValue(Double value) {
return (int) (value * 100) + "%";
}
}
@@ -2,20 +2,32 @@ package com.sap.sailing.datamining.impl.data;
import java.util.concurrent.TimeUnit; 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.HasRaceOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedRaceContext; import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Course; 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.base.Waypoint;
import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.ManeuverType; import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NoWindException; 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.Speed;
import com.sap.sailing.domain.common.Tack; 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.Maneuver;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sse.common.TimePoint; 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 { public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
@@ -52,6 +64,18 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
return startOfRace == null ? null : getTrackedRace().getTack(getCompetitor(), startOfRace); 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<Double> cluster = clusterGroups.getPercentageClusterGroup().getClusterFor(normalizedDistance);
return new ClusterDTO(clusterGroups.getPercentageClusterFormatter().format(cluster));
}
@Override @Override
public Distance getDistanceToStartLineAtStart() { public Distance getDistanceToStartLineAtStart() {
return getTrackedRace().getDistanceToStartLine(getCompetitor(), 0); return getTrackedRace().getDistanceToStartLine(getCompetitor(), 0);
@@ -71,6 +95,46 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
return distance / length; 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<Mark, GPSFix> advantageousMarkTrack = trackedRace.getOrCreateTrack(advantageousMark);
Position advantageousMarkPosition = advantageousMarkTrack.getEstimatedPosition(startOfRace, false);
GPSFixTrack<Competitor, GPSFixMoving> 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<Double> cluster = clusterGroups.getPercentageClusterGroup().getClusterFor(relativeScore);
return new ClusterDTO(clusterGroups.getPercentageClusterFormatter().format(cluster));
}
@Override @Override
public Speed getSpeedWhenStarting() { public Speed getSpeedWhenStarting() {
return getTrackedRace().getSpeedWhenCrossingStartLine(getCompetitor()); return getTrackedRace().getSpeedWhenCrossingStartLine(getCompetitor());
@@ -91,7 +155,7 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
} }
@Override @Override
public Double getAbsoluteRankThirtySecondsAfterStart() { public Double getRankThirtySecondsAfterStart() {
TimePoint startOfRace = getTrackedRace().getStartOfRace(); TimePoint startOfRace = getTrackedRace().getStartOfRace();
if (startOfRace == null) { if (startOfRace == null) {
return null; return null;
@@ -101,6 +165,21 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
return rank == 0 ? null : Double.valueOf(rank); 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 @Override
public Double getRankAtFirstMark() { public Double getRankAtFirstMark() {
Course course = getTrackedRace().getRace().getCourse(); Course course = getTrackedRace().getRace().getCourse();
@@ -113,15 +192,10 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
@Override @Override
public Double getRankGainsOrLossesBetweenFirstMarkAndFinish() { public Double getRankGainsOrLossesBetweenFirstMarkAndFinish() {
Double rankAtFirstMark = getRankAtFirstMark(); Double rankAtFirstMark = getRankAtFirstMark();
Double rankAtFinish = getRankAtFinish(); Double rankAtFinish = getTrackedRaceContext().getRankAtFinishForCompetitor(getCompetitor());
return rankAtFirstMark != null && rankAtFinish != null ? rankAtFirstMark - rankAtFinish : null; 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 @Override
public int getNumberOfManeuvers() { public int getNumberOfManeuvers() {
return getNumberOfTacks() + getNumberOfJibes(); return getNumberOfTacks() + getNumberOfJibes();
@@ -1,5 +1,7 @@
package com.sap.sailing.datamining.impl.data; 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.HasTrackedLegContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext; import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.domain.base.Competitor; 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.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util; 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 { public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompetitorContext {
@@ -48,6 +52,18 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti
return getCompetitor().getSearchTag(); return getCompetitor().getSearchTag();
} }
@Override
public ClusterDTO getPercentageClusterForRelativeScoreInRace() {
Double relativeScore = getTrackedLegContext().getTrackedRaceContext().getRelativeScoreForCompetitor(getCompetitor());
if (relativeScore == null) {
return null;
}
SailingClusterGroups clusterGroups = Activator.getClusterGroups();
Cluster<Double> cluster = clusterGroups.getPercentageClusterGroup().getClusterFor(relativeScore);
return new ClusterDTO(clusterGroups.getPercentageClusterFormatter().format(cluster));
}
@Override @Override
public Distance getDistanceTraveled() { public Distance getDistanceTraveled() {
TimePoint timePoint = getTrackedLegContext().getTrackedRaceContext().getTrackedRace().getEndOfTracking(); TimePoint timePoint = getTrackedLegContext().getTrackedRaceContext().getTrackedRace().getEndOfTracking();
@@ -94,4 +110,15 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti
return rankAtFinish; 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;
}
} }
@@ -11,9 +11,11 @@ import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta; 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.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving; import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack; import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.LineDetails;
import com.sap.sailing.domain.tracking.TrackedRace; import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util; import com.sap.sse.common.Util;
@@ -90,6 +92,12 @@ public class TrackedRaceWithContext implements HasTrackedRaceContext {
calendar.setTime(time.asDate()); calendar.setTime(time.asDate());
return calendar.get(Calendar.YEAR); return calendar.get(Calendar.YEAR);
} }
@Override
public NauticalSide getAdvantageousEndOfLine() {
LineDetails startLine = getTrackedRace().getStartLine(getTrackedRace().getStartOfRace());
return startLine.getAdvantageousSideWhileApproachingLine();
}
@Override @Override
public Boolean isTracked() { public Boolean isTracked() {
@@ -125,5 +133,20 @@ public class TrackedRaceWithContext implements HasTrackedRaceContext {
} }
return number; 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);
}
} }
@@ -3,10 +3,13 @@ package com.sap.sailing.domain.common.dto;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.MaxPointsReason; import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.RaceIdentifier; import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.Tack; 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.Duration;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
/** /**
* Holds a single competitor's scoring details for a single race. It may optionally contain * 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 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 * 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 * 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() { public boolean hasScoreCorrection() {
return totalPointsCorrected || (reasonForMaxPoints != null && reasonForMaxPoints != MaxPointsReason.NONE); 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 @Override
public int hashCode() { public int hashCode() {
@@ -174,6 +217,7 @@ public class LeaderboardEntryDTO implements Serializable {
result = prime * result result = prime * result
+ ((speedOverGroundAtStartOfRaceInKnots == null) ? 0 : speedOverGroundAtStartOfRaceInKnots.hashCode()); + ((speedOverGroundAtStartOfRaceInKnots == null) ? 0 : speedOverGroundAtStartOfRaceInKnots.hashCode());
result = prime * result + ((startTack == null) ? 0 : startTack.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 + ((averageSamplingInterval == null) ? 0 : averageSamplingInterval.hashCode());
result = prime * result + ((timeSinceLastPositionFixInSeconds == null) ? 0 : timeSinceLastPositionFixInSeconds.hashCode()); result = prime * result + ((timeSinceLastPositionFixInSeconds == null) ? 0 : timeSinceLastPositionFixInSeconds.hashCode());
result = prime * result + ((netPoints == null) ? 0 : netPoints.hashCode()); result = prime * result + ((netPoints == null) ? 0 : netPoints.hashCode());
@@ -282,6 +326,11 @@ public class LeaderboardEntryDTO implements Serializable {
return false; return false;
if (startTack != other.startTack) if (startTack != other.startTack)
return false; return false;
if (averageRideHeightInMeters == null) {
if (other.averageRideHeightInMeters != null)
return false;
} else if (!averageRideHeightInMeters.equals(other.averageRideHeightInMeters))
return false;
if (averageSamplingInterval == null) { if (averageSamplingInterval == null) {
if (other.averageSamplingInterval != null) if (other.averageSamplingInterval != null)
return false; return false;
@@ -4,6 +4,9 @@ import java.io.Serializable;
import java.util.Date; import java.util.Date;
import java.util.Map; 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}. * 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; 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 @Override
public String toString() { public String toString() {
return "LeaderboardRowDTO for competitor "+competitor; return "LeaderboardRowDTO for competitor "+competitor;
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.common.tracking; package com.sap.sailing.domain.common.tracking;
import com.sap.sailing.domain.common.Distance;
/** /**
* Specific {@link SensorFix} for bravo devices (http://www.bravosystems.es/). * Specific {@link SensorFix} for bravo devices (http://www.bravosystems.es/).
* *
@@ -12,5 +14,5 @@ public interface BravoFix extends SensorFix {
* *
* @return the ride height * @return the ride height
*/ */
double getRideHeight(); Distance getRideHeight();
} }
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.common.tracking.impl; 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.sensordata.BravoSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.BravoFix; import com.sap.sailing.domain.common.tracking.BravoFix;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix; import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
@@ -32,8 +34,8 @@ public class BravoFixImpl implements BravoFix {
} }
@Override @Override
public double getRideHeight() { public Distance getRideHeight() {
return fix.get(BravoSensorDataMetadata.INSTANCE.rideHeightColumn); return new MeterDistance(fix.get(BravoSensorDataMetadata.INSTANCE.rideHeightColumn));
} }
} }
@@ -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.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl; import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl; 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.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition; import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; 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.sensordata.BravoSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix; import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving; 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_TIMESTAMP2 = 120;
private static final long FIX_TIMESTAMP3 = 210; private static final long FIX_TIMESTAMP3 = 210;
private static final long AFTER_LAST_FIX = FIX_TIMESTAMP3 + 1; private static final long AFTER_LAST_FIX = FIX_TIMESTAMP3 + 1;
private static final double FIX_RIDE_HEIGHT = 1337.0; private static final Distance FIX_RIDE_HEIGHT = new MeterDistance(1337.0);
private static final double FIX_RIDE_HEIGHT2 = 1338.0; private static final Distance FIX_RIDE_HEIGHT2 = new MeterDistance(1338.0);
private static final double FIX_RIDE_HEIGHT3 = 1336.0; private static final Distance FIX_RIDE_HEIGHT3 = new MeterDistance(1336.0);
private static final double FIX_TEST_VALUE = 12.0; private static final double FIX_TEST_VALUE = 12.0;
protected final MockSmartphoneImeiServiceFinderFactory serviceFinderFactory = new MockSmartphoneImeiServiceFinderFactory(); protected final MockSmartphoneImeiServiceFinderFactory serviceFinderFactory = new MockSmartphoneImeiServiceFinderFactory();
protected final DeviceIdentifier device = new SmartphoneImeiIdentifier("a"); protected final DeviceIdentifier device = new SmartphoneImeiIdentifier("a");
@@ -384,20 +386,14 @@ public class SensorFixStoreAndLoadTest {
public void testBravoFixIsCorrectlyWrapped() throws InterruptedException { public void testBravoFixIsCorrectlyWrapped() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp, regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING))); device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
addBravoFixes(); addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker(); FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog); trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog); trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish(); trackedRace.waitForLoadingToFinish();
BravoFixTrack<Competitor> bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME); BravoFixTrack<Competitor> bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME);
assertEquals(FIX_RIDE_HEIGHT, assertEquals(FIX_RIDE_HEIGHT,
bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getRideHeight()); bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getRideHeight());
fixLoaderAndTracker.stop(true); fixLoaderAndTracker.stop(true);
} }
@@ -482,9 +478,9 @@ public class SensorFixStoreAndLoadTest {
} }
private void addBravoFixes() { private void addBravoFixes() {
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP, FIX_RIDE_HEIGHT)); store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP, FIX_RIDE_HEIGHT.getMeters()));
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2)); store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2.getMeters()));
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP3, FIX_RIDE_HEIGHT3)); store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP3, FIX_RIDE_HEIGHT3.getMeters()));
} }
private FixLoaderAndTracker createFixLoaderAndTracker() { private FixLoaderAndTracker createFixLoaderAndTracker() {
@@ -541,15 +541,12 @@ public class MockedTrackedRace implements DynamicTrackedRace {
@Override @Override
public boolean isControlTrackingFromStartAndFinishTimes() { public boolean isControlTrackingFromStartAndFinishTimes() {
// TODO Auto-generated method stub
return false; return false;
} }
@Override @Override
public void setControlTrackingFromStartAndFinishTimes( public void setControlTrackingFromStartAndFinishTimes(
boolean controlTrackingFromStartAndFinishTimes) { boolean controlTrackingFromStartAndFinishTimes) {
// TODO Auto-generated method stub
} }
}; };
} }
@@ -1135,4 +1132,9 @@ public class MockedTrackedRace implements DynamicTrackedRace {
public NavigableSet<MarkPassing> getUpdatedMarkPassings(Competitor competitor) { public NavigableSet<MarkPassing> getUpdatedMarkPassings(Competitor competitor) {
return null; return null;
} }
@Override
public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint) {
return null;
}
} }
@@ -686,4 +686,9 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
public NavigableSet<MarkPassing> getUpdatedMarkPassings(Competitor competitor) { public NavigableSet<MarkPassing> getUpdatedMarkPassings(Competitor competitor) {
return null; return null;
} }
@Override
public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint) {
return null;
}
} }
@@ -82,8 +82,8 @@ public class BravoFixTrackSerializationTest {
assertRideHeight(track, testFixData.timePoint, testFixData.rideHeight); assertRideHeight(track, testFixData.timePoint, testFixData.rideHeight);
} }
private void assertRideHeight(BravoFixTrack<Competitor> track, TimePoint timePoint, double expected) { private void assertRideHeight(BravoFixTrack<Competitor> track, TimePoint timePoint, double expectedRideHeightInMeters) {
assertEquals(expected, track.getRideHeight(timePoint), 0.0); assertEquals(expectedRideHeightInMeters, track.getRideHeight(timePoint).getMeters(), 0.0);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@@ -1437,6 +1437,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
} else { } else {
entryDTO.timeSinceLastPositionFixInSeconds = null; entryDTO.timeSinceLastPositionFixInSeconds = null;
} }
final Distance averageRideHeight = trackedRace.getAverageRideHeight(competitor, timePoint);
entryDTO.averageRideHeightInMeters = averageRideHeight == null ? null : averageRideHeight.getMeters();
} }
if (addLegDetails && trackedRace != null) { if (addLegDetails && trackedRace != null) {
try { try {
@@ -1721,11 +1723,13 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
Double speedOverGroundInKnots; Double speedOverGroundInKnots;
if (trackedLeg.hasFinishedLeg(timePoint)) { if (trackedLeg.hasFinishedLeg(timePoint)) {
speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots(); 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 { } else {
final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint); final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint);
speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots(); 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; result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots;
Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint); Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint);
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.tracking;
import java.io.Serializable; import java.io.Serializable;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.tracking.BravoFix; import com.sap.sailing.domain.common.tracking.BravoFix;
import com.sap.sse.common.TimePoint; import com.sap.sse.common.TimePoint;
import com.sap.sse.common.WithID; import com.sap.sse.common.WithID;
@@ -22,7 +23,7 @@ public interface BravoFixTrack<ItemType extends WithID & Serializable> extends S
* @return the ride height for the given {@link TimePoint} or <code>null</code> if the {@link TimePoint} is not in the range * @return the ride height for the given {@link TimePoint} or <code>null</code> if the {@link TimePoint} is not in the range
* where we have fixes for. * where we have fixes for.
*/ */
Double getRideHeight(TimePoint timePoint); Distance getRideHeight(TimePoint timePoint);
/** /**
* Calculates the average ride height for the given time range. * Calculates the average ride height for the given time range.
@@ -34,5 +35,5 @@ public interface BravoFixTrack<ItemType extends WithID & Serializable> extends S
* @return the average ride height for the given time range or <code>null</code> if there are no fixes in the given * @return the average ride height for the given time range or <code>null</code> if there are no fixes in the given
* time range. * time range.
*/ */
Double getAverageRideHeight(TimePoint from, TimePoint to); Distance getAverageRideHeight(TimePoint from, TimePoint to);
} }
@@ -97,7 +97,7 @@ public interface TrackedLegOfCompetitor extends Serializable {
* <code>timePoint</code>. If the competitor already completed the leg at <code>timePoint</code> and the respective * <code>timePoint</code>. If the competitor already completed the leg at <code>timePoint</code> and the respective
* mark passing event was already received, the average ride height for the entire leg (and no further) is computed. * 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 <code>null</code> if the competitor hasn't started this leg yet, otherwise the fix where the maximum speed was * @return <code>null</code> 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 {
* <code>timePoint</code>, returns the current ride height for this time point. If the competitor has already * <code>timePoint</code>, 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. * 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 * Computes the distance along the wind track to the wind-projected position of the race's overall leader. If leader
@@ -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.common.tracking.SensorFix;
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException; import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarDataService; 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;
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
import com.sap.sailing.domain.tracking.impl.NonCachingMarkPositionAtTimePointCache; import com.sap.sailing.domain.tracking.impl.NonCachingMarkPositionAtTimePointCache;
@@ -691,6 +692,8 @@ public interface TrackedRace extends Serializable, IsManagedByCache<SharedDomain
Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly,
boolean waitForLatestAnalysis) throws NoWindException; boolean waitForLatestAnalysis) throws NoWindException;
public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint);
WindStore getWindStore(); WindStore getWindStore();
Competitor getOverallLeader(TimePoint timePoint); Competitor getOverallLeader(TimePoint timePoint);
@@ -1,10 +1,8 @@
package com.sap.sailing.domain.tracking.impl; package com.sap.sailing.domain.tracking.impl;
import java.io.Serializable; import java.io.Serializable;
import java.util.OptionalDouble;
import java.util.Spliterator;
import java.util.stream.StreamSupport;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata; import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.BravoFix; import com.sap.sailing.domain.common.tracking.BravoFix;
import com.sap.sailing.domain.tracking.BravoFixTrack; import com.sap.sailing.domain.tracking.BravoFixTrack;
@@ -34,7 +32,7 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
} }
@Override @Override
public Double getRideHeight(TimePoint timePoint) { public Distance getRideHeight(TimePoint timePoint) {
BravoFix fixAfter = getFirstFixAtOrAfter(timePoint); BravoFix fixAfter = getFirstFixAtOrAfter(timePoint);
if (fixAfter != null && fixAfter.getTimePoint().compareTo(timePoint) == 0) { if (fixAfter != null && fixAfter.getTimePoint().compareTo(timePoint) == 0) {
// exact match of timepoint -> no interpolation necessary // exact match of timepoint -> no interpolation necessary
@@ -54,17 +52,24 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
} }
@Override @Override
public Double getAverageRideHeight(TimePoint from, TimePoint to) { public Distance getAverageRideHeight(TimePoint from, TimePoint to) {
final Distance result;
lockForRead(); lockForRead();
try { try {
Spliterator<BravoFix> fixes = getFixes(from, true, to, true).spliterator(); Distance sum = Distance.NULL;
OptionalDouble average = StreamSupport.stream(fixes, false).mapToDouble(BravoFix::getRideHeight).average(); int count = 0;
if (average.isPresent()) { for (final BravoFix fix : getFixes(from, true, to, true)) {
return average.getAsDouble(); sum = sum.add(fix.getRideHeight());
count++;
}
if (count > 0) {
result = sum.scale(1./count);
} else {
result = null;
} }
} finally { } finally {
unlockAfterRead(); unlockAfterRead();
} }
return null; return result;
} }
} }
@@ -174,8 +174,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
} }
if (timePointToUse != null) { if (timePointToUse != null) {
Distance d = getDistanceTraveled(timePointToUse); Distance d = getDistanceTraveled(timePointToUse);
long millis = timePointToUse.asMillis() - legStart.getTimePoint().asMillis(); result = d.inTime(legStart.getTimePoint().until(timePointToUse));
result = d.inTime(millis);
} else { } else {
result = null; result = null;
} }
@@ -184,7 +183,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
} }
@Override @Override
public Double getAverageRideHeight(TimePoint timePoint) { public Distance getAverageRideHeight(TimePoint timePoint) {
MarkPassing legStart = getMarkPassingForLegStart(); MarkPassing legStart = getMarkPassingForLegStart();
if (legStart != null) { if (legStart != null) {
BravoFixTrack<Competitor> track = getTrackedRace() BravoFixTrack<Competitor> track = getTrackedRace()
@@ -677,15 +676,17 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
} }
@Override @Override
public Double getRideHeight(TimePoint at) { public Distance getRideHeight(TimePoint at) {
final Distance result;
if (hasStartedLeg(at)) { if (hasStartedLeg(at)) {
TimePoint timePoint =hasFinishedLeg(at) ? getMarkPassingForLegEnd().getTimePoint() : at; TimePoint timePoint =hasFinishedLeg(at) ? getMarkPassingForLegEnd().getTimePoint() : at;
BravoFixTrack<Competitor> track = getTrackedRace() BravoFixTrack<Competitor> track = getTrackedRace()
.<BravoFix, BravoFixTrack<Competitor>> getSensorTrack(competitor, BravoFixTrack.TRACK_NAME); .<BravoFix, BravoFixTrack<Competitor>> getSensorTrack(competitor, BravoFixTrack.TRACK_NAME);
return track == null ? null : track.getRideHeight(timePoint); result = track == null ? null : track.getRideHeight(timePoint);
} else { } else {
return null; result = null;
} }
return result;
} }
@Override @Override
@@ -1548,6 +1548,22 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
return result; return result;
} }
@Override
public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint) {
final Distance result;
BravoFixTrack<Competitor> 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 @Override
public TrackedLegOfCompetitor getCurrentLeg(Competitor competitor, TimePoint timePoint) { public TrackedLegOfCompetitor getCurrentLeg(Competitor competitor, TimePoint timePoint) {
// If the mark passing that starts a leg happened exactly at timePoint, the MarkPassingByTimeComparator won't consider // If the mark passing that starts a leg happened exactly at timePoint, the MarkPassingByTimeComparator won't consider
@@ -865,4 +865,9 @@ public class MockedTrackedRace implements DynamicTrackedRace {
public NavigableSet<MarkPassing> getUpdatedMarkPassings(Competitor competitor) { public NavigableSet<MarkPassing> getUpdatedMarkPassings(Competitor competitor) {
return null; return null;
} }
@Override
public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint) {
return null;
}
} }
@@ -7,8 +7,16 @@ import com.google.gwt.event.shared.EventBus;
import com.google.gwt.user.client.Window; import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AcceptsOneWidget; 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.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.mvp.ErrorView;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveLifecycleWithAllSettings;
import com.sap.sse.gwt.client.useragent.UserAgentDetails; import com.sap.sse.gwt.client.useragent.UserAgentDetails;
import com.sap.sse.gwt.shared.GwtHttpRequestUtils; import com.sap.sse.gwt.shared.GwtHttpRequestUtils;
@@ -26,6 +34,17 @@ public class PlayerActivity extends AbstractActivity {
this.autoPlayController = null; 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 @Override
public void start(final AcceptsOneWidget panel, EventBus eventBus) { public void start(final AcceptsOneWidget panel, EventBus eventBus) {
final boolean showRaceDetails = GwtHttpRequestUtils.getBooleanParameter(PARAM_SHOW_RACE_DETAILS, false); 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<EventDTO>() { clientFactory.getSailingService().getEventById(eventUUID, true, new AsyncCallback<EventDTO>() {
@Override @Override
public void onSuccess(final EventDTO event) { 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<LeaderboardWithHeaderPerspectiveLifecycle, LeaderboardWithHeaderPerspectiveSettings> leaderboardPerspectiveLifecyclesAndSettings = new PerspectiveLifecycleWithAllSettings<>(leaderboardPerspectiveLifecycle, leaderboardPerspectiveLifecycle.createDefaultSettings());
RaceBoardPerspectiveLifecycle raceboardPerspectiveLifecycle = new RaceBoardPerspectiveLifecycle(leaderBoardDTO, StringMessages.INSTANCE);
PerspectiveLifecycleWithAllSettings<RaceBoardPerspectiveLifecycle, RaceBoardPerspectiveSettings> raceboardPerspectiveLifecyclesAndSettings = new PerspectiveLifecycleWithAllSettings<>(raceboardPerspectiveLifecycle, raceboardPerspectiveLifecycle.createDefaultSettings());
UserAgentDetails userAgent = new UserAgentDetails(Window.Navigator.getUserAgent()); UserAgentDetails userAgent = new UserAgentDetails(Window.Navigator.getUserAgent());
PlayerView view = clientFactory.createPlayerView(); PlayerView view = clientFactory.createPlayerView();
@@ -44,8 +73,8 @@ public class PlayerActivity extends AbstractActivity {
autoPlayController = new AutoPlayController(clientFactory.getSailingService(), clientFactory autoPlayController = new AutoPlayController(clientFactory.getSailingService(), clientFactory
.getMediaService(), clientFactory.getUserService(), clientFactory.getErrorReporter(), .getMediaService(), clientFactory.getUserService(), clientFactory.getErrorReporter(),
playerPlace.getConfiguration(), userAgent, delayToLiveMillis, showRaceDetails, view, playerPlace.getConfiguration(), userAgent, delayToLiveMillis, showRaceDetails, view,
playerPlace.getLeaderboardPerspectiveLifecycleWithAllSettings(), leaderboardPerspectiveLifecyclesAndSettings,
playerPlace.getRaceboardPerspectiveLifecycleWithAllSettings()); raceboardPerspectiveLifecyclesAndSettings);
autoPlayController.updatePlayMode(AutoPlayModes.Leaderboard); autoPlayController.updatePlayMode(AutoPlayModes.Leaderboard);
} }
@@ -23,6 +23,8 @@ public class PlayerPlace extends AbstractBasePlace {
Integer timeToSwitchBeforeRaceStartInSeconds = Integer.valueOf(getParameter(AutoPlayerConfiguration.PARAM_TIME_TO_SWITCH_BEFORE_RACE_START)); Integer timeToSwitchBeforeRaceStartInSeconds = Integer.valueOf(getParameter(AutoPlayerConfiguration.PARAM_TIME_TO_SWITCH_BEFORE_RACE_START));
this.playerConfiguration = new AutoPlayerConfiguration(eventUuidAsString, leaderboardName, fullscreen, timeToSwitchBeforeRaceStartInSeconds); 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.leaderboardPerspectiveLifecycleWithAllSettings = null;
this.raceboardPerspectiveLifecycleWithAllSettings = null; this.raceboardPerspectiveLifecycleWithAllSettings = null;
} }
@@ -55,7 +55,7 @@ public class TabletAndDesktopRegattaEventView extends Composite implements Event
if(currentPresenter.getEventDTO().getType() == EventType.SERIES_EVENT) { if(currentPresenter.getEventDTO().getType() == EventType.SERIES_EVENT) {
final PlaceNavigation<SeriesDefaultPlace> currentEventSeriesNavigation = currentPresenter.getCurrentEventSeriesNavigation(); final PlaceNavigation<SeriesDefaultPlace> currentEventSeriesNavigation = currentPresenter.getCurrentEventSeriesNavigation();
Anchor seriesAnchor = new Anchor(currentPresenter.getEventDTO().getSeriesName()); Anchor seriesAnchor = new Anchor(i18n.overallLeaderboardSelection());
seriesAnchor.setHref(currentEventSeriesNavigation.getTargetUrl()); seriesAnchor.setHref(currentEventSeriesNavigation.getTargetUrl());
seriesAnchor.addClickHandler(new ClickHandler() { seriesAnchor.addClickHandler(new ClickHandler() {
@Override @Override
@@ -41,7 +41,7 @@ public class DeviceConfigurationQRIdentifierDialog extends DialogBox {
Window.alert(stringMessages.notCapableOfGeneratingACodeForIdentifier()); Window.alert(stringMessages.notCapableOfGeneratingACodeForIdentifier());
} else if (!identifierBox.getValue().isEmpty() && !serverBox.getValue().isEmpty()) { } else if (!identifierBox.getValue().isEmpty() && !serverBox.getValue().isEmpty()) {
String apkUrl = getServerUrlWithoutFinalSlash() + rcAppApkPath; 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; return null;
} }
@@ -37,14 +37,11 @@ public class EventCreateDialog extends EventDialog {
endDateBox = createDateTimeBox(now); endDateBox = createDateTimeBox(now);
endDateBox.setFormat("dd/mm/yyyy hh:ii"); endDateBox.setFormat("dd/mm/yyyy hh:ii");
endDateBox.ensureDebugId("EndDateTimeBox"); endDateBox.ensureDebugId("EndDateTimeBox");
officialWebsiteURLEntryField = createTextBox(null);
officialWebsiteURLEntryField.setVisibleLength(50);
baseURLEntryField = createTextBox(null);
baseURLEntryField.setVisibleLength(50);
sailorsInfoWebsiteURLEntryFields = createTextBoxesForLocalesAndDefault(Collections.<String, String> emptyMap());
isPublicCheckBox = createCheckbox(""); isPublicCheckBox = createCheckbox("");
isPublicCheckBox.setValue(false); isPublicCheckBox.setValue(false);
isPublicCheckBox.ensureDebugId("IsPublicCheckBox"); isPublicCheckBox.ensureDebugId("IsPublicCheckBox");
baseURLEntryField = createTextBox(null);
baseURLEntryField.setVisibleLength(50);
imagesListComposite.fillImages(Collections.<ImageDTO>emptyList()); imagesListComposite.fillImages(Collections.<ImageDTO>emptyList());
videosListComposite.fillVideos(Collections.<VideoDTO>emptyList()); videosListComposite.fillVideos(Collections.<VideoDTO>emptyList());
// add default course area // add default course area
@@ -5,7 +5,6 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; 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.EventDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO; import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.VenueDTO; 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.IconResources;
import com.sap.sse.gwt.client.controls.listedit.GenericStringListInlineEditorComposite; import com.sap.sse.gwt.client.controls.listedit.GenericStringListInlineEditorComposite;
import com.sap.sse.gwt.client.controls.listedit.StringConstantsListEditorComposite; import com.sap.sse.gwt.client.controls.listedit.StringConstantsListEditorComposite;
@@ -48,14 +46,13 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
protected BetterDateTimeBox endDateBox; protected BetterDateTimeBox endDateBox;
protected CheckBox isPublicCheckBox; protected CheckBox isPublicCheckBox;
protected UUID id; protected UUID id;
protected TextBox officialWebsiteURLEntryField;
protected TextBox baseURLEntryField; protected TextBox baseURLEntryField;
protected Map<String, TextBox> sailorsInfoWebsiteURLEntryFields = new HashMap<>();
protected CourseAreaListInlineEditorComposite courseAreaNameList; protected CourseAreaListInlineEditorComposite courseAreaNameList;
protected StringConstantsListEditorComposite leaderboardGroupList; protected StringConstantsListEditorComposite leaderboardGroupList;
protected Map<String, LeaderboardGroupDTO> availableLeaderboardGroupsByName; protected Map<String, LeaderboardGroupDTO> availableLeaderboardGroupsByName;
protected ImagesListComposite imagesListComposite; protected ImagesListComposite imagesListComposite;
protected VideosListComposite videosListComposite; protected VideosListComposite videosListComposite;
protected ExternalLinksComposite externalLinksComposite;
protected static class EventParameterValidator implements Validator<EventDTO> { protected static class EventParameterValidator implements Validator<EventDTO> {
@@ -162,6 +159,7 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
imagesListComposite = new ImagesListComposite(sailingService, stringMessages); imagesListComposite = new ImagesListComposite(sailingService, stringMessages);
videosListComposite = new VideosListComposite(stringMessages); videosListComposite = new VideosListComposite(stringMessages);
externalLinksComposite = new ExternalLinksComposite(stringMessages);
} }
@Override @Override
@@ -176,13 +174,9 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
} }
result.setName(nameEntryField.getText()); result.setName(nameEntryField.getText());
result.setDescription(descriptionEntryField.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()); result.setBaseURL(baseURLEntryField.getText().trim().isEmpty() ? null : baseURLEntryField.getText().trim());
for (Map.Entry<String, TextBox> sailorsInfoWebsiteUrlEntry : sailorsInfoWebsiteURLEntryFields.entrySet()) { result.setSailorsInfoWebsiteURLs(externalLinksComposite.getSailorsInfoWebsiteURLs());
TextBox sailorsInfoWebsiteURLEntryField = sailorsInfoWebsiteUrlEntry.getValue();
String sailorsInfoWebsiteURL = sailorsInfoWebsiteURLEntryField.getText().trim();
result.setSailorsInfoWebsiteURL(sailorsInfoWebsiteUrlEntry.getKey(), sailorsInfoWebsiteURL.isEmpty() ? null : sailorsInfoWebsiteURLEntryField.getText().trim());
}
result.startDate = startDateBox.getValue(); result.startDate = startDateBox.getValue();
result.endDate = endDateBox.getValue(); result.endDate = endDateBox.getValue();
result.isPublic = isPublicCheckBox.getValue(); result.isPublic = isPublicCheckBox.getValue();
@@ -206,7 +200,7 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
if (additionalWidget != null) { if (additionalWidget != null) {
panel.add(additionalWidget); panel.add(additionalWidget);
} }
Grid formGrid = new Grid(10 + GWTLocaleUtil.getLanguageCountWithDefault(), 2); Grid formGrid = new Grid(8, 2);
int rowIndex = 0; int rowIndex = 0;
formGrid.setWidget(rowIndex, 0, new Label(stringMessages.name() + ":")); formGrid.setWidget(rowIndex, 0, new Label(stringMessages.name() + ":"));
formGrid.setWidget(rowIndex++, 1, nameEntryField); formGrid.setWidget(rowIndex++, 1, nameEntryField);
@@ -222,24 +216,18 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
formGrid.setWidget(rowIndex++, 1, endDateBox); formGrid.setWidget(rowIndex++, 1, endDateBox);
formGrid.setWidget(rowIndex, 0, new Label(stringMessages.isPublic() + ":")); formGrid.setWidget(rowIndex, 0, new Label(stringMessages.isPublic() + ":"));
formGrid.setWidget(rowIndex++, 1, isPublicCheckBox); 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, 0, new Label(stringMessages.eventBaseURL() + ":"));
formGrid.setWidget(rowIndex++, 1, baseURLEntryField); formGrid.setWidget(rowIndex++, 1, baseURLEntryField);
for (Map.Entry<String, TextBox> 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); TabLayoutPanel tabPanel = new TabLayoutPanel(30, Unit.PX);
tabPanel.ensureDebugId("EventDialogTabs"); tabPanel.ensureDebugId("EventDialogTabs");
tabPanel.setHeight("525px"); tabPanel.setHeight("400px");
panel.add(tabPanel); panel.add(tabPanel);
final ScrollPanel eventTab = new ScrollPanel(formGrid); final ScrollPanel eventTab = new ScrollPanel(formGrid);
eventTab.ensureDebugId("EventTab"); eventTab.ensureDebugId("EventTab");
tabPanel.add(eventTab, stringMessages.event()); 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); final ScrollPanel leaderboardGroupTab = new ScrollPanel(leaderboardGroupList);
leaderboardGroupTab.ensureDebugId("LeaderboardGroupsTab"); leaderboardGroupTab.ensureDebugId("LeaderboardGroupsTab");
tabPanel.add(leaderboardGroupTab, stringMessages.leaderboardGroups()); tabPanel.add(leaderboardGroupTab, stringMessages.leaderboardGroups());
@@ -259,14 +247,4 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
protected FocusWidget getInitialFocusWidget() { protected FocusWidget getInitialFocusWidget() {
return nameEntryField; return nameEntryField;
} }
protected Map<String, TextBox> createTextBoxesForLocalesAndDefault(Map<String, String> initialValues) {
Map<String, TextBox> result = new LinkedHashMap<>();
for(String localeName : GWTLocaleUtil.getAvailableLocalesAndDefault()) {
TextBox sailorsInfoWebsiteURLEntryField = createTextBox(initialValues.get(localeName));
sailorsInfoWebsiteURLEntryField.setVisibleLength(50);
result.put(localeName, sailorsInfoWebsiteURLEntryField);
}
return result;
}
} }
@@ -27,12 +27,9 @@ public class EventEditDialog extends EventDialog {
endDateBox.setFormat("dd/mm/yyyy hh:ii"); endDateBox.setFormat("dd/mm/yyyy hh:ii");
isPublicCheckBox = createCheckbox(""); isPublicCheckBox = createCheckbox("");
isPublicCheckBox.setValue(event.isPublic); isPublicCheckBox.setValue(event.isPublic);
id = event.id;
officialWebsiteURLEntryField = createTextBox(event.getOfficialWebsiteURL());
officialWebsiteURLEntryField.setVisibleLength(50);
baseURLEntryField = createTextBox(event.getBaseURL()); baseURLEntryField = createTextBox(event.getBaseURL());
baseURLEntryField.setVisibleLength(50); baseURLEntryField.setVisibleLength(50);
sailorsInfoWebsiteURLEntryFields = createTextBoxesForLocalesAndDefault(event.getSailorsInfoWebsiteURLs()); id = event.id;
courseAreaNameList.setValue(new ArrayList<>(event.venue.getCourseAreas())); courseAreaNameList.setValue(new ArrayList<>(event.venue.getCourseAreas()));
List<String> leaderboardGroupNames = new ArrayList<>(); List<String> leaderboardGroupNames = new ArrayList<>();
for(LeaderboardGroupDTO leaderboardGroupDTO: event.getLeaderboardGroups()) { for(LeaderboardGroupDTO leaderboardGroupDTO: event.getLeaderboardGroups()) {
@@ -41,5 +38,6 @@ public class EventEditDialog extends EventDialog {
leaderboardGroupList.setValue(leaderboardGroupNames); leaderboardGroupList.setValue(leaderboardGroupNames);
imagesListComposite.fillImages(event.getImages()); imagesListComposite.fillImages(event.getImages());
videosListComposite.fillVideos(event.getVideos()); videosListComposite.fillVideos(event.getVideos());
externalLinksComposite.fillExternalLinks(event);
} }
} }
@@ -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<String, TextBox> 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.<String, String> 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<String, TextBox> 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<String, String> getSailorsInfoWebsiteURLs() {
Map<String, String> sailorsInfoWebsiteURLsMap = new HashMap<>();
for (Map.Entry<String, TextBox> 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<String, String> initialValues = event.getSailorsInfoWebsiteURLs();
for (String localeName : GWTLocaleUtil.getAvailableLocalesAndDefault()) {
sailorsInfoWebsiteURLEntryFields.get(localeName).setValue(initialValues.get(localeName));
}
officialWebsiteURLEntryField.setValue(event.getOfficialWebsiteURL());
}
private Map<String, TextBox> createTextBoxesForLocalesAndDefault(Map<String, String> initialValues) {
Map<String, TextBox> 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;
}
}
@@ -1736,6 +1736,9 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String lastFix(); String lastFix();
String filterDeviceMappings(); String filterDeviceMappings();
String raceStartAnalysis(); String raceStartAnalysis();
String urlForLanguage(String language);
String urlForAllLanguages();
String externalLinks();
String buoyZoneRadiusInHullLengths(); String buoyZoneRadiusInHullLengths();
String incorrectValueForRegattaBuoyZoneRadiusInHullLengths(); String incorrectValueForRegattaBuoyZoneRadiusInHullLengths();
} }
@@ -1614,10 +1614,12 @@ raceAnalysis=Race analysis
startAnalysis=Start analysis startAnalysis=Start analysis
winningLanes=Winning lanes winningLanes=Winning lanes
maximumNumberOfDiscards=Maximum number of discards 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. 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"? 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. 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. chartSelectionHint=Hint: You can also use the leaderboard (on the LEADERBOARD tab) to select the competitors displayed in the chart.
chooseALanguage=Choose a language chooseALanguage=Choose a language
solutions=Solutions solutions=Solutions
@@ -1710,4 +1712,4 @@ lastFix=Last fix
filterDeviceMappings=Filter device mappings filterDeviceMappings=Filter device mappings
raceStartAnalysis=Race Start Analysis raceStartAnalysis=Race Start Analysis
buoyZoneRadiusInHullLengths=Buoy zone radius in hull lengths (e.g., 2 or 3) buoyZoneRadiusInHullLengths=Buoy zone radius in hull lengths (e.g., 2 or 3)
incorrectValueForRegattaBuoyZoneRadiusInHullLengths=Incorrect value for the buoy zone radius in hull lengths incorrectValueForRegattaBuoyZoneRadiusInHullLengths=Incorrect value for the buoy zone radius in hull lengths
@@ -1600,10 +1600,12 @@ raceAnalysis=Renn-Analyse
startAnalysis=Start-Analyse startAnalysis=Start-Analyse
winningLanes=Sieger-Kurse winningLanes=Sieger-Kurse
maximumNumberOfDiscards=Maximale Anzahl von Streichern 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. 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? warningOverrideRegattaConfigurationCourseDesignerToByMarks=Aktive Regattakonfiguration existiert bereits, verwendet aber den falschenKursdesigner.\nKursdesigner auf "By Marks" umstellen?
allRacesHaveBeenDenoted=Alle Rennen sind für Smartphone Tracking gekennzeichnet worden. 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. chartSelectionHint=Hinweis: Du kannst auch die Ergebnistabelle (im Menü ERGEBNISSE) nutzen, um die im Diagramm angezeigten Teilnehmer auszuwählen.
chooseALanguage=Wähle eine Sprache chooseALanguage=Wähle eine Sprache
solutions=Lösungen solutions=Lösungen
@@ -1612,6 +1612,9 @@ raceAnalysis=レース分析
startAnalysis=スタート分析 startAnalysis=スタート分析
winningLanes=勝利レーン winningLanes=勝利レーン
maximumNumberOfDiscards=カットレースの最大数 maximumNumberOfDiscards=カットレースの最大数
externalLinks=External links
urlForAllLanguages=URL for all languages
urlForLanguage=URL for language ''{0}''
notificationRegattaConfigurationUpdatedUsingByMarks="マーク別" コースデザイナーを使用して、有効なレガッタ設定が行われました。 notificationRegattaConfigurationUpdatedUsingByMarks="マーク別" コースデザイナーを使用して、有効なレガッタ設定が行われました。
warningOverrideRegattaConfigurationCourseDesignerToByMarks=有効なレガッタ設定がすでに登録されていますが、コースデザイナーの設定が適切でありません。\n"マーク別" へのコースデザイナー設定を上書きしますか。 warningOverrideRegattaConfigurationCourseDesignerToByMarks=有効なレガッタ設定がすでに登録されていますが、コースデザイナーの設定が適切でありません。\n"マーク別" へのコースデザイナー設定を上書きしますか。
allRacesHaveBeenDenoted=すべてのレースがレースログ追跡に表示されています。 allRacesHaveBeenDenoted=すべてのレースがレースログ追跡に表示されています。
@@ -1612,6 +1612,9 @@ raceAnalysis=Анализ гонки
startAnalysis=Начать анализ startAnalysis=Начать анализ
winningLanes=Выигрышные маршруты winningLanes=Выигрышные маршруты
maximumNumberOfDiscards=Максимальное число исключений maximumNumberOfDiscards=Максимальное число исключений
externalLinks=External links
urlForAllLanguages=URL for all languages
urlForLanguage=URL for language ''{0}''
notificationRegattaConfigurationUpdatedUsingByMarks=Активная конфигурация регаты была настроена с использованием дизайнера курса "По отметкам". notificationRegattaConfigurationUpdatedUsingByMarks=Активная конфигурация регаты была настроена с использованием дизайнера курса "По отметкам".
warningOverrideRegattaConfigurationCourseDesignerToByMarks=Активная конфигурация регаты уже существует, но с неверно выбранным дизайнером курса.\nПереопределить выбор дизайнера курса на "По отметкам"? warningOverrideRegattaConfigurationCourseDesignerToByMarks=Активная конфигурация регаты уже существует, но с неверно выбранным дизайнером курса.\nПереопределить выбор дизайнера курса на "По отметкам"?
allRacesHaveBeenDenoted=Отслеживание журнала всех гонок отменено. allRacesHaveBeenDenoted=Отслеживание журнала всех гонок отменено.
@@ -1610,7 +1610,9 @@ raceAnalysis=比赛轮次分析
startAnalysis=起航分析 startAnalysis=起航分析
winningLanes=获胜的船道 winningLanes=获胜的船道
maximumNumberOfDiscards=最大放弃次数 maximumNumberOfDiscards=最大放弃次数
externalLinks=External links
urlForAllLanguages=URL for all languages
urlForLanguage=URL for language ''{0}''
chartSelectionHint=提示:您还可以使用积分榜(在 LEADERBOARD 选项卡中)选择在记录中显示的参赛队。 chartSelectionHint=提示:您还可以使用积分榜(在 LEADERBOARD 选项卡中)选择在记录中显示的参赛队。
chooseALanguage=选择一种语言 chooseALanguage=选择一种语言
solutions=解决方案 solutions=解决方案
@@ -1612,6 +1612,9 @@ raceAnalysis=比赛轮次分析
startAnalysis=起航分析 startAnalysis=起航分析
winningLanes=获胜的船道 winningLanes=获胜的船道
maximumNumberOfDiscards=最大放弃次数 maximumNumberOfDiscards=最大放弃次数
externalLinks=External links
urlForAllLanguages=URL for all languages
urlForLanguage=URL for language ''{0}''
notificationRegattaConfigurationUpdatedUsingByMarks=已使用“按标志”场地设计器设置有效的比赛配置。 notificationRegattaConfigurationUpdatedUsingByMarks=已使用“按标志”场地设计器设置有效的比赛配置。
warningOverrideRegattaConfigurationCourseDesignerToByMarks=有效的比赛配置已存在,但是场地设计器设置错误。\n是否将场地设计器设置覆盖为“按标志”? warningOverrideRegattaConfigurationCourseDesignerToByMarks=有效的比赛配置已存在,但是场地设计器设置错误。\n是否将场地设计器设置覆盖为“按标志”?
allRacesHaveBeenDenoted=已为比赛轮次日志跟踪描述所有比赛轮次。 allRacesHaveBeenDenoted=已为比赛轮次日志跟踪描述所有比赛轮次。
@@ -21,9 +21,9 @@ import com.sap.sailing.domain.common.LeaderboardType;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO; 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.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.client.StringMessages;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies;
import com.sap.sailing.gwt.ui.shared.EventDTO; import com.sap.sailing.gwt.ui.shared.EventDTO;
@@ -45,7 +45,6 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint {
private String leaderboardName; private String leaderboardName;
private String leaderboardGroupName; private String leaderboardGroupName;
private LeaderboardType leaderboardType; private LeaderboardType leaderboardType;
private GlobalNavigationPanel globalNavigationPanel;
private EventDTO event; private EventDTO event;
@Override @Override
@@ -121,30 +120,15 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint {
private void createUI(boolean showRaceDetails, boolean embedded, boolean hideToolbar, EventDTO event) { private void createUI(boolean showRaceDetails, boolean embedded, boolean hideToolbar, EventDTO event) {
DockLayoutPanel mainPanel = new DockLayoutPanel(Unit.PX); DockLayoutPanel mainPanel = new DockLayoutPanel(Unit.PX);
RootLayoutPanel.get().add(mainPanel); RootLayoutPanel.get().add(mainPanel);
LogoAndTitlePanel logoAndTitlePanel = null;
if (!embedded) { if (!embedded) {
// Hack to shorten the leaderboardName in case of overall leaderboards // Hack to shorten the leaderboardName in case of overall leaderboards
String leaderboardDisplayName = Window.Location.getParameter("displayName"); String leaderboardDisplayName = Window.Location.getParameter("displayName");
if (leaderboardDisplayName == null || leaderboardDisplayName.isEmpty()) { if (leaderboardDisplayName == null || leaderboardDisplayName.isEmpty()) {
leaderboardDisplayName = leaderboardName; leaderboardDisplayName = leaderboardName;
} }
globalNavigationPanel = new GlobalNavigationPanel(getStringMessages(), true, null, leaderboardGroupName, event, null); SAPSailingHeaderWithAuthentication header = new SAPSailingHeaderWithAuthentication(leaderboardDisplayName);
logoAndTitlePanel = new LogoAndTitlePanel(leaderboardGroupName, leaderboardDisplayName, getStringMessages(), this, getUserService()) { new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView());
@Override mainPanel.addNorth(header, 75);
public void onResize() {
super.onResize();
if (isSmallWidth()) {
remove(globalNavigationPanel);
} else {
add(globalNavigationPanel);
}
}
};
logoAndTitlePanel.addStyleName("LogoAndTitlePanel");
if (!isSmallWidth()) {
logoAndTitlePanel.add(globalNavigationPanel);
}
mainPanel.addNorth(logoAndTitlePanel, 68);
} }
ScrollPanel contentScrollPanel = new ScrollPanel(); ScrollPanel contentScrollPanel = new ScrollPanel();
long delayBetweenAutoAdvancesInMilliseconds = DEFAULT_REFRESH_INTERVAL_MILLIS; long delayBetweenAutoAdvancesInMilliseconds = DEFAULT_REFRESH_INTERVAL_MILLIS;
@@ -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;
import com.google.gwt.view.client.SelectionChangeEvent.Handler; import com.google.gwt.view.client.SelectionChangeEvent.Handler;
import com.sap.sailing.domain.common.DetailType; 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.InvertibleComparator;
import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.MaxPointsReason; 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.DetailTypeColumn.LegDetailField;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO; 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;
import com.sap.sse.common.Util.Pair; import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.filter.BinaryOperator; import com.sap.sse.common.filter.BinaryOperator;
@@ -972,10 +974,10 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
new FormattedDoubleDetailTypeColumn(DetailType.RACE_GAP_TO_LEADER_IN_SECONDS, new RaceGapToLeaderInSeconds(), new FormattedDoubleDetailTypeColumn(DetailType.RACE_GAP_TO_LEADER_IN_SECONDS, new RaceGapToLeaderInSeconds(),
LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this));
result.put(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, result.put(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS,
new FormattedDoubleDetailTypeColumn(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, new CurrentSpeedOverGroundInKnots(), new FormattedDoubleDetailTypeColumn(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, new RaceCurrentSpeedOverGroundInKnots(),
LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this));
result.put(DetailType.RACE_CURRENT_RIDE_HEIGHT_IN_METERS, new RideHeightColumn(DetailType.RACE_CURRENT_RIDE_HEIGHT_IN_METERS, result.put(DetailType.RACE_CURRENT_RIDE_HEIGHT_IN_METERS, new RideHeightColumn(DetailType.RACE_CURRENT_RIDE_HEIGHT_IN_METERS,
new CurrentRideHeightInMeters(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); new RaceCurrentRideHeightInMeters(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this));
result.put(DetailType.RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS, result.put(DetailType.RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS,
new FormattedDoubleDetailTypeColumn(DetailType.RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS, new RaceDistanceToCompetitorFarthestAheadInMeters(), new FormattedDoubleDetailTypeColumn(DetailType.RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS, new RaceDistanceToCompetitorFarthestAheadInMeters(),
LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this)); LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this));
@@ -1098,26 +1100,13 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
private class RaceAverageSpeedInKnots implements LegDetailField<Double> { private class RaceAverageSpeedInKnots implements LegDetailField<Double> {
@Override @Override
public Double get(LeaderboardRowDTO row) { public Double get(LeaderboardRowDTO row) {
Double result = null; final Distance distanceTraveledInMeters = row.getDistanceTraveledInMeters(getRaceColumnName());
LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName()); final Duration time = row.getTimeSailed(getRaceColumnName());
if (fieldsForRace != null && fieldsForRace.legDetails != null) { final Double result;
double distanceTraveledInMeters = 0; if (distanceTraveledInMeters != null && time != null) {
long timeInMilliseconds = 0; result = distanceTraveledInMeters.inTime(time).getKnots();
for (LegEntryDTO legDetail : fieldsForRace.legDetails) { } else {
if (legDetail != null) { result = 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;
}
} }
return result; return result;
} }
@@ -1299,21 +1288,7 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
private class RaceDistanceTraveledInMeters implements LegDetailField<Double> { private class RaceDistanceTraveledInMeters implements LegDetailField<Double> {
@Override @Override
public Double get(LeaderboardRowDTO row) { public Double get(LeaderboardRowDTO row) {
Double result = null; return row.getDistanceTraveledInMeters(getRaceColumnName()).getMeters();
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;
} }
} }
@@ -1446,27 +1421,48 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
lastLegDetail = fieldsForRace.legDetails.get(--lastLegIndex); lastLegDetail = fieldsForRace.legDetails.get(--lastLegIndex);
} }
if (lastLegDetail != null) { if (lastLegDetail != null) {
result = get(lastLegDetail); if (lastLegDetail.finished) {
result = getAfterLastLegFinished(row);
} else {
result = getBeforeLastLegFinished(lastLegDetail);
}
} }
} }
} }
return result; return result;
} }
protected abstract T get(LegEntryDTO lastLegDetail); protected abstract T getBeforeLastLegFinished(LegEntryDTO currentLegDetail);
protected abstract T getAfterLastLegFinished(LeaderboardRowDTO row);
} }
private class CurrentSpeedOverGroundInKnots extends AbstractLastLegDetailField<Double> { private class RaceCurrentSpeedOverGroundInKnots extends AbstractLastLegDetailField<Double> {
@Override @Override
protected Double get(LegEntryDTO lastLegDetail) { protected Double getBeforeLastLegFinished(LegEntryDTO currentLegDetail) {
return lastLegDetail.currentSpeedOverGroundInKnots; return currentLegDetail.currentSpeedOverGroundInKnots;
}
@Override
protected Double getAfterLastLegFinished(LeaderboardRowDTO row) {
return new RaceAverageSpeedInKnots().get(row);
} }
} }
private class CurrentRideHeightInMeters extends AbstractLastLegDetailField<Double> { private class RaceCurrentRideHeightInMeters extends AbstractLastLegDetailField<Double> {
@Override @Override
protected Double get(LegEntryDTO lastLegDetail) { protected Double getBeforeLastLegFinished(LegEntryDTO currentLegDetail) {
return lastLegDetail.currentRideHeightInMeters; 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;
} }
} }
} }
@@ -76,7 +76,7 @@
font-size: 14px; font-size: 14px;
margin: 0 0 0 0; margin: 0 0 0 0;
padding: 2px 7px 2px 3px; padding: 2px 7px 2px 3px;
height: 18px; height: 22px;
display: inline-block; display: inline-block;
text-decoration: none; text-decoration: none;
-moz-border-radius: 5px 0 0 5px; -moz-border-radius: 5px 0 0 5px;
@@ -2941,7 +2941,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
final BravoFixTrack<Competitor> bravoFixTrack = trackedRace final BravoFixTrack<Competitor> bravoFixTrack = trackedRace
.<BravoFix, BravoFixTrack<Competitor>> getSensorTrack(competitor, BravoFixTrack.TRACK_NAME); .<BravoFix, BravoFixTrack<Competitor>> getSensorTrack(competitor, BravoFixTrack.TRACK_NAME);
if (bravoFixTrack != null) { if (bravoFixTrack != null) {
result = bravoFixTrack.getRideHeight(timePoint); final Distance rideHeight = bravoFixTrack.getRideHeight(timePoint);
result = rideHeight == null ? null : rideHeight.getMeters();
} }
break; break;
default: default:
@@ -4,6 +4,12 @@
<div id="mainContent"> <div id="mainContent">
<h4 class="articleHeadline">What's New - SAP Sailing Race Manager</h4> <h4 class="articleHeadline">What's New - SAP Sailing Race Manager</h4>
<div class="innerContent"> <div class="innerContent">
<h5 class="articleSubheadline">October 2016</h5>
<ul class="bulletList">
<li>Fixed missing German translations for some buttons and labels</li>
</ul>
<h5 class="articleSubheadline">September 2016</h5> <h5 class="articleSubheadline">September 2016</h5>
<ul class="bulletList"> <ul class="bulletList">
@@ -37,4 +37,5 @@
<inherits name="com.sap.sailing.gwt.ui.SourceAdminConsole" /> <inherits name="com.sap.sailing.gwt.ui.SourceAdminConsole" />
<inherits name='com.sap.sse.gwt.CommonControlCSS_Autoinject'/> <inherits name='com.sap.sse.gwt.CommonControlCSS_Autoinject'/>
<inherits name='com.sap.sailing.gwt.common.AuthenticationCommon'/>
</module> </module>
@@ -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.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarDataService; import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.polars.PolarsChangedListener; 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.domain.tracking.TrackedRace;
import com.sap.sailing.polars.PolarDataOperation; import com.sap.sailing.polars.PolarDataOperation;
import com.sap.sailing.polars.mining.BearingClusterGroup; import com.sap.sailing.polars.mining.BearingClusterGroup;
@@ -236,8 +237,14 @@ public class PolarDataServiceImpl implements PolarDataService,
@Override @Override
public void insertExistingFixes(TrackedRace trackedRace) { public void insertExistingFixes(TrackedRace trackedRace) {
for (Competitor competitor : trackedRace.getRace().getCompetitors()) { for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
for (GPSFixMoving fix : trackedRace.getTrack(competitor).getFixes()) { GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
competitorPositionChanged(fix, competitor, trackedRace); track.lockForRead();
try {
for (GPSFixMoving fix : track.getFixes()) {
competitorPositionChanged(fix, competitor, trackedRace);
}
} finally {
track.unlockAfterRead();
} }
} }
} }
@@ -9,8 +9,6 @@ import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.Response.Status;
import org.apache.commons.fileupload.FileItem; 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) * 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 @Override
protected void process(List<FileItem> fileItems, HttpServletRequest req, HttpServletResponse resp) throws UnsupportedEncodingException, IOException { protected void process(List<FileItem> fileItems, HttpServletRequest req, HttpServletResponse resp) throws UnsupportedEncodingException, IOException {
@@ -61,13 +59,16 @@ public class FileUploadServlet extends AbstractFileUploadServlet {
} }
try { try {
if (fileItem.getSize() > 1024 * 1024 * MAX_SIZE_IN_MB) { if (fileItem.getSize() > 1024 * 1024 * MAX_SIZE_IN_MB) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST) final String errorMessage = "Image is larger than " + MAX_SIZE_IN_MB + "MB";
.entity("Image is larger than " + MAX_SIZE_IN_MB + "MB").build()); 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) { } catch (IOException | OperationFailedException | InvalidPropertiesException | NoCorrespondingServiceRegisteredException e) {
final String errorMessage = "Could not store file"+ (e.getMessage()==null?"":(": " + e.getMessage())); final String errorMessage = "Could not store file"+ (e.getMessage()==null?"":(": " + e.getMessage()));
logger.log(Level.WARNING, "Could not store file", e); logger.log(Level.WARNING, "Could not store file", e);
@@ -19,12 +19,12 @@ public class BravoDataImportTest {
private final DoubleVectorFixImporter bravoDataImporter = new BravoDataImporterImpl(); private final DoubleVectorFixImporter bravoDataImporter = new BravoDataImporterImpl();
private int callbackCallCount = 0; private int callbackCallCount = 0;
private double sumRideHeight = 0.0; private double sumRideHeightInMeters = 0.0;
@Before @Before
public void setUp() { public void setUp() {
this.callbackCallCount = 0; this.callbackCallCount = 0;
this.sumRideHeight = 0.0; this.sumRideHeightInMeters = 0.0;
} }
@Test @Test
@@ -51,12 +51,12 @@ public class BravoDataImportTest {
bravoDataImporter.importFixes(importData.getInputStream(), (fixes, device) -> { bravoDataImporter.importFixes(importData.getInputStream(), (fixes, device) -> {
for (DoubleVectorFix fix : fixes) { for (DoubleVectorFix fix : fixes) {
callbackCallCount++; callbackCallCount++;
sumRideHeight += new BravoFixImpl(fix).getRideHeight(); sumRideHeightInMeters += new BravoFixImpl(fix).getRideHeight().getMeters();
} }
}, "filename", "source"); }, "filename", "source");
Assert.assertEquals(importData.expectedFixesCount, callbackCallCount); Assert.assertEquals(importData.expectedFixesCount, callbackCallCount);
Assert.assertEquals(importData.expectedAverageRideHeight, Assert.assertEquals(importData.expectedAverageRideHeight,
callbackCallCount == 0 ? sumRideHeight : sumRideHeight / callbackCallCount, 0.00001); callbackCallCount == 0 ? sumRideHeightInMeters : sumRideHeightInMeters / callbackCallCount, 0.00001);
} }
private enum ImportData { private enum ImportData {
@@ -3484,6 +3484,8 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
trackedRace.setPolarDataService(service); trackedRace.setPolarDataService(service);
service.insertExistingFixes(trackedRace); service.insertExistingFixes(trackedRace);
} }
} catch (Throwable e) {
logger.log(Level.SEVERE, "Error reconstructing the polars for tracked races", e);
} finally { } finally {
trackedRegatta.unlockTrackedRacesAfterRead(); trackedRegatta.unlockTrackedRacesAfterRead();
} }
@@ -67,22 +67,26 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
private static final long serialVersionUID = -11522605089325440L; private static final long serialVersionUID = -11522605089325440L;
private Regatta regatta; private Regatta regatta;
public DummyTrackedRace(final Serializable raceId, final Iterable<? extends Competitor> competitors, final Regatta regatta, final TrackedRegatta trackedRegatta, final WindStore windStore) { public DummyTrackedRace(final Serializable raceId, final Iterable<? extends Competitor> competitors,
this(competitors, regatta, trackedRegatta, new RaceDefinitionImpl("DummyRace", new CourseImpl("Dummy Course", Collections.<Waypoint>emptyList()), final Regatta regatta, final TrackedRegatta trackedRegatta, final WindStore windStore) {
regatta.getBoatClass(), competitors, raceId), windStore); this(competitors, regatta, trackedRegatta,
new RaceDefinitionImpl("DummyRace", new CourseImpl("Dummy Course", Collections.<Waypoint> emptyList()),
regatta.getBoatClass(), competitors, raceId),
windStore);
} }
public DummyTrackedRace(final Iterable<? extends Competitor> competitors, final Regatta regatta, final TrackedRegatta trackedRegatta, public DummyTrackedRace(final Iterable<? extends Competitor> competitors, final Regatta regatta,
RaceDefinition race, WindStore windStore) { final TrackedRegatta trackedRegatta, RaceDefinition race, WindStore windStore) {
super(race, trackedRegatta, windStore, -1); super(race, trackedRegatta, windStore, -1);
this.regatta = regatta; this.regatta = regatta;
} }
public DummyTrackedRace(final Iterable<? extends Competitor> competitors, final Regatta regatta, final TrackedRegatta trackedRegatta,
RaceDefinition race) { public DummyTrackedRace(final Iterable<? extends Competitor> competitors, final Regatta regatta,
this(competitors, regatta, trackedRegatta, race, EmptyWindStore.INSTANCE); final TrackedRegatta trackedRegatta, RaceDefinition race) {
this(competitors, regatta, trackedRegatta, race, EmptyWindStore.INSTANCE);
} }
public DummyTrackedRace(final String raceName, final Serializable raceId) { public DummyTrackedRace(final String raceName, final Serializable raceId) {
super(new RaceDefinitionImpl(raceName, new CourseImpl("Dummy Course", Collections.<Waypoint> emptyList()), super(new RaceDefinitionImpl(raceName, new CourseImpl("Dummy Course", Collections.<Waypoint> emptyList()),
/* boatClass */ null, new HashSet<Competitor>(), raceId), null, EmptyWindStore.INSTANCE, -1); /* boatClass */ null, new HashSet<Competitor>(), raceId), null, EmptyWindStore.INSTANCE, -1);
@@ -214,7 +218,8 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
} }
@Override @Override
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) { public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint,
MarkPositionAtTimePointCache markPositionCache) {
return null; return null;
} }
@@ -294,7 +299,7 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
public Tack getTack(Competitor competitor, TimePoint timePoint) throws NoWindException { public Tack getTack(Competitor competitor, TimePoint timePoint) throws NoWindException {
return null; return null;
} }
@Override @Override
public Tack getTack(SpeedWithBearing speedWithBearing, Wind wind, TimePoint timePoint) { public Tack getTack(SpeedWithBearing speedWithBearing, Wind wind, TimePoint timePoint) {
return null; return null;
@@ -346,7 +351,7 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
@Override @Override
public void removeListener(RaceChangeListener listener) { public void removeListener(RaceChangeListener listener) {
} }
@Override @Override
@@ -355,7 +360,8 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
} }
@Override @Override
public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint, WindPositionMode windPositionMode) { public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint,
WindPositionMode windPositionMode) {
return null; return null;
} }
@@ -382,12 +388,12 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
@Override @Override
public void setWindSourcesToExclude(Iterable<? extends WindSource> windSourcesToExclude) { public void setWindSourcesToExclude(Iterable<? extends WindSource> windSourcesToExclude) {
} }
@Override @Override
public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint,
throws NoWindException { boolean waitForLatestAnalysis) throws NoWindException {
return null; return null;
} }
@@ -431,8 +437,8 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
} }
@Override @Override
public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly, public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint from, TimePoint to,
boolean waitForLatestAnalyses) throws NoWindException { boolean upwindOnly, boolean waitForLatestAnalyses) throws NoWindException {
return null; return null;
} }
@@ -465,7 +471,7 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
@Override @Override
public void addCourseDesignChangedListener(CourseDesignChangedListener listener) { public void addCourseDesignChangedListener(CourseDesignChangedListener listener) {
} }
@Override @Override
@@ -596,13 +602,13 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
@Override @Override
public void setPolarDataService(PolarDataService polarDataService) { public void setPolarDataService(PolarDataService polarDataService) {
} }
@Override @Override
public Duration getEstimatedTimeToComplete(TimePoint timepoint) throws NotEnoughDataHasBeenAddedException, public Duration getEstimatedTimeToComplete(TimePoint timepoint)
NoWindException { throws NotEnoughDataHasBeenAddedException, NoWindException {
return null; return null;
} }
@Override @Override
public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint, public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint,
WindPositionMode windPositionMode, RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) { WindPositionMode windPositionMode, RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) {
@@ -640,7 +646,7 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
@Override @Override
public void updateStartAndEndOfTracking(boolean waitForGPSFixesToLoad) { public void updateStartAndEndOfTracking(boolean waitForGPSFixesToLoad) {
} }
@Override @Override
public <FixT extends SensorFix, TrackT extends SensorFixTrack<Competitor, FixT>> TrackT getSensorTrack( public <FixT extends SensorFix, TrackT extends SensorFixTrack<Competitor, FixT>> TrackT getSensorTrack(
Competitor competitor, String trackName) { Competitor competitor, String trackName) {
@@ -661,4 +667,9 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
public NavigableSet<MarkPassing> getUpdatedMarkPassings(Competitor competitor) { public NavigableSet<MarkPassing> getUpdatedMarkPassings(Competitor competitor) {
return null; return null;
} }
@Override
public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint) {
return null;
}
} }
@@ -29,6 +29,11 @@
can be used to determine the radius of the zone around the marks in terms of 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) 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.</li> based on the hull length as known from the boat class.</li>
<li>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.</li>
</ul> </ul>
<h2 class="articleSubheadline">September 2016</h2> <h2 class="articleSubheadline">September 2016</h2>
@@ -47,7 +47,7 @@ public class NaturalComparator implements Comparator<String> {
// Compare all-digit blocks as numbers - all-char blocks as strings // Compare all-digit blocks as numbers - all-char blocks as strings
if (Character.isDigit(aBlock.charAt(0)) && Character.isDigit(bBlock.charAt(0))) { 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 { } else {
result = aBlock.compareTo(bBlock); result = aBlock.compareTo(bBlock);
} }
@@ -460,20 +460,20 @@
<string name="not_available">N/A</string> <string name="not_available">N/A</string>
<string name="accuracy_timestamp">(vor %s)</string> <string name="accuracy_timestamp">(vor %s)</string>
<string name="prev_course">Vorheriger Kurs</string> <string name="prev_course">Vorheriger Kurs</string>
<string name="layout_course">Kurs Layout</string> <string name="layout_course">Kurs-Layout</string>
<string name="wind_degree">°</string> <string name="wind_degree">°</string>
<string name="add_waypoint">Wegpunkt hinzufügen</string> <string name="add_waypoint">Wegpunkt hinzufügen</string>
<string name="unpublished_course">Unpublished course design</string> <string name="unpublished_course">Unveröffentlichtes Kurs-Layout</string>
<string name="reset_state_1">Neu</string> <string name="reset_state_1">Neu</string>
<string name="reset_state_2">Zurücksetzen</string> <string name="reset_state_2">Zurücksetzen</string>
<string name="reset_message_1">Soll die Liste komplett gelöscht werden?</string> <string name="reset_message_1">Soll die Liste komplett gelöscht werden?</string>
<string name="reset_message_2">Soll die Liste zum letzten bekannten Stand zurückgesetzt werden?</string> <string name="reset_message_2">Soll die Liste zum letzten bekannten Stand zurückgesetzt werden?</string>
<string name="onboarding_scan">Scan QR-Code</string> <string name="onboarding_scan">QR-Code scannen</string>
<string name="onboarding_input">Manuelle Eingabe</string> <string name="onboarding_input">Manuelle Eingabe</string>
<string name="race_group">Group Race</string> <string name="race_group">Gruppenwettfahrt</string>
<string name="wind_select_race">Select Race</string> <string name="wind_select_race">Wettfahrt auswählen</string>
<string name="added_to_result_list">\'%1$s\' zur Ergebnisliste hinzugefügt an Position %2$s</string> <string name="added_to_result_list">\'%1$s\' zur Ergebnisliste hinzugefügt an Position %2$s</string>
<string name="get_started">Getting Started</string> <string name="get_started">Anmelden</string>
<string name="position">Position</string> <string name="position">Position</string>
<string name="server_deeplink_message">Server erfolgreich geändert auf %1$s.</string> <string name="server_deeplink_message">Server erfolgreich geändert auf %1$s.</string>
<string name="penalty">Strafe</string> <string name="penalty">Strafe</string>