bug5396: added COG column; drive-by fix: show course middle line (Rhumb line) also pre-start

This commit is contained in:
Axel Uhl
2020-10-15 00:31:17 +02:00
parent d70fc37e8b
commit 621321146f
8 changed files with 70 additions and 17 deletions
@@ -372,6 +372,7 @@ public enum DetailType implements Serializable {
allowed.add(RACE_IMPLIED_WIND);
allowed.add(RACE_CALCULATED_TIME_AT_ESTIMATED_ARRIVAL_AT_COMPETITOR_FARTHEST_AHEAD);
allowed.add(RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS);
allowed.add(RACE_CURRENT_COURSE_OVER_GROUND_IN_TRUE_DEGREES);
allowed.add(RACE_CURRENT_DISTANCE_FOILED_IN_METERS);
allowed.add(RACE_CURRENT_DURATION_FOILED_IN_SECONDS);
allowed.add(RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS);
@@ -5,6 +5,7 @@ import java.util.List;
import com.sap.sailing.domain.common.MaxPointsReason;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sse.common.Bearing;
@@ -141,6 +142,8 @@ public class LeaderboardEntryDTO implements Serializable {
* farthest ahead for all subsequent legs.
*/
public Duration calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead;
public SpeedWithBearing currentSpeedAndCourseOverGround;
/**
* If <code>null</code>, no leg details are known yet, the race is not being tracked or the details
@@ -618,6 +621,8 @@ public class LeaderboardEntryDTO implements Serializable {
result = prime * result + ((calculatedTime == null) ? 0 : calculatedTime.hashCode());
result = prime * result + ((calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead == null) ? 0
: calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead.hashCode());
result = prime * result + ((currentSpeedAndCourseOverGround == null) ? 0
: currentSpeedAndCourseOverGround.hashCode());
result = prime * result + (discarded ? 1231 : 1237);
result = prime * result + ((distanceToStarboardSideOfStartLineInMeters == null) ? 0
: distanceToStarboardSideOfStartLineInMeters.hashCode());
@@ -703,6 +708,12 @@ public class LeaderboardEntryDTO implements Serializable {
} else if (!calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead
.equals(other.calculatedTimeAtEstimatedArrivalAtCompetitorFarthestAhead))
return false;
if (currentSpeedAndCourseOverGround == null) {
if (other.currentSpeedAndCourseOverGround != null)
return false;
} else if (!currentSpeedAndCourseOverGround
.equals(other.currentSpeedAndCourseOverGround))
return false;
if (discarded != other.discarded)
return false;
if (distanceToStarboardSideOfStartLineInMeters == null) {
@@ -689,6 +689,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
Date timePointOfLastPositionFixAtOrBeforeQueryTimePoint = getTimePointOfLastFixAtOrBefore(competitor, trackedRace, timePoint);
if (track != null) {
entryDTO.averageSamplingInterval = track.getAverageIntervalBetweenRawFixes();
entryDTO.currentSpeedAndCourseOverGround = track.getEstimatedSpeed(timePoint);
}
if (timePointOfLastPositionFixAtOrBeforeQueryTimePoint != null) {
long timeDifferenceInMs = timePoint.asMillis() - timePointOfLastPositionFixAtOrBeforeQueryTimePoint.getTime();
@@ -14,6 +14,10 @@
of tracks and course broke. This has been fixed now.</li>
<li>For better visibility especially with satellite view enabled, rhumb line display now uses a bright
green color.</li>
<li>Added an optional COG (Course over Ground) column to the leaderboard which shows all competitors'
COG in true degrees.</li>
<li>The rhumb line ("course middle line") for the first leg is now already displayed prior to the
start time point.</li>
</ul>
<h5 class="articleSubheadline">September 2020</h5>
<ul class="bulletList">
@@ -170,7 +170,7 @@ import com.sap.sse.gwt.shared.DebugConstants;
public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> implements TimeListener, CompetitorSelectionChangeListener,
RaceTimesInfoProviderListener, TailFactory, ColorMapperChangedListener, RequiresDataInitialization, RequiresResize, QuickFlagDataValuesProvider {
/* Line colors */
static private final RGBColor COURSE_MIDDLE_LINE_COLOR = new RGBColor("#0eed1d");
static private final RGBColor COURSE_MIDDLE_LINE_COLOR = new RGBColor("#0eed1d"); // selected by Larry Rosenfeld...
static final Color ADVANTAGE_LINE_COLOR = new RGBColor("#ff9900"); // orange
static final Color START_LINE_COLOR = Color.WHITE;
static final Color FINISH_LINE_COLOR = Color.BLACK;
@@ -1994,8 +1994,9 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
boolean showCourseMiddleLine = keysAlreadyHandled.containsKey(key) && keysAlreadyHandled.get(key).getA() ||
settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) ||
(settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEMIDDLELINE)
&& courseDTO.currentLegNumber > 0
&& courseDTO.currentLegNumber-1 == zeroBasedIndexOfStartWaypoint);
// show the line for the current leg or for the first leg if we are still before the start
&& (courseDTO.currentLegNumber-1 == zeroBasedIndexOfStartWaypoint) ||
courseDTO.currentLegNumber == 0 && zeroBasedIndexOfStartWaypoint == 0);
keysAlreadyHandled.put(key, new Pair<>(showCourseMiddleLine, zeroBasedIndexOfStartWaypoint));
}
Set<Set<ControlPointDTO>> keysToConsider = new HashSet<>(keysAlreadyHandled.keySet());
@@ -2069,7 +2070,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
}
};
return showOrRemoveOrUpdateLine(lineToShowOrRemoveOrUpdate, showLine, position1DTO, position2DTO,
lineInfoProvider, COURSE_MIDDLE_LINE_COLOR.getAsHtml() /* selected by Larry Rosenfeld... */, STANDARD_LINE_STROKEWEIGHT, STANDARD_LINE_OPACITY);
lineInfoProvider, COURSE_MIDDLE_LINE_COLOR.getAsHtml(), STANDARD_LINE_STROKEWEIGHT, STANDARD_LINE_OPACITY);
}
/**
@@ -1203,6 +1203,10 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> extends A
new FormattedDoubleLeaderboardRowDTODetailTypeColumn(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS,
new RaceCurrentSpeedOverGroundInKnots(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE,
LeaderboardPanel.this));
result.put(DetailType.RACE_CURRENT_COURSE_OVER_GROUND_IN_TRUE_DEGREES,
new FormattedDoubleLeaderboardRowDTODetailTypeColumn(DetailType.RACE_CURRENT_COURSE_OVER_GROUND_IN_TRUE_DEGREES,
new RaceCurrentCourseOverGroundInTrueDegrees(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE,
LeaderboardPanel.this));
result.put(DetailType.BRAVO_RACE_CURRENT_RIDE_HEIGHT_IN_METERS,
new RideHeightColumn(DetailType.BRAVO_RACE_CURRENT_RIDE_HEIGHT_IN_METERS,
new RaceCurrentRideHeightInMeters(), LEG_COLUMN_HEADER_STYLE, LEG_COLUMN_STYLE,
@@ -1580,6 +1584,44 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> extends A
}
}
/**
* Fetches the average absolute (distance always counted as positive, no matter whether left or right)
* cross-track error for the race
*
* @author Axel Uhl (D043530)
*/
private class RaceCurrentSpeedOverGroundInKnots implements DataExtractor<Double, LeaderboardRowDTO> {
@Override
public Double get(LeaderboardRowDTO row) {
Double result = null;
LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName());
if (fieldsForRace != null) {
result = fieldsForRace.currentSpeedAndCourseOverGround == null ? null :
fieldsForRace.currentSpeedAndCourseOverGround.getKnots();
}
return result;
}
}
/**
* Fetches the average absolute (distance always counted as positive, no matter whether left or right)
* cross-track error for the race
*
* @author Axel Uhl (D043530)
*/
private class RaceCurrentCourseOverGroundInTrueDegrees implements DataExtractor<Double, LeaderboardRowDTO> {
@Override
public Double get(LeaderboardRowDTO row) {
Double result = null;
LeaderboardEntryDTO fieldsForRace = row.fieldsByRaceColumnName.get(getRaceColumnName());
if (fieldsForRace != null) {
result = fieldsForRace.currentSpeedAndCourseOverGround == null ? null :
fieldsForRace.currentSpeedAndCourseOverGround.getBearing().getDegrees();
}
return result;
}
}
/**
* Fetches the average absolute (distance always counted as positive, no matter whether left or right)
* cross-track error for the race
@@ -1912,18 +1954,6 @@ public abstract class LeaderboardPanel<LS extends LeaderboardSettings> extends A
}
}
private class RaceCurrentSpeedOverGroundInKnots extends AbstractLastLegDetailField<Double> {
@Override
protected Double getBeforeLastLegFinished(LegEntryDTO currentLegDetail) {
return currentLegDetail.currentSpeedOverGroundInKnots;
}
@Override
protected Double getAfterLastLegFinished(LeaderboardRowDTO row) {
return new RaceAverageSpeedInKnots().get(row);
}
}
private class RaceCurrentRideHeightInMeters extends AbstractLastLegDetailField<Double> {
@Override
protected Double getBeforeLastLegFinished(LegEntryDTO currentLegDetail) {
@@ -14,6 +14,11 @@ public class CoursePositionsDTO implements IsSerializable {
public Set<MarkDTO> marks;
public int totalLegsCount;
/**
* The leg number is 0 before the start, the number of the current leg during the race
* and the number of the last leg at the end of the race even if the race has finished.
*/
public int currentLegNumber;
/**
@@ -8,7 +8,7 @@ public enum DetailCheckboxInfo {
REGATTA_RANK(DetailType.REGATTA_RANK, "Regatta Rank"), TOTAL_DISTANCE(DetailType.OVERALL_TOTAL_DISTANCE_TRAVELED , "Total distance"), TOTAL_AVERAGE_SPEED_OVER_GROUND(DetailType.OVERALL_TOTAL_AVERAGE_SPEED_OVER_GROUND, "∅ SOG"), TOTAL_TIME(DetailType.OVERALL_TOTAL_TIME_SAILED_IN_SECONDS, "Total time"), MAXIMUM_SPEED_OVER_GROUND(DetailType.OVERALL_MAXIMUM_SPEED_OVER_GROUND_IN_KNOTS, "Max SOG"), TIME_ON_TIME_FACTOR(DetailType.OVERALL_TIME_ON_TIME_FACTOR, "ToT Factor"), TIME_ON_DISTANCE_ALLOWANCE(DetailType.OVERALL_TIME_ON_DISTANCE_ALLOWANCE_IN_SECONDS_PER_NAUTICAL_MILE, "ToD Allowance"),
// Race details
RACE_GAP_TO_LEADER(DetailType.RACE_GAP_TO_LEADER_IN_SECONDS, "Gap to leader"), RACE_AVERAGE_SPEED_OVER_GROUND(DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, "∅ Speed"), RACE_DISTANCE(DetailType.RACE_DISTANCE_TRAVELED, "Distance"), RACE_DISTANCE_INCLUDING_GATE_START(DetailType.RACE_DISTANCE_TRAVELED_INCLUDING_GATE_START, "Distance (w/ Gate Start)"), RACE_TIME(DetailType.RACE_TIME_TRAVELED, "Time"), RACE_CURRENT_SPEED_OVER_GROUND(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, "SOG"), RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD(DetailType.RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS, "Gap"), NUMBER_OF_MANEUVERS(DetailType.NUMBER_OF_MANEUVERS, "Maneuvers"), DISPLAY_LEGS(DetailType.RACE_DISPLAY_LEGS, "Legs"), CURRENT_LEG(DetailType.RACE_CURRENT_LEG, "On Leg"), RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR(DetailType.RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS, "∅ XTE"), RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR(DetailType.RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS, "∅ XTE +/-"), RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL(DetailType.RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL, "GPS Lag"),
RACE_GAP_TO_LEADER(DetailType.RACE_GAP_TO_LEADER_IN_SECONDS, "Gap to leader"), RACE_AVERAGE_SPEED_OVER_GROUND(DetailType.RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS, "∅ Speed"), RACE_DISTANCE(DetailType.RACE_DISTANCE_TRAVELED, "Distance"), RACE_DISTANCE_INCLUDING_GATE_START(DetailType.RACE_DISTANCE_TRAVELED_INCLUDING_GATE_START, "Distance (w/ Gate Start)"), RACE_TIME(DetailType.RACE_TIME_TRAVELED, "Time"), RACE_CURRENT_SPEED_OVER_GROUND(DetailType.RACE_CURRENT_SPEED_OVER_GROUND_IN_KNOTS, "SOG"), RACE_CURRENT_COURSE_OVER_GROUND(DetailType.RACE_CURRENT_COURSE_OVER_GROUND_IN_TRUE_DEGREES, "COG"), RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD(DetailType.RACE_DISTANCE_TO_COMPETITOR_FARTHEST_AHEAD_IN_METERS, "Gap"), NUMBER_OF_MANEUVERS(DetailType.NUMBER_OF_MANEUVERS, "Maneuvers"), DISPLAY_LEGS(DetailType.RACE_DISPLAY_LEGS, "Legs"), CURRENT_LEG(DetailType.RACE_CURRENT_LEG, "On Leg"), RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR(DetailType.RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS, "∅ XTE"), RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR(DetailType.RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS, "∅ XTE +/-"), RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL(DetailType.RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL, "GPS Lag"),
// Race Start Analysis
RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_START(DetailType.RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_RACE_START, "Distance to line 5s before start"), RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START(DetailType.RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START, "SOG 5s before start"), DISTANCE_TO_START_AT_RACE_START(DetailType.DISTANCE_TO_START_AT_RACE_START, "Distance to line at Start"), TIME_BETWEEN_RACE_START_AND_COMPETITOR_START(DetailType.TIME_BETWEEN_RACE_START_AND_COMPETITOR_START, "Start delay"), SPEED_OVER_GROUND_AT_RACE_START(DetailType.SPEED_OVER_GROUND_AT_RACE_START, "SOG at Start"), SPEED_OVER_GROUND_WHEN_STARTING(DetailType.SPEED_OVER_GROUND_WHEN_PASSING_START, "SOG when Starting"), DISTANCE_TO_STARBOARD_END_OF_STARTLINE_WHEN_STARTING(DetailType.DISTANCE_TO_STARBOARD_END_OF_STARTLINE_WHEN_PASSING_START_IN_METERS, "Dist. Stb-Side of Line when Starting"), START_TACK(DetailType.START_TACK, "Start Tack"),