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
+1028 -221

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
Bft12=Hurricane
RelativeScore=Relative Score (0 best; 1 worst)
RelativeScoreInPercent=Relative Score in %
RelativeScoreInRaceInPercent=Relative Score in Race in %
AbsoluteRank=Absolute Rank
WindSpeedInBeaufort=Wind Speed in Bft
DistanceAtStart=Distance to Start at Start
@@ -76,4 +78,7 @@ PassingSide=Passing Side
TackAtStart=Tack at Start
SpeedTenSecondsAfterStart=Speed 10s After Start
RankThirtySecondsAfterStart=Rank 30s After Start
StartAdvantage=Start Advantage
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
Bft12=Orkan
RelativeScore=Relative Wertung (0 am besten; 1 am schlechtesten)
RelativeScoreInPercent=Relative Wertung in %
RelativeScoreInRaceInPercent=Relative Wertung im Rennen in %
AbsoluteRank=Absolute Platzierung
WindSpeedInBeaufort=Windgeschwindigkeit in Bft.
DistanceAtStart=Distanz zur Startlinie zum Startzeitpunkt
@@ -76,4 +78,7 @@ PassingSide=Passierende Seite
TackAtStart=Startbug
SpeedTenSecondsAfterStart=Geschwindigkeit 10s nach dem Startzeitpunkt
RankThirtySecondsAfterStart=Absolute Platzierung 30s nach dem Startzeitpunkt
StartAdvantage=Startvorteil
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.ParallelDistanceMinAggregationProcessor;
import com.sap.sailing.datamining.impl.components.aggregators.ParallelDistanceSumAggregationProcessor;
import com.sap.sailing.domain.common.Speed;
import com.sap.sse.datamining.DataSourceProvider;
import com.sap.sse.datamining.components.AggregationProcessorDefinition;
import com.sap.sse.datamining.components.DataRetrieverChainDefinition;
import com.sap.sse.datamining.data.ClusterGroup;
import com.sap.sse.datamining.impl.AbstractDataMiningActivatorWithPredefinedQueries;
import com.sap.sse.datamining.shared.dto.StatisticQueryDefinitionDTO;
import com.sap.sse.datamining.shared.impl.PredefinedQueryIdentifier;
@@ -127,8 +125,8 @@ public class Activator extends AbstractDataMiningActivatorWithPredefinedQueries
dataSourceProviders.add(new RacingEventServiceProvider(context));
}
public static ClusterGroup<Speed> getWindStrengthInBeaufortClusterGroup() {
return clusterGroups.getWindStrengthInBeaufortCluster();
public static SailingClusterGroups getClusterGroups() {
return clusterGroups;
}
public static Activator getDefault() {
@@ -3,6 +3,9 @@ package com.sap.sailing.datamining;
import java.util.ArrayList;
import java.util.Collection;
import com.sap.sailing.datamining.data.ClusterFormatter;
import com.sap.sailing.datamining.impl.data.LinearDoubleClusterGroup;
import com.sap.sailing.datamining.impl.data.PercentageClusterFormatter;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sse.datamining.data.Cluster;
@@ -17,7 +20,10 @@ import com.sap.sse.datamining.impl.data.LocalizedCluster;
public class SailingClusterGroups {
private final ClusterGroup<Speed> windStrengthInBeaufortCluster;
private final ClusterGroup<Speed> windStrengthInBeaufortClusterGroup;
private final ClusterGroup<Double> percentageClusterGroup;
private final ClusterFormatter<Double> percentageClusterFormatter;
public SailingClusterGroups() {
Collection<Cluster<Speed>> clusters = new ArrayList<>();
@@ -98,11 +104,22 @@ public class SailingClusterGroups {
lowerBound = new ComparableClusterBoundary<Speed>(lowerBoundWindSpeed, ComparisonStrategy.GREATER_EQUALS_THAN);
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() {
return windStrengthInBeaufortCluster;
public ClusterGroup<Speed> getWindStrengthInBeaufortClusterGroup() {
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.Dimension;
import com.sap.sse.datamining.annotations.Statistic;
import com.sap.sse.datamining.shared.impl.dto.ClusterDTO;
public interface HasRaceOfCompetitorContext {
@@ -23,43 +24,52 @@ public interface HasRaceOfCompetitorContext {
@Dimension(messageKey="TackAtStart", ordinal=12)
public Tack getTackAtStart() throws NoWindException;
@Dimension(messageKey="DistanceToStarboardSideAtStart", ordinal=13)
public ClusterDTO getPercentageClusterForDistanceToStarboardSideAtStart();
@Dimension(messageKey="RelativeScoreInPercent", ordinal=14)
public ClusterDTO getPercentageClusterForRelativeScore();
@Statistic(messageKey="DistanceAtStart", resultDecimals=2, ordinal=0)
public Distance getDistanceToStartLineAtStart();
@Statistic(messageKey="DistanceToStarboardSideAtStart", resultDecimals=2, ordinal=1)
public Double getNormalizedDistanceToStarboardSideAtStart();
// @Statistic(messageKey="StartAdvantage", resultDecimals=2, ordinal=2)
// public Double getNormalizedDistanceToFavoredEndAtStart();
@Statistic(messageKey="WindwardDistanceToAdvantageousEndOfLine", resultDecimals=2, ordinal=2)
public Distance getWindwardDistanceToAdvantageousLineEndAtStart();
@Connector(messageKey="SpeedWhenStarting", ordinal=2)
@Connector(messageKey="SpeedWhenStarting", ordinal=3)
public Speed getSpeedWhenStarting();
@Connector(messageKey="SpeedTenSecondsBeforeStart", ordinal=3)
@Connector(messageKey="SpeedTenSecondsBeforeStart", ordinal=4)
public Speed getSpeedTenSecondsBeforeStart();
@Connector(messageKey="SpeedTenSecondsAfterStart", ordinal=4)
@Connector(messageKey="SpeedTenSecondsAfterStart", ordinal=5)
public Speed getSpeedTenSecondsAfterStart();
@Statistic(messageKey="RankThirtySecondsAfterStart", resultDecimals=2, ordinal=5)
public Double getAbsoluteRankThirtySecondsAfterStart();
@Statistic(messageKey="RankThirtySecondsAfterStart", resultDecimals=2, ordinal=6)
public Double getRankThirtySecondsAfterStart();
@Statistic(messageKey="RankAtFirstMark", resultDecimals=2, ordinal=6)
@Statistic(messageKey="RankAfterHalfOfTheFirstLeg", resultDecimals=2, ordinal=7)
public Double getRankAfterHalfOfTheFirstLeg();
@Statistic(messageKey="RankAtFirstMark", resultDecimals=2, ordinal=8)
public Double getRankAtFirstMark();
@Statistic(messageKey="RankGainsOrLossesBetweenFirstMarkAndFinish", resultDecimals=2, ordinal=7)
@Statistic(messageKey="RankGainsOrLossesBetweenFirstMarkAndFinish", resultDecimals=2, ordinal=9)
public Double getRankGainsOrLossesBetweenFirstMarkAndFinish();
@Statistic(messageKey="NumberOfManeuvers", resultDecimals=0, ordinal=8)
@Statistic(messageKey="NumberOfManeuvers", resultDecimals=0, ordinal=10)
public int getNumberOfManeuvers();
@Statistic(messageKey="NumberOfTacks", resultDecimals=2, ordinal=9)
@Statistic(messageKey="NumberOfTacks", resultDecimals=2, ordinal=11)
public int getNumberOfTacks();
@Statistic(messageKey="NumberOfJibes", resultDecimals=2, ordinal=10)
@Statistic(messageKey="NumberOfJibes", resultDecimals=2, ordinal=12)
public int getNumberOfJibes();
@Statistic(messageKey="NumberOfPenaltyCircles", resultDecimals=2, ordinal=11)
@Statistic(messageKey="NumberOfPenaltyCircles", resultDecimals=2, ordinal=13)
public int getNumberOfPenaltyCircles();
}
@@ -6,6 +6,7 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension;
import com.sap.sse.datamining.annotations.Statistic;
import com.sap.sse.datamining.shared.impl.dto.ClusterDTO;
public interface HasTrackedLegOfCompetitorContext {
@@ -17,6 +18,9 @@ public interface HasTrackedLegOfCompetitorContext {
@Dimension(messageKey="CompetitorSearchTag", ordinal=11) // TODO Clean me: Move Dimension to Competitor when possible
public String getCompetitorSearchTag();
@Dimension(messageKey="RelativeScoreInRaceInPercent", ordinal=12)
public ClusterDTO getPercentageClusterForRelativeScoreInRace();
@Connector(messageKey="Competitor")
public Competitor getCompetitor();
@@ -32,4 +36,7 @@ public interface HasTrackedLegOfCompetitorContext {
@Statistic(messageKey="AbsoluteRank", resultDecimals=2, ordinal=3)
public Double getAbsoluteRank();
@Statistic(messageKey="TimeSpentInSeconds", resultDecimals=0, ordinal=4)
public Long getTimeTakenInSeconds();
}
@@ -1,10 +1,12 @@
package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.CourseArea;
import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension;
@@ -35,7 +37,10 @@ public interface HasTrackedRaceContext {
@Dimension(messageKey="Year", ordinal=2)
public Integer getYear();
@Dimension(messageKey="IsTracked", ordinal=6)
@Dimension(messageKey="AdvantageousEndOfLine", ordinal=6)
public NauticalSide getAdvantageousEndOfLine();
@Dimension(messageKey="IsTracked", ordinal=7)
public Boolean isTracked();
@Statistic(messageKey="NumberOfCompetitorFixes", resultDecimals=0, ordinal=0)
@@ -44,4 +49,9 @@ public interface HasTrackedRaceContext {
@Statistic(messageKey="NumberOfMarkFixes", resultDecimals=0, ordinal=1)
public int getNumberOfMarkFixes();
// Convenience methods for race dependent calculation to avoid code duplication
public Double getRelativeScoreForCompetitor(Competitor competitor);
public Double getRankAtFinishForCompetitor(Competitor competitor);
}
@@ -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
public ClusterDTO getWindStrengthAsBeaufortCluster(Locale locale, ResourceBundleStringMessages stringMessages) {
Wind wind = getWind();
Cluster<?> cluster = Activator.getWindStrengthInBeaufortClusterGroup().getClusterFor(wind);
Cluster<?> cluster = Activator.getClusterGroups().getWindStrengthInBeaufortClusterGroup().getClusterFor(wind);
return new ClusterDTO(cluster.asLocalizedString(locale, stringMessages));
}
@@ -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 com.sap.sailing.datamining.Activator;
import com.sap.sailing.datamining.SailingClusterGroups;
import com.sap.sailing.datamining.data.HasRaceOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Course;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.LineDetails;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindPositionMode;
import com.sap.sse.common.TimePoint;
import com.sap.sse.datamining.data.Cluster;
import com.sap.sse.datamining.shared.impl.dto.ClusterDTO;
public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
@@ -52,6 +64,18 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
return startOfRace == null ? null : getTrackedRace().getTack(getCompetitor(), startOfRace);
}
@Override
public ClusterDTO getPercentageClusterForDistanceToStarboardSideAtStart() {
Double normalizedDistance = getNormalizedDistanceToStarboardSideAtStart();
if (normalizedDistance == null) {
return null;
}
SailingClusterGroups clusterGroups = Activator.getClusterGroups();
Cluster<Double> cluster = clusterGroups.getPercentageClusterGroup().getClusterFor(normalizedDistance);
return new ClusterDTO(clusterGroups.getPercentageClusterFormatter().format(cluster));
}
@Override
public Distance getDistanceToStartLineAtStart() {
return getTrackedRace().getDistanceToStartLine(getCompetitor(), 0);
@@ -71,6 +95,46 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
return distance / length;
}
@Override
public Distance getWindwardDistanceToAdvantageousLineEndAtStart() {
TrackedRace trackedRace = getTrackedRace();
TimePoint startOfRace = trackedRace.getStartOfRace();
LineDetails startLine = trackedRace.getStartLine(startOfRace);
Mark advantageousMark = null;
switch (startLine.getAdvantageousSideWhileApproachingLine()) {
case PORT:
advantageousMark = startLine.getPortMarkWhileApproachingLine();
break;
case STARBOARD:
advantageousMark = startLine.getStarboardMarkWhileApproachingLine();
break;
}
if (advantageousMark == null) {
return null;
}
GPSFixTrack<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
public Speed getSpeedWhenStarting() {
return getTrackedRace().getSpeedWhenCrossingStartLine(getCompetitor());
@@ -91,7 +155,7 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
}
@Override
public Double getAbsoluteRankThirtySecondsAfterStart() {
public Double getRankThirtySecondsAfterStart() {
TimePoint startOfRace = getTrackedRace().getStartOfRace();
if (startOfRace == null) {
return null;
@@ -101,6 +165,21 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
return rank == 0 ? null : Double.valueOf(rank);
}
@Override
public Double getRankAfterHalfOfTheFirstLeg() {
Course course = getTrackedRace().getRace().getCourse();
TrackedLegOfCompetitor trackedLeg = getTrackedRace().getTrackedLeg(getCompetitor(), course.getFirstLeg());
TimePoint startTime = trackedLeg.getStartTime();
TimePoint finishTime = trackedLeg.getFinishTime();
if (startTime == null || finishTime == null) {
return null;
}
long halfOffset = (finishTime.asMillis() - startTime.asMillis()) / 2;
int rank = getTrackedRace().getRank(getCompetitor(), startTime.plus(halfOffset));
return rank == 0 ? null : Double.valueOf(rank);
}
@Override
public Double getRankAtFirstMark() {
Course course = getTrackedRace().getRace().getCourse();
@@ -113,15 +192,10 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
@Override
public Double getRankGainsOrLossesBetweenFirstMarkAndFinish() {
Double rankAtFirstMark = getRankAtFirstMark();
Double rankAtFinish = getRankAtFinish();
Double rankAtFinish = getTrackedRaceContext().getRankAtFinishForCompetitor(getCompetitor());
return rankAtFirstMark != null && rankAtFinish != null ? rankAtFirstMark - rankAtFinish : null;
}
private Double getRankAtFinish() {
int rank = getTrackedRace().getRank(getCompetitor(), getTrackedRace().getEndOfTracking());
return rank == 0 ? null : Double.valueOf(rank);
}
@Override
public int getNumberOfManeuvers() {
return getNumberOfTacks() + getNumberOfJibes();
@@ -1,5 +1,7 @@
package com.sap.sailing.datamining.impl.data;
import com.sap.sailing.datamining.Activator;
import com.sap.sailing.datamining.SailingClusterGroups;
import com.sap.sailing.datamining.data.HasTrackedLegContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.domain.base.Competitor;
@@ -9,6 +11,8 @@ import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.datamining.data.Cluster;
import com.sap.sse.datamining.shared.impl.dto.ClusterDTO;
public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompetitorContext {
@@ -48,6 +52,18 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti
return getCompetitor().getSearchTag();
}
@Override
public ClusterDTO getPercentageClusterForRelativeScoreInRace() {
Double relativeScore = getTrackedLegContext().getTrackedRaceContext().getRelativeScoreForCompetitor(getCompetitor());
if (relativeScore == null) {
return null;
}
SailingClusterGroups clusterGroups = Activator.getClusterGroups();
Cluster<Double> cluster = clusterGroups.getPercentageClusterGroup().getClusterFor(relativeScore);
return new ClusterDTO(clusterGroups.getPercentageClusterFormatter().format(cluster));
}
@Override
public Distance getDistanceTraveled() {
TimePoint timePoint = getTrackedLegContext().getTrackedRaceContext().getTrackedRace().getEndOfTracking();
@@ -94,4 +110,15 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti
return rankAtFinish;
}
@Override
public Long getTimeTakenInSeconds() {
TimePoint startTime = getTrackedLegOfCompetitor().getStartTime();
TimePoint finishTime = getTrackedLegOfCompetitor().getFinishTime();
if (startTime == null || finishTime == null) {
return null;
}
return (finishTime.asMillis() - startTime.asMillis()) / 1000;
}
}
@@ -11,9 +11,11 @@ import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.LineDetails;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
@@ -91,6 +93,12 @@ public class TrackedRaceWithContext implements HasTrackedRaceContext {
return calendar.get(Calendar.YEAR);
}
@Override
public NauticalSide getAdvantageousEndOfLine() {
LineDetails startLine = getTrackedRace().getStartLine(getTrackedRace().getStartOfRace());
return startLine.getAdvantageousSideWhileApproachingLine();
}
@Override
public Boolean isTracked() {
return getTrackedRace().hasStarted(MillisecondsTimePoint.now());
@@ -126,4 +134,19 @@ public class TrackedRaceWithContext implements HasTrackedRaceContext {
return number;
}
// Convenience methods for race dependent calculation to avoid code duplication
public Double getRelativeScoreForCompetitor(Competitor competitor) {
Double rankAtFinish = getRankAtFinishForCompetitor(competitor);
if (rankAtFinish == null) {
return null;
}
return rankAtFinish / Util.size(getTrackedRace().getRace().getCompetitors());
}
@Override
public Double getRankAtFinishForCompetitor(Competitor competitor) {
int rank = getTrackedRace().getRank(competitor, getTrackedRace().getEndOfTracking());
return rank == 0 ? null : Double.valueOf(rank);
}
}
@@ -3,10 +3,13 @@ package com.sap.sailing.domain.common.dto;
import java.io.Serializable;
import java.util.List;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sse.common.Duration;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
/**
* Holds a single competitor's scoring details for a single race. It may optionally contain
@@ -78,6 +81,8 @@ public class LeaderboardEntryDTO implements Serializable {
*/
public Duration averageSamplingInterval;
public Double averageRideHeightInMeters;
/**
* The time gap to the competitor leading the race; for one-design races this is the time the competitor is expected
* to need to reach the leader's (windward) position (if in the same leg) or the difference between the time at
@@ -133,6 +138,44 @@ public class LeaderboardEntryDTO implements Serializable {
return totalPointsCorrected || (reasonForMaxPoints != null && reasonForMaxPoints != MaxPointsReason.NONE);
}
public Duration getTimeSailedInMillis() {
final Duration result;
if (legDetails != null) {
long timeInMilliseconds = 0;
for (LegEntryDTO legDetail : legDetails) {
if (legDetail != null) {
if (legDetail.distanceTraveledInMeters != null && legDetail.timeInMilliseconds != null) {
timeInMilliseconds += legDetail.timeInMilliseconds;
} else {
timeInMilliseconds = 0;
break;
}
}
}
result = new MillisecondsDurationImpl(timeInMilliseconds);
} else {
result = null;
}
return result;
}
public Distance getDistanceTraveledInMeters() {
Distance result = null;
if (legDetails != null) {
for (LegEntryDTO legDetail : legDetails) {
if (legDetail != null) {
if (legDetail.distanceTraveledInMeters != null) {
if (result == null) {
result = Distance.NULL;
}
result = result.add(new MeterDistance(legDetail.distanceTraveledInMeters));
}
}
}
}
return result;
}
@Override
public int hashCode() {
final int prime = 31;
@@ -174,6 +217,7 @@ public class LeaderboardEntryDTO implements Serializable {
result = prime * result
+ ((speedOverGroundAtStartOfRaceInKnots == null) ? 0 : speedOverGroundAtStartOfRaceInKnots.hashCode());
result = prime * result + ((startTack == null) ? 0 : startTack.hashCode());
result = prime * result + ((averageRideHeightInMeters == null) ? 0 : averageRideHeightInMeters.hashCode());
result = prime * result + ((averageSamplingInterval == null) ? 0 : averageSamplingInterval.hashCode());
result = prime * result + ((timeSinceLastPositionFixInSeconds == null) ? 0 : timeSinceLastPositionFixInSeconds.hashCode());
result = prime * result + ((netPoints == null) ? 0 : netPoints.hashCode());
@@ -282,6 +326,11 @@ public class LeaderboardEntryDTO implements Serializable {
return false;
if (startTack != other.startTack)
return false;
if (averageRideHeightInMeters == null) {
if (other.averageRideHeightInMeters != null)
return false;
} else if (!averageRideHeightInMeters.equals(other.averageRideHeightInMeters))
return false;
if (averageSamplingInterval == null) {
if (other.averageSamplingInterval != null)
return false;
@@ -4,6 +4,9 @@ import java.io.Serializable;
import java.util.Date;
import java.util.Map;
import com.sap.sailing.domain.common.Distance;
import com.sap.sse.common.Duration;
/**
* Holds data about one competitor and all races represented by the owning {@link LeaderboardDTO leaderboard}.
*
@@ -117,6 +120,28 @@ public class LeaderboardRowDTO implements Serializable {
return true;
}
public Distance getDistanceTraveledInMeters(String raceColumnName) {
final Distance result;
LeaderboardEntryDTO fieldsForRace = fieldsByRaceColumnName.get(raceColumnName);
if (fieldsForRace != null) {
result = fieldsForRace.getDistanceTraveledInMeters();
} else {
result = null;
}
return result;
}
public Duration getTimeSailed(String raceColumnName) {
final Duration result;
LeaderboardEntryDTO fieldsForRace = fieldsByRaceColumnName.get(raceColumnName);
if (fieldsForRace != null) {
result = fieldsForRace.getTimeSailedInMillis();
} else {
result = null;
}
return result;
}
@Override
public String toString() {
return "LeaderboardRowDTO for competitor "+competitor;
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.common.tracking;
import com.sap.sailing.domain.common.Distance;
/**
* Specific {@link SensorFix} for bravo devices (http://www.bravosystems.es/).
*
@@ -12,5 +14,5 @@ public interface BravoFix extends SensorFix {
*
* @return the ride height
*/
double getRideHeight();
Distance getRideHeight();
}
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.common.tracking.impl;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.BravoFix;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
@@ -32,8 +34,8 @@ public class BravoFixImpl implements BravoFix {
}
@Override
public double getRideHeight() {
return fix.get(BravoSensorDataMetadata.INSTANCE.rideHeightColumn);
public Distance getRideHeight() {
return new MeterDistance(fix.get(BravoSensorDataMetadata.INSTANCE.rideHeightColumn));
}
}
@@ -49,9 +49,11 @@ import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.WaypointImpl;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
@@ -95,9 +97,9 @@ public class SensorFixStoreAndLoadTest {
private static final long FIX_TIMESTAMP2 = 120;
private static final long FIX_TIMESTAMP3 = 210;
private static final long AFTER_LAST_FIX = FIX_TIMESTAMP3 + 1;
private static final double FIX_RIDE_HEIGHT = 1337.0;
private static final double FIX_RIDE_HEIGHT2 = 1338.0;
private static final double FIX_RIDE_HEIGHT3 = 1336.0;
private static final Distance FIX_RIDE_HEIGHT = new MeterDistance(1337.0);
private static final Distance FIX_RIDE_HEIGHT2 = new MeterDistance(1338.0);
private static final Distance FIX_RIDE_HEIGHT3 = new MeterDistance(1336.0);
private static final double FIX_TEST_VALUE = 12.0;
protected final MockSmartphoneImeiServiceFinderFactory serviceFinderFactory = new MockSmartphoneImeiServiceFinderFactory();
protected final DeviceIdentifier device = new SmartphoneImeiIdentifier("a");
@@ -384,20 +386,14 @@ public class SensorFixStoreAndLoadTest {
public void testBravoFixIsCorrectlyWrapped() throws InterruptedException {
regattaLog.add(new RegattaLogDeviceCompetitorBravoMappingEventImpl(new MillisecondsTimePoint(3), author, comp,
device, new MillisecondsTimePoint(START_OF_TRACKING), new MillisecondsTimePoint(MID_OF_TRACKING)));
addBravoFixes();
FixLoaderAndTracker fixLoaderAndTracker = createFixLoaderAndTracker();
trackedRace.attachRaceLog(raceLog);
trackedRace.attachRegattaLog(regattaLog);
trackedRace.waitForLoadingToFinish();
BravoFixTrack<Competitor> bravoFixTrack = trackedRace.getSensorTrack(comp, BravoFixTrack.TRACK_NAME);
assertEquals(FIX_RIDE_HEIGHT,
bravoFixTrack.getFirstFixAtOrAfter(new MillisecondsTimePoint(FIX_TIMESTAMP)).getRideHeight());
fixLoaderAndTracker.stop(true);
}
@@ -482,9 +478,9 @@ public class SensorFixStoreAndLoadTest {
}
private void addBravoFixes() {
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP, FIX_RIDE_HEIGHT));
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2));
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP3, FIX_RIDE_HEIGHT3));
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP, FIX_RIDE_HEIGHT.getMeters()));
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP2, FIX_RIDE_HEIGHT2.getMeters()));
store.storeFix(device, createBravoDoubleVectorFixWithRideHeight(FIX_TIMESTAMP3, FIX_RIDE_HEIGHT3.getMeters()));
}
private FixLoaderAndTracker createFixLoaderAndTracker() {
@@ -541,15 +541,12 @@ public class MockedTrackedRace implements DynamicTrackedRace {
@Override
public boolean isControlTrackingFromStartAndFinishTimes() {
// TODO Auto-generated method stub
return false;
}
@Override
public void setControlTrackingFromStartAndFinishTimes(
boolean controlTrackingFromStartAndFinishTimes) {
// TODO Auto-generated method stub
}
};
}
@@ -1135,4 +1132,9 @@ public class MockedTrackedRace implements DynamicTrackedRace {
public NavigableSet<MarkPassing> getUpdatedMarkPassings(Competitor competitor) {
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) {
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);
}
private void assertRideHeight(BravoFixTrack<Competitor> track, TimePoint timePoint, double expected) {
assertEquals(expected, track.getRideHeight(timePoint), 0.0);
private void assertRideHeight(BravoFixTrack<Competitor> track, TimePoint timePoint, double expectedRideHeightInMeters) {
assertEquals(expectedRideHeightInMeters, track.getRideHeight(timePoint).getMeters(), 0.0);
}
@SuppressWarnings("unchecked")
@@ -1437,6 +1437,8 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
} else {
entryDTO.timeSinceLastPositionFixInSeconds = null;
}
final Distance averageRideHeight = trackedRace.getAverageRideHeight(competitor, timePoint);
entryDTO.averageRideHeightInMeters = averageRideHeight == null ? null : averageRideHeight.getMeters();
}
if (addLegDetails && trackedRace != null) {
try {
@@ -1721,11 +1723,13 @@ public abstract class AbstractSimpleLeaderboardImpl implements Leaderboard, Race
Double speedOverGroundInKnots;
if (trackedLeg.hasFinishedLeg(timePoint)) {
speedOverGroundInKnots = averageSpeedOverGround == null ? null : averageSpeedOverGround.getKnots();
result.currentRideHeightInMeters = trackedLeg.getAverageRideHeight(timePoint);
final Distance averageRideHeight = trackedLeg.getAverageRideHeight(timePoint);
result.currentRideHeightInMeters = averageRideHeight == null ? null : averageRideHeight.getMeters();
} else {
final SpeedWithBearing speedOverGround = trackedLeg.getSpeedOverGround(timePoint);
speedOverGroundInKnots = speedOverGround == null ? null : speedOverGround.getKnots();
result.currentRideHeightInMeters = trackedLeg.getRideHeight(timePoint);
final Distance rideHeight = trackedLeg.getRideHeight(timePoint);
result.currentRideHeightInMeters = rideHeight == null ? null : rideHeight.getMeters();
}
result.currentSpeedOverGroundInKnots = speedOverGroundInKnots == null ? null : speedOverGroundInKnots;
Distance distanceTraveled = trackedLeg.getDistanceTraveled(timePoint);
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.tracking;
import java.io.Serializable;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.tracking.BravoFix;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.WithID;
@@ -22,7 +23,7 @@ public interface BravoFixTrack<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
* where we have fixes for.
*/
Double getRideHeight(TimePoint timePoint);
Distance getRideHeight(TimePoint timePoint);
/**
* Calculates the average ride height for the given time range.
@@ -34,5 +35,5 @@ public interface BravoFixTrack<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
* 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
* 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
@@ -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
* 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
@@ -47,6 +47,7 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.common.tracking.SensorFix;
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
import com.sap.sailing.domain.ranking.RankingMetric;
import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo;
import com.sap.sailing.domain.tracking.impl.NonCachingMarkPositionAtTimePointCache;
@@ -691,6 +692,8 @@ public interface TrackedRace extends Serializable, IsManagedByCache<SharedDomain
Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly,
boolean waitForLatestAnalysis) throws NoWindException;
public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint);
WindStore getWindStore();
Competitor getOverallLeader(TimePoint timePoint);
@@ -1,10 +1,8 @@
package com.sap.sailing.domain.tracking.impl;
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.tracking.BravoFix;
import com.sap.sailing.domain.tracking.BravoFixTrack;
@@ -34,7 +32,7 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
}
@Override
public Double getRideHeight(TimePoint timePoint) {
public Distance getRideHeight(TimePoint timePoint) {
BravoFix fixAfter = getFirstFixAtOrAfter(timePoint);
if (fixAfter != null && fixAfter.getTimePoint().compareTo(timePoint) == 0) {
// exact match of timepoint -> no interpolation necessary
@@ -54,17 +52,24 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
}
@Override
public Double getAverageRideHeight(TimePoint from, TimePoint to) {
public Distance getAverageRideHeight(TimePoint from, TimePoint to) {
final Distance result;
lockForRead();
try {
Spliterator<BravoFix> fixes = getFixes(from, true, to, true).spliterator();
OptionalDouble average = StreamSupport.stream(fixes, false).mapToDouble(BravoFix::getRideHeight).average();
if (average.isPresent()) {
return average.getAsDouble();
Distance sum = Distance.NULL;
int count = 0;
for (final BravoFix fix : getFixes(from, true, to, true)) {
sum = sum.add(fix.getRideHeight());
count++;
}
if (count > 0) {
result = sum.scale(1./count);
} else {
result = null;
}
} finally {
unlockAfterRead();
}
return null;
return result;
}
}
@@ -174,8 +174,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
}
if (timePointToUse != null) {
Distance d = getDistanceTraveled(timePointToUse);
long millis = timePointToUse.asMillis() - legStart.getTimePoint().asMillis();
result = d.inTime(millis);
result = d.inTime(legStart.getTimePoint().until(timePointToUse));
} else {
result = null;
}
@@ -184,7 +183,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
}
@Override
public Double getAverageRideHeight(TimePoint timePoint) {
public Distance getAverageRideHeight(TimePoint timePoint) {
MarkPassing legStart = getMarkPassingForLegStart();
if (legStart != null) {
BravoFixTrack<Competitor> track = getTrackedRace()
@@ -677,15 +676,17 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
}
@Override
public Double getRideHeight(TimePoint at) {
public Distance getRideHeight(TimePoint at) {
final Distance result;
if (hasStartedLeg(at)) {
TimePoint timePoint =hasFinishedLeg(at) ? getMarkPassingForLegEnd().getTimePoint() : at;
BravoFixTrack<Competitor> track = getTrackedRace()
.<BravoFix, BravoFixTrack<Competitor>> getSensorTrack(competitor, BravoFixTrack.TRACK_NAME);
return track == null ? null : track.getRideHeight(timePoint);
result = track == null ? null : track.getRideHeight(timePoint);
} else {
return null;
result = null;
}
return result;
}
@Override
@@ -1548,6 +1548,22 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
return result;
}
@Override
public Distance getAverageRideHeight(Competitor competitor, TimePoint timePoint) {
final Distance result;
BravoFixTrack<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
public TrackedLegOfCompetitor getCurrentLeg(Competitor competitor, TimePoint timePoint) {
// 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) {
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.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.sap.sailing.gwt.autoplay.client.shared.leaderboard.LeaderboardWithHeaderPerspectiveLifecycle;
import com.sap.sailing.gwt.autoplay.client.shared.leaderboard.LeaderboardWithHeaderPerspectiveSettings;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.raceboard.RaceBoardPerspectiveLifecycle;
import com.sap.sailing.gwt.ui.raceboard.RaceBoardPerspectiveSettings;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.StrippedLeaderboardDTO;
import com.sap.sse.gwt.client.mvp.ErrorView;
import com.sap.sse.gwt.client.shared.perspective.PerspectiveLifecycleWithAllSettings;
import com.sap.sse.gwt.client.useragent.UserAgentDetails;
import com.sap.sse.gwt.shared.GwtHttpRequestUtils;
@@ -26,6 +34,17 @@ public class PlayerActivity extends AbstractActivity {
this.autoPlayController = null;
}
private StrippedLeaderboardDTO getSelectedLeaderboard(EventDTO event,String leaderBoardName) {
for (LeaderboardGroupDTO leaderboardGroup : event.getLeaderboardGroups()) {
for (StrippedLeaderboardDTO leaderboard : leaderboardGroup.getLeaderboards()) {
if (leaderboard.name.equals(leaderBoardName)) {
return leaderboard;
}
}
}
return null;
}
@Override
public void start(final AcceptsOneWidget panel, EventBus eventBus) {
final boolean showRaceDetails = GwtHttpRequestUtils.getBooleanParameter(PARAM_SHOW_RACE_DETAILS, false);
@@ -36,6 +55,16 @@ public class PlayerActivity extends AbstractActivity {
clientFactory.getSailingService().getEventById(eventUUID, true, new AsyncCallback<EventDTO>() {
@Override
public void onSuccess(final EventDTO event) {
//This place fixes a specific null pointer bug3950, once the settings are redone, this should be removed, and instead be done via playerplace (see comment there)
StrippedLeaderboardDTO leaderBoardDTO = getSelectedLeaderboard(event,playerPlace.getConfiguration().getLeaderboardName());
LeaderboardWithHeaderPerspectiveLifecycle leaderboardPerspectiveLifecycle = new LeaderboardWithHeaderPerspectiveLifecycle(leaderBoardDTO, StringMessages.INSTANCE);
PerspectiveLifecycleWithAllSettings<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());
PlayerView view = clientFactory.createPlayerView();
@@ -44,8 +73,8 @@ public class PlayerActivity extends AbstractActivity {
autoPlayController = new AutoPlayController(clientFactory.getSailingService(), clientFactory
.getMediaService(), clientFactory.getUserService(), clientFactory.getErrorReporter(),
playerPlace.getConfiguration(), userAgent, delayToLiveMillis, showRaceDetails, view,
playerPlace.getLeaderboardPerspectiveLifecycleWithAllSettings(),
playerPlace.getRaceboardPerspectiveLifecycleWithAllSettings());
leaderboardPerspectiveLifecyclesAndSettings,
raceboardPerspectiveLifecyclesAndSettings);
autoPlayController.updatePlayMode(AutoPlayModes.Leaderboard);
}
@@ -23,6 +23,8 @@ public class PlayerPlace extends AbstractBasePlace {
Integer timeToSwitchBeforeRaceStartInSeconds = Integer.valueOf(getParameter(AutoPlayerConfiguration.PARAM_TIME_TO_SWITCH_BEFORE_RACE_START));
this.playerConfiguration = new AutoPlayerConfiguration(eventUuidAsString, leaderboardName, fullscreen, timeToSwitchBeforeRaceStartInSeconds);
//these are currently regenerated in the PlayerActivity, based on the historydata. Once the settings are properly stored, this should be replaced.
this.leaderboardPerspectiveLifecycleWithAllSettings = null;
this.raceboardPerspectiveLifecycleWithAllSettings = null;
}
@@ -55,7 +55,7 @@ public class TabletAndDesktopRegattaEventView extends Composite implements Event
if(currentPresenter.getEventDTO().getType() == EventType.SERIES_EVENT) {
final PlaceNavigation<SeriesDefaultPlace> currentEventSeriesNavigation = currentPresenter.getCurrentEventSeriesNavigation();
Anchor seriesAnchor = new Anchor(currentPresenter.getEventDTO().getSeriesName());
Anchor seriesAnchor = new Anchor(i18n.overallLeaderboardSelection());
seriesAnchor.setHref(currentEventSeriesNavigation.getTargetUrl());
seriesAnchor.addClickHandler(new ClickHandler() {
@Override
@@ -41,7 +41,7 @@ public class DeviceConfigurationQRIdentifierDialog extends DialogBox {
Window.alert(stringMessages.notCapableOfGeneratingACodeForIdentifier());
} else if (!identifierBox.getValue().isEmpty() && !serverBox.getValue().isEmpty()) {
String apkUrl = getServerUrlWithoutFinalSlash() + rcAppApkPath;
return DeviceConfigurationQRCodeUtils.composeQRContent(URL.encodeQueryString(identifierBox.getValue()), apkUrl, accessToken);
return DeviceConfigurationQRCodeUtils.composeQRContent(URL.encodeQueryString(identifierBox.getValue()), apkUrl, URL.encodeQueryString(accessToken));
}
return null;
}
@@ -37,14 +37,11 @@ public class EventCreateDialog extends EventDialog {
endDateBox = createDateTimeBox(now);
endDateBox.setFormat("dd/mm/yyyy hh:ii");
endDateBox.ensureDebugId("EndDateTimeBox");
officialWebsiteURLEntryField = createTextBox(null);
officialWebsiteURLEntryField.setVisibleLength(50);
baseURLEntryField = createTextBox(null);
baseURLEntryField.setVisibleLength(50);
sailorsInfoWebsiteURLEntryFields = createTextBoxesForLocalesAndDefault(Collections.<String, String> emptyMap());
isPublicCheckBox = createCheckbox("");
isPublicCheckBox.setValue(false);
isPublicCheckBox.ensureDebugId("IsPublicCheckBox");
baseURLEntryField = createTextBox(null);
baseURLEntryField.setVisibleLength(50);
imagesListComposite.fillImages(Collections.<ImageDTO>emptyList());
videosListComposite.fillVideos(Collections.<VideoDTO>emptyList());
// add default course area
@@ -5,7 +5,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@@ -32,7 +31,6 @@ import com.sap.sailing.gwt.ui.shared.CourseAreaDTO;
import com.sap.sailing.gwt.ui.shared.EventDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.VenueDTO;
import com.sap.sse.gwt.client.GWTLocaleUtil;
import com.sap.sse.gwt.client.IconResources;
import com.sap.sse.gwt.client.controls.listedit.GenericStringListInlineEditorComposite;
import com.sap.sse.gwt.client.controls.listedit.StringConstantsListEditorComposite;
@@ -48,14 +46,13 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
protected BetterDateTimeBox endDateBox;
protected CheckBox isPublicCheckBox;
protected UUID id;
protected TextBox officialWebsiteURLEntryField;
protected TextBox baseURLEntryField;
protected Map<String, TextBox> sailorsInfoWebsiteURLEntryFields = new HashMap<>();
protected CourseAreaListInlineEditorComposite courseAreaNameList;
protected StringConstantsListEditorComposite leaderboardGroupList;
protected Map<String, LeaderboardGroupDTO> availableLeaderboardGroupsByName;
protected ImagesListComposite imagesListComposite;
protected VideosListComposite videosListComposite;
protected ExternalLinksComposite externalLinksComposite;
protected static class EventParameterValidator implements Validator<EventDTO> {
@@ -162,6 +159,7 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
imagesListComposite = new ImagesListComposite(sailingService, stringMessages);
videosListComposite = new VideosListComposite(stringMessages);
externalLinksComposite = new ExternalLinksComposite(stringMessages);
}
@Override
@@ -176,13 +174,9 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
}
result.setName(nameEntryField.getText());
result.setDescription(descriptionEntryField.getText());
result.setOfficialWebsiteURL(officialWebsiteURLEntryField.getText().trim().isEmpty() ? null : officialWebsiteURLEntryField.getText().trim());
result.setOfficialWebsiteURL(externalLinksComposite.getOfficialWebsiteURLValue());
result.setBaseURL(baseURLEntryField.getText().trim().isEmpty() ? null : baseURLEntryField.getText().trim());
for (Map.Entry<String, TextBox> sailorsInfoWebsiteUrlEntry : sailorsInfoWebsiteURLEntryFields.entrySet()) {
TextBox sailorsInfoWebsiteURLEntryField = sailorsInfoWebsiteUrlEntry.getValue();
String sailorsInfoWebsiteURL = sailorsInfoWebsiteURLEntryField.getText().trim();
result.setSailorsInfoWebsiteURL(sailorsInfoWebsiteUrlEntry.getKey(), sailorsInfoWebsiteURL.isEmpty() ? null : sailorsInfoWebsiteURLEntryField.getText().trim());
}
result.setSailorsInfoWebsiteURLs(externalLinksComposite.getSailorsInfoWebsiteURLs());
result.startDate = startDateBox.getValue();
result.endDate = endDateBox.getValue();
result.isPublic = isPublicCheckBox.getValue();
@@ -206,7 +200,7 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
if (additionalWidget != null) {
panel.add(additionalWidget);
}
Grid formGrid = new Grid(10 + GWTLocaleUtil.getLanguageCountWithDefault(), 2);
Grid formGrid = new Grid(8, 2);
int rowIndex = 0;
formGrid.setWidget(rowIndex, 0, new Label(stringMessages.name() + ":"));
formGrid.setWidget(rowIndex++, 1, nameEntryField);
@@ -222,24 +216,18 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
formGrid.setWidget(rowIndex++, 1, endDateBox);
formGrid.setWidget(rowIndex, 0, new Label(stringMessages.isPublic() + ":"));
formGrid.setWidget(rowIndex++, 1, isPublicCheckBox);
formGrid.setWidget(rowIndex, 0, new Label(stringMessages.eventOfficialWebsiteURL() + ":"));
formGrid.setWidget(rowIndex++, 1, officialWebsiteURLEntryField);
formGrid.setWidget(rowIndex, 0, new Label(stringMessages.eventBaseURL() + ":"));
formGrid.setWidget(rowIndex++, 1, baseURLEntryField);
for (Map.Entry<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);
tabPanel.ensureDebugId("EventDialogTabs");
tabPanel.setHeight("525px");
tabPanel.setHeight("400px");
panel.add(tabPanel);
final ScrollPanel eventTab = new ScrollPanel(formGrid);
eventTab.ensureDebugId("EventTab");
tabPanel.add(eventTab, stringMessages.event());
final ScrollPanel externalLinksCompositeTab = new ScrollPanel(externalLinksComposite);
externalLinksCompositeTab.ensureDebugId("ExternalLinksCompositeTab");
tabPanel.add(externalLinksCompositeTab, stringMessages.externalLinks());
final ScrollPanel leaderboardGroupTab = new ScrollPanel(leaderboardGroupList);
leaderboardGroupTab.ensureDebugId("LeaderboardGroupsTab");
tabPanel.add(leaderboardGroupTab, stringMessages.leaderboardGroups());
@@ -259,14 +247,4 @@ public abstract class EventDialog extends DataEntryDialogWithBootstrap<EventDTO>
protected FocusWidget getInitialFocusWidget() {
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");
isPublicCheckBox = createCheckbox("");
isPublicCheckBox.setValue(event.isPublic);
id = event.id;
officialWebsiteURLEntryField = createTextBox(event.getOfficialWebsiteURL());
officialWebsiteURLEntryField.setVisibleLength(50);
baseURLEntryField = createTextBox(event.getBaseURL());
baseURLEntryField.setVisibleLength(50);
sailorsInfoWebsiteURLEntryFields = createTextBoxesForLocalesAndDefault(event.getSailorsInfoWebsiteURLs());
id = event.id;
courseAreaNameList.setValue(new ArrayList<>(event.venue.getCourseAreas()));
List<String> leaderboardGroupNames = new ArrayList<>();
for(LeaderboardGroupDTO leaderboardGroupDTO: event.getLeaderboardGroups()) {
@@ -41,5 +38,6 @@ public class EventEditDialog extends EventDialog {
leaderboardGroupList.setValue(leaderboardGroupNames);
imagesListComposite.fillImages(event.getImages());
videosListComposite.fillVideos(event.getVideos());
externalLinksComposite.fillExternalLinks(event);
}
}
@@ -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 filterDeviceMappings();
String raceStartAnalysis();
String urlForLanguage(String language);
String urlForAllLanguages();
String externalLinks();
String buoyZoneRadiusInHullLengths();
String incorrectValueForRegattaBuoyZoneRadiusInHullLengths();
}
@@ -1614,10 +1614,12 @@ raceAnalysis=Race analysis
startAnalysis=Start analysis
winningLanes=Winning lanes
maximumNumberOfDiscards=Maximum number of discards
externalLinks=External links
urlForAllLanguages=URL for all languages
urlForLanguage=URL for language ''{0}''
notificationRegattaConfigurationUpdatedUsingByMarks=Active regatta configuration was set, using the "By Marks" course designer.
warningOverrideRegattaConfigurationCourseDesignerToByMarks=Active regatta configuration already exists, but with the wrong course designer setting.\nWould you like to override course designer setting to "By Marks"?
allRacesHaveBeenDenoted=All races have been denoted for racelog tracking.
chartSelectionHint=Hint: You can also use the leaderboard (on the LEADERBOARD tab) to select the competitors displayed in the chart.
chooseALanguage=Choose a language
solutions=Solutions
@@ -1600,10 +1600,12 @@ raceAnalysis=Renn-Analyse
startAnalysis=Start-Analyse
winningLanes=Sieger-Kurse
maximumNumberOfDiscards=Maximale Anzahl von Streichern
externalLinks=Externe Links
urlForAllLanguages=URL für alle Sprachen
urlForLanguage=URL für die Sprache ''{0}''
notificationRegattaConfigurationUpdatedUsingByMarks=Aktive Regattakonfiguration wurde gesetzt, "By Marks" Kursdesigner ist ausgewählt.
warningOverrideRegattaConfigurationCourseDesignerToByMarks=Aktive Regattakonfiguration existiert bereits, verwendet aber den falschenKursdesigner.\nKursdesigner auf "By Marks" umstellen?
allRacesHaveBeenDenoted=Alle Rennen sind für Smartphone Tracking gekennzeichnet worden.
chartSelectionHint=Hinweis: Du kannst auch die Ergebnistabelle (im Menü ERGEBNISSE) nutzen, um die im Diagramm angezeigten Teilnehmer auszuwählen.
chooseALanguage=Wähle eine Sprache
solutions=Lösungen
@@ -1612,6 +1612,9 @@ raceAnalysis=レース分析
startAnalysis=スタート分析
winningLanes=勝利レーン
maximumNumberOfDiscards=カットレースの最大数
externalLinks=External links
urlForAllLanguages=URL for all languages
urlForLanguage=URL for language ''{0}''
notificationRegattaConfigurationUpdatedUsingByMarks="マーク別" コースデザイナーを使用して、有効なレガッタ設定が行われました。
warningOverrideRegattaConfigurationCourseDesignerToByMarks=有効なレガッタ設定がすでに登録されていますが、コースデザイナーの設定が適切でありません。\n"マーク別" へのコースデザイナー設定を上書きしますか。
allRacesHaveBeenDenoted=すべてのレースがレースログ追跡に表示されています。
@@ -1612,6 +1612,9 @@ raceAnalysis=Анализ гонки
startAnalysis=Начать анализ
winningLanes=Выигрышные маршруты
maximumNumberOfDiscards=Максимальное число исключений
externalLinks=External links
urlForAllLanguages=URL for all languages
urlForLanguage=URL for language ''{0}''
notificationRegattaConfigurationUpdatedUsingByMarks=Активная конфигурация регаты была настроена с использованием дизайнера курса "По отметкам".
warningOverrideRegattaConfigurationCourseDesignerToByMarks=Активная конфигурация регаты уже существует, но с неверно выбранным дизайнером курса.\nПереопределить выбор дизайнера курса на "По отметкам"?
allRacesHaveBeenDenoted=Отслеживание журнала всех гонок отменено.
@@ -1610,7 +1610,9 @@ raceAnalysis=比赛轮次分析
startAnalysis=起航分析
winningLanes=获胜的船道
maximumNumberOfDiscards=最大放弃次数
externalLinks=External links
urlForAllLanguages=URL for all languages
urlForLanguage=URL for language ''{0}''
chartSelectionHint=提示:您还可以使用积分榜(在 LEADERBOARD 选项卡中)选择在记录中显示的参赛队。
chooseALanguage=选择一种语言
solutions=解决方案
@@ -1612,6 +1612,9 @@ raceAnalysis=比赛轮次分析
startAnalysis=起航分析
winningLanes=获胜的船道
maximumNumberOfDiscards=最大放弃次数
externalLinks=External links
urlForAllLanguages=URL for all languages
urlForLanguage=URL for language ''{0}''
notificationRegattaConfigurationUpdatedUsingByMarks=已使用“按标志”场地设计器设置有效的比赛配置。
warningOverrideRegattaConfigurationCourseDesignerToByMarks=有效的比赛配置已存在,但是场地设计器设置错误。\n是否将场地设计器设置覆盖为“按标志”?
allRacesHaveBeenDenoted=已为比赛轮次日志跟踪描述所有比赛轮次。
@@ -21,9 +21,9 @@ import com.sap.sailing.domain.common.LeaderboardType;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO;
import com.sap.sailing.gwt.common.authentication.FixedSailingAuthentication;
import com.sap.sailing.gwt.common.authentication.SAPSailingHeaderWithAuthentication;
import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint;
import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel;
import com.sap.sailing.gwt.ui.client.LogoAndTitlePanel;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies;
import com.sap.sailing.gwt.ui.shared.EventDTO;
@@ -45,7 +45,6 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint {
private String leaderboardName;
private String leaderboardGroupName;
private LeaderboardType leaderboardType;
private GlobalNavigationPanel globalNavigationPanel;
private EventDTO event;
@Override
@@ -121,30 +120,15 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint {
private void createUI(boolean showRaceDetails, boolean embedded, boolean hideToolbar, EventDTO event) {
DockLayoutPanel mainPanel = new DockLayoutPanel(Unit.PX);
RootLayoutPanel.get().add(mainPanel);
LogoAndTitlePanel logoAndTitlePanel = null;
if (!embedded) {
// Hack to shorten the leaderboardName in case of overall leaderboards
String leaderboardDisplayName = Window.Location.getParameter("displayName");
if (leaderboardDisplayName == null || leaderboardDisplayName.isEmpty()) {
leaderboardDisplayName = leaderboardName;
}
globalNavigationPanel = new GlobalNavigationPanel(getStringMessages(), true, null, leaderboardGroupName, event, null);
logoAndTitlePanel = new LogoAndTitlePanel(leaderboardGroupName, leaderboardDisplayName, getStringMessages(), this, getUserService()) {
@Override
public void onResize() {
super.onResize();
if (isSmallWidth()) {
remove(globalNavigationPanel);
} else {
add(globalNavigationPanel);
}
}
};
logoAndTitlePanel.addStyleName("LogoAndTitlePanel");
if (!isSmallWidth()) {
logoAndTitlePanel.add(globalNavigationPanel);
}
mainPanel.addNorth(logoAndTitlePanel, 68);
SAPSailingHeaderWithAuthentication header = new SAPSailingHeaderWithAuthentication(leaderboardDisplayName);
new FixedSailingAuthentication(getUserService(), header.getAuthenticationMenuView());
mainPanel.addNorth(header, 75);
}
ScrollPanel contentScrollPanel = new ScrollPanel();
long delayBetweenAutoAdvancesInMilliseconds = DEFAULT_REFRESH_INTERVAL_MILLIS;
@@ -58,6 +58,7 @@ import com.google.gwt.view.client.MultiSelectionModel;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SelectionChangeEvent.Handler;
import com.sap.sailing.domain.common.DetailType;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.InvertibleComparator;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.MaxPointsReason;
@@ -97,6 +98,7 @@ import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil;
import com.sap.sailing.gwt.ui.leaderboard.DetailTypeColumn.LegDetailField;
import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings.RaceColumnSelectionStrategies;
import com.sap.sailing.gwt.ui.shared.RaceTimesInfoDTO;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.filter.BinaryOperator;
@@ -972,10 +974,10 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
new FormattedDoubleDetailTypeColumn(DetailType.RACE_GAP_TO_LEADER_IN_SECONDS, new RaceGapToLeaderInSeconds(),
LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE, LeaderboardPanel.this));
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));
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,
new FormattedDoubleDetailTypeColumn(DetailType.RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS, new RaceDistanceToCompetitorFarthestAheadInMeters(),
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> {
@Override
public Double get(LeaderboardRowDTO row) {
Double result = null;
LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName());
if (fieldsForRace != null && fieldsForRace.legDetails != null) {
double distanceTraveledInMeters = 0;
long timeInMilliseconds = 0;
for (LegEntryDTO legDetail : fieldsForRace.legDetails) {
if (legDetail != null) {
if (legDetail.distanceTraveledInMeters != null && legDetail.timeInMilliseconds != null) {
distanceTraveledInMeters += legDetail.distanceTraveledInMeters;
timeInMilliseconds += legDetail.timeInMilliseconds;
final Distance distanceTraveledInMeters = row.getDistanceTraveledInMeters(getRaceColumnName());
final Duration time = row.getTimeSailed(getRaceColumnName());
final Double result;
if (distanceTraveledInMeters != null && time != null) {
result = distanceTraveledInMeters.inTime(time).getKnots();
} else {
distanceTraveledInMeters = 0;
timeInMilliseconds = 0;
break;
}
}
}
if (timeInMilliseconds != 0) {
result = distanceTraveledInMeters / (double) timeInMilliseconds * 1000 * 3600 / 1852;
}
result = null;
}
return result;
}
@@ -1299,21 +1288,7 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
private class RaceDistanceTraveledInMeters implements LegDetailField<Double> {
@Override
public Double get(LeaderboardRowDTO row) {
Double result = null;
LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName());
if (fieldsForRace != null && fieldsForRace.legDetails != null) {
for (LegEntryDTO legDetail : fieldsForRace.legDetails) {
if (legDetail != null) {
if (legDetail.distanceTraveledInMeters != null) {
if (result == null) {
result = 0.0;
}
result += legDetail.distanceTraveledInMeters;
}
}
}
}
return result;
return row.getDistanceTraveledInMeters(getRaceColumnName()).getMeters();
}
}
@@ -1446,27 +1421,48 @@ public class LeaderboardPanel extends SimplePanel implements Component<Leaderboa
lastLegDetail = fieldsForRace.legDetails.get(--lastLegIndex);
}
if (lastLegDetail != null) {
result = get(lastLegDetail);
if (lastLegDetail.finished) {
result = getAfterLastLegFinished(row);
} else {
result = getBeforeLastLegFinished(lastLegDetail);
}
}
}
}
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
protected Double get(LegEntryDTO lastLegDetail) {
return lastLegDetail.currentSpeedOverGroundInKnots;
protected Double getBeforeLastLegFinished(LegEntryDTO currentLegDetail) {
return currentLegDetail.currentSpeedOverGroundInKnots;
}
@Override
protected Double getAfterLastLegFinished(LeaderboardRowDTO row) {
return new RaceAverageSpeedInKnots().get(row);
}
}
private class CurrentRideHeightInMeters extends AbstractLastLegDetailField<Double> {
private class RaceCurrentRideHeightInMeters extends AbstractLastLegDetailField<Double> {
@Override
protected Double get(LegEntryDTO lastLegDetail) {
return lastLegDetail.currentRideHeightInMeters;
protected Double getBeforeLastLegFinished(LegEntryDTO currentLegDetail) {
return currentLegDetail.currentRideHeightInMeters;
}
@Override
protected Double getAfterLastLegFinished(LeaderboardRowDTO row) {
Double result = null;
LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName());
if (fieldsForRace != null) {
result = fieldsForRace.averageRideHeightInMeters;
}
return result;
}
}
}
@@ -76,7 +76,7 @@
font-size: 14px;
margin: 0 0 0 0;
padding: 2px 7px 2px 3px;
height: 18px;
height: 22px;
display: inline-block;
text-decoration: none;
-moz-border-radius: 5px 0 0 5px;
@@ -2941,7 +2941,8 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
final BravoFixTrack<Competitor> bravoFixTrack = trackedRace
.<BravoFix, BravoFixTrack<Competitor>> getSensorTrack(competitor, BravoFixTrack.TRACK_NAME);
if (bravoFixTrack != null) {
result = bravoFixTrack.getRideHeight(timePoint);
final Distance rideHeight = bravoFixTrack.getRideHeight(timePoint);
result = rideHeight == null ? null : rideHeight.getMeters();
}
break;
default:
@@ -4,6 +4,12 @@
<div id="mainContent">
<h4 class="articleHeadline">What's New - SAP Sailing Race Manager</h4>
<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>
<ul class="bulletList">
@@ -37,4 +37,5 @@
<inherits name="com.sap.sailing.gwt.ui.SourceAdminConsole" />
<inherits name='com.sap.sse.gwt.CommonControlCSS_Autoinject'/>
<inherits name='com.sap.sailing.gwt.common.AuthenticationCommon'/>
</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.PolarDataService;
import com.sap.sailing.domain.polars.PolarsChangedListener;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.polars.PolarDataOperation;
import com.sap.sailing.polars.mining.BearingClusterGroup;
@@ -236,9 +237,15 @@ public class PolarDataServiceImpl implements PolarDataService,
@Override
public void insertExistingFixes(TrackedRace trackedRace) {
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
for (GPSFixMoving fix : trackedRace.getTrack(competitor).getFixes()) {
GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
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.HttpServletResponse;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.fileupload.FileItem;
@@ -40,7 +38,7 @@ public class FileUploadServlet extends AbstractFileUploadServlet {
/**
* The maximum size of an image uploaded by a user as a team image, in megabytes (1024*1024 bytes)
*/
private static final int MAX_SIZE_IN_MB = 5;
private static final int MAX_SIZE_IN_MB = 500;
@Override
protected void process(List<FileItem> fileItems, HttpServletRequest req, HttpServletResponse resp) throws UnsupportedEncodingException, IOException {
@@ -61,13 +59,16 @@ public class FileUploadServlet extends AbstractFileUploadServlet {
}
try {
if (fileItem.getSize() > 1024 * 1024 * MAX_SIZE_IN_MB) {
throw new WebApplicationException(Response.status(Status.BAD_REQUEST)
.entity("Image is larger than " + MAX_SIZE_IN_MB + "MB").build());
}
final String errorMessage = "Image is larger than " + MAX_SIZE_IN_MB + "MB";
logger.warning("Ignoring file storage request because file "+fileItem.getName()+" is larger than "+MAX_SIZE_IN_MB+"MB");
result.put("status", Status.INTERNAL_SERVER_ERROR.name());
result.put("message", errorMessage);
} else {
final URI fileUri = getService().getFileStorageManagementService().getActiveFileStorageService()
.storeFile(fileItem.getInputStream(), fileExtension, fileItem.getSize());
result.put(JSON_FILE_NAME, fileItem.getName());
result.put(JSON_FILE_URI, fileUri.toString());
}
} catch (IOException | OperationFailedException | InvalidPropertiesException | NoCorrespondingServiceRegisteredException e) {
final String errorMessage = "Could not store file"+ (e.getMessage()==null?"":(": " + e.getMessage()));
logger.log(Level.WARNING, "Could not store file", e);
@@ -19,12 +19,12 @@ public class BravoDataImportTest {
private final DoubleVectorFixImporter bravoDataImporter = new BravoDataImporterImpl();
private int callbackCallCount = 0;
private double sumRideHeight = 0.0;
private double sumRideHeightInMeters = 0.0;
@Before
public void setUp() {
this.callbackCallCount = 0;
this.sumRideHeight = 0.0;
this.sumRideHeightInMeters = 0.0;
}
@Test
@@ -51,12 +51,12 @@ public class BravoDataImportTest {
bravoDataImporter.importFixes(importData.getInputStream(), (fixes, device) -> {
for (DoubleVectorFix fix : fixes) {
callbackCallCount++;
sumRideHeight += new BravoFixImpl(fix).getRideHeight();
sumRideHeightInMeters += new BravoFixImpl(fix).getRideHeight().getMeters();
}
}, "filename", "source");
Assert.assertEquals(importData.expectedFixesCount, callbackCallCount);
Assert.assertEquals(importData.expectedAverageRideHeight,
callbackCallCount == 0 ? sumRideHeight : sumRideHeight / callbackCallCount, 0.00001);
callbackCallCount == 0 ? sumRideHeightInMeters : sumRideHeightInMeters / callbackCallCount, 0.00001);
}
private enum ImportData {
@@ -3484,6 +3484,8 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes
trackedRace.setPolarDataService(service);
service.insertExistingFixes(trackedRace);
}
} catch (Throwable e) {
logger.log(Level.SEVERE, "Error reconstructing the polars for tracked races", e);
} finally {
trackedRegatta.unlockTrackedRacesAfterRead();
}
@@ -67,18 +67,22 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
private static final long serialVersionUID = -11522605089325440L;
private Regatta regatta;
public DummyTrackedRace(final Serializable raceId, final Iterable<? extends Competitor> competitors, final Regatta regatta, final TrackedRegatta trackedRegatta, final WindStore windStore) {
this(competitors, regatta, trackedRegatta, new RaceDefinitionImpl("DummyRace", new CourseImpl("Dummy Course", Collections.<Waypoint>emptyList()),
regatta.getBoatClass(), competitors, raceId), windStore);
public DummyTrackedRace(final Serializable raceId, final Iterable<? extends Competitor> competitors,
final Regatta regatta, final TrackedRegatta trackedRegatta, final WindStore 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,
RaceDefinition race, WindStore windStore) {
public DummyTrackedRace(final Iterable<? extends Competitor> competitors, final Regatta regatta,
final TrackedRegatta trackedRegatta, RaceDefinition race, WindStore windStore) {
super(race, trackedRegatta, windStore, -1);
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,
final TrackedRegatta trackedRegatta, RaceDefinition race) {
this(competitors, regatta, trackedRegatta, race, EmptyWindStore.INSTANCE);
}
@@ -214,7 +218,8 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
}
@Override
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) {
public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint,
MarkPositionAtTimePointCache markPositionCache) {
return null;
}
@@ -355,7 +360,8 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
}
@Override
public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint, WindPositionMode windPositionMode) {
public Distance getWindwardDistanceToCompetitorFarthestAhead(Competitor competitor, TimePoint timePoint,
WindPositionMode windPositionMode) {
return null;
}
@@ -386,8 +392,8 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
}
@Override
public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis)
throws NoWindException {
public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint timePoint,
boolean waitForLatestAnalysis) throws NoWindException {
return null;
}
@@ -431,8 +437,8 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
}
@Override
public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint from, TimePoint to, boolean upwindOnly,
boolean waitForLatestAnalyses) throws NoWindException {
public Distance getAverageAbsoluteCrossTrackError(Competitor competitor, TimePoint from, TimePoint to,
boolean upwindOnly, boolean waitForLatestAnalyses) throws NoWindException {
return null;
}
@@ -598,8 +604,8 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
}
@Override
public Duration getEstimatedTimeToComplete(TimePoint timepoint) throws NotEnoughDataHasBeenAddedException,
NoWindException {
public Duration getEstimatedTimeToComplete(TimePoint timepoint)
throws NotEnoughDataHasBeenAddedException, NoWindException {
return null;
}
@@ -661,4 +667,9 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials {
public NavigableSet<MarkPassing> getUpdatedMarkPassings(Competitor competitor) {
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
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>
<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>
<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
if (Character.isDigit(aBlock.charAt(0)) && Character.isDigit(bBlock.charAt(0))) {
result = Integer.valueOf(aBlock).compareTo(Integer.valueOf(bBlock));
result = Long.valueOf(aBlock).compareTo(Long.valueOf(bBlock));
} else {
result = aBlock.compareTo(bBlock);
}
@@ -460,20 +460,20 @@
<string name="not_available">N/A</string>
<string name="accuracy_timestamp">(vor %s)</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="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_2">Zurücksetzen</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="onboarding_scan">Scan QR-Code</string>
<string name="onboarding_scan">QR-Code scannen</string>
<string name="onboarding_input">Manuelle Eingabe</string>
<string name="race_group">Group Race</string>
<string name="wind_select_race">Select Race</string>
<string name="race_group">Gruppenwettfahrt</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="get_started">Getting Started</string>
<string name="get_started">Anmelden</string>
<string name="position">Position</string>
<string name="server_deeplink_message">Server erfolgreich geändert auf %1$s.</string>
<string name="penalty">Strafe</string>