Merge branch 'master' into bug2570_2

This commit is contained in:
Axel Uhl committed 2016-10-22 14:37:25 +02:00
commit 95f295d79a
20 files changed
+612 -36

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;
@@ -126,9 +124,9 @@ public class Activator extends AbstractDataMiningActivatorWithPredefinedQueries
dataSourceProviders = new HashSet<>();
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 {
@@ -16,6 +17,9 @@ public interface HasTrackedLegOfCompetitorContext {
@Dimension(messageKey="CompetitorSearchTag", ordinal=11) // TODO Clean me: Move Dimension to Competitor when possible
public String getCompetitorSearchTag();
@Dimension(messageKey="RelativeScoreInRaceInPercent", ordinal=12)
public ClusterDTO getPercentageClusterForRelativeScoreInRace();
@Connector(messageKey="Competitor")
public Competitor getCompetitor();
@@ -31,5 +35,8 @@ public interface HasTrackedLegOfCompetitorContext {
@Statistic(messageKey="AbsoluteRank", resultDecimals=2, ordinal=3)
public Double getAbsoluteRank();
@Statistic(messageKey="TimeSpentInSeconds", resultDecimals=0, ordinal=4)
public Long getTimeTakenInSeconds();
}
@@ -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;
@@ -90,6 +92,12 @@ public class TrackedRaceWithContext implements HasTrackedRaceContext {
calendar.setTime(time.asDate());
return calendar.get(Calendar.YEAR);
}
@Override
public NauticalSide getAdvantageousEndOfLine() {
LineDetails startLine = getTrackedRace().getStartLine(getTrackedRace().getStartOfRace());
return startLine.getAdvantageousSideWhileApproachingLine();
}
@Override
public Boolean isTracked() {
@@ -125,5 +133,20 @@ public class TrackedRaceWithContext implements HasTrackedRaceContext {
}
return number;
}
// Convenience methods for race dependent calculation to avoid code duplication
public Double getRelativeScoreForCompetitor(Competitor competitor) {
Double rankAtFinish = getRankAtFinishForCompetitor(competitor);
if (rankAtFinish == null) {
return null;
}
return rankAtFinish / Util.size(getTrackedRace().getRace().getCompetitors());
}
@Override
public Double getRankAtFinishForCompetitor(Competitor competitor) {
int rank = getTrackedRace().getRank(competitor, getTrackedRace().getEndOfTracking());
return rank == 0 ? null : Double.valueOf(rank);
}
}
@@ -35,6 +35,7 @@ import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException;
import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.polars.PolarsChangedListener;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.polars.PolarDataOperation;
import com.sap.sailing.polars.mining.BearingClusterGroup;
@@ -236,8 +237,14 @@ public class PolarDataServiceImpl implements PolarDataService,
@Override
public void insertExistingFixes(TrackedRace trackedRace) {
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
for (GPSFixMoving fix : trackedRace.getTrack(competitor).getFixes()) {
competitorPositionChanged(fix, competitor, trackedRace);
GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
track.lockForRead();
try {
for (GPSFixMoving fix : track.getFixes()) {
competitorPositionChanged(fix, competitor, trackedRace);
}
} finally {
track.unlockAfterRead();
}
}
}
@@ -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();
}
@@ -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);
}