Merge branch 'master' into translation

Change-Id: I9fc0dd2c41c922acf14fe60a1018d74d405b2131
This commit is contained in:
Axel Uhl committed 2017-02-16 20:36:06 +01:00
commit cffcfa3d41
87 files changed
+1290 -522

No files matched your search

@@ -38,7 +38,7 @@
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m -Dgwt.watchFileChanges=false"/>
</launchConfiguration>
@@ -16,7 +16,7 @@ Race=Race
Competitor=Competitor
Boat=Boat
Team=Team
CompetitorSearchTag=Competitor Search Tag
SearchTag=Search Tag
LeaderboardSailingDomainRetrieverChain=Leaderboards
RaceSailingDomainRetrieverChain=Races
LegSailingDomainRetrieverChain=Legs
@@ -102,7 +102,7 @@ WindTrack=Wind Track
timeSpentFoiling=Time spent foiling
yaw=Yaw
pitch=Pitch
roll=Heel
heel=Heel
rideHeight=Ride Height
rideHeightPort=Ride Height Port Hull
rideHeightStarboard=Ride Height Starboard Hull
@@ -122,4 +122,5 @@ EndsOnLegType=Type of leg at landing
FoilingSegments=Foiled Segments
AverageAbsoluteTrueWindAngle=Average absolute true wind angle
AbsoluteTrueWindAngle=Absolute true wind angle (TWA)
Wind=Wind
Wind=Wind
VMG=Velocity Made Good
@@ -3,7 +3,7 @@ DistanceTraveled=zur
Boat=Boot
Team=Team
Name=Name
CompetitorSearchTag=Teilnehmer Such-Tag
CompetitorSearchTag=Such-Tag
Nationality=Nationalitäts
Acronym=Abkürzung
SailID=Segelnummer
@@ -102,7 +102,7 @@ WindTrack=Windmessreihe
timeSpentFoiling=Zeit auf Foils
yaw=Gierung
pitch=Neigung
roll=Krängung
heel=Krängung
rideHeight=Foiling-Höhe
rideHeightPort=Foiling-Höhe Backbord-Rumpf
rideHeightStarboard=Foiling-Höhe Steuerbord-Rumpf
@@ -122,4 +122,5 @@ EndsOnLegType=Bahnschenkeltyp beim Landen
FoilingSegments=Gefoilte Abschnitte
AverageAbsoluteTrueWindAngle=Durchschnittlicher absoluter Winkel zum wahren Wind (TWA)
AbsoluteTrueWindAngle=Absoluter Winkel zum wahren Wind (TWA)
Wind=Wind
Wind=Wind
VMG=Luv-/Leegeschwindigkeit (VMG)
@@ -16,7 +16,7 @@ Race=レース
Competitor=競技者
Boat=艇
Team=チーム
CompetitorSearchTag=競技者検索タグ
SearchTag=競技者検索タグ
LeaderboardSailingDomainRetrieverChain=リーダーボード
RaceSailingDomainRetrieverChain=レース
LegSailingDomainRetrieverChain=レグ
@@ -16,7 +16,7 @@ Race=Гонка
Competitor=Участник
Boat=Лодка
Team=Команда
CompetitorSearchTag=Тег поиска участника
SearchTag=Тег поиска участника
LeaderboardSailingDomainRetrieverChain=Таблицы лидеров
RaceSailingDomainRetrieverChain=Гонки
LegSailingDomainRetrieverChain=Отрезки
@@ -65,8 +65,8 @@
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="Team" resname="Team">
<source>Team</source>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="CompetitorSearchTag" resname="CompetitorSearchTag">
<source>Competitor Search Tag</source>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="SearchTag" resname="SearchTag">
<source>Search Tag</source>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="LeaderboardSailingDomainRetrieverChain" resname="LeaderboardSailingDomainRetrieverChain">
<source>Leaderboards</source>
@@ -83,8 +83,8 @@
<source>Team</source>
<target state="signed-off">チーム</target>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="CompetitorSearchTag" resname="CompetitorSearchTag">
<source>Competitor Search Tag</source>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="SearchTag" resname="SearchTag">
<source>Search Tag</source>
<target state="signed-off">競技者検索タグ</target>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="LeaderboardSailingDomainRetrieverChain" resname="LeaderboardSailingDomainRetrieverChain">
@@ -83,8 +83,8 @@
<source>Team</source>
<target state="signed-off">Команда</target>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="CompetitorSearchTag" resname="CompetitorSearchTag">
<source>Competitor Search Tag</source>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="SearchTag" resname="SearchTag">
<source>Search Tag</source>
<target state="signed-off">Тег поиска участника</target>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="LeaderboardSailingDomainRetrieverChain" resname="LeaderboardSailingDomainRetrieverChain">
@@ -83,8 +83,8 @@
<source>Team</source>
<target state="signed-off">团队</target>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="CompetitorSearchTag" resname="CompetitorSearchTag">
<source>Competitor Search Tag</source>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="SearchTag" resname="SearchTag">
<source>Search Tag</source>
<target>参赛队搜索标记</target>
</trans-unit>
<trans-unit datatype="x-urn:x-sap:sls-mlt:java:flexible-converter:messageformat" id="LeaderboardSailingDomainRetrieverChain" resname="LeaderboardSailingDomainRetrieverChain">
@@ -3,20 +3,20 @@ 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;
import com.sap.sse.datamining.data.ClusterBoundary;
import com.sap.sse.datamining.data.ClusterFormatter;
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;
import com.sap.sse.datamining.impl.data.FixClusterGroup;
import com.sap.sse.datamining.impl.data.LinearDoubleClusterGroup;
import com.sap.sse.datamining.impl.data.LocalizedCluster;
import com.sap.sse.datamining.impl.data.PercentageClusterFormatter;
public class SailingClusterGroups {
@@ -2,6 +2,7 @@ package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.tracking.BravoFix;
@@ -29,4 +30,7 @@ public interface HasBravoFixContext extends HasWindOnTrackedLeg {
default TimePoint getTimePoint() {
return getBravoFix().getTimePoint();
}
@Connector(messageKey="VMG")
Speed getVelocityMadeGood();
}
@@ -5,8 +5,11 @@ import com.sap.sailing.domain.common.Positioned;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Timed;
import com.sap.sse.datamining.annotations.Connector;
public interface HasManeuver extends Positioned, Timed {
@Connector
Maneuver getManeuver();
@Override
@@ -3,8 +3,6 @@ package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.SpeedWithBearing;
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;
@@ -13,27 +11,15 @@ public interface HasManeuverContext extends HasWindOnTrackedLeg, HasManeuver, Ha
@Connector(scanForStatistics=false)
HasTrackedLegOfCompetitorContext getTrackedLegOfCompetitorContext();
//TODO Clean-Up:
// Move Dimensions and Statistics to Maneuver and connect to it
@Dimension(messageKey="ManeuverType", ordinal=12)
ManeuverType getManeuverType();
@Dimension(messageKey="Tack", ordinal=13)
Tack getTack();
@Dimension(messageKey="ToSide")
@Dimension(messageKey="ToSide", ordinal=16)
NauticalSide getToSide();
@Connector(messageKey="SpeedBefore", ordinal=0)
SpeedWithBearing getSpeedBefore();
@Connector(messageKey="SpeedAfter", ordinal=1)
SpeedWithBearing getSpeedAfter();
@Statistic(messageKey="DirectionChange", resultDecimals=2, ordinal=2)
Double getDirectionChangeInDegrees();
@Statistic(messageKey="AbsoluteDirectionChange", resultDecimals=2, ordinal=3)
Double getAbsoluteDirectionChangeInDegrees();
@Statistic(messageKey="ManeuverLoss", resultDecimals=0, ordinal=4)
Distance getManeuverLoss();
@@ -2,8 +2,6 @@ package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension;
import com.sap.sse.datamining.annotations.Statistic;
@@ -11,27 +9,13 @@ import com.sap.sse.datamining.annotations.Statistic;
public interface HasMarkPassingContext extends HasWindOnTrackedLeg, HasManeuver, HasTrackedLegOfCompetitor {
@Connector(scanForStatistics=false)
HasTrackedLegOfCompetitorContext getTrackedLegOfCompetitorContext();
//TODO Clean-Up:
// Move Dimensions and Statistics to Maneuver/MarkPassingManeuver and connect to it
// Find a way to implement HasManeuverContext and HasMarkPassingManeuver with a base class
@Dimension(messageKey="Tack", ordinal=12)
Tack getTack();
@Connector(messageKey="Waypoint", ordinal=13)
@Connector(messageKey="Waypoint", ordinal=14)
Waypoint getWaypoint();
@Dimension(messageKey="PassingSide", ordinal=13)
@Dimension(messageKey="PassingSide", ordinal=15)
NauticalSide getPassingSide();
@Connector(messageKey="SpeedBefore", ordinal=0)
SpeedWithBearing getSpeedBefore();
@Connector(messageKey="SpeedAfter", ordinal=1)
SpeedWithBearing getSpeedAfter();
@Statistic(messageKey="DirectionChange", resultDecimals=2, ordinal=2)
Double getDirectionChangeInDegrees();
@Statistic(messageKey="RelativeScore", ordinal=3, resultDecimals=2)
Double getRelativeRank();
@@ -19,9 +19,6 @@ public interface HasRaceOfCompetitorContext {
@Statistic(messageKey="")
public Competitor getCompetitor();
@Dimension(messageKey="CompetitorSearchTag", ordinal=11) // TODO Clean me: Move Dimension to Competitor when possible
public String getCompetitorSearchTag();
@Dimension(messageKey="TackAtStart", ordinal=12)
public Tack getTackAtStart() throws NoWindException;
@@ -21,9 +21,6 @@ public interface HasRaceResultOfCompetitorContext {
@Dimension(messageKey="Regatta", ordinal=4)
String getRegattaName();
@Dimension(messageKey="CompetitorSearchTag", ordinal=5) // TODO Clean me: Move Dimension to Competitor when possible
public String getCompetitorSearchTag();
@Dimension(messageKey="RelativeScoreInPercent", ordinal=6)
public ClusterDTO getPercentageClusterForRelativeScore();
@@ -14,9 +14,6 @@ public interface HasTrackedLegOfCompetitorContext extends HasWindOnTrackedLeg {
public TrackedLegOfCompetitor getTrackedLegOfCompetitor();
@Dimension(messageKey="CompetitorSearchTag", ordinal=11) // TODO Clean me: Move Dimension to Competitor when possible
public String getCompetitorSearchTag();
@Dimension(messageKey="RelativeScoreInRaceInPercent", ordinal=12)
public ClusterDTO getPercentageClusterForRelativeScoreInRace();
@@ -1,143 +0,0 @@
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;
}
}
}
@@ -6,12 +6,14 @@ import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Bearing;
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.SpeedWithBearing;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.tracking.BravoFix;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindPositionMode;
public class BravoFixWithContext implements HasBravoFixContext {
private static final long serialVersionUID = -4537126043228674949L;
@@ -51,6 +53,11 @@ public class BravoFixWithContext implements HasBravoFixContext {
return getGpsFixTrack().getEstimatedSpeed(getTimePoint());
}
@Override
public Speed getVelocityMadeGood() {
return getTrackedLegOfCompetitorContext().getTrackedLegOfCompetitor().getVelocityMadeGood(getTimePoint(), WindPositionMode.EXACT);
}
@Override
public Wind getWind() {
return getTrackedRace().getWind(getGpsFixTrack().getEstimatedPosition(getTimePoint(), /* extrapolate */ true), getTimePoint());
@@ -5,8 +5,6 @@ import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.domain.common.Distance;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.tracking.Maneuver;
@@ -37,35 +35,15 @@ public class ManeuverWithContext implements HasManeuverContext {
}
@Override
public Tack getTack() {
return getManeuver().getNewTack();
}
@Override
public SpeedWithBearing getSpeedBefore() {
return getManeuver().getSpeedWithBearingBefore();
}
@Override
public SpeedWithBearing getSpeedAfter() {
return getManeuver().getSpeedWithBearingAfter();
}
@Override
public Double getDirectionChangeInDegrees() {
return getManeuver().getDirectionChangeInDegrees();
public NauticalSide getToSide() {
return getManeuver().getDirectionChangeInDegrees() >= 0 ? NauticalSide.STARBOARD : NauticalSide.PORT;
}
@Override
public Double getAbsoluteDirectionChangeInDegrees() {
return Math.abs(getManeuver().getDirectionChangeInDegrees());
}
@Override
public NauticalSide getToSide() {
return getManeuver().getDirectionChangeInDegrees() >= 0 ? NauticalSide.STARBOARD : NauticalSide.PORT;
}
@Override
public Distance getManeuverLoss() {
return getManeuver().getManeuverLoss();
@@ -5,8 +5,6 @@ import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.tracking.MarkPassingManeuver;
@@ -36,11 +34,6 @@ public class MarkPassingWithContext implements HasMarkPassingContext {
public MarkPassingManeuver getManeuver() {
return maneuver;
}
@Override
public Tack getTack() {
return getManeuver().getNewTack();
}
@Override
public Waypoint getWaypoint() {
@@ -52,21 +45,6 @@ public class MarkPassingWithContext implements HasMarkPassingContext {
return getManeuver().getSide();
}
@Override
public SpeedWithBearing getSpeedBefore() {
return getManeuver().getSpeedWithBearingBefore();
}
@Override
public SpeedWithBearing getSpeedAfter() {
return getManeuver().getSpeedWithBearingAfter();
}
@Override
public Double getDirectionChangeInDegrees() {
return getManeuver().getDirectionChangeInDegrees();
}
@Override
public Double getRelativeRank() {
Leaderboard leaderboard = getTrackedLegOfCompetitorContext().getTrackedLegContext().getTrackedRaceContext().getLeaderboardContext().getLeaderboard();
@@ -52,11 +52,6 @@ public class RaceOfCompetitorWithContext implements HasRaceOfCompetitorContext {
public Competitor getCompetitor() {
return competitor;
}
@Override
public String getCompetitorSearchTag() {
return getCompetitor().getSearchTag();
}
@Override
public Tack getTackAtStart() throws NoWindException {
@@ -77,11 +77,6 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti
final String result = leaderboard.getName();
return result;
}
@Override
public String getCompetitorSearchTag() {
return getCompetitor().getSearchTag();
}
@Override
public ClusterDTO getPercentageClusterForRelativeScore() {
@@ -51,11 +51,6 @@ public class TrackedLegOfCompetitorWithContext implements HasTrackedLegOfCompeti
public Competitor getCompetitor() {
return competitor;
}
@Override
public String getCompetitorSearchTag() {
return getCompetitor().getSearchTag();
}
@Override
public ClusterDTO getPercentageClusterForRelativeScoreInRace() {
@@ -1,5 +1,7 @@
package com.sap.sailing.domain.common;
import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID;
/**
* Possible sources for wind data. Used to key and select between different {@link WindTrack}s. Literals
* are given in descending order of precedence. Particularly, the {@link #COURSE_BASED} source should
@@ -43,7 +45,13 @@ public enum WindSourceType {
* their measures are stored in the race log.
*
*/
RACECOMMITTEE(false, 0.9, /* useSpeed */ true);
RACECOMMITTEE(false, 0.9, /* useSpeed */ true),
/**
* Like {@link #COMBINED}, only that when combined with an ID into a {@link WindSourceWithAdditionalID} then the
* position for the wind is taken to be the middle of the tracked leg whose number is identified by the ID.
*/
LEG_MIDDLE(/* can be stored */ false, /* base confidence */ 0.9, /* useSpeed */ true);
private final boolean canBeStored;
@@ -5,49 +5,79 @@ import java.util.Collections;
import java.util.List;
/**
* Metadata that defines the column structure of {@link com.sap.sailing.domain.common.tracking.DoubleVectorFix}es when imported.
* Metadata that defines the column structure of {@link com.sap.sailing.domain.common.tracking.DoubleVectorFix}es when
* imported.
*
* The current implementation only stores a subset of the information available during the import.
*/
public enum BravoSensorDataMetadata {
INSTANCE;
private static final String RIDE_HEIGHT = "RideHeight";
private static final String RIDE_HEIGHT_PORT_HULL = "RideHeightPortHull";
private static final String RIDE_HEIGHT_STBD_HULL = "RideHeightStbdHull";
private static final String YAW = "ImuSensor_Yaw";
private static final String ROLL = "ImuSensor_Roll";
private static final String HEEL = "Heel";
private static final String PITCH = "ImuSensor_Pitch";
private final int HEADER_COLUMN_OFFSET = 3;
private final List<String> columns = Collections.unmodifiableList(Arrays.asList(RIDE_HEIGHT, RIDE_HEIGHT_PORT_HULL,
RIDE_HEIGHT_STBD_HULL, "Heel", "Trim", "ImuSensor_GyroX", "ImuSensor_GyroY", "ImuSensor_GyroZ",
PITCH, ROLL, YAW, "ImuSensor_LinearAccX", "ImuSensor_LinearAccY",
"ImuSensor_LinearAccZ", "Hb_Z", "Dn_Z", "Db_Z", "LKF_ride_hgh", "LKF_ride_hgh_Position",
"LKF_ride_hgh_Velocity", "LKF_ride_hgh_Acceleration", "LKF_ride_hgh_PositionError",
"LKF_ride_hgh_VelocityError", "LKF_ride_hgh_AccelerationError", "Gps_Gga_PosFixTime", "Gps_Gga_Lat",
"Gps_Gga_Lon", "Gps_Gga_QI", "Gps_Gga_HDOP", "Gps_Gga_AntHeight", "Gps_Vtg_TMG", "Gps_Vtg_SOGKnots",
"Gps_Rmc_MagVar", "Gps_EastVelocity", "Gps_NorthVelocity", "Gps_UpVelocity", "BravoNet_Node0x0A0_Ch0",
"BravoNet_Node0x0A0_Voltage", "BravoNet_Node0x0A0_Temperature", "BravoNet_Node0x0A0_Current"));
public final int rideHeightColumn = getColumnIndex(RIDE_HEIGHT);
public final int rideHeightPortHullColumn = getColumnIndex(RIDE_HEIGHT_PORT_HULL);
public final int rideHeightStarboardHullColumn = getColumnIndex(RIDE_HEIGHT_STBD_HULL);
public final int pitchColumn = getColumnIndex(PITCH);
public final int rollColumn = getColumnIndex(ROLL);
public final int yawColumn = getColumnIndex(YAW);
public final int columnCount = columns.size();
/**
*
* Available information in file: <br/>
* The columns field defines which information will be loaded into the system
*
*/
private final List<String> fileColumns = Collections.unmodifiableList(Arrays.asList( //
"RideHeight", RIDE_HEIGHT_PORT_HULL, RIDE_HEIGHT_STBD_HULL, HEEL, "Trim", "ImuSensor_GyroX",
"ImuSensor_GyroY", "ImuSensor_GyroZ", PITCH, "ImuSensor_Roll", "ImuSensor_Yaw", "ImuSensor_LinearAccX",
"ImuSensor_LinearAccY", "ImuSensor_LinearAccZ",
"Hb_Z", "Dn_Z", "Db_Z", "LKF_ride_hgh", "LKF_ride_hgh_Position", "LKF_ride_hgh_Velocity",
"LKF_ride_hgh_Acceleration", "LKF_ride_hgh_PositionError", "LKF_ride_hgh_VelocityError",
"LKF_ride_hgh_AccelerationError", "Gps_Gga_PosFixTime", "Gps_Gga_Lat", "Gps_Gga_Lon", "Gps_Gga_QI",
"Gps_Gga_HDOP", "Gps_Gga_AntHeight", "Gps_Vtg_TMG", "Gps_Vtg_SOGKnots", "Gps_Rmc_MagVar", "Gps_EastVelocity",
"Gps_NorthVelocity", "Gps_UpVelocity", "BravoNet_Node0x0A0_Ch0", "BravoNet_Node0x0A0_Voltage",
"BravoNet_Node0x0A0_Temperature", "BravoNet_Node0x0A0_Current"
));
public List<String> getColumns() {
return columns;
private final List<String> trackColumns = Collections.unmodifiableList(Arrays.asList( //
RIDE_HEIGHT_PORT_HULL, //
RIDE_HEIGHT_STBD_HULL, //
HEEL, //
PITCH //
));
public final int trackColumnCount = trackColumns.size();
public final int rideHeightPortHullColumn = getTrackColumnIndex(RIDE_HEIGHT_PORT_HULL);
public final int rideHeightStarboardHullColumn = getTrackColumnIndex(RIDE_HEIGHT_STBD_HULL);
public final int heelColumn = getTrackColumnIndex(HEEL);
public final int pitchColumn = getTrackColumnIndex(PITCH);
/**
* The fields available in the import file
*
* @return
*/
public List<String> getFileColumns() {
return fileColumns;
}
public boolean hasColumn(String columnName) {
return columns.contains(columnName);
/**
* The fields to be loaded into a track
*
* @return
*/
public List<String> getTrackColumns() {
return trackColumns;
}
public int getColumnIndex(String columnName) {
return columns.indexOf(columnName);
public boolean trackHasColumn(String columnName) {
return trackColumns.contains(columnName);
}
public int getTrackColumnIndex(String columnName) {
return trackColumns.indexOf(columnName);
}
public int getHeaderColumnOffset() {
return HEADER_COLUMN_OFFSET;
}
@@ -33,9 +33,7 @@ public interface BravoFix extends SensorFix {
@Statistic(messageKey="pitch", resultDecimals=1)
double getPitch();
@Statistic(messageKey="yaw", resultDecimals=1)
double getYaw();
@Statistic(messageKey = "heel", resultDecimals = 1)
double getHeel();
@Statistic(messageKey="roll", resultDecimals=1)
double getRoll();
}
@@ -6,23 +6,39 @@ import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.BravoFix;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
/**
* Implementation of {@link BravoFix} that wraps a {@link DoubleVectorFix} which holds the actual sensor data.
*/
public class BravoFixImpl implements BravoFix {
private static final long serialVersionUID = 2033254212013221160L;
private final DoubleVectorFix fix;
private final MeterDistance computedRideHeight;
private final MeterDistance rideHeightPortHull;
private final MeterDistance rideHeightStarboardHull;
private final double pitch;
private final double heel;
private final boolean computedIsFoiling;
public BravoFixImpl(DoubleVectorFix fix) {
this.fix = fix;
pitch = fix.get(BravoSensorDataMetadata.INSTANCE.pitchColumn);
heel = fix.get(BravoSensorDataMetadata.INSTANCE.heelColumn);
double rideHeightPortHullasDouble = fix.get(BravoSensorDataMetadata.INSTANCE.rideHeightPortHullColumn);
double rideHeightStarboardHullasDouble = fix
.get(BravoSensorDataMetadata.INSTANCE.rideHeightStarboardHullColumn);
rideHeightPortHull = new MeterDistance(rideHeightPortHullasDouble);
rideHeightStarboardHull = new MeterDistance(rideHeightStarboardHullasDouble);
computedRideHeight = new MeterDistance(Math.min(rideHeightPortHullasDouble, rideHeightStarboardHullasDouble));
computedIsFoiling = computedRideHeight.compareTo(MIN_FOILING_HEIGHT_THRESHOLD) >= 0;
}
@Override
public double get(String valueName) {
int index = BravoSensorDataMetadata.INSTANCE.getColumnIndex(valueName);
int index = BravoSensorDataMetadata.INSTANCE.getTrackColumnIndex(valueName);
if (index < 0) {
throw new IllegalArgumentException("Unknown value \"" + valueName + "\" for " + getClass().getSimpleName());
}
@@ -36,36 +52,31 @@ public class BravoFixImpl implements BravoFix {
@Override
public Distance getRideHeight() {
return Util.min(getRideHeightPortHull(), getRideHeightStarboardHull());
return computedRideHeight;
}
@Override
public Distance getRideHeightPortHull() {
return new MeterDistance(fix.get(BravoSensorDataMetadata.INSTANCE.rideHeightPortHullColumn));
return rideHeightPortHull;
}
@Override
public Distance getRideHeightStarboardHull() {
return new MeterDistance(fix.get(BravoSensorDataMetadata.INSTANCE.rideHeightStarboardHullColumn));
return rideHeightStarboardHull;
}
@Override
public boolean isFoiling() {
return getRideHeight().compareTo(MIN_FOILING_HEIGHT_THRESHOLD) >= 0;
return computedIsFoiling;
}
@Override
public double getPitch() {
return fix.get(BravoSensorDataMetadata.INSTANCE.pitchColumn);
return pitch;
}
@Override
public double getYaw() {
return fix.get(BravoSensorDataMetadata.INSTANCE.yawColumn);
}
@Override
public double getRoll() {
return fix.get(BravoSensorDataMetadata.INSTANCE.rollColumn);
public double getHeel() {
return heel;
}
}
@@ -513,8 +513,7 @@ public class SensorFixStoreAndLoadTest {
}
private DoubleVectorFix createBravoDoubleVectorFixWithRideHeight(long timestamp, double rideHeight) {
double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.columnCount];
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightColumn] = rideHeight;
double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.trackColumnCount];
// fill the port/starboard columns as well because their minimum defines the true ride height
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightPortHullColumn] = rideHeight;
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightStarboardHullColumn] = rideHeight;
@@ -330,8 +330,9 @@ public class SensorFixStoreTest {
}
private DoubleVectorFix createBravoDoubleVectorFixWithRideHeight(long timestamp, double rideHeight) {
double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.columnCount];
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightColumn] = rideHeight;
double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.trackColumnCount];
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightPortHullColumn] = rideHeight;
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightStarboardHullColumn] = rideHeight;
return new DoubleVectorFixImpl(new MillisecondsTimePoint(timestamp), fixData);
}
}
@@ -32,4 +32,4 @@ public class BravoDataFixMapper implements SensorFixMapper<BravoFix, DynamicSens
public boolean isResponsibleFor(Class<? extends RegattaLogDeviceMappingEvent<?>> eventType) {
return RegattaLogDeviceCompetitorBravoMappingEventImpl.class.isAssignableFrom(eventType);
}
}
}
@@ -8,6 +8,7 @@ import com.sap.sse.common.Duration;
import com.sap.sse.common.IsManagedByCache;
import com.sap.sse.common.NamedWithID;
import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension;
public interface Competitor extends NamedWithID, IsManagedByCache<SharedDomainFactory> {
@Connector(messageKey="Team", ordinal=9)
@@ -22,6 +23,7 @@ public interface Competitor extends NamedWithID, IsManagedByCache<SharedDomainFa
boolean hasEmail();
@Dimension(messageKey="SearchTag", ordinal=11)
String getSearchTag();
/**
@@ -0,0 +1,130 @@
package com.sap.sailing.domain.test;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.WindImpl;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.impl.CombinedWindAsNavigableSet;
import com.sap.sailing.domain.tracking.impl.CombinedWindTrackImpl;
import com.sap.sailing.domain.tracking.impl.VirtualWindFixesAsNavigableSet;
import com.sap.sse.InvalidDateException;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import com.sap.sse.util.DateParser;
/**
* Tests for {@link VirtualWindFixesAsNavigableSet}, based mainly on bug 4063.
*
* @author Axel Uhl (d043530)
*
*/
public class VirtualWindFixesTest {
private CombinedWindTrackImpl windTrack;
private CombinedWindAsNavigableSet virtualSet;
private TrackedRace trackedRace;
private TimePoint oddStart;
private TimePoint evenStart;
@Before
public void setUp() throws InvalidDateException {
oddStart = new MillisecondsTimePoint(DateParser.parse("2017-02-15T09:59:59.9+01:00"));
evenStart = new MillisecondsTimePoint(DateParser.parse("2017-02-15T10:00:00+01:00"));
trackedRace = mock(TrackedRace.class);
RaceDefinition race = mock(RaceDefinition.class);
when(race.getName()).thenReturn("Race Name");
when(trackedRace.getRace()).thenReturn(race);
when(trackedRace.getStartOfRace()).thenReturn(oddStart);
when(trackedRace.getWind(any(), any())).thenAnswer(invocation->new WindImpl(
(Position) invocation.getArguments()[0], (TimePoint) invocation.getArguments()[1],
new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(123))));
windTrack = new CombinedWindTrackImpl(trackedRace, /* baseConfidence */ 0.9) {
private static final long serialVersionUID = 123L;
@Override
protected CombinedWindAsNavigableSet createVirtualInternalRawFixes(TrackedRace trackedRace) {
virtualSet = new CombinedWindAsNavigableSet(windTrack, trackedRace, 10000l) {
private static final long serialVersionUID = -8903939477027009728L;
@Override
protected CombinedWindTrackImpl getTrack() {
return windTrack;
}
};
return virtualSet;
}
};
}
@Test
public void testSubsetWithInclusiveEnd() {
windTrack.lockForRead();
try {
final Iterable<Wind> fixes = windTrack.getFixes(evenStart, /* fromInclusive */ true,
evenStart.plus(virtualSet.getResolutionInMilliseconds()), /* toInclusive */ true);
assertEquals(2, Util.size(fixes));
Wind wind1 = fixes.iterator().next();
assertEquals(evenStart, wind1.getTimePoint());
Wind wind2 = Util.get(fixes, 1);
assertEquals(evenStart.plus(virtualSet.getResolutionInMilliseconds()), wind2.getTimePoint());
} finally {
windTrack.unlockAfterRead();
}
}
@Test
public void testSubsetWithInclusiveEndAndExclusiveStart() {
windTrack.lockForRead();
try {
final Iterable<Wind> fixes = windTrack.getFixes(evenStart, /* fromInclusive */ false,
evenStart.plus(virtualSet.getResolutionInMilliseconds()), /* toInclusive */ true);
assertEquals(1, Util.size(fixes));
Wind wind1 = fixes.iterator().next();
assertEquals(evenStart.plus(virtualSet.getResolutionInMilliseconds()), wind1.getTimePoint());
} finally {
windTrack.unlockAfterRead();
}
}
@Test
public void testSubset() {
windTrack.lockForRead();
try {
final Iterable<Wind> fixes = windTrack.getFixes(evenStart, /* fromInclusive */ true,
evenStart.plus(virtualSet.getResolutionInMilliseconds()), /* toInclusive */ false);
assertEquals(1, Util.size(fixes));
Wind wind = fixes.iterator().next();
assertEquals(evenStart, wind.getTimePoint());
} finally {
windTrack.unlockAfterRead();
}
}
@Test
public void testSubsetWithExclusiveEndButToIsSlightlyAfterSecondFix() {
windTrack.lockForRead();
try {
final Iterable<Wind> fixes = windTrack.getFixes(evenStart, /* fromInclusive */ true,
evenStart.plus(virtualSet.getResolutionInMilliseconds()).plus(1), /* toInclusive */ false);
assertEquals(2, Util.size(fixes));
Wind wind1 = fixes.iterator().next();
assertEquals(evenStart, wind1.getTimePoint());
Wind wind2 = Util.get(fixes, 1);
assertEquals(evenStart.plus(virtualSet.getResolutionInMilliseconds()), wind2.getTimePoint());
} finally {
windTrack.unlockAfterRead();
}
}
}
@@ -116,8 +116,7 @@ public class BravoFixTrackSerializationTest {
}
private void addOrReplaceBravoFixToTrack(boolean replace) {
double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.columnCount];
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightColumn] = rideHeight;
double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.trackColumnCount];
// fill the port/starboard columns as well because their minimum defines the true ride height
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightPortHullColumn] = rideHeight;
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightStarboardHullColumn] = rideHeight;
@@ -366,4 +366,19 @@ public class FlexibleLeaderboardImpl extends AbstractLeaderboardImpl implements
protected LeaderboardType getLeaderboardType() {
return LeaderboardType.FlexibleLeaderboard;
}
/**
* In addition to invoking the superclass implementation, a flexible leaderboard also
* detaches all race logs from any tracked race currently linked to any of the race columns
* of this leaderboard.
*/
@Override
public void destroy() {
super.destroy();
for (final RaceColumn raceColumn : getRaceColumns()) {
for (final Fleet fleet : raceColumn.getFleets()) {
raceColumn.setTrackedRace(fleet, null); // this will in particular detach the race log
}
}
}
}
@@ -26,6 +26,13 @@ public interface BravoFixTrack<ItemType extends WithID & Serializable> extends S
*/
Distance getRideHeight(TimePoint timePoint);
/**
* Tells if the competitor to which this track belongs is considered to be foiling at the time point
* given. This depends on the {@link #getRideHeight} and a threshold above which a boat is considered
* to be foiling.
*/
boolean isFoiling(TimePoint timePoint);
/**
* Calculates the average ride height for the given time range.
*
@@ -5,16 +5,23 @@ import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.tracking.GPSFix;
import com.sap.sse.datamining.annotations.Connector;
import com.sap.sse.datamining.annotations.Dimension;
import com.sap.sse.datamining.annotations.Statistic;
public interface Maneuver extends GPSFix {
ManeuverType getType();
@Dimension(messageKey="Tack", ordinal=13)
Tack getNewTack();
@Connector(messageKey="SpeedBefore", ordinal=0)
SpeedWithBearing getSpeedWithBearingBefore();
@Connector(messageKey="SpeedAfter", ordinal=1)
SpeedWithBearing getSpeedWithBearingAfter();
@Statistic(messageKey="DirectionChange", resultDecimals=2, ordinal=2)
double getDirectionChangeInDegrees();
Distance getManeuverLoss();
@@ -376,15 +376,17 @@ public interface TrackedRace extends Serializable, IsManagedByCache<SharedDomain
/**
* Retrieves the wind sources used so far by this race that have the specified <code>type</code> as their
* {@link WindSource#getType() type}. Always returns a non-<code>null</code> iterable which may be empty in case the
* race does not use any wind source of the specified type (yet). Additional sources may be returned after
* race does not use any wind source of the specified type (yet).<p>
*
* It is possible to ask for the {@link WindSourceType#COMBINED} and {@link WindSourceType#LEG_MIDDLE} types and
* get a non-empty result although those sources are never returned by {@link #getWindSources()}.
*/
Set<WindSource> getWindSources(WindSourceType type);
/**
* Retrieves all wind sources known to this race, including those {@link #getWindSourcesToExclude() to exclude}.
* Callers can freely iterate because a copied collection is returned. The {@link WindSourceType#COMBINED} wind source
* is never part of the result.
* as well as the {@link WindSourceType#LEG_MIDDLE} sources are never part of the result.
*/
Set<WindSource> getWindSources();
@@ -11,7 +11,10 @@ import java.util.logging.Logger;
import com.sap.sailing.domain.base.RaceDefinition;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID;
import com.sap.sailing.domain.tracking.impl.CombinedWindTrackImpl;
import com.sap.sailing.domain.tracking.impl.LegMiddleWindTrackImpl;
import com.sap.sse.concurrent.LockUtil;
import com.sap.sse.concurrent.NamedReentrantReadWriteLock;
@@ -75,6 +78,9 @@ public abstract class TrackedRaceWithWindEssentials implements TrackedRace {
* @param delayForWindEstimationCacheInvalidation
* if <code>-1</code> and the parameter is accessed, it will be replaced by
* {@link #getMillisecondsOverWhichToAverageWind()}/2
*
* @return {@code null} in case the {@link WindSourceType#LEG_MIDDLE} type is requested for an
* {@link WindSourceWithAdditionalID#getId() ID} that doesn't identify an existing leg
*/
@Override
public WindTrack getOrCreateWindTrack(WindSource windSource, long delayForWindEstimationCacheInvalidation) {
@@ -84,6 +90,8 @@ public abstract class TrackedRaceWithWindEssentials implements TrackedRace {
combinedWindTrack = new CombinedWindTrackImpl(this, WindSourceType.COMBINED.getBaseConfidence());
}
result = combinedWindTrack;
} else if (windSource.getType() == WindSourceType.LEG_MIDDLE && windSource instanceof WindSourceWithAdditionalID) {
result = getLegMiddleWindTrack((WindSourceWithAdditionalID) windSource);
} else {
synchronized (windTracks) {
result = windTracks.get(windSource);
@@ -121,12 +129,36 @@ public abstract class TrackedRaceWithWindEssentials implements TrackedRace {
return result;
}
private WindSource getLegMiddleWindSource(TrackedLeg trackedLeg) {
return new WindSourceWithAdditionalID(WindSourceType.LEG_MIDDLE, getWindSourceIdForLegMiddle(trackedLeg));
}
private String getWindSourceIdForLegMiddle(TrackedLeg trackedLeg) {
return Integer.toString(getRace().getCourse().getIndexOfWaypoint(trackedLeg.getLeg().getFrom()));
}
private WindTrack getLegMiddleWindTrack(WindSourceWithAdditionalID windSource) {
assert windSource.getType() == WindSourceType.LEG_MIDDLE;
final String id = windSource.getId();
final Integer zeroBasedLegIndex = Integer.valueOf(id);
final TrackedLeg trackedLeg = getTrackedLeg(getRace().getCourse().getLeg(zeroBasedLegIndex));
return new LegMiddleWindTrackImpl(this, trackedLeg, WindSourceType.LEG_MIDDLE.getBaseConfidence());
}
@Override
public Set<WindSource> getWindSources(WindSourceType type) {
Set<WindSource> result = new HashSet<WindSource>();
for (WindSource windSource : getWindSources()) {
if (windSource.getType() == type) {
result.add(windSource);
if (type == WindSourceType.COMBINED) {
result.add(new WindSourceImpl(WindSourceType.COMBINED));
} else if (type == WindSourceType.LEG_MIDDLE) {
for (final TrackedLeg trackedLeg : getTrackedLegs()) {
result.add(getLegMiddleWindSource(trackedLeg));
}
} else {
for (WindSource windSource : getWindSources()) {
if (windSource.getType() == type) {
result.add(windSource);
}
}
}
return result;
@@ -28,7 +28,7 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
* the name of the track by which it can be obtained from the {@link TrackedRace}.
*/
public BravoFixTrackImpl(ItemType trackedItem, String trackName) {
super(trackedItem, trackName, BravoSensorDataMetadata.INSTANCE.getColumns(),
super(trackedItem, trackName, BravoSensorDataMetadata.INSTANCE.getFileColumns(),
BravoFixTrack.TRACK_NAME + " for " + trackedItem);
}
@@ -52,6 +52,12 @@ public class BravoFixTrackImpl<ItemType extends WithID & Serializable> extends S
return fixBefore.getRideHeight();
}
@Override
public boolean isFoiling(TimePoint timePoint) {
final Distance rideHeight = getRideHeight(timePoint);
return rideHeight != null && rideHeight.compareTo(BravoFix.MIN_FOILING_HEIGHT_THRESHOLD) >= 0;
}
@Override
public Distance getAverageRideHeight(TimePoint from, TimePoint to) {
final Distance result;
@@ -6,6 +6,7 @@ import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Wind;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.domain.tracking.impl.WindTrackImpl.DummyWind;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
@@ -19,7 +20,7 @@ public class CombinedWindAsNavigableSet extends VirtualWindFixesAsNavigableSet {
private static final long serialVersionUID = -153959652212518644L;
public CombinedWindAsNavigableSet(WindTrack track, TrackedRace trackedRace, long resolutionInMilliseconds) {
public CombinedWindAsNavigableSet(CombinedWindTrackImpl track, TrackedRace trackedRace, long resolutionInMilliseconds) {
super(track, trackedRace, resolutionInMilliseconds);
}
@@ -28,6 +29,11 @@ public class CombinedWindAsNavigableSet extends VirtualWindFixesAsNavigableSet {
super(track, trackedRace, from, to, resolutionInMilliseconds);
}
@Override
protected CombinedWindTrackImpl getTrack() {
return (CombinedWindTrackImpl) super.getTrack();
}
@Override
protected Wind getWind(Position p, TimePoint timePoint) {
return getTrackedRace().getWind(p, timePoint);
@@ -52,4 +58,13 @@ public class CombinedWindAsNavigableSet extends VirtualWindFixesAsNavigableSet {
: ceilingToResolution(getTrackedRace().getEndOfRace()) : getToInternal();
}
/**
* Uses the {@link TrackedRace#getCenterOfCourse(TimePoint) center of the course} as the position for which to
* compute the combined wind
*/
@Override
protected DummyWind createDummyWindFix(TimePoint timePoint) {
return new DummyWind(timePoint, getTrack().getDefaultPosition(timePoint));
}
}
@@ -28,13 +28,27 @@ public class CombinedWindTrackImpl extends VirtualWindTrackImpl {
public CombinedWindTrackImpl(TrackedRace trackedRace, double baseConfidence) {
super(trackedRace, /* millisecondsOverWhichToAverage not used, see overridden method */ -1, baseConfidence,
/* useSpeed */ WindSourceType.COMBINED.useSpeed());
virtualInternalRawFixes = new CombinedWindAsNavigableSet(this, trackedRace, /* resolutionInMilliseconds */ 10000l);
virtualInternalRawFixes = createVirtualInternalRawFixes(trackedRace);
}
protected CombinedWindAsNavigableSet createVirtualInternalRawFixes(TrackedRace trackedRace) {
return new CombinedWindAsNavigableSet(this, trackedRace, /* resolutionInMilliseconds */ 1000l);
}
@Override
protected CombinedWindAsNavigableSet getInternalRawFixes() {
return virtualInternalRawFixes;
}
/**
* Delivers the position to use when a caller of {@link #getAveragedWind(Position, TimePoint)} or
* {@link #getAveragedWindWithConfidence(Position, TimePoint)} provides a {@code null} position. This position is
* also used for the fixes produced by the fix iterators for this track. This implementation returns the
* {@link TrackedRace#getCenterOfCourse(TimePoint) center of the course}.
*/
protected Position getDefaultPosition(TimePoint at) {
return getTrackedRace().getCenterOfCourse(at);
}
/**
* The combined wind source already averages the wind from each wind source considered. There is no use in again
@@ -49,11 +63,11 @@ public class CombinedWindTrackImpl extends VirtualWindTrackImpl {
@Override
public Wind getAveragedWind(Position p, TimePoint at) {
return getTrackedRace().getWind(p, at);
return getTrackedRace().getWind(p==null?getDefaultPosition(at):p, at);
}
@Override
public WindWithConfidence<Util.Pair<Position, TimePoint>> getAveragedWindWithConfidence(Position p, TimePoint at) {
return getTrackedRace().getWindWithConfidence(p, at);
return getTrackedRace().getWindWithConfidence(p==null?getDefaultPosition(at):p, at);
}
}
@@ -0,0 +1,21 @@
package com.sap.sailing.domain.tracking.impl;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sse.common.TimePoint;
public class LegMiddleWindTrackImpl extends CombinedWindTrackImpl {
private static final long serialVersionUID = -3014653185579457125L;
private final TrackedLeg trackedLeg;
public LegMiddleWindTrackImpl(TrackedRace trackedRace, TrackedLeg trackedLeg, double baseConfidence) {
super(trackedRace, baseConfidence);
this.trackedLeg = trackedLeg;
}
@Override
protected Position getDefaultPosition(TimePoint at) {
return trackedLeg.getMiddleOfLeg(at);
}
}
@@ -3851,8 +3851,9 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
public Position getCenterOfCourse(TimePoint at) {
int count = 0;
ScalablePosition sum = null;
final MarkPositionAtTimePointCache cache = new MarkPositionAtTimePointCacheImpl(this, at);
for (Waypoint waypoint : getRace().getCourse().getWaypoints()) {
final Position waypointPosition = getApproximatePosition(waypoint, at);
final Position waypointPosition = getApproximatePosition(waypoint, at, cache);
if (waypointPosition != null) {
ScalablePosition p = new ScalablePosition(waypointPosition);
if (sum == null) {
@@ -122,7 +122,7 @@ public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiabl
protected TimePoint higherToResolution(TimePoint timePoint) {
TimePoint result;
final TimePoint startOfTracking = getTrackedRace().getStartOfTracking();
if (startOfTracking == null && timePoint.compareTo(startOfTracking) < 0) {
if (startOfTracking != null && timePoint.compareTo(startOfTracking) < 0) {
result = higherToResolution(startOfTracking);
} else {
result = new MillisecondsTimePoint((timePoint.asMillis() / getResolutionInMilliseconds() + 1)
@@ -192,7 +192,7 @@ public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiabl
@Override
public Wind next() {
Wind result = floor(new DummyWind(timePoint));
Wind result = floor(createDummyWindFix(timePoint));
timePoint = new MillisecondsTimePoint(timePoint.asMillis() + getResolutionInMilliseconds());
return result;
}
@@ -235,7 +235,7 @@ public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiabl
@Override
public NavigableSet<Wind> subSet(Wind fromElement, boolean fromInclusive, Wind toElement, boolean toInclusive) {
return createSubset(track, getTrackedRace(), fromInclusive ? ceilingToResolution(fromElement.getTimePoint())
return createSubset(getTrack(), getTrackedRace(), fromInclusive ? ceilingToResolution(fromElement.getTimePoint())
: higherToResolution(fromElement.getTimePoint()), toInclusive ? floorToResolution(toElement.getTimePoint())
: lowerToResolution(toElement.getTimePoint()));
}
@@ -247,13 +247,13 @@ public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiabl
@Override
public NavigableSet<Wind> headSet(Wind toElement, boolean inclusive) {
return createSubset(track, getTrackedRace(), /* from */ null,
return createSubset(getTrack(), getTrackedRace(), /* from */ null,
inclusive ? ceilingToResolution(toElement.getTimePoint()) : lowerToResolution(toElement.getTimePoint()));
}
@Override
public NavigableSet<Wind> tailSet(Wind fromElement, boolean inclusive) {
return createSubset(track, getTrackedRace(), inclusive ? floorToResolution(fromElement.getTimePoint())
return createSubset(getTrack(), getTrackedRace(), inclusive ? floorToResolution(fromElement.getTimePoint())
: higherToResolution(fromElement.getTimePoint()),
/* to */ null);
}
@@ -332,5 +332,9 @@ public abstract class VirtualWindFixesAsNavigableSet extends AbstractUnmodifiabl
protected TimePoint getToInternal() {
return to;
}
protected DummyWind createDummyWindFix(TimePoint timePoint) {
return new DummyWind(timePoint);
}
}
@@ -331,12 +331,17 @@ public class WindTrackImpl extends TrackImpl<Wind> implements WindTrack {
protected static class DummyWind extends DummyTimed implements Wind {
private static final long serialVersionUID = -311172509910032149L;
private final Position position;
public DummyWind(TimePoint timePoint) {
this(timePoint, /* position */ null);
}
public DummyWind(TimePoint timePoint, Position position) {
super(timePoint);
this.position = position;
}
@Override
public Position getPosition() {
return null;
return position;
}
@Override
public Bearing getBearing() {
@@ -69,7 +69,7 @@
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-incremental -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war &quot;${project_loc:com.sap.sailing.gwt.ui}&quot; -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto -startupUrl /gwt/Home.html com.sap.sailing.gwt.home.Home -startupUrl /gwt/AdminConsole.html com.sap.sailing.gwt.ui.AdminConsole -startupUrl /gwt/LeaderboardEditing.html com.sap.sailing.gwt.ui.LeaderboardEditing -startupUrl /gwt/Leaderboard.html com.sap.sailing.gwt.ui.Leaderboard -startupUrl /gwt/Spectator.html com.sap.sailing.gwt.ui.Spectator -startupUrl /gwt/RaceBoard.html com.sap.sailing.gwt.ui.RaceBoard -startupUrl /gwt/RegattaOverview.html com.sap.sailing.gwt.regattaoverview.RegattaOverview -startupUrl /gwt/DataMining.html com.sap.sailing.gwt.ui.DataMining -startupUrl /gwt/Simulator.html com.sap.sailing.gwt.ui.Simulator -startupUrl /gwt/VideoPopup.html com.sap.sailing.gwt.ui.VideoPopup -startupUrl /gwt/AutoPlay.html com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.home.Home com.sap.sailing.gwt.ui.AdminConsole com.sap.sailing.gwt.AutoPlay com.sap.sailing.gwt.ui.YoutubePopup com.sap.sailing.gwt.ui.Simulator com.sap.sailing.gwt.ui.Leaderboard com.sap.sailing.gwt.regattaoverview.RegattaOverview com.sap.sailing.gwt.ui.Spectator com.sap.sailing.gwt.ui.LeaderboardEditing com.sap.sailing.gwt.ui.DataMining com.sap.sailing.gwt.ui.RaceBoard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.gwt.ui"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m -Dgwt-usearchives=false -Dgwt.persistentunitcache=false -Dgwt.watchFileChanges=false"/>
</launchConfiguration>
@@ -302,6 +302,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String details();
String noGroupSelected();
String combinedWindSourceTypeName();
String legMiddleWindSourceTypeName();
String courseBasedWindSourceTypeName();
String trackBasedEstimationWindSourceTypeName();
String windSensorWindSourceTypeName();
@@ -925,6 +926,7 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String showWindSpeedSeriesTooltip();
String showWindDirectionSeriesTooltip();
String combinedWindSourceTypeTooltip();
String legMiddleWindSourceTypeTooltip();
String courseBasedWindSourceTypeTooltip();
String trackBasedEstimationWindSourceTypeTooltip();
String windSensorWindSourceTypeTooltip();
@@ -310,6 +310,7 @@ notAvailable=Not available
details=Details
noGroupSelected=No group selected
combinedWindSourceTypeName=Combined
legMiddleWindSourceTypeName=Middle of Leg
courseBasedWindSourceTypeName=Course Layout
trackBasedEstimationWindSourceTypeName=Estimation
windSensorWindSourceTypeName=Measured
@@ -897,6 +898,7 @@ gribWindImport_Title=Import wind from GRIB files
showWindSpeedSeriesTooltip=Toggles the visibility of the wind speed for all data sources
showWindDirectionSeriesTooltip=Toggles the visibility of the wind direction for all data sources
combinedWindSourceTypeTooltip=Combines all other wind sources to one data set
legMiddleWindSourceTypeTooltip=Combined wind at the mid point of a leg''s rhumb line
courseBasedWindSourceTypeTooltip=A value based on the course layout (under the assumption, that the race starts with an upwind leg)
trackBasedEstimationWindSourceTypeTooltip=Calculated data from the competitor tracks
windSensorWindSourceTypeTooltip=Data captured from mobile measuring systems
@@ -8,6 +8,8 @@ public class WindSourceTypeFormatter {
switch (windSourceType) {
case COMBINED:
return stringMessages.combinedWindSourceTypeName();
case LEG_MIDDLE:
return stringMessages.legMiddleWindSourceTypeName();
case COURSE_BASED:
return stringMessages.courseBasedWindSourceTypeName();
case TRACK_BASED_ESTIMATION:
@@ -26,6 +28,8 @@ public class WindSourceTypeFormatter {
switch (windSourceType) {
case COMBINED:
return stringMessages.combinedWindSourceTypeTooltip();
case LEG_MIDDLE:
return stringMessages.legMiddleWindSourceTypeTooltip();
case COURSE_BASED:
return stringMessages.courseBasedWindSourceTypeTooltip();
case TRACK_BASED_ESTIMATION:
@@ -42,6 +42,7 @@ import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.domain.common.impl.ColorMapImpl;
import com.sap.sailing.gwt.ui.actions.GetWindInfoAction;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
@@ -60,7 +61,7 @@ import com.sap.sse.gwt.client.shared.components.SettingsDialog;
import com.sap.sse.gwt.client.shared.components.SettingsDialogComponent;
public class WindChart extends AbstractRaceChart<WindChartSettings> implements RequiresResize {
public static final String LODA_WIND_CHART_DATA_CATEGORY = "loadWindChartData";
public static final String LOAD_WIND_CHART_DATA_CATEGORY = "loadWindChartData";
private static final int LINE_WIDTH = 1;
@@ -435,15 +436,23 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
*/
@Override
public void updateSettings(WindChartSettings newSettings) {
boolean clearCacheAndReload = false;
final Set<String> oldWindSourceTypesToRequest = getNamesOfWindSourceTypesOfWhichToDisplaySpeedOrDirection();
if (newSettings.getResolutionInMilliseconds() != settings.getResolutionInMilliseconds()) {
settings.setResolutionInMilliseconds(newSettings.getResolutionInMilliseconds());
clearCacheAndReload();
clearCacheAndReload = true;
}
settings.setShowWindDirectionsSeries(newSettings.isShowWindDirectionsSeries());
settings.setWindDirectionSourcesToDisplay(newSettings.getWindDirectionSourcesToDisplay());
settings.setShowWindSpeedSeries(newSettings.isShowWindSpeedSeries());
settings.setWindSpeedSourcesToDisplay(newSettings.getWindSpeedSourcesToDisplay());
if (!oldWindSourceTypesToRequest.equals(getNamesOfWindSourceTypesOfWhichToDisplaySpeedOrDirection())) {
clearCacheAndReload = true;
}
if (clearCacheAndReload) {
clearCacheAndReload();
}
updateVisibleSeries();
}
@@ -453,7 +462,6 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
windSourceDirectionPoints.clear();
windSourceSpeedPoints.clear();
firstPointOfFirstSeries = null;
loadData(timeRangeWithZoomProvider.getFromTime(), timeRangeWithZoomProvider.getToTime(), /* append */false);
}
@@ -473,9 +481,10 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
showLoading(stringMessages.windChartLoading());
}
GetWindInfoAction getWindInfoAction = new GetWindInfoAction(sailingService, selectedRaceIdentifier,
from, to, settings.getResolutionInMilliseconds(), null, /* onlyUpToNewestEvent==true because we don't want
from, to, settings.getResolutionInMilliseconds(), getNamesOfWindSourceTypesOfWhichToDisplaySpeedOrDirection(),
/* onlyUpToNewestEvent==true because we don't want
to overshoot the evidence so far */ true);
asyncActionsExecutor.execute(getWindInfoAction, LODA_WIND_CHART_DATA_CATEGORY,
asyncActionsExecutor.execute(getWindInfoAction, LOAD_WIND_CHART_DATA_CATEGORY,
new AsyncCallback<WindInfoForRaceDTO>() {
@Override
public void onSuccess(WindInfoForRaceDTO result) {
@@ -501,6 +510,17 @@ public class WindChart extends AbstractRaceChart<WindChartSettings> implements R
}
}
private Set<String> getNamesOfWindSourceTypesOfWhichToDisplaySpeedOrDirection() {
final Set<String> result = new HashSet<>();
for (WindSourceType speedType : getSettings().getWindSpeedSourcesToDisplay()) {
result.add(speedType.name());
}
for (WindSourceType speedType : getSettings().getWindDirectionSourcesToDisplay()) {
result.add(speedType.name());
}
return result;
}
private void clearChart() {
chart.removeAllSeries();
}
@@ -1,5 +1,6 @@
package com.sap.sailing.gwt.ui.client.shared.charts;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -75,9 +76,7 @@ public class WindChartSettingsDialogComponent implements SettingsDialogComponent
}
}
});
windSpeedSourcesPanel.add(showWindSpeedSeriesCheckbox);
showWindDirectionsSeriesCheckbox = dialog.createCheckbox(stringMessages.showWindDirectionSeries());
showWindDirectionsSeriesCheckbox.setTitle(stringMessages.showWindDirectionSeriesTooltip());
showWindDirectionsSeriesCheckbox.setValue(initialSettings.isShowWindDirectionsSeries());
@@ -92,7 +91,7 @@ public class WindChartSettingsDialogComponent implements SettingsDialogComponent
windDirectionSourcesPanel.add(showWindDirectionsSeriesCheckbox);
for (WindSourceType windSourceType : WindSourceType.values()) {
for (WindSourceType windSourceType : getWindSourceTypesToOffer()) {
CheckBox checkbox = dialog.createCheckbox(WindSourceTypeFormatter.format(windSourceType, stringMessages));
checkbox.setTitle(WindSourceTypeFormatter.tooltipFor(windSourceType, stringMessages));
checkbox.setEnabled(initialSettings.isShowWindDirectionsSeries());
@@ -102,7 +101,7 @@ public class WindChartSettingsDialogComponent implements SettingsDialogComponent
windDirectionSourcesPanel.add(checkbox);
}
for (WindSourceType windSourceType : WindSourceType.values()) {
for (WindSourceType windSourceType : getWindSourceTypesToOffer()) {
CheckBox checkbox = dialog.createCheckbox(WindSourceTypeFormatter.format(windSourceType, stringMessages));
checkbox.setTitle(WindSourceTypeFormatter.tooltipFor(windSourceType, stringMessages));
checkbox.setEnabled(initialSettings.isShowWindSpeedSeries() && windSourceType.useSpeed());
@@ -115,6 +114,12 @@ public class WindChartSettingsDialogComponent implements SettingsDialogComponent
return vp;
}
private Iterable<WindSourceType> getWindSourceTypesToOffer() {
final Set<WindSourceType> result = new HashSet<>();
result.addAll(Arrays.asList(WindSourceType.values()));
return result;
}
@Override
public WindChartSettings getResult() {
Set<WindSourceType> windDirectionSourcesToDisplay = new HashSet<WindSourceType>();
@@ -1365,6 +1365,10 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
* "best effort" readings are provided for the time interval requested, no matter if based on any sensor
* evidence or not, regardless of {@link TrackedRace#getTimePointOfNewestEvent()
* trackedRace.getTimePointOfNewestEvent()}.
* @param windSourceTypeNames
* if {@code null}, all wind sources delivered by {@link TrackedRace#getWindSources()} plus the
* {@link WindSourceType#COMBINED} wind source are delivered. Note that this does not include
* the {@link WindSourceType#LEG_MIDDLE} wind sources.
* @param includeCombinedWindForAllLegMiddles
* if <code>true</code>, the result will return non-<code>null</code> results for calls to
* {@link WindInfoForRaceDTO#getCombinedWindOnLegMiddle(int)}.
@@ -1384,20 +1388,23 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
result.windSourcesToExclude = windSourcesToExclude;
Map<WindSource, WindTrackInfoDTO> windTrackInfoDTOs = new HashMap<WindSource, WindTrackInfoDTO>();
result.windTrackInfoByWindSource = windTrackInfoDTOs;
List<WindSource> windSourcesToDeliver = new ArrayList<WindSource>();
Util.addAll(trackedRace.getWindSources(), windSourcesToDeliver);
final WindSource combinedWindSource = new WindSourceImpl(WindSourceType.COMBINED);
windSourcesToDeliver.add(combinedWindSource);
final List<WindSource> windSourcesToDeliver = new ArrayList<WindSource>();
final WindSourceImpl combinedWindSource = new WindSourceImpl(WindSourceType.COMBINED);
if (windSourceTypeNames == null) {
Util.addAll(trackedRace.getWindSources(), windSourcesToDeliver);
windSourcesToDeliver.add(combinedWindSource);
} else {
for (final String windSourceTypeToAdd : windSourceTypeNames) {
for (final WindSource windSource : trackedRace.getWindSources(WindSourceType.valueOf(windSourceTypeToAdd))) {
windSourcesToDeliver.add(windSource);
}
}
}
for (final WindSource windSource : windSourcesToDeliver) {
// TODO consider parallelizing
if (windSourceTypeNames == null || windSourceTypeNames.contains(windSource.getType().name())) {
WindTrackInfoDTO windTrackInfoDTO = createWindTrackInfoDTO(from, millisecondsStepWidth,
numberOfFixes, trackedRace, onlyUpToNewestEvent, newestEvent, windSource,
new PositionAtTimeProvider() { @Override public Position getPosition(TimePoint at) {
return windSource == combinedWindSource ? trackedRace.getCenterOfCourse(at) : null;
}});
windTrackInfoDTOs.put(windSource, windTrackInfoDTO);
}
WindTrackInfoDTO windTrackInfoDTO = createWindTrackInfoDTO(from, millisecondsStepWidth,
numberOfFixes, trackedRace, onlyUpToNewestEvent, newestEvent, windSource, /* use default positions */ at->null);
windTrackInfoDTOs.put(windSource, windTrackInfoDTO);
}
if (includeCombinedWindForAllLegMiddles) {
int zeroBasedLegNumber = 0;
@@ -1,8 +1,8 @@
package com.sap.sailing.polars.datamining.components.aggregators;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
@@ -33,7 +33,7 @@ import com.sap.sse.datamining.shared.GroupKey;
public class PolarBackendDataAggregationProcessor extends AbstractParallelGroupedDataAggregationProcessor<HasBackendPolarBoatClassContext, PolarBackendData> {
private static final String POLARS_MESSAGE_KEY = "Polars";
private final Map<GroupKey, PolarBackendData> resultMap = new HashMap<>();
private final ConcurrentHashMap<GroupKey, PolarBackendData> resultMap = new ConcurrentHashMap<>();
public PolarBackendDataAggregationProcessor(ExecutorService executor,
Collection<Processor<Map<GroupKey, PolarBackendData>, ?>> resultReceivers) {
@@ -46,6 +46,11 @@ public class PolarBackendDataAggregationProcessor extends AbstractParallelGroupe
public static AggregationProcessorDefinition<HasBackendPolarBoatClassContext, PolarBackendData> getDefinition() {
return DEFINITION;
}
@Override
protected boolean needsSynchronization() {
return false;
}
@Override
protected void handleElement(GroupedDataEntry<HasBackendPolarBoatClassContext> element) {
@@ -17,4 +17,7 @@ public interface HasGPSFixPolarContext {
@Connector(scanForStatistics=false)
HasCompetitorPolarContext getCompetitorPolarContext();
@Dimension(messageKey="IsFoiling")
boolean isFoiling();
}
@@ -4,6 +4,7 @@ import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.BravoFixTrack;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.polars.datamining.data.HasCompetitorPolarContext;
@@ -51,4 +52,15 @@ public class GPSFixWithPolarContext implements HasGPSFixPolarContext {
return competitorPolarContext;
}
@Override
public boolean isFoiling() {
final boolean result;
final BravoFixTrack<Competitor> competitorBravoFixTrack = trackedRace.getSensorTrack(competitor, BravoFixTrack.TRACK_NAME);
if (competitorBravoFixTrack != null) {
result = competitorBravoFixTrack.isFoiling(fix.getTimePoint());
} else {
result = false;
}
return result;
}
}
@@ -18,24 +18,23 @@ public class DefaultWindTrackJsonSerializer implements WindTrackJsonSerializer {
public static final String FIELD_PUBLICATION_URL = "publicationUrl";
public static final String FIELD_VENUE = "venue";
private TimePoint fromTime;
private TimePoint toTime;
private WindSource windSource;
private final TimePoint fromTime;
private final TimePoint toTime;
private final WindSource windSource;
/**
* -1 means unlimited.
*/
private final int maxNumberOfFixes;
public DefaultWindTrackJsonSerializer(int maxNumberOfFixes) {
public DefaultWindTrackJsonSerializer(int maxNumberOfFixes, TimePoint fromTime, TimePoint toTime, WindSource windSource) {
super();
this.maxNumberOfFixes = maxNumberOfFixes;
this.fromTime = fromTime;
this.toTime = toTime;
this.windSource = windSource;
}
public DefaultWindTrackJsonSerializer() {
this(-1);
}
public JSONObject serialize(WindTrack windTrack) {
JSONObject result = new JSONObject();
JSONArray jsonWindFixes = new JSONArray();
@@ -74,16 +73,4 @@ public class DefaultWindTrackJsonSerializer implements WindTrackJsonSerializer {
result.put(windSource.getType() + (windSource.getId() != null ? "-"+windSource.getId().toString() : ""), jsonWindFixes);
return result;
}
public void setFromTime(TimePoint fromTime) {
this.fromTime = fromTime;
}
public void setToTime(TimePoint toTime) {
this.toTime = toTime;
}
public void setWindSource(WindSource windSource) {
this.windSource = windSource;
}
}
@@ -2,6 +2,7 @@ package com.sap.sailing.server.gateway.serialization.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
@@ -12,7 +13,6 @@ import com.sap.sailing.domain.common.impl.WindSourceImpl;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
import com.sap.sse.common.TimePoint;
public class TrackedRaceJsonSerializer implements JsonSerializer<TrackedRace> {
public static final String FIELD_ID = "id";
@@ -24,16 +24,12 @@ public class TrackedRaceJsonSerializer implements JsonSerializer<TrackedRace> {
private final String windSourceToSerialize;
private final String windSourceIdToSerialize;
private final TimePoint fromTime;
private final TimePoint toTime;
private final WindTrackJsonSerializer windTrackSerializer;
private final Function<WindSource, WindTrackJsonSerializer> windTrackSerializerProducer;
public TrackedRaceJsonSerializer(WindTrackJsonSerializer windTrackSerializer, String windSourceToSerialize, String windSourceIdToSerialize, TimePoint fromTime, TimePoint toTime) {
this.windTrackSerializer = windTrackSerializer;
public TrackedRaceJsonSerializer(Function<WindSource, WindTrackJsonSerializer> windTrackSerializer, String windSourceToSerialize, String windSourceIdToSerialize) {
this.windTrackSerializerProducer = windTrackSerializer;
this.windSourceToSerialize = windSourceToSerialize;
this.windSourceIdToSerialize = windSourceIdToSerialize;
this.fromTime = fromTime;
this.toTime = toTime;
windSourceToSerialize = WindSourceType.COMBINED.name();
}
@@ -43,7 +39,7 @@ public class TrackedRaceJsonSerializer implements JsonSerializer<TrackedRace> {
jsonRace.put(FIELD_NAME, trackedRace.getRace().getName());
jsonRace.put(FIELD_REGATTA, trackedRace.getRaceIdentifier().getRegattaName());
if(windTrackSerializer != null) {
if (windTrackSerializerProducer != null) {
JSONArray windTracks = new JSONArray();
List<WindSource> windSources = getAvailableWindSources(trackedRace);
@@ -60,18 +56,13 @@ public class TrackedRaceJsonSerializer implements JsonSerializer<TrackedRace> {
if (windSourceIdToSerialize != null && windSource.getId() != null && !windSource.getId().toString().equalsIgnoreCase(windSourceIdToSerialize)) {
continue;
}
windTrackSerializer.setFromTime(fromTime);
windTrackSerializer.setToTime(toTime);
windTrackSerializer.setWindSource(windSource);
WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
JSONObject jsonWindTrack = windTrackSerializer.serialize(windTrack);
JSONObject jsonWindTrack = windTrackSerializerProducer.apply(windSource).serialize(windTrack);
windTracks.add(jsonWindTrack);
}
}
jsonRace.put(FIELD_WINDSOURCES, windTracks);
}
return jsonRace;
}
@@ -84,6 +75,9 @@ public class TrackedRaceJsonSerializer implements JsonSerializer<TrackedRace> {
windSources.remove(windSourceToExclude);
}
windSources.add(new WindSourceImpl(WindSourceType.COMBINED));
for (final WindSource trackedLegMiddleWindSource : trackedRace.getWindSources(WindSourceType.LEG_MIDDLE)) {
windSources.add(trackedLegMiddleWindSource);
}
return windSources;
}
}
@@ -1,12 +1,7 @@
package com.sap.sailing.server.gateway.serialization.impl;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.tracking.WindTrack;
import com.sap.sailing.server.gateway.serialization.JsonSerializer;
import com.sap.sse.common.TimePoint;
public interface WindTrackJsonSerializer extends JsonSerializer<WindTrack> {
void setFromTime(TimePoint fromTime);
void setToTime(TimePoint toTime);
void setWindSource(WindSource windSource);
}
@@ -758,10 +758,10 @@ public class RegattasResource extends AbstractSailingServerResource {
.type(MediaType.TEXT_PLAIN).build();
}
// Crop request interval to startOfTracking / [endOfTracking|timePointOfLastEvent]
from = Util.getLatestOfTimePoints(from, trackedRace.getStartOfTracking());
to = Util.getEarliestOfTimePoints(to, Util.getEarliestOfTimePoints(trackedRace.getEndOfTracking(), trackedRace.getTimePointOfNewestEvent()));
final TimePoint finalFrom = Util.getLatestOfTimePoints(from, trackedRace.getStartOfTracking());
final TimePoint finalTo = Util.getEarliestOfTimePoints(to, Util.getEarliestOfTimePoints(trackedRace.getEndOfTracking(), trackedRace.getTimePointOfNewestEvent()));
TrackedRaceJsonSerializer serializer = new TrackedRaceJsonSerializer(
new DefaultWindTrackJsonSerializer(/* maxNumberOfFixes */ 10000), windSource, windSourceId, from, to);
ws->new DefaultWindTrackJsonSerializer(/* maxNumberOfFixes */ 10000, finalFrom, finalTo, ws), windSource, windSourceId);
JSONObject jsonWindTracks = serializer.serialize(trackedRace);
String json = jsonWindTracks.toJSONString();
@@ -307,9 +307,10 @@ public class MasterDataImportTest {
logTimePoint4, author, competitor, deviceIdentifier2, logTimePoint,
logTimePoint3);
regatta.getRegattaLog().add(bravoMappingEvent);
double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.columnCount];
double[] fixData = new double[BravoSensorDataMetadata.INSTANCE.trackColumnCount];
double rideHeightValue = 1337.0;
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightColumn] = rideHeightValue;
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightPortHullColumn] = rideHeightValue;
fixData[BravoSensorDataMetadata.INSTANCE.rideHeightStarboardHullColumn] = rideHeightValue;
DoubleVectorFixImpl doubleVectorFix = new DoubleVectorFixImpl(logTimePoint2, fixData);
sourceService.getSensorFixStore().storeFix(deviceIdentifier2, doubleVectorFix);
@@ -0,0 +1,138 @@
package com.sap.sailing.server.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogResolver;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Event;
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.base.Sideline;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.BoatClassImpl;
import com.sap.sailing.domain.base.impl.CourseImpl;
import com.sap.sailing.domain.base.impl.FleetImpl;
import com.sap.sailing.domain.base.impl.RaceDefinitionImpl;
import com.sap.sailing.domain.base.impl.RegattaImpl;
import com.sap.sailing.domain.base.impl.SeriesImpl;
import com.sap.sailing.domain.common.RegattaName;
import com.sap.sailing.domain.common.ScoringSchemeType;
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.domain.leaderboard.impl.LowPoint;
import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore;
import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore;
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.TrackedRegatta;
import com.sap.sailing.domain.tracking.impl.EmptyWindStore;
import com.sap.sailing.server.impl.RacingEventServiceImpl;
import com.sap.sailing.server.operationaltransformation.CreateFlexibleLeaderboard;
import com.sap.sailing.server.operationaltransformation.RemoveEvent;
import com.sap.sailing.server.operationaltransformation.RemoveLeaderboard;
import com.sap.sailing.server.operationaltransformation.RemoveLeaderboardGroup;
import com.sap.sailing.server.operationaltransformation.RemoveRegatta;
import com.sap.sse.common.Util;
public class RemoveLeaderboardTest {
private static final String R1 = "R1";
private RacingEventServiceImpl server;
private static final String LEADERBOARD_NAME = "A Flexible Leaderboard";
private static final String RACENAME1 = "racedef1";
private final static String EVENTNAME = "TESTEVENT";
private final static String BOATCLASSNAME = "HAPPYBOATCLASS";
private final static String SERIES_NAME = "Opening";
private Regatta regatta;
private BoatClass boatClass;
private RaceDefinition raceDef1;
private DynamicTrackedRace trackedRace;
private Fleet defaultFleet;
private FlexibleLeaderboard leaderboard;
@Before
public void setUp() {
server = new RacingEventServiceImpl();
List<Event> allEvents = new ArrayList<>();
Util.addAll(server.getAllEvents(), allEvents);
for (final Event e : allEvents) {
server.apply(new RemoveEvent(e.getId()));
}
Map<String, Leaderboard> allLeaderboards = new HashMap<>(server.getLeaderboards());
for (final String leaderboardName : allLeaderboards.keySet()) {
server.apply(new RemoveLeaderboard(leaderboardName));
}
Map<String, LeaderboardGroup> allLeaderboardGroups = new HashMap<>(server.getLeaderboardGroups());
for (final String leaderboardGroupName : allLeaderboardGroups.keySet()) {
server.apply(new RemoveLeaderboardGroup(leaderboardGroupName));
}
leaderboard = server.apply(
new CreateFlexibleLeaderboard(LEADERBOARD_NAME, /* display name */ null, new int[0], new LowPoint(), /* default course area ID */ null));
leaderboard.addRaceColumn(R1, /* medalRace */ false);
boatClass = new BoatClassImpl(BOATCLASSNAME, /* typicallyStartsUpwind */ true);
regatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE,
RegattaImpl.getDefaultName(EVENTNAME, boatClass.getName()), boatClass, /*startDate*/ null, /*endDate*/ null, /* trackedRegattaRegistry */
null, DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), UUID.randomUUID(), null);
TrackedRegatta trackedRegatta1 = server.getOrCreateTrackedRegatta(regatta);
defaultFleet = new FleetImpl("Default");
regatta.addSeries(new SeriesImpl(SERIES_NAME, /* is medal */ false, /* can fleets run in parallel */ true,
Collections.singleton(defaultFleet), Collections.singleton(R1),
/* regatta registry */ server));
raceDef1 = new RaceDefinitionImpl(RACENAME1, new CourseImpl("Course1", new ArrayList<Waypoint>()), boatClass, new ArrayList<Competitor>());
regatta.addRace(raceDef1);
trackedRace = trackedRegatta1.createTrackedRace(raceDef1, Collections.<Sideline> emptyList(),
/* windStore */ EmptyWindStore.INSTANCE, /* delayToLiveInMillis */ 0l,
/* millisecondsOverWhichToAverageWind */ 0l,
/* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null, /*useMarkPassingCalculator*/ false, mock(RaceLogResolver.class));
}
@Test
public void testRaceLogOfRaceColumnInSeriesOfRegattaIsAttached() {
regatta.getRaceColumnByName(R1).setTrackedRace(defaultFleet, trackedRace);
assertEquals(1, Util.size(trackedRace.getAttachedRaceLogs()));
RaceLog raceLog = trackedRace.getAttachedRaceLogs().iterator().next();
assertSame(regatta.getRaceColumnByName(R1).getRaceLog(defaultFleet), raceLog);
}
@Test
public void testRaceLogOfRaceColumnInSeriesDetachedWhenRegattaIsRemoved() {
regatta.getRaceColumnByName(R1).setTrackedRace(defaultFleet, trackedRace);
assertEquals(1, Util.size(trackedRace.getAttachedRaceLogs()));
server.apply(new RemoveRegatta(new RegattaName(regatta.getName())));
assertTrue(Util.isEmpty(trackedRace.getAttachedRaceLogs()));
}
@Test
public void testRaceLogOfRaceColumnInFlexibleLeaderboardIsAttached() {
leaderboard.getRaceColumnByName(R1).setTrackedRace(leaderboard.getRaceColumnByName(R1).getFleets().iterator().next(), trackedRace);
assertEquals(1, Util.size(trackedRace.getAttachedRaceLogs()));
RaceLog raceLog = trackedRace.getAttachedRaceLogs().iterator().next();
assertSame(leaderboard.getRaceColumnByName(R1).getRaceLog(leaderboard.getRaceColumnByName(R1).getFleets().iterator().next()), raceLog);
}
@Test
public void testRemovingFlexibleLeaderboardDetachesAllRaceLogsFromTrackedRaces() {
leaderboard.getRaceColumnByName(R1).setTrackedRace(leaderboard.getRaceColumnByName(R1).getFleets().iterator().next(), trackedRace);
assertEquals(1, Util.size(trackedRace.getAttachedRaceLogs()));
server.apply(new RemoveLeaderboard(LEADERBOARD_NAME));
assertTrue(Util.isEmpty(trackedRace.getAttachedRaceLogs()));
}
}
@@ -9,15 +9,31 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sailing.domain.common.tracking.impl.BravoFixImpl;
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
import com.sap.sailing.domain.trackimport.DoubleVectorFixImporter;
import com.sap.sailing.domain.trackimport.FormatNotSupportedException;
import com.sap.sailing.server.trackfiles.impl.BravoDataImporterImpl;
import com.sap.sailing.server.trackfiles.impl.doublefix.DownsamplerTo1HzProcessor;
import com.sap.sailing.server.trackfiles.impl.doublefix.LearningBatchProcessor;
public class BravoDataImportTest {
private final DoubleVectorFixImporter bravoDataImporter = new BravoDataImporterImpl();
private LearningBatchProcessor batchProcessor;
private DownsamplerTo1HzProcessor downsampler;
private final DoubleVectorFixImporter bravoDataImporter = new BravoDataImporterImpl() {
protected com.sap.sailing.server.trackfiles.impl.doublefix.DoubleFixProcessor createProcessor(
BravoSensorDataMetadata metadata,
DoubleVectorFixImporter.Callback callback,
TrackFileImportDeviceIdentifier trackIdentifier) {
batchProcessor = new LearningBatchProcessor(5000, 5000, callback, trackIdentifier);
downsampler = new DownsamplerTo1HzProcessor(metadata.trackColumnCount, batchProcessor);
return downsampler;
};
};
private int callbackCallCount = 0;
private double sumRideHeightInMeters = 0.0;
@@ -48,39 +64,40 @@ public class BravoDataImportTest {
}
private void testImport(ImportData importData) throws FormatNotSupportedException, IOException {
bravoDataImporter.importFixes(importData.getInputStream(), (fixes, device) -> {
for (DoubleVectorFix fix : fixes) {
callbackCallCount++;
sumRideHeightInMeters += new BravoFixImpl(fix).getRideHeight().getMeters();
}
}, "filename", "source");
Assert.assertEquals(importData.expectedFixesCount, callbackCallCount);
Assert.assertEquals(importData.expectedAverageRideHeight,
callbackCallCount == 0 ? sumRideHeightInMeters : sumRideHeightInMeters / callbackCallCount, 0.00001);
Assert.assertEquals(importData.expectedFixesCount, downsampler.getCountSourceTtl());
Assert.assertEquals(importData.expectedFixesConsolidated, downsampler.getCountImportedTtl());
Assert.assertEquals(importData.expectedFixesConsolidated, callbackCallCount);
}
private enum ImportData {
// find out the number of fixes using the following bash line:
// tail -n +5 Undefined\ Race\ -\ BRAVO.txt | awk '{if ($5<$6) print v=$5; else v=$6; sum+=v; count++; } END {print "Sum: ", sum, "Count: ", count, "Average:", sum/count;}'
FILE_UNDEFINED_RACE_BRAVO(870, 0.661037) {
FILE_UNDEFINED_RACE_BRAVO(870, 89) {
@Override
protected InputStream getInputStream() {
return getClass().getResourceAsStream("/Undefined Race - BRAVO.txt");
}
},
DUMMY_DEFAULT_HEADER_NO_DATA(0, 0.0) {
DUMMY_DEFAULT_HEADER_NO_DATA(0, 0) {
@Override
protected InputStream getInputStream() {
return new ByteArrayInputStream(HEADER_ORDER_DEFAULT.getBytes(StandardCharsets.UTF_8));
}
},
DUMMY_DEAFULT_HEADER_ONE_LINE(1, 0.661044) {
DUMMY_DEAFULT_HEADER_ONE_LINE(1, 1) {
@Override
protected InputStream getInputStream() {
return new ByteArrayInputStream((HEADER_ORDER_DEFAULT + DUMMY_CONTENT).getBytes(StandardCharsets.UTF_8));
}
},
DUMMY_RANDOM_HAEDER_ONE_LINE(1, 0.092727) {
DUMMY_RANDOM_HAEDER_ONE_LINE(1, 1) {
@Override
protected InputStream getInputStream() {
return new ByteArrayInputStream((HEADER_ORDER_RANDOM + DUMMY_CONTENT).getBytes(StandardCharsets.UTF_8));
@@ -88,11 +105,11 @@ public class BravoDataImportTest {
};
private final int expectedFixesCount;
private final double expectedAverageRideHeight;
private final int expectedFixesConsolidated;
private ImportData(int expectedFixesCount, double expectedAverageRideHeight) {
private ImportData(int expectedFixesCount, int expectedFixesConsolidated) {
this.expectedFixesCount = expectedFixesCount;
this.expectedAverageRideHeight = expectedAverageRideHeight;
this.expectedFixesConsolidated = expectedFixesConsolidated;
}
protected abstract InputStream getInputStream();
@@ -0,0 +1,92 @@
package com.sap.sailing.server.trackfiles.test;
import org.junit.Test;
import com.sap.sailing.server.trackfiles.impl.doublefix.DoubleFixProcessor;
import com.sap.sailing.server.trackfiles.impl.doublefix.DoubleVectorFixData;
import com.sap.sailing.server.trackfiles.impl.doublefix.DownsamplerTo1HzProcessor;
import junit.framework.Assert;
public class DownsamplerTo1HzProcessorTest {
@Test
public void testConsolidation() {
MyProcessor delegate = new MyProcessor();
DownsamplerTo1HzProcessor processor = new DownsamplerTo1HzProcessor(4, delegate);
int second = 1 * 1000;
processor.accept(new DoubleVectorFixData(second + 100, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 300, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 550, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 900, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 999, new double[] { 0d, 0d, 0d, 0d }));
second = 10 * 1000;
processor.accept(new DoubleVectorFixData(second + 130, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 200, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 560, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 600, new double[] { 0d, 0d, 0d, 0d }));
second = 30 * 1000;
processor.accept(new DoubleVectorFixData(second + 130, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 200, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 560, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 561, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 562, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 563, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 564, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 600, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 700, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 800, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 900, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 910, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(second + 920, new double[] { 0d, 0d, 0d, 0d }));
second = 50 * 1000;
processor.accept(new DoubleVectorFixData(second + 130, new double[] { 0d, 0d, 0d, 0d }));
Assert.assertEquals("Count consolidated fixes", 3, delegate.countAccepted);
Assert.assertFalse("Not finished yet", delegate.fineshedWasCalled);
processor.finish();
Assert.assertEquals("Count consolidated fixes", 4, delegate.countAccepted);
Assert.assertTrue("Finished", delegate.fineshedWasCalled);
}
@Test
public void testAverageComputation() {
MyProcessor delegate = new MyProcessor();
DownsamplerTo1HzProcessor processor = new DownsamplerTo1HzProcessor(4, delegate);
int second = 1 * 1000;
processor.accept(new DoubleVectorFixData(second + 100, new double[] { 12.40d, 1d, 1d, 0d }));
processor.accept(new DoubleVectorFixData(second + 300, new double[] { 0.01d, 2d, 1d, 0d }));
processor.accept(new DoubleVectorFixData(second + 550, new double[] { -10.00d, 3d, 1d, 0d }));
processor.accept(new DoubleVectorFixData(second + 900, new double[] { 1.00d, 2d, 1d, 0d }));
processor.accept(new DoubleVectorFixData(second + 999, new double[] { 0.00d, 1d, 1d, 0d }));
processor.finish();
Assert.assertTrue("Finished", delegate.fineshedWasCalled);
Assert.assertEquals("Count consolidated fixes", 1, delegate.countAccepted);
DoubleVectorFixData lastFix = delegate.lastFix;
Assert.assertNotNull("Lastfix", lastFix);
Assert.assertEquals("Avg col 1", (12.4d + 0.01d - 10d + 1d + 0d) / 5d, lastFix.getFix()[0]);
Assert.assertEquals("Avg col 2", 9d / 5d, lastFix.getFix()[1]);
Assert.assertEquals("Avg col 3", 1d, lastFix.getFix()[2]);
Assert.assertEquals("Avg col 4", 0d, lastFix.getFix()[3]);
}
private final class MyProcessor implements DoubleFixProcessor {
public int countAccepted = 0;
public boolean fineshedWasCalled = false;
public DoubleVectorFixData lastFix;
@Override
public void finish() {
fineshedWasCalled = true;
}
@Override
public void accept(DoubleVectorFixData fix) {
lastFix = fix;
countAccepted++;
}
}
}
@@ -0,0 +1,116 @@
package com.sap.sailing.server.trackfiles.test;
import java.util.UUID;
import org.junit.Test;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
import com.sap.sailing.domain.trackimport.DoubleVectorFixImporter;
import com.sap.sailing.server.trackfiles.impl.doublefix.DoubleVectorFixData;
import com.sap.sailing.server.trackfiles.impl.doublefix.LearningBatchProcessor;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.impl.MillisecondsTimePoint;
import junit.framework.Assert;
public class LearningBatchProcessorTest {
@Test
public void testConsolidation() {
MyCallback delegate = new MyCallback();
LearningBatchProcessor processor = new LearningBatchProcessor(5, 10, delegate, device);
int currentSecondAsMillis = 1 * 1000;
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 100, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 300, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 550, new double[] { 0d, 0d, 0d, 0d }));
Assert.assertEquals("Batch not full", 0, delegate.batchesDelivered);
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 900, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 999, new double[] { 0d, 0d, 0d, 0d }));
Assert.assertEquals("Batch not full, should be learing", 0, delegate.batchesDelivered);
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 130, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 200, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 560, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 600, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 130, new double[] { 0d, 0d, 0d, 0d }));
Assert.assertEquals("Batches after learning", 2, delegate.batchesDelivered);
Assert.assertEquals("Last batch after learning", 5, delegate.lastBatchSize);
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 200, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 560, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 561, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 562, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 563, new double[] { 0d, 0d, 0d, 0d }));
Assert.assertEquals("Next batch received", 3, delegate.batchesDelivered);
Assert.assertEquals("First batch after learning", 5, delegate.lastBatchSize);
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 564, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 600, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 700, new double[] { 0d, 0d, 0d, 0d }));
processor.accept(new DoubleVectorFixData(currentSecondAsMillis + 800, new double[] { 0d, 0d, 0d, 0d }));
Assert.assertEquals("Still on same batch", 3, delegate.batchesDelivered);
processor.finish();
Assert.assertEquals("Batch count after finish", 4, delegate.batchesDelivered);
Assert.assertEquals("Last batch size after finish", 4, delegate.lastBatchSize);
Assert.assertEquals("Last fix ", (currentSecondAsMillis + 800), delegate.lastFix.getTimePoint().asMillis());
}
private final class MyCallback implements DoubleVectorFixImporter.Callback {
public int lastBatchSize = 0;
public int batchesDelivered = 0;
public DoubleVectorFix lastFix;
@Override
public void addFixes(Iterable<DoubleVectorFix> fix, TrackFileImportDeviceIdentifier device) {
lastBatchSize = Util.size(fix);
batchesDelivered++;
lastFix = Util.last(fix);
}
}
TrackFileImportDeviceIdentifier device = new TrackFileImportDeviceIdentifier() {
private static final long serialVersionUID = 870061505508090216L;
private UUID uuid = UUID.randomUUID();
private MillisecondsTimePoint uploadedAt = new MillisecondsTimePoint(0);
@Override
public String getStringRepresentation() {
return "stringRepresentation";
}
@Override
public String getIdentifierType() {
return "identifierType";
}
@Override
public TimePoint getUploadedAt() {
return uploadedAt;
}
@Override
public String getTrackName() {
return "trackname";
}
@Override
public UUID getId() {
return uuid;
}
@Override
public String getFileName() {
return "filename";
}
};
}
@@ -5,12 +5,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -18,7 +12,6 @@ import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.IntStream;
import java.util.zip.GZIPInputStream;
import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
@@ -26,13 +19,15 @@ import com.sap.sailing.domain.abstractlog.regatta.events.RegattaLogDeviceCompeti
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceCompetitorBravoMappingEventImpl;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.common.sensordata.BravoSensorDataMetadata;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
import com.sap.sailing.domain.racelogtracking.DeviceIdentifier;
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifierImpl;
import com.sap.sailing.domain.trackimport.DoubleVectorFixImporter;
import com.sap.sailing.domain.trackimport.FormatNotSupportedException;
import com.sap.sailing.server.trackfiles.impl.doublefix.DoubleFixProcessor;
import com.sap.sailing.server.trackfiles.impl.doublefix.DoubleVectorFixData;
import com.sap.sailing.server.trackfiles.impl.doublefix.DownsamplerTo1HzProcessor;
import com.sap.sailing.server.trackfiles.impl.doublefix.LearningBatchProcessor;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
@@ -42,10 +37,8 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
*/
public class BravoDataImporterImpl implements DoubleVectorFixImporter {
private final Logger LOG = Logger.getLogger(DoubleVectorFixImporter.class.getName());
private final static DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyyMMdd.HHmmss.SSSSSS");
private final BravoSensorDataMetadata metadata = BravoSensorDataMetadata.INSTANCE;
private final String BOF = "jjlDATE\tjjlTIME\tEpoch";
private static final int BATCH_SIZE = 5000;
public void importFixes(InputStream inputStream, Callback callback, final String filename, String sourceName)
throws FormatNotSupportedException, IOException {
@@ -79,22 +72,15 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter {
}
LOG.fine("Validate and parse header columns");
final Map<String, Integer> colIndices = validateAndParseHeader(headerLine);
LOG.fine("Parse and store data in batches of " + BATCH_SIZE + " items");
final ArrayList<DoubleVectorFix> collectedFixes = new ArrayList<>(BATCH_SIZE);
DoubleFixProcessor downsampler = createProcessor(metadata, callback, trackIdentifier);
buffer.lines().forEach(line -> {
lineNr.incrementAndGet();
DoubleVectorFixImpl vectorFix = parseLine(lineNr.get(), filename, line, colIndices);
if (vectorFix != null) {
collectedFixes.add(vectorFix);
if (collectedFixes.size() == BATCH_SIZE) {
callback.addFixes(collectedFixes, trackIdentifier);
collectedFixes.clear();
}
}
downsampler.accept(parseLine(lineNr.get(), filename, line, colIndices));
});
if (!collectedFixes.isEmpty()) {
callback.addFixes(collectedFixes, trackIdentifier);
}
downsampler.finish();
buffer.close();
}
} catch (Exception e) {
@@ -102,40 +88,46 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter {
}
}
/**
* This method creates the double fix processor chain used to downsample and batch the stream of fixes parsed by the
* importer.
*
* This method is protected so it can be overridden by the test case.
*
* @param metadata
* @param callback
* @param trackIdentifier
* @return
*/
protected DoubleFixProcessor createProcessor(BravoSensorDataMetadata metadata, Callback callback,
final TrackFileImportDeviceIdentifier trackIdentifier) {
LearningBatchProcessor batchProcessor = new LearningBatchProcessor(5000, 5000, callback, trackIdentifier);
DoubleFixProcessor downsampler = new DownsamplerTo1HzProcessor(metadata.trackColumnCount, batchProcessor);
return downsampler;
}
/**
* Parses the CSV line and reads the double data values in the order defined by the col enums.
*/
private DoubleVectorFixImpl parseLine(long lineNr, String filename, String line, Map<String, Integer> colIndices) {
private DoubleVectorFixData parseLine(long lineNr, String filename, String line, Map<String, Integer> columnsInFile) {
try {
String[] contentTokens = split(line);
TimePoint fixTp;
String epochColValue = contentTokens[2];
String[] fileContentTokens = split(line);
String epochColValue = fileContentTokens[2];
long epoch;
if (epochColValue != null && epochColValue.length() > 0) {
epochColValue = epochColValue.substring(0, epochColValue.indexOf("."));
long epoch = Long.parseLong(epochColValue);
fixTp = new MillisecondsTimePoint(epoch);
epoch = Long.parseLong(epochColValue);
} else {
String jjLDATE = contentTokens[0].substring(0, contentTokens[0].indexOf("."));
StringBuilder dtb = new StringBuilder(jjLDATE);
String jjlTIME = contentTokens[1];
int offset = 6 - jjlTIME.indexOf(".");
IntStream.range(0, offset).forEach(n -> dtb.append("0"));
dtb.append(jjlTIME);
LocalDateTime day = DATE_FMT.parse(dtb.toString(), LocalDateTime::from);
Instant instant = day.toInstant(ZoneOffset.UTC);
fixTp = new MillisecondsTimePoint(Date.from(instant));
// we don't have epoch, skip the line
return null;
}
// code for time adjustment for foiling test data
// long r16TP = ZonedDateTime.of(LocalDateTime.of(2016, 3, 19, 11, 55), ZoneId.of("Europe/Berlin"))
// .toEpochSecond() * 1000;
// fixTp = fixTp.plus(r16TP - 1461828459589l);
double[] fixData = new double[metadata.columnCount];
for (int columnIndexInFix = 0; columnIndexInFix < fixData.length; columnIndexInFix++) {
String columnName = metadata.getColumns().get(columnIndexInFix);
Integer columnIndexInFile = colIndices.get(columnName);
fixData[columnIndexInFix] = Double.parseDouble(contentTokens[columnIndexInFile]);
double[] trackFixData = new double[metadata.trackColumnCount];
for (int trackColumnIdx = 0; trackColumnIdx < metadata.trackColumnCount; trackColumnIdx++) {
String columnNameToSearchForInFile = metadata.getTrackColumns().get(trackColumnIdx);
Integer columnsInFileIdx = columnsInFile.get(columnNameToSearchForInFile);
trackFixData[trackColumnIdx] = Double.parseDouble(fileContentTokens[columnsInFileIdx]);
}
return new DoubleVectorFixImpl(fixTp, fixData);
return new DoubleVectorFixData(epoch, trackFixData);
} catch (Exception e) {
LOG.warning(
"Error parsing line nr " + lineNr + " in file " + filename + "with exception: " + e.getMessage());
@@ -150,7 +142,7 @@ public class BravoDataImporterImpl implements DoubleVectorFixImporter {
String header = headerTokens[j];
colIndicesInFile.put(header, j);
}
List<String> columnsInFix = metadata.getColumns();
List<String> columnsInFix = metadata.getFileColumns();
if (colIndicesInFile.size() != columnsInFix.size() || !colIndicesInFile.keySet().containsAll(columnsInFix)) {
LOG.log(Level.SEVERE, "Missing headers");
throw new RuntimeException("Missing headers in import files");
@@ -0,0 +1,7 @@
package com.sap.sailing.server.trackfiles.impl.doublefix;
public interface DoubleFixProcessor {
void accept(DoubleVectorFixData fix);
void finish();
}
@@ -0,0 +1,28 @@
package com.sap.sailing.server.trackfiles.impl.doublefix;
public final class DoubleVectorFixData {
private long timepointInMs;
private double[] fix;
public DoubleVectorFixData(long timepoint, double[] fix) {
super();
this.timepointInMs = timepoint;
this.fix = fix;
}
public void correctTimepointBy(long offset) {
timepointInMs += offset;
}
public long getTimepointInMs() {
return timepointInMs;
}
public long getFixSecond() {
return timepointInMs / 1000;
}
public double[] getFix() {
return fix;
}
}
@@ -0,0 +1,78 @@
package com.sap.sailing.server.trackfiles.impl.doublefix;
import java.util.ArrayList;
import java.util.logging.Logger;
import com.sap.sailing.domain.trackimport.DoubleVectorFixImporter;
/**
* This class consolidates fixes in a sub-second range to one fix per second and computes the average of all values.
*
*/
public final class DownsamplerTo1HzProcessor implements DoubleFixProcessor {
private final Logger LOG = Logger.getLogger(DoubleVectorFixImporter.class.getName());
final private ArrayList<DoubleVectorFixData> fixesInTheCurrentSecond = new ArrayList<>();
final private int nrOfColumsInTrack;
final private DoubleFixProcessor delegateProcesssor;
private long currentSecond = 0;
private long currentOffsetInMs = 0;
private long countSourceTtl = 0;
private long countImportedTtl = 0;
public DownsamplerTo1HzProcessor(int nrOfColumsInTrack,
DoubleFixProcessor consumerOfConsolidatedFixes) {
this.nrOfColumsInTrack = nrOfColumsInTrack;
this.delegateProcesssor = consumerOfConsolidatedFixes;
}
@Override
public void accept(DoubleVectorFixData fix) {
if (fix == null)
return;
fix.correctTimepointBy(currentOffsetInMs);
if (fix.getFixSecond() < currentSecond) {
currentOffsetInMs = (currentSecond - fix.getFixSecond() + 1) * 1000;
LOG.warning("Timepoint before last second, using offser of " + currentOffsetInMs + "ms from now on");
}
if (currentSecond != fix.getFixSecond()) {
computeDownsampledFixForCurrentSecond();
currentSecond = fix.getFixSecond();
}
countSourceTtl++;
fixesInTheCurrentSecond.add(fix);
}
public void finish() {
computeDownsampledFixForCurrentSecond();
delegateProcesssor.finish();
LOG.fine("Imported " + countImportedTtl + " fixes from " + countSourceTtl);
}
private void computeDownsampledFixForCurrentSecond() {
final double[] computedAverage = new double[nrOfColumsInTrack];
final int numberOfFixesInSecond = fixesInTheCurrentSecond.size();
if (numberOfFixesInSecond == 0) {
return;
}
for (DoubleVectorFixData d : fixesInTheCurrentSecond) {
final double[] fix = d.getFix();
for (int colIdx = 0; colIdx < nrOfColumsInTrack; colIdx++) {
computedAverage[colIdx] += fix[colIdx];
}
}
fixesInTheCurrentSecond.clear();
for (int colIdx = 0; colIdx < nrOfColumsInTrack; colIdx++) {
computedAverage[colIdx] /= (double) numberOfFixesInSecond;
}
delegateProcesssor.accept(new DoubleVectorFixData(currentSecond * 1000 + 500, computedAverage));
countImportedTtl++;
}
public long getCountImportedTtl() {
return countImportedTtl;
}
public long getCountSourceTtl() {
return countSourceTtl;
}
}
@@ -0,0 +1,90 @@
package com.sap.sailing.server.trackfiles.impl.doublefix;
import java.util.ArrayList;
import com.sap.sailing.domain.common.tracking.DoubleVectorFix;
import com.sap.sailing.domain.common.tracking.impl.DoubleVectorFixImpl;
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
import com.sap.sailing.domain.trackimport.DoubleVectorFixImporter.Callback;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public final class LearningBatchProcessor implements DoubleFixProcessor {
private final int batchSize;
private final ArrayList<DoubleVectorFixData> learnedFixes;
private final ArrayList<DoubleVectorFix> collectedFixes;
private final Callback callback;
private final TrackFileImportDeviceIdentifier deviceIdentifier;
private boolean isLearning = true;
private long fixesToLearn;
public LearningBatchProcessor(int batchSize, int fixesToLearn, Callback callback,
TrackFileImportDeviceIdentifier deviceIdentifier) {
super();
this.batchSize = batchSize;
this.fixesToLearn = fixesToLearn;
this.collectedFixes = new ArrayList<>(batchSize);
this.learnedFixes = new ArrayList<>(fixesToLearn);
this.callback = callback;
this.deviceIdentifier = deviceIdentifier;
}
@Override
public void accept(DoubleVectorFixData fix) {
if (isLearning) {
learnFix(fix);
} else {
processFix(fix);
}
}
/**
* In the learn phase the fixes just get collected for learning.
*
* @param fix
*/
private void learnFix(DoubleVectorFixData fix) {
learnedFixes.add(fix);
if (learnedFixes.size() >= fixesToLearn) {
finishLearning();
}
}
private void finishLearning() {
// Now we have enough data to process
isLearning = false;
// process the loaded learning fixes
for (DoubleVectorFixData doubleVectorFix : learnedFixes) {
processFix(doubleVectorFix);
}
}
/**
* When processing a fix, data can be corrected before added to the collection of Fixes that will get stored in the
* mongo....
*
* @param fix
*/
private void processFix(DoubleVectorFixData fix) {
// TODO process fix
collectedFixes.add(new DoubleVectorFixImpl(new MillisecondsTimePoint(fix.getTimepointInMs()), fix.getFix()));
int currentlyCollectedFixes = collectedFixes.size();
if (currentlyCollectedFixes >= batchSize) {
pushCollectedFixes();
}
}
@Override
public void finish() {
if (isLearning) {
finishLearning();
}
pushCollectedFixes();
}
private void pushCollectedFixes() {
callback.addFixes(collectedFixes, deviceIdentifier);
collectedFixes.clear();
}
}
@@ -27,5 +27,5 @@
<booleanAttribute key="tracing" value="false"/>
<booleanAttribute key="useCustomFeatures" value="false"/>
<booleanAttribute key="useDefaultConfigArea" value="true"/>
<stringAttribute key="workspace_bundles" value="com.google.gwt.servlet@default:default,com.googlecode.java-diff-utils@default:default,com.googlecode.mgwt@default:default,com.sap.sailing.barbados.resultimport@default:default,com.sap.sailing.competitorimport@default:default,com.sap.sailing.dashboards.gwt@5:true,com.sap.sailing.datamining.provider@default:default,com.sap.sailing.datamining.shared@default:default,com.sap.sailing.datamining@default:default,com.sap.sailing.declination@default:default,com.sap.sailing.domain.common@default:default,com.sap.sailing.domain.deckmanadapter@4:true,com.sap.sailing.domain.igtimiadapter.persistence@default:default,com.sap.sailing.domain.igtimiadapter@default:default,com.sap.sailing.domain.persistence@default:default,com.sap.sailing.domain.racelogtrackingadapter@3:default,com.sap.sailing.domain.shared.android@default:default,com.sap.sailing.domain.swisstimingadapter.persistence@default:default,com.sap.sailing.domain.swisstimingadapter@4:true,com.sap.sailing.domain.swisstimingreplayadapter@4:true,com.sap.sailing.domain.tractracadapter.persistence@default:default,com.sap.sailing.domain.tractracadapter@4:true,com.sap.sailing.domain@default:default,com.sap.sailing.ess40.resultimport@default:default,com.sap.sailing.expeditionconnector@default:default,com.sap.sailing.freg.resultimport@default:default,com.sap.sailing.geocoding@default:default,com.sap.sailing.gwt.ui@5:true,com.sap.sailing.kiworesultimport@default:default,com.sap.sailing.manage2sail.resultimport@default:default,com.sap.sailing.manage2sail@default:default,com.sap.sailing.news@default:default,com.sap.sailing.polars.datamining.shared@default:default,com.sap.sailing.polars.datamining@default:default,com.sap.sailing.polars@4:true,com.sap.sailing.resultimport@default:default,com.sap.sailing.sailwave.resultimport@default:default,com.sap.sailing.server.gateway.serialization.shared.android@default:default,com.sap.sailing.server.gateway.serialization@default:default,com.sap.sailing.server.gateway@5:true,com.sap.sailing.server.trackfiles@default:default,com.sap.sailing.server@default:default,com.sap.sailing.simulator@default:default,com.sap.sailing.udpconnector@default:default,com.sap.sailing.velum.resultimport@default:default,com.sap.sailing.www@4:true,com.sap.sailing.xrr.resultimport@default:default,com.sap.sailing.xrr.schema@default:default,com.sap.sailing.xrr.structureimport@default:default,com.sap.sailing.yachtscoring.resultimport@default:default,com.sap.sse.common@default:default,com.sap.sse.datamining.annotations@default:default,com.sap.sse.datamining.shared@default:default,com.sap.sse.datamining@default:default,com.sap.sse.filestorage@default:default,com.sap.sse.gwt.adminconsole@default:default,com.sap.sse.gwt@default:default,com.sap.sse.mail@4:true,com.sap.sse.mongodb@default:default,com.sap.sse.operationaltransformation@default:default,com.sap.sse.replication@4:true,com.sap.sse.security.common@default:default,com.sap.sse.security.ui@6:true,com.sap.sse.security.userstore.mongodb@4:true,com.sap.sse.security@5:true,com.sap.sse.shared.android@default:default,com.sap.sse@default:default,com.tractrac.clientmodule@default:default,org.json.simple@default:default,org.moxieapps.gwt.highcharts@default:default"/>
<stringAttribute key="workspace_bundles" value="com.google.gwt.servlet@default:default,com.googlecode.java-diff-utils@default:default,com.googlecode.mgwt@default:default,com.sap.sailing.barbados.resultimport@default:default,com.sap.sailing.competitorimport@default:default,com.sap.sailing.dashboards.gwt@5:true,com.sap.sailing.datamining.provider@default:default,com.sap.sailing.datamining.shared@default:default,com.sap.sailing.datamining@default:default,com.sap.sailing.declination@default:default,com.sap.sailing.domain.common@default:default,com.sap.sailing.domain.deckmanadapter@4:true,com.sap.sailing.domain.igtimiadapter.persistence@default:default,com.sap.sailing.domain.igtimiadapter@default:default,com.sap.sailing.domain.persistence@default:default,com.sap.sailing.domain.racelogtrackingadapter@3:default,com.sap.sailing.domain.shared.android@default:default,com.sap.sailing.domain.swisstimingadapter.persistence@default:default,com.sap.sailing.domain.swisstimingadapter@4:true,com.sap.sailing.domain.swisstimingreplayadapter@4:true,com.sap.sailing.domain.tractracadapter.persistence@default:default,com.sap.sailing.domain.tractracadapter@4:true,com.sap.sailing.domain@default:default,com.sap.sailing.ess40.resultimport@default:default,com.sap.sailing.expeditionconnector@default:default,com.sap.sailing.freg.resultimport@default:default,com.sap.sailing.geocoding@default:default,com.sap.sailing.grib@default:default,com.sap.sailing.gwt.ui@5:true,com.sap.sailing.kiworesultimport@default:default,com.sap.sailing.manage2sail.resultimport@default:default,com.sap.sailing.manage2sail@default:default,com.sap.sailing.news@default:default,com.sap.sailing.polars.datamining.shared@default:default,com.sap.sailing.polars.datamining@default:default,com.sap.sailing.polars@4:true,com.sap.sailing.resultimport@default:default,com.sap.sailing.sailwave.resultimport@default:default,com.sap.sailing.server.gateway.serialization.shared.android@default:default,com.sap.sailing.server.gateway.serialization@default:default,com.sap.sailing.server.gateway@5:true,com.sap.sailing.server.trackfiles@default:default,com.sap.sailing.server@default:default,com.sap.sailing.simulator@default:default,com.sap.sailing.udpconnector@default:default,com.sap.sailing.velum.resultimport@default:default,com.sap.sailing.www@4:true,com.sap.sailing.xrr.resultimport@default:default,com.sap.sailing.xrr.schema@default:default,com.sap.sailing.xrr.structureimport@default:default,com.sap.sailing.yachtscoring.resultimport@default:default,com.sap.sse.common@default:default,com.sap.sse.datamining.annotations@default:default,com.sap.sse.datamining.shared@default:default,com.sap.sse.datamining@default:default,com.sap.sse.filestorage@default:default,com.sap.sse.gwt.adminconsole@default:default,com.sap.sse.gwt@default:default,com.sap.sse.mail@4:true,com.sap.sse.mongodb@default:default,com.sap.sse.operationaltransformation@default:default,com.sap.sse.replication@4:true,com.sap.sse.security.common@default:default,com.sap.sse.security.ui@6:true,com.sap.sse.security.userstore.mongodb@4:true,com.sap.sse.security@5:true,com.sap.sse.shared.android@default:default,com.sap.sse@default:default,com.tractrac.clientmodule@default:default,org.json.simple@default:default,org.moxieapps.gwt.highcharts@default:default"/>
</launchConfiguration>
@@ -38,6 +38,13 @@
the GRIB files you use are adequate in resolution and region as well as adequate in time. Obtaining
a forecast 72h in the future makes little sense for an in-shore race starting in 30min.
</li>
<li>The REST service for wind (/sailingservice/api/v1/regattas/{regatta}/races/{race}/wind) now considers
the center of the course to calculate the COMBINED wind readings. Previously, all wind readings for the COMBINED
wind track as published through the REST API were equally considered, regardless of how far away a
wind sensor was from the course. Now, sensors closer to the course count more than those further away.
Furthermore, the time between two readings in the COMBINED wind track in the REST output has been
reduced from ten seconds to one second.
</li>
</ul>
<h2 class="articleSubheadline">January 2017</h2>
<ul class="bulletList">
@@ -1,4 +1,4 @@
package com.sap.sailing.datamining.impl.data;
package com.sap.sse.datamining.impl.data;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
@@ -12,8 +12,8 @@ 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;
import com.sap.sse.datamining.impl.data.LinearDoubleClusterGroup;
// TODO Move to sse bundle, after 49er analysis
public class TestLinearDoubleClusterGroup {
private LinearDoubleClusterGroup hardPercentageClusterGroup;
@@ -1,4 +1,4 @@
package com.sap.sailing.datamining.impl.data;
package com.sap.sse.datamining.impl.data;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@@ -8,6 +8,8 @@ import org.junit.Test;
import com.sap.sse.datamining.data.Cluster;
import com.sap.sse.datamining.data.ClusterGroup;
import com.sap.sse.datamining.impl.data.LinearDoubleClusterGroup;
import com.sap.sse.datamining.impl.data.PercentageClusterFormatter;
public class TestPercentageClusterFormatter {
@@ -2,4 +2,3 @@ eclipse.preferences.version=1
encoding//resources/stringmessages/StringMessages_ja.properties=UTF-8
encoding//resources/stringmessages/StringMessages_ru.properties=UTF-8
encoding//resources/stringmessages/StringMessages_zh.properties=UTF-8
encoding//resources/stringmessages/StringMessages_zh_CN.properties=UTF-8
@@ -18,6 +18,7 @@ public interface ClusterBoundary<ElementType extends Serializable> extends Seria
public boolean contains(ElementType value);
public ComparisonStrategy getStrategy();
public ElementType getBoundaryValue();
public Class<ElementType> getClusterElementsType();
@@ -1,10 +1,7 @@
package com.sap.sailing.datamining.data;
package com.sap.sse.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);
@@ -27,17 +27,37 @@ public abstract class AbstractParallelAggregationProcessor<InputType, Aggregated
@Override
protected AbstractProcessorInstruction<AggregatedType> createInstruction(final InputType element) {
return new AbstractProcessorInstruction<AggregatedType>(this, ProcessorInstructionPriority.Aggregation) {
@Override
public AggregatedType computeResult() {
synchronized (monitor) {
handleElement(element);
if (needsSynchronization()) {
return new AbstractProcessorInstruction<AggregatedType>(this, ProcessorInstructionPriority.Aggregation) {
@Override
public AggregatedType computeResult() {
synchronized (monitor) {
handleElement(element);
}
return AbstractParallelAggregationProcessor.super.createInvalidResult();
}
return AbstractParallelAggregationProcessor.super.createInvalidResult();
}
};
};
} else {
return new AbstractProcessorInstruction<AggregatedType>(this, ProcessorInstructionPriority.Aggregation) {
@Override
protected AggregatedType computeResult() throws Exception {
handleElement(element);
return AbstractParallelAggregationProcessor.super.createInvalidResult();
}
};
}
}
/**
* Defines if {@link #handleElement(Object) handleElement} needs to be called in a synchronized block.
* Default is <code>true</code>.</br>
* Override if the concrete aggregator implementation will handle the synchronization on its own.
* @return
*/
protected boolean needsSynchronization() {
return true;
}
/**
* Method to handle the element. This method is only called in a way that is thread safe so
@@ -14,9 +14,7 @@ import com.sap.sse.common.Util;
import com.sap.sse.datamining.components.AdditionalResultDataBuilder;
import com.sap.sse.datamining.components.AggregationProcessorDefinition;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.impl.components.AbstractProcessorInstruction;
import com.sap.sse.datamining.impl.components.GroupedDataEntry;
import com.sap.sse.datamining.impl.components.ProcessorInstructionPriority;
import com.sap.sse.datamining.impl.components.SimpleAggregationProcessorDefinition;
import com.sap.sse.datamining.shared.GroupKey;
@@ -45,20 +43,10 @@ public class ParallelGroupedDataCountDistinctAggregationProcessor
super(executor, resultReceivers, "CountDistinct");
countMap = new ConcurrentHashMap<>();
}
/**
* We don't need synchronization here because we're using a {@link ConcurrentHashSet} and a {@link ConcurrentHashMap} to
* aggregate the results.
*/
@Override
protected AbstractProcessorInstruction<Map<GroupKey, Number>> createInstruction(GroupedDataEntry<Object> element) {
return new AbstractProcessorInstruction<Map<GroupKey, Number>>(this, ProcessorInstructionPriority.Aggregation) {
@Override
public Map<GroupKey, Number> computeResult() {
handleElement(element);
return createInvalidResult();
}
};
protected boolean needsSynchronization() {
return false;
}
@Override
@@ -45,7 +45,7 @@ public abstract class AbstractCluster<ElementType extends Serializable> implemen
return getBoundariesAsString();
}
protected Collection<ClusterBoundary<ElementType>> getClusterBoundaries() {
public Iterable<ClusterBoundary<ElementType>> getClusterBoundaries() {
return boundaries;
}
@@ -22,7 +22,8 @@ public abstract class AbstractClusterBoundary<ElementType extends Serializable>
protected abstract int compare(ElementType value);
protected ElementType getBoundaryValue() {
@Override
public ElementType getBoundaryValue() {
return boundaryValue;
}
@@ -0,0 +1,78 @@
package com.sap.sse.datamining.impl.data;
import java.io.Serializable;
import com.sap.sse.common.Util;
import com.sap.sse.datamining.data.Cluster;
import com.sap.sse.datamining.data.ClusterBoundary;
import com.sap.sse.datamining.data.ClusterFormatter;
public abstract class AbstractClusterFormatter<T extends Serializable> implements ClusterFormatter<T> {
private static final char INFINITE = '\u221e';
@Override
public String format(Cluster<T> cluster) {
if (cluster instanceof LocalizedCluster) {
format(((LocalizedCluster<T>) cluster).getInnerCluster());
}
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) {
Iterable<ClusterBoundary<T>> boundaries = cluster.getClusterBoundaries();
if (boundaries == null) {
return "<Unformattable>";
}
ClusterBoundary<T> boundary = Util.first(boundaries);
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) {
Iterable<ClusterBoundary<T>> boundaries = cluster.getClusterBoundaries();
if (boundaries == null) {
return "<Unformattable>";
}
return String.format("%s - %s", formatBoundary(Util.get(boundaries, 0)), formatBoundary(Util.get(boundaries, 1)));
}
protected String formatBoundary(ClusterBoundary<T> boundary) {
String value = formatValue(boundary.getBoundaryValue());
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);
}
@@ -1,16 +1,12 @@
package com.sap.sailing.datamining.impl.data;
package com.sap.sse.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
// TODO: Cache the clusters
public class LinearDoubleClusterGroup implements ClusterGroup<Double> {
private static final long serialVersionUID = 1559316450183865115L;
@@ -31,5 +31,9 @@ public class LocalizedCluster<ElementType extends Serializable> implements Clust
public Class<ElementType> getClusterElementsType() {
return cluster.getClusterElementsType();
}
public Cluster<ElementType> getInnerCluster() {
return cluster;
}
}
@@ -1,4 +1,4 @@
package com.sap.sailing.datamining.impl.data;
package com.sap.sse.datamining.impl.data;
public class PercentageClusterFormatter extends AbstractClusterFormatter<Double> {
@@ -4,4 +4,3 @@ encoding//src/com/sap/sse/gwt/client/controls/filestorage/StringMessages_de.prop
encoding//src/com/sap/sse/gwt/client/controls/filestorage/StringMessages_ja.properties=UTF-8
encoding//src/com/sap/sse/gwt/client/controls/filestorage/StringMessages_ru.properties=UTF-8
encoding//src/com/sap/sse/gwt/client/controls/filestorage/StringMessages_zh.properties=UTF-8
encoding//src/com/sap/sse/gwt/client/controls/filestorage/StringMessages_zh_CN.properties=UTF-8
@@ -5,7 +5,6 @@ encoding//src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_de.pr
encoding//src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_ja.properties=UTF-8
encoding//src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_ru.properties=UTF-8
encoding//src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_zh.properties=UTF-8
encoding//src/main/java/com/sap/sse/security/ui/client/i18n/StringMessages_zh_CN.properties=UTF-8
encoding//src/main/java/com/sap/sse/security/ui/login/LoginView.java=utf-8
encoding//src/main/java/com/sap/sse/security/ui/oauth/client/util/ClientUtils.java=utf-8
encoding//src/main/java/com/sap/sse/security/ui/servlet/RedirectServlet.java=utf-8