Merge remote-tracking branch 'origin/bug4572'

This commit is contained in:
Alessandro Stoltenberg committed 2018-07-19 09:42:37 +02:00
commit 628ddf5470
46 files changed
+1114 -357

No files matched your search

@@ -163,7 +163,7 @@ public class StartAnalysisCard extends Composite implements HasWidgets, StartAna
defaultRaceMapSettings.isShowWindStreamletOverlay(), defaultRaceMapSettings.isShowSimulationOverlay(),
defaultRaceMapSettings.isShowMapControls(), defaultRaceMapSettings.getManeuverTypesToShow(),
defaultRaceMapSettings.isShowDouglasPeuckerPoints(), defaultRaceMapSettings.isShowEstimatedDuration(),
defaultRaceMapSettings.getStartCountDownFontSizeScaling());
defaultRaceMapSettings.getStartCountDownFontSizeScaling(), defaultRaceMapSettings.isShowManeuverLossVisualization());
RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingServiceAsync,
@@ -26,7 +26,7 @@ public interface HasManeuverContext extends HasManeuver, HasTrackedLegOfCompetit
Double getAbsoluteDirectionChangeInDegrees();
@Statistic(messageKey = "ManeuverLoss", resultDecimals = 0, ordinal = 4)
Distance getManeuverLoss();
Distance getManeuverLossDistanceLost();
@Statistic(messageKey = "WindSpeedVsManeuverLoss", resultDecimals = 2, ordinal = 4)
Pair<Double, Double> getWindSpeedVsManeuverLoss();
@@ -107,8 +107,8 @@ public class ManeuverWithContext implements HasManeuverContext {
}
@Override
public Distance getManeuverLoss() {
return getManeuver().getManeuverLoss();
public Distance getManeuverLossDistanceLost() {
return getManeuver().getManeuverLoss() == null ? null : getManeuver().getManeuverLoss().getProjectedDistanceLost();
}
@Override
@@ -147,7 +147,7 @@ public class ManeuverWithContext implements HasManeuverContext {
public Pair<Double, Double> getWindSpeedVsManeuverLoss(){
Wind wind = getTrackedLegOfCompetitorContext().getTrackedRace().getWind(maneuver.getPosition(), getTimePointBeforeForAnalysis());
return new Pair<>(wind.getKnots(), getManeuverLoss().getMeters());
return new Pair<>(wind.getKnots(), getManeuverLossDistanceLost().getMeters());
}
@Override
@@ -38,7 +38,7 @@ public class PreciseCompactGPSFixMovingImpl extends AbstractCompactGPSFixMovingI
private double cachedEstimatedSpeedInKnots;
private class PreciseCompactSpeedWithBearing extends AbstractCompactSpeedWithBearing implements SpeedWithBearing {
public class PreciseCompactSpeedWithBearing extends AbstractCompactSpeedWithBearing implements SpeedWithBearing {
private static final long serialVersionUID = 1802065090733146728L;
@Override
@@ -72,7 +72,7 @@ public class VeryCompactGPSFixMovingImpl extends AbstractCompactGPSFixMovingImpl
}
}
private class VeryCompactSpeedWithBearing extends AbstractCompactSpeedWithBearing {
public class VeryCompactSpeedWithBearing extends AbstractCompactSpeedWithBearing {
private static final long serialVersionUID = 1802065090733146728L;
@Override
@@ -104,7 +104,7 @@ public class VeryCompactGPSFixMovingImpl extends AbstractCompactGPSFixMovingImpl
}
}
private class VeryCompactEstimatedSpeed extends AbstractSpeedWithAbstractBearingImpl {
public class VeryCompactEstimatedSpeed extends AbstractSpeedWithAbstractBearingImpl {
private static final long serialVersionUID = -5871855443391817248L;
@Override
@@ -56,6 +56,6 @@ public class ManeuverLossOnPenaltyCircleTest extends OnlineTracTracBasedTest {
Competitor canottieri = getCompetitorByName("Club Canottieri Roggero di Lauria");
final Iterable<Maneuver> maneuversCanottieri = getTrackedRace().getManeuvers(canottieri, getTrackedRace().getStartOfRace(), getTrackedRace().getEndOfRace(), /* waitForLatest */ true);
final Optional<Maneuver> penaltyCircleCanottieri = StreamSupport.stream(maneuversCanottieri.spliterator(), /* parallel */ false).filter(m->m.getType()==ManeuverType.PENALTY_CIRCLE).findAny();
assertThat(penaltyCircleCanottieri.get().getManeuverLoss(), new IsGreaterThan<>(new MeterDistance(30)));
assertThat(penaltyCircleCanottieri.get().getManeuverLoss().getProjectedDistanceLost(), new IsGreaterThan<>(new MeterDistance(30)));
}
}
@@ -1014,7 +1014,7 @@ public abstract class AbstractLeaderboardWithCache implements Leaderboard {
result.numberOfManeuvers.get(maneuver.getType()) + 1);
totalManeuverLossInMeters.put(maneuver.getType(),
totalManeuverLossInMeters.get(maneuver.getType())
+ maneuver.getManeuverLoss().getMeters());
+ maneuver.getManeuverLoss().getProjectedDistanceLost().getMeters());
}
}
break;
@@ -26,6 +26,7 @@ import com.sap.sailing.domain.tracking.CompleteManeuverCurve;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
import com.sap.sailing.domain.tracking.ManeuverLoss;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.SpeedWithBearingStep;
import com.sap.sailing.domain.tracking.SpeedWithBearingStepsIterable;
@@ -639,8 +640,8 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl {
ManeuverCurveBoundaries maneuverUnstableCourseAndSpeedBoundaries, Wind wind, MarkPassing markPassing) {
final Maneuver maneuver;
final ManeuverType maneuverType;
Distance maneuverLoss = null;
Tack tackAfterManeuver = null;
ManeuverLoss maneuverLoss = null;
final Position maneuverPosition = track.getEstimatedPosition(maneuverMainCurveDetails.getTimePoint(),
/* extrapolate */false);
int numberOfJibes = getNumberOfJibes(maneuverMainCurveDetails, wind);
@@ -679,11 +680,11 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl {
} else {
tackAfterManeuver = null;
}
maneuverLoss = getManeuverLoss(maneuverUnstableCourseAndSpeedBoundaries).getDistanceLost();
maneuverLoss = getManeuverLoss(maneuverUnstableCourseAndSpeedBoundaries);
maneuver = new ManeuverWithStableSpeedAndCourseBoundariesImpl(maneuverType, tackAfterManeuver,
maneuverPosition, maneuverLoss, maneuverMainCurveDetails.getTimePoint(),
maneuverPosition, maneuverMainCurveDetails.getTimePoint(),
maneuverMainCurveDetails.extractCurveBoundariesOnly(), maneuverUnstableCourseAndSpeedBoundaries,
maneuverMainCurveDetails.getMaxTurningRateInDegreesPerSecond(), markPassing);
maneuverMainCurveDetails.getMaxTurningRateInDegreesPerSecond(), markPassing, maneuverLoss);
} else {
// Logic for head-up and bear-away
try {
@@ -697,10 +698,9 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl {
} catch (NoWindException e) {
tackAfterManeuver = null;
}
maneuver = new ManeuverWithMainCurveBoundariesImpl(maneuverType, tackAfterManeuver, maneuverPosition,
maneuverLoss, maneuverMainCurveDetails.getTimePoint(),
maneuver = new ManeuverWithMainCurveBoundariesImpl(maneuverType, tackAfterManeuver, maneuverPosition, maneuverMainCurveDetails.getTimePoint(),
maneuverMainCurveDetails.extractCurveBoundariesOnly(), maneuverUnstableCourseAndSpeedBoundaries,
maneuverMainCurveDetails.getMaxTurningRateInDegreesPerSecond(), markPassing);
maneuverMainCurveDetails.getMaxTurningRateInDegreesPerSecond(), markPassing, maneuverLoss);
}
return maneuver;
}
@@ -715,14 +715,15 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl {
*/
protected ManeuverLoss getManeuverLoss(ManeuverCurveBoundaries maneuverBoundaries) {
final GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
SpeedWithBearing speedWhenSpeedStartedToDrop = maneuverBoundaries.getSpeedWithBearingBefore();
SpeedWithBearing speedAfterManeuver = maneuverBoundaries.getSpeedWithBearingAfter();
SpeedWithBearing speedWithBearingWhenSpeedStartedToDrop = maneuverBoundaries.getSpeedWithBearingBefore();
SpeedWithBearing speedWithBearingAfterManeuver = maneuverBoundaries.getSpeedWithBearingAfter();
TimePoint timePointWhenSpeedStartedToDrop = maneuverBoundaries.getTimePointBefore();
TimePoint timePointWhenSpeedLevelledOffAfterManeuver = maneuverBoundaries.getTimePointAfter();
Duration maneuverDuration = timePointWhenSpeedStartedToDrop.until(timePointWhenSpeedLevelledOffAfterManeuver);
// For upwind/downwind legs, find the mean course between inbound and outbound course and project actual
// and
// extrapolated positions onto it:
Bearing middleManeuverAngle = speedWhenSpeedStartedToDrop.getBearing().middle(speedAfterManeuver.getBearing());
Bearing middleManeuverAngle = speedWithBearingWhenSpeedStartedToDrop.getBearing().middle(speedWithBearingAfterManeuver.getBearing());
// extrapolate maximum speed before maneuver to time point of maximum speed after maneuver and project
// resulting position
// onto the average maneuver course; compare to the projected position actually reached at the time
@@ -730,7 +731,7 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl {
// maneuver:
Position positionWhenSpeedStartedToDrop = track.getEstimatedPosition(timePointWhenSpeedStartedToDrop,
/* extrapolate */ false);
Position extrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver = speedWhenSpeedStartedToDrop.travelTo(
Position extrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver = speedWithBearingWhenSpeedStartedToDrop.travelTo(
positionWhenSpeedStartedToDrop, timePointWhenSpeedStartedToDrop,
timePointWhenSpeedLevelledOffAfterManeuver);
Position actualPositionAtTimePointOfMaxSpeedAfterManeuver = track
@@ -743,7 +744,10 @@ public class ManeuverDetectorImpl extends AbstractManeuverDetectorImpl {
.getDistance(projectedActualPositionAtTimePointOfMaxSpeedAfterManeuver);
Distance projectedDistanceSailedIfNotManeuvering = positionWhenSpeedStartedToDrop
.getDistance(projectedExtrapolatedPositionAtTimePointOfMaxSpeedAfterManeuver);
return new ManeuverLoss(projectedDistanceSailed, projectedDistanceSailedIfNotManeuvering);
return new ManeuverLoss(projectedDistanceSailed, projectedDistanceSailedIfNotManeuvering,
positionWhenSpeedStartedToDrop,
actualPositionAtTimePointOfMaxSpeedAfterManeuver, maneuverDuration, speedWithBearingWhenSpeedStartedToDrop,
middleManeuverAngle);
}
protected Duration getDurationForDouglasPeuckerExtensionForMainCurveAnalysis(Duration approximateManeuverDuration) {
@@ -23,6 +23,7 @@ import com.sap.sailing.domain.polars.PolarDataService;
import com.sap.sailing.domain.tracking.CompleteManeuverCurve;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
import com.sap.sailing.domain.tracking.ManeuverLoss;
import com.sap.sailing.domain.tracking.SpeedWithBearingStep;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.impl.CompleteManeuverCurveImpl;
@@ -277,8 +278,8 @@ public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl
stepWithHighestSpeed.getSpeedWithBearing(), stepWithHighestSpeed.getTimePoint(),
maneuverCurve.getMainCurveBoundaries().getTimePoint(),
maneuverCurve.getMainCurveBoundaries().getMaxTurningRateInDegreesPerSecond(), courseAtMaxTurningRate,
distanceSailedWithinManeuver, projectedManeuverLoss.getDistanceSailed(), distanceSailedIfNotManeuvering,
projectedManeuverLoss.getDistanceSailedIfNotManeuvering(),
distanceSailedWithinManeuver, projectedManeuverLoss.getDistanceSailedProjectedOnMiddleManeuverAngle(), distanceSailedIfNotManeuvering,
projectedManeuverLoss.getDistanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle(),
Math.abs(maneuverCurve.getMainCurveBoundaries().getDirectionChangeInDegrees())
/ maneuverCurve.getMainCurveBoundaries().getDuration().asSeconds(),
gpsFixCountWithinMainCurve, longestGpsFixIntervalBetweenTwoFixes);
@@ -336,8 +337,8 @@ public class ManeuverDetectorWithEstimationDataSupportDecoratorImpl
durationAndAvgSpeedWithBearingBefore.getB(), durationAndAvgSpeedWithBearingBefore.getA(),
gpsFixesCountFromPreviousManeuver, durationAndAvgSpeedWithBearingAfter.getB(),
durationAndAvgSpeedWithBearingAfter.getA(), gpsFixesCountToNextManeuver, distanceSailedWithinManeuver,
projectedManeuverLoss.getDistanceSailed(), distanceSailedIfNotManeuvering,
projectedManeuverLoss.getDistanceSailedIfNotManeuvering(), gpsFixCountWithinWholeCurve,
projectedManeuverLoss.getDistanceSailedProjectedOnMiddleManeuverAngle(), distanceSailedIfNotManeuvering,
projectedManeuverLoss.getDistanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle(), gpsFixCountWithinWholeCurve,
longestGpsFixIntervalBetweenTwoFixes, intervalBetweenLastFixOfCurveAndNextFix,
intervalBetweenFirstFixOfCurveAndPreviousFix);
TimePoint maneuverTimePoint = maneuverCurve.getMainCurveBoundaries().getTimePoint();
@@ -1,41 +0,0 @@
package com.sap.sailing.domain.maneuverdetection.impl;
import com.sap.sailing.domain.common.impl.MeterDistance;
import com.sap.sse.common.Distance;
/**
*
* @author Vladislav Chumak (D069712)
*
*/
public class ManeuverLoss {
private final Distance distanceSailed;
private final Distance distanceSailedIfNotManeuvering;
public ManeuverLoss(Distance distanceSailed, Distance distanceSailedIfNotManeuvering) {
this.distanceSailed = distanceSailed;
this.distanceSailedIfNotManeuvering = distanceSailedIfNotManeuvering;
}
public Distance getDistanceSailed() {
return distanceSailed;
}
public Distance getDistanceSailedIfNotManeuvering() {
return distanceSailedIfNotManeuvering;
}
public Distance getDistanceLost() {
return new MeterDistance(distanceSailedIfNotManeuvering.getMeters() - distanceSailed.getMeters());
}
/**
* Gets the ratio between {@link #getDistanceSailed()} and
* {@link #getDistanceSailedIfNotManeuvering()}.
*/
public double getRatioBetweenDistanceSailedWithAndWithoutManeuver() {
return getDistanceSailed().getMeters() / getDistanceSailedIfNotManeuvering().getMeters();
}
}
@@ -5,7 +5,7 @@ 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.tracking.GPSFix;
import com.sap.sse.common.Distance;
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Speed;
import com.sap.sse.common.TimePoint;
@@ -61,8 +61,14 @@ public interface Maneuver extends GPSFix {
* and exiting the maneuver that the boat lost compared to not having maneuvered. The maneuver loss is calculated
* considering the maneuver curve, which was performed between {@link #getTimePointBefore()} and
* {@link #getTimePointAfter()}.
// */
// Distance getManeuverLossDistanceLost();
/**
* Gets the ManeuverLoss Object of a maneuver, that is calculated in {@link ManeuverDetectorImpl#getManeuverLoss}
*
*/
Distance getManeuverLoss();
ManeuverLoss getManeuverLoss();
/**
* Gets the time point of the corresponding maneuver. The time point refers to a point within the main curve of
@@ -0,0 +1,74 @@
package com.sap.sailing.domain.tracking;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Distance;
import com.sap.sse.common.Duration;
/**
* Updated ManeuverLoss class to use it to visualize the correct maneuver loss on the race map. To draw the ManeuverLoss
* needs the maneuverStartPosition, maneuverEndPosition, middleManeuverAngle, speedWithBearingBefore and
* maneuverDuration. The ManeuverLoss is calculated by {@link ManeuverDetectorImpl#getManeuverLoss()}.
*
* @author Vladislav Chumak (D069712)
*/
public class ManeuverLoss {
private final Distance distanceSailedProjectedOnMiddleManeuverAngle;
private final Distance distanceSailedIfNotManeuveringProjectedOnMiddeManeuverAngle;
private final Position maneuverStartPosition;
private final Position maneuverEndPosition;
private final Duration maneuverDuration;
private final SpeedWithBearing speedWithBearingBefore;
private final Bearing middleManeuverAngle;
public ManeuverLoss(Distance distanceSailedProjectedOnMiddleManeuverAngle,
Distance distanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle, Position maneuverStartPosition,
Position maneuverEndPosition, Duration maneuverDuration, SpeedWithBearing speedWithBearingBefore,
Bearing middleManeuverAngle) {
this.distanceSailedProjectedOnMiddleManeuverAngle = distanceSailedProjectedOnMiddleManeuverAngle;
this.distanceSailedIfNotManeuveringProjectedOnMiddeManeuverAngle = distanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle;
this.maneuverStartPosition = maneuverStartPosition;
this.maneuverEndPosition = maneuverEndPosition;
this.maneuverDuration = maneuverDuration;
this.speedWithBearingBefore = speedWithBearingBefore;
this.middleManeuverAngle = middleManeuverAngle;
}
public Distance getDistanceSailedProjectedOnMiddleManeuverAngle() {
return distanceSailedProjectedOnMiddleManeuverAngle;
}
public Distance getDistanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle() {
return distanceSailedIfNotManeuveringProjectedOnMiddeManeuverAngle;
}
public Distance getProjectedDistanceLost() {
return distanceSailedIfNotManeuveringProjectedOnMiddeManeuverAngle.add(distanceSailedProjectedOnMiddleManeuverAngle.scale(-1));
}
/**
* Gets the ratio between {@link #getDistanceSailedProjectedOnMiddleManeuverAngle()} and
* {@link #getDistanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle()}.
*/
public double getRatioBetweenDistanceSailedWithAndWithoutManeuver() {
return getDistanceSailedProjectedOnMiddleManeuverAngle().getMeters() / getDistanceSailedIfNotManeuveringProjectedOnMiddleManeuverAngle().getMeters();
}
public Position getManeuverStartPosition() {
return maneuverStartPosition;
}
public Position getManeuverEndPosition() {
return maneuverEndPosition;
}
public Duration getManeuverDuration() {
return maneuverDuration;
}
public SpeedWithBearing getSpeedWithBearingBefore() {
return speedWithBearingBefore;
}
public Bearing getMiddleManeuverAngle() {
return middleManeuverAngle;
}
}
@@ -8,8 +8,8 @@ import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.tracking.impl.AbstractGPSFixImpl;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
import com.sap.sailing.domain.tracking.ManeuverLoss;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sse.common.Distance;
import com.sap.sse.common.Duration;
import com.sap.sse.common.Speed;
import com.sap.sse.common.TimePoint;
@@ -24,25 +24,25 @@ public abstract class ManeuverImpl extends AbstractGPSFixImpl implements Maneuve
private final Tack newTack;
private final Position position;
private final TimePoint timePoint;
private final Distance maneuverLoss;
private final double maxTurningRateInDegreesPerSecond;
private final ManeuverCurveBoundaries mainCurveBoundaries;
private final ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries;
private final MarkPassing markPassing;
private final ManeuverLoss maneuverLoss;
public ManeuverImpl(ManeuverType type, Tack newTack, Position position, Distance maneuverLoss, TimePoint timePoint,
public ManeuverImpl(ManeuverType type, Tack newTack, Position position, TimePoint timePoint,
ManeuverCurveBoundaries mainCurveBoundaries,
ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries,
double maxTurningRateInDegreesPerSecond, MarkPassing markPassing) {
double maxTurningRateInDegreesPerSecond, MarkPassing markPassing, ManeuverLoss maneuverLoss) {
this.type = type;
this.newTack = newTack;
this.position = position;
this.maneuverLoss = maneuverLoss;
this.timePoint = timePoint;
this.mainCurveBoundaries = mainCurveBoundaries;
this.maneuverCurveWithStableSpeedAndCourseBoundaries = maneuverCurveWithStableSpeedAndCourseBoundaries;
this.maxTurningRateInDegreesPerSecond = maxTurningRateInDegreesPerSecond;
this.markPassing = markPassing;
this.maneuverLoss = maneuverLoss;
}
@Override
@@ -75,11 +75,6 @@ public abstract class ManeuverImpl extends AbstractGPSFixImpl implements Maneuve
return newTack;
}
@Override
public Distance getManeuverLoss() {
return maneuverLoss;
}
@Override
public double getDirectionChangeInDegrees() {
return getManeuverBoundaries().getDirectionChangeInDegrees();
@@ -105,7 +100,7 @@ public abstract class ManeuverImpl extends AbstractGPSFixImpl implements Maneuve
return super.toString() + " " + type + " on new tack " + newTack + " on position " + position
+ " at time point " + timePoint + ", " + getManeuverBoundaries() + ", max. turning rate: "
+ maxTurningRateInDegreesPerSecond
+ (getManeuverLoss() == null ? "" : ", Lost approximately " + getManeuverLoss()) + ", Mark passing: "
+ (getManeuverLoss() == null ? "" : ", Lost approximately " + getManeuverLoss().getProjectedDistanceLost()) + ", Mark passing: "
+ markPassing;
}
@@ -139,5 +134,11 @@ public abstract class ManeuverImpl extends AbstractGPSFixImpl implements Maneuve
return Math.abs(getMainCurveBoundaries().getDirectionChangeInDegrees())
/ getMainCurveBoundaries().getDuration().asSeconds();
}
@Override
public ManeuverLoss getManeuverLoss() {
return maneuverLoss;
}
}
@@ -26,7 +26,9 @@ public class ManeuverWithCoarseGrainedBoundariesImpl extends ManeuverImpl {
public ManeuverWithCoarseGrainedBoundariesImpl(ManeuverType type, Tack newTack, Position position,
TimePoint timePoint, ManeuverCurveBoundaries maneuverBoundaries) {
super(type, newTack, position, null, timePoint, maneuverBoundaries, maneuverBoundaries, Math.abs(maneuverBoundaries.getDirectionChangeInDegrees()), null);
super(type, newTack, position, timePoint, maneuverBoundaries, maneuverBoundaries,
Math.abs(maneuverBoundaries.getDirectionChangeInDegrees()), /* mark passing */ null,
/* maneuver loss */ null);
}
@Override
@@ -5,8 +5,8 @@ import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
import com.sap.sailing.domain.tracking.ManeuverLoss;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sse.common.Distance;
import com.sap.sse.common.TimePoint;
/**
@@ -20,11 +20,11 @@ public class ManeuverWithMainCurveBoundariesImpl extends ManeuverImpl {
private static final long serialVersionUID = 5831188137884083419L;
public ManeuverWithMainCurveBoundariesImpl(ManeuverType type, Tack newTack, Position position,
Distance maneuverLoss, TimePoint timePoint, ManeuverCurveBoundaries mainCurveBoundaries,
TimePoint timePoint, ManeuverCurveBoundaries mainCurveBoundaries,
ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries,
double maxTurningRateInDegreesPerSecond, MarkPassing markPassing) {
super(type, newTack, position, maneuverLoss, timePoint, mainCurveBoundaries,
maneuverCurveWithStableSpeedAndCourseBoundaries, maxTurningRateInDegreesPerSecond, markPassing);
double maxTurningRateInDegreesPerSecond, MarkPassing markPassing, ManeuverLoss maneuverLoss) {
super(type, newTack, position, timePoint, mainCurveBoundaries,
maneuverCurveWithStableSpeedAndCourseBoundaries, maxTurningRateInDegreesPerSecond, markPassing, maneuverLoss);
}
@Override
@@ -5,8 +5,8 @@ import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.ManeuverCurveBoundaries;
import com.sap.sailing.domain.tracking.ManeuverLoss;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sse.common.Distance;
import com.sap.sse.common.TimePoint;
/**
@@ -21,11 +21,11 @@ public class ManeuverWithStableSpeedAndCourseBoundariesImpl extends ManeuverImpl
private static final long serialVersionUID = 5831188137884083419L;
public ManeuverWithStableSpeedAndCourseBoundariesImpl(ManeuverType type, Tack newTack, Position position,
Distance maneuverLoss, TimePoint timePoint, ManeuverCurveBoundaries mainCurveBoundaries,
TimePoint timePoint, ManeuverCurveBoundaries mainCurveBoundaries,
ManeuverCurveBoundaries maneuverCurveWithStableSpeedAndCourseBoundaries,
double maxTurningRateInDegreesPerSecond, MarkPassing markPassing) {
super(type, newTack, position, maneuverLoss, timePoint, mainCurveBoundaries,
maneuverCurveWithStableSpeedAndCourseBoundaries, maxTurningRateInDegreesPerSecond, markPassing);
double maxTurningRateInDegreesPerSecond, MarkPassing markPassing, ManeuverLoss maneuverLoss) {
super(type, newTack, position, timePoint, mainCurveBoundaries,
maneuverCurveWithStableSpeedAndCourseBoundaries, maxTurningRateInDegreesPerSecond, markPassing, maneuverLoss);
}
@Override
@@ -0,0 +1,50 @@
package com.google.gwt.user.client.rpc.core.com.sap.sailing.domain.common.impl;
import com.google.gwt.user.client.rpc.CustomFieldSerializer;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sse.common.impl.DegreeBearingImpl;
public class KnotSpeedWithBearingImpl_CustomFieldSerializer extends CustomFieldSerializer<KnotSpeedWithBearingImpl> {
@Override
public void serializeInstance(SerializationStreamWriter streamWriter, KnotSpeedWithBearingImpl instance)
throws SerializationException {
serialize(streamWriter, instance);
}
public static void serialize(SerializationStreamWriter streamWriter, KnotSpeedWithBearingImpl instance)
throws SerializationException {
streamWriter.writeDouble(instance.getKnots());
streamWriter.writeDouble(instance.getBearing().getDegrees());
}
@Override
public boolean hasCustomInstantiateInstance() {
return true;
}
@Override
public KnotSpeedWithBearingImpl instantiateInstance(SerializationStreamReader streamReader)
throws SerializationException {
return instantiate(streamReader);
}
public static KnotSpeedWithBearingImpl instantiate(SerializationStreamReader streamReader)
throws SerializationException {
return new KnotSpeedWithBearingImpl(streamReader.readDouble(), new DegreeBearingImpl(streamReader.readDouble()));
}
@Override
public void deserializeInstance(SerializationStreamReader streamReader, KnotSpeedWithBearingImpl instance)
throws SerializationException {
deserialize(streamReader, instance);
}
public static void deserialize(SerializationStreamReader streamReader, KnotSpeedWithBearingImpl instance) {
// Done by instantiateInstance
}
}
@@ -5,7 +5,10 @@ import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.tracking.impl.PreciseCompactGPSFixMovingImpl.PreciseCompactPosition;
import com.sap.sailing.domain.common.tracking.impl.PreciseCompactGPSFixMovingImpl.PreciseCompactSpeedWithBearing;
import com.sap.sse.common.impl.DegreeBearingImpl;
public final class PreciseCompactGPSFixMovingImpl {
public static final class PreciseCompactPosition_CustomFieldSerializer extends CustomFieldSerializer<PreciseCompactPosition> {
@@ -49,4 +52,46 @@ public final class PreciseCompactGPSFixMovingImpl {
streamWriter.writeDouble(instance.getLngDeg());
}
}
public static final class PreciseCompactSpeedWithBearing_CustomFieldSerializer extends CustomFieldSerializer<PreciseCompactSpeedWithBearing> {
@Override
public boolean hasCustomInstantiateInstance() {
return true;
}
@Override
public PreciseCompactSpeedWithBearing instantiateInstance(SerializationStreamReader streamReader)
throws SerializationException {
return instantiate(streamReader);
}
public static PreciseCompactSpeedWithBearing instantiate(SerializationStreamReader streamReader) throws SerializationException {
final double speedInKnots = streamReader.readDouble();
final double bearingDeg = streamReader.readDouble();
return (PreciseCompactSpeedWithBearing) new com.sap.sailing.domain.common.tracking.impl.PreciseCompactGPSFixMovingImpl(
/* dummy position */ new DegreePosition(0, 0), /* timePoint */null, new KnotSpeedWithBearingImpl(speedInKnots, new DegreeBearingImpl(bearingDeg))).getSpeed();
}
@Override
public void deserializeInstance(SerializationStreamReader streamReader, PreciseCompactSpeedWithBearing instance)
throws SerializationException {
deserialize(streamReader, instance);
}
public static void deserialize(SerializationStreamReader streamReader, PreciseCompactSpeedWithBearing instance) {
// handled by instantiate
}
@Override
public void serializeInstance(SerializationStreamWriter streamWriter, PreciseCompactSpeedWithBearing instance)
throws SerializationException {
serialize(streamWriter, instance);
}
public static void serialize(SerializationStreamWriter streamWriter, PreciseCompactSpeedWithBearing instance)
throws SerializationException {
streamWriter.writeDouble(instance.getKnots());
streamWriter.writeDouble(instance.getBearing().getDegrees());
}
}
}
@@ -8,8 +8,12 @@ import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.client.rpc.SerializationStreamReader;
import com.google.gwt.user.client.rpc.SerializationStreamWriter;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.tracking.impl.CompactionNotPossibleException;
import com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl.VeryCompactEstimatedSpeed;
import com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl.VeryCompactPosition;
import com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl.VeryCompactSpeedWithBearing;
import com.sap.sse.common.impl.DegreeBearingImpl;
public final class VeryCompactGPSFixMovingImpl {
private static final Logger logger = Logger.getLogger(VeryCompactGPSFixMovingImpl.class.getName());
@@ -61,4 +65,103 @@ public final class VeryCompactGPSFixMovingImpl {
streamWriter.writeDouble(instance.getLngDeg());
}
}
public static final class VeryCompactSpeedWithBearing_CustomFieldSerializer extends CustomFieldSerializer<VeryCompactSpeedWithBearing> {
@Override
public boolean hasCustomInstantiateInstance() {
return true;
}
@Override
public VeryCompactSpeedWithBearing instantiateInstance(SerializationStreamReader streamReader)
throws SerializationException {
return instantiate(streamReader);
}
public static VeryCompactSpeedWithBearing instantiate(SerializationStreamReader streamReader) throws SerializationException {
final double knotSpeed = streamReader.readDouble();
final double bearingDeg = streamReader.readDouble();
try {
return (VeryCompactSpeedWithBearing) new com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl(
/* position */ null, /* timePoint */null, new KnotSpeedWithBearingImpl(knotSpeed, new DegreeBearingImpl(bearingDeg))).getSpeed();
} catch (CompactionNotPossibleException e) {
logger.log(Level.SEVERE, "Internal error: an object that was a very compact position and was serialized "+
"couldn't be de-serialized again as such an object", e);
throw new RuntimeException(e);
}
}
@Override
public void deserializeInstance(SerializationStreamReader streamReader, VeryCompactSpeedWithBearing instance)
throws SerializationException {
deserialize(streamReader, instance);
}
public static void deserialize(SerializationStreamReader streamReader, VeryCompactSpeedWithBearing instance) {
// handled by instantiate
}
@Override
public void serializeInstance(SerializationStreamWriter streamWriter, VeryCompactSpeedWithBearing instance)
throws SerializationException {
serialize(streamWriter, instance);
}
public static void serialize(SerializationStreamWriter streamWriter, VeryCompactSpeedWithBearing instance)
throws SerializationException {
streamWriter.writeDouble(instance.getKnots());
streamWriter.writeDouble(instance.getBearing().getDegrees());
}
}
public static final class VeryCompactEstimatedSpeed_CustomFieldSerializer extends CustomFieldSerializer<VeryCompactEstimatedSpeed> {
@Override
public boolean hasCustomInstantiateInstance() {
return true;
}
@Override
public VeryCompactEstimatedSpeed instantiateInstance(SerializationStreamReader streamReader)
throws SerializationException {
return instantiate(streamReader);
}
public static VeryCompactEstimatedSpeed instantiate(SerializationStreamReader streamReader) throws SerializationException {
final double knotSpeed = streamReader.readDouble();
final double bearingDeg = streamReader.readDouble();
try {
final KnotSpeedWithBearingImpl speed = new KnotSpeedWithBearingImpl(knotSpeed, new DegreeBearingImpl(bearingDeg));
com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl fix = new com.sap.sailing.domain.common.tracking.impl.VeryCompactGPSFixMovingImpl(
/* dummy position */ new DegreePosition(0, 0), /* timePoint */null, speed);
fix.cacheEstimatedSpeed(speed);
return (VeryCompactEstimatedSpeed) fix.getCachedEstimatedSpeed();
} catch (CompactionNotPossibleException e) {
logger.log(Level.SEVERE, "Internal error: an object that was a very compact position and was serialized "+
"couldn't be de-serialized again as such an object", e);
throw new RuntimeException(e);
}
}
@Override
public void deserializeInstance(SerializationStreamReader streamReader, VeryCompactEstimatedSpeed instance)
throws SerializationException {
deserialize(streamReader, instance);
}
public static void deserialize(SerializationStreamReader streamReader, VeryCompactEstimatedSpeed instance) {
// handled by instantiate
}
@Override
public void serializeInstance(SerializationStreamWriter streamWriter, VeryCompactEstimatedSpeed instance)
throws SerializationException {
serialize(streamWriter, instance);
}
public static void serialize(SerializationStreamWriter streamWriter, VeryCompactEstimatedSpeed instance)
throws SerializationException {
streamWriter.writeDouble(instance.getKnots());
streamWriter.writeDouble(instance.getBearing().getDegrees());
}
}
}
@@ -284,7 +284,7 @@ public class AutoplayHelper {
RaceMapZoomSettings autoFollowRace = new RaceMapZoomSettings(typesToConsiderOnZoom, true);
RaceMapSettings settings = new RaceMapSettings(autoFollowRace, new RaceMapHelpLinesSettings(), false, 15,
100000l, false, RaceMapSettings.DEFAULT_BUOY_ZONE_RADIUS, false, true, false, false, false, false,
RaceMapSettings.getDefaultManeuvers(), false, false, /* startCountDownFontSizeScaling */ 1.5);
RaceMapSettings.getDefaultManeuvers(), false, false, /* startCountDownFontSizeScaling */ 1.5, /* showManeuverLossVisualization */ false);
RaceMapLifecycle raceMapLifecycle = new RaceMapLifecycle(StringMessages.INSTANCE);
final CompetitorColorProvider colorProvider = new CompetitorColorProviderImpl(currentLiveRace, competitorsAndTheirBoats);
RaceCompetitorSelectionModel competitorSelectionProvider = new RaceCompetitorSelectionModel(
@@ -6,6 +6,7 @@
<div class="innerContent">
<h5 class="articleSubheadline">July 2018</h5>
<ul class="bulletList">
<li>In <tt>RaceBoard.html</tt> there is a new feature for maneuver loss visualization. There is a setting in the map settings and in the maneuver pop up.
<li>In <tt>RaceBoard.html</tt> when zooming into a specific time range, the time slider will provide a "reset zoom" button in expanded mode.</li>
</ul>
@@ -2066,6 +2066,9 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String multiVideoDoNoAdd();
String multiVideoOffsetInput();
String multiVideoDescription();
String maneuverAngle();
String jibeAngle();
String tackAngle();
String multiUrlChangeMediaTrack();
String multiUrlChangeReplace();
String multiUrlChangeFind();
@@ -2077,6 +2080,11 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
String resetZoom();
String lastEvent(String locationOrVenue);
String teaserOverallLinkToolTip();
String middleManeuverAngle();
String extrapolatedManeuverStartPosition();
String projectedExtrapolatedManeuverStartPosition();
String projectedManeuverEndPosition();
String bearingAtManeuverEndPosition();
String gender();
String manage2SailEventIdBox();
String manage2SailEventURLBox();
@@ -2061,6 +2061,9 @@ multiVideoIdle=Work queue is idle
multiVideoDoNoAdd=Do not add
multiVideoOffsetInput=Global video offset in milliseconds:
multiVideoDescription=The webserver is required to provide index listing (subfolders supported if indexed) to allow file discovery. After an initial analysis of the metadata contained in the mp4 files, the videos that should be added, need to be selected via the checkbox column on the left. The "Add Audio/Video" button will create media tracks for all selected files, and add those files to all races selected in the right column.
jibeAngle=Jibe angle
tackAngle=Tacking angle
maneuverAngle=Maneuver angle
multiUrlChangeMediaTrack=Adjust multipe media track urls
multiUrlChangeReplace=Replace with
multiUrlChangeFind=Find
@@ -2072,6 +2075,11 @@ multiUrlChangeExplain=This dialog will bulk replace common parts at the start of
resetZoom=Reset zoom
lastEvent=Last Event: {0}
teaserOverallLinkToolTip=To see the overall series, click the yellow corner
middleManeuverAngle=Middle maneuver angle
extrapolatedManeuverStartPosition=Extrapolated Maneuver Start Position
projectedExtrapolatedManeuverStartPosition=Projection of the extrapolated maneuver start position on middle maneuver angle line
projectedManeuverEndPosition=Projection of the maneuver end position on middle maneuver angle line
bearingAtManeuverEndPosition=Bearing at maneuver end position
gender=Gender
manage2SailEventIdBox=Manage2Sail Event ID
manage2SailEventURLBox=Manage2Sail EventURL (json)
@@ -2056,6 +2056,9 @@ multiVideoIdle=Keine Arbeit offen
multiVideoDoNoAdd=Nicht hinzufügen
multiVideoOffsetInput=Globales Videooffset in Millisekunden:
multiVideoDescription=Ein Webserver mit Folderindex wird benötigt (Unterordner werden unterstützt, sofern diese auch indiziert sind), um die Erkennung von Datein zu ermöglichen. Nach einer initialen Analyse der Metadaten von den mp4 Dateien, müssen die gewünschen Videos in der linken Spalte ausgewählt werden. Der "Audio/Video hinzufügen" Knopf erstellt für die ausgewählten Videos Mediatracks, und fügt diese zu allen Rennen hinzu, die in der rechten Spalte ausgewählt wurden.
jibeAngle=Halsenwinkel
tackAngle=Wendewinkel
maneuverAngle=Manöverwinkel
multiUrlChangeMediaTrack=Mehrere Mediatrack URLs anpassen
multiUrlChangeReplace=Ersetzen mit
multiUrlChangeFind=Suchen
@@ -2067,6 +2070,11 @@ multiUrlChangeExplain=Dieser Dialog ermöglicht es, mehrere URL-Anfänge gleichz
resetZoom=Reset zoom
lastEvent=Letztes Event: {0}
teaserOverallLinkToolTip=Um die Serien-Übersicht zu sehen, in die gelbe Ecke klicken
middleManeuverAngle=Manöverwinkel-Halbierende
extrapolatedManeuverStartPosition=Extrapolierte Manöver Anfangsposition
projectedExtrapolatedManeuverStartPosition=Projektion der extrapolierten Manöver Startposition auf die Manöverwinkel-Halbierende
projectedManeuverEndPosition=Projektion der Manöverendposition auf die Manöverwinkel-Halbierende
bearingAtManeuverEndPosition=Anliegender Kurs an Manöverendposition
gender=Geschlecht
manage2SailEventId=Manage2Sail Event ID
manage2SailEventURL=Manage2Sail Event URL (json)
@@ -54,7 +54,7 @@ public class CombinedWindPanel extends FlowPanel {
newShowStreamletsOverlaySetting, oldRaceMapSettings.isShowSimulationOverlay(),
oldRaceMapSettings.isShowMapControls(), oldRaceMapSettings.getManeuverTypesToShow(),
oldRaceMapSettings.isShowDouglasPeuckerPoints(), oldRaceMapSettings.isShowEstimatedDuration(),
oldRaceMapSettings.getStartCountDownFontSizeScaling());
oldRaceMapSettings.getStartCountDownFontSizeScaling(), oldRaceMapSettings.isShowManeuverLossVisualization());
map.updateSettings(newRaceMapSettings);
}
});
@@ -0,0 +1,12 @@
package com.sap.sailing.gwt.ui.client.shared.racemap;
/**
* Used to get the information that appears on hovering over a line and that is displayed in
* {@link #adjustInfoOverlayForVisibleLine}
*/
interface LineInfoProvider {
String getLineInfo();
default boolean isShowInfoOverlayWithHelplines() {
return true;
};
}
@@ -6,7 +6,7 @@ public enum ManeuverColor {
RED, GREEN;
public static ManeuverColor getManeuverColor(ManeuverDTO maneuver) {
return maneuver.directionChangeInDegrees < 0 ? RED : GREEN;
return maneuver.getDirectionChangeInDegrees() < 0 ? RED : GREEN;
}
}
@@ -0,0 +1,423 @@
package com.sap.sailing.gwt.ui.client.shared.racemap;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.google.gwt.canvas.dom.client.CssColor;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.base.LatLng;
import com.google.gwt.maps.client.overlays.Marker;
import com.google.gwt.maps.client.overlays.MarkerOptions;
import com.google.gwt.maps.client.overlays.Polyline;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.maneuverdetection.impl.ManeuverDetectorImpl;
import com.sap.sailing.gwt.ui.client.ManeuverTypeFormatter;
import com.sap.sailing.gwt.ui.client.NumberFormatterFactory;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
import com.sap.sailing.gwt.ui.shared.SpeedWithBearingDTO;
import com.sap.sse.common.Bearing;
import com.sap.sse.common.Distance;
import com.sap.sse.common.TimeRange;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.Util.Triple;
import com.sap.sse.common.impl.DegreeBearingImpl;
import com.sap.sse.gwt.client.ErrorReporter;
import com.sap.sse.gwt.client.player.Timer.PlayStates;
/**
* Encapsulates the diamond-shaped maneuver markers that can be displayed on a competitor's
* tail, as well as the optional maneuver loss indicators that may be shown for a maneuver
* marker.<p>
*
* Invariants:
*
* <ul>
* <li>When a maneuver loss indicator is shown then the corresponding maneuver marker is
* visible as well.</li>
* <li>When adding a maneuver marker to the map display, the {@link RaceMapSettings#isShowManeuverLossVisualization()} setting
* decides whether a maneuver loss indicator is added to the map display as well.</li>
* </ul>
*
* @author Axel Uhl (d043530)
*
*/
public class ManeuverMarkersAndLossIndicators {
private static final String MANEUVERLOSSLINES_PROJECTEDLINES_COLOR = "#ffff00";
private static final String MANEUVERLOSSLINES_EXTRAPOLATEDLINES_COLOR = "#ffffff";
private static final String MANEUVERLOSSLINES_RED = "#ff0000";
private static final String MANEUVERLOSSLINES_GREEN = "#00ff00";
private static final double LOWLIGHTED_LINE_OPACITY = 0.5;
private static final int HIGHLIGHTED_LINE_STROKEWEIGHT = 2;
private static final NumberFormat numberFormatOneDecimal = NumberFormatterFactory.getDecimalFormat(1);
private final RaceMap raceMap;
private final SailingServiceAsync sailingService;
private final StringMessages stringMessages;
private final ErrorReporter errorReporter;
private Map<CompetitorDTO, List<ManeuverDTO>> lastManeuverResult;
/**
* markers displayed in response to
* {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)}
*/
private final Map<Triple<String, Date, ManeuverType>, Marker> maneuverMarkers;
/** The Map where the polylines for the specific maneuver are stored. */
private final Map<Triple<String, Date, ManeuverType>, Set<Polyline>> maneuverLossLinesMap;
/** The Map where the info overlays for a maneuver loss visualization are stored. */
private final Map<Triple<String, Date, ManeuverType>, SmallTransparentInfoOverlay> maneuverLossInfoOverlayMap;
public ManeuverMarkersAndLossIndicators(RaceMap raceMap, SailingServiceAsync sailingService, ErrorReporter errorReporter, StringMessages stringMessages) {
this.raceMap = raceMap;
this.sailingService = sailingService;
this.errorReporter = errorReporter;
this.stringMessages = stringMessages;
this.maneuverMarkers = new HashMap<>();
this.maneuverLossLinesMap = new HashMap<>();
this.maneuverLossInfoOverlayMap = new HashMap<>();
}
public void getAndShowManeuvers(RegattaAndRaceIdentifier race, Map<CompetitorDTO, TimeRange> timeRange) {
sailingService.getManeuvers(race, timeRange, new AsyncCallback<Map<CompetitorDTO, List<ManeuverDTO>>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error obtaining maneuvers: " + caught.getMessage(), true /*silentMode */);
}
@Override
public void onSuccess(Map<CompetitorDTO, List<ManeuverDTO>> result) {
lastManeuverResult = result;
removeAllManeuverMarkers();
if (raceMap.getTimer().getPlayState() != PlayStates.Playing) {
showManeuvers(result);
}
}
});
}
/**
* Removes the visualizations of maneuvers from the map. The maneuver objects as received from the server are remembered
* (see {@link #lastManeuverResult}) and may be restored by a subsequent call to {@link #showManeuvers(Map)} as it is, e.g.,
* called by {@link #updateManeuverMarkersAfterSettingsChanged()}.
*/
void removeAllManeuverMarkers() {
// clone entry set to avoid concurrent modification exception
for (final Entry<Triple<String, Date, ManeuverType>, Marker> keyAndMarker : new HashSet<>(maneuverMarkers.entrySet())) {
removeManeuverMarker(keyAndMarker.getKey(), keyAndMarker.getValue());
}
}
private void removeManeuverMarker(Triple<String, Date, ManeuverType> key, Marker marker) {
marker.setMap((MapWidget) null);
removeManeuverLossLinesAndInfoOverlayForManeuver(key);
maneuverMarkers.remove(key);
}
private void showManeuvers(Map<CompetitorDTO, List<ManeuverDTO>> maneuvers) {
if (raceMap.getMap() != null && maneuvers != null) {
Set<CompetitorDTO> keySet = maneuvers.keySet();
Iterator<CompetitorDTO> iter = keySet.iterator();
while (iter.hasNext()) {
CompetitorDTO competitorDTO = iter.next();
List<ManeuverDTO> maneuversForCompetitor = maneuvers.get(competitorDTO);
for (ManeuverDTO maneuver : maneuversForCompetitor) {
if (raceMap.getSettings().isShowManeuverType(maneuver.getType())) {
createAndAddMarkerOfManeuver(maneuver, competitorDTO);
}
}
}
}
}
/**
* Creates a maneuver marker. Additionally, if the {@link RaceMapSettings#isShowManeuverLossVisualization()} setting is {@code true},
* a maneuver loss indicator is shown.
*/
private void createAndAddMarkerOfManeuver(ManeuverDTO maneuver, CompetitorDTO competitor) {
LatLng latLng = raceMap.getCoordinateSystem().toLatLng(maneuver.getPosition());
Marker maneuverMarker = raceMap.getRaceMapImageManager().getManeuverIconsForTypeAndDirectionIndicatingColor()
.get(new Pair<ManeuverType, ManeuverColor>(maneuver.getType(), ManeuverColor.getManeuverColor(maneuver)));
MarkerOptions options = MarkerOptions.newInstance();
options.setIcon(maneuverMarker.getIcon_MarkerImage());
Marker marker = Marker.newInstance(options);
marker.setPosition(latLng);
marker.setTitle(ManeuverTypeFormatter.format(maneuver.getType(), stringMessages));
marker.addClickHandler(event -> {
LatLng where = raceMap.getCoordinateSystem().toLatLng(maneuver.getPosition());
Widget content = getInfoWindowContent(maneuver, competitor);
raceMap.getManagedInfoWindow().openAtPosition(content, where);
});
final Triple<String, Date, ManeuverType> key = createManeuverKey(maneuver, competitor);
maneuverMarkers.put(key, marker);
marker.setMap(raceMap.getMap());
// maneuver loss visualization: if the setting is active, show loss visualization for the maneuver by default
if (raceMap.getSettings().isShowManeuverLossVisualization() && maneuver.getManeuverLoss() != null) {
createManeuverLossLinesAndInfoOverlays(maneuver, competitor);
}
}
private Triple<String, Date, ManeuverType> createManeuverKey(ManeuverDTO maneuver, CompetitorDTO competitor) {
return new Triple<>(competitor.getIdAsString(), maneuver.getTimePoint(), maneuver.getType());
}
final LineInfoProvider middleManeuverAngleLineInfoProvider = new LineInfoProvider() {
@Override
public String getLineInfo() {
return stringMessages.middleManeuverAngle();
}
@Override
public boolean isShowInfoOverlayWithHelplines() {
return false;
}
};
final LineInfoProvider extrapolatedLineInfoProvider = new LineInfoProvider() {
@Override
public String getLineInfo() {
return stringMessages.extrapolatedManeuverStartPosition();
}
@Override
public boolean isShowInfoOverlayWithHelplines() {
return false;
}
};
final LineInfoProvider projectedExtrapolatedLineInfoProvider = new LineInfoProvider() {
@Override
public String getLineInfo() {
return stringMessages.projectedExtrapolatedManeuverStartPosition();
}
@Override
public boolean isShowInfoOverlayWithHelplines() {
return false;
}
};
final LineInfoProvider projectedManeuverEndLineInfoProvider = new LineInfoProvider() {
@Override
public String getLineInfo() {
return stringMessages.projectedManeuverEndPosition();
}
@Override
public boolean isShowInfoOverlayWithHelplines() {
return false;
}
};
final LineInfoProvider maneuverLossLineInfoProvider = new LineInfoProvider() {
@Override
public String getLineInfo() {
return stringMessages.maneuverLoss();
}
@Override
public boolean isShowInfoOverlayWithHelplines() {
return false;
}
};
final LineInfoProvider bearingAtManeuverEndPositionLineInfoProvider = new LineInfoProvider() {
@Override
public String getLineInfo() {
return stringMessages.bearingAtManeuverEndPosition();
}
@Override
public boolean isShowInfoOverlayWithHelplines() {
return false;
}
};
/**
* Removes the polylines and the info overlay visualizing one maneuver from the map. Also removes the identifying
* Triple<competitor.getIdAsString(), maneuver.getTimePoint(), maneuver.getType()> from the corresponding
* {@link #maneuverLossLinesMap} and {@link #maneuverLossInfoOverlayMap}.
*/
private void removeManeuverLossLinesAndInfoOverlayForManeuver(ManeuverDTO maneuver, CompetitorDTO competitor) {
Triple<String, Date, ManeuverType> key = createManeuverKey(maneuver, competitor);
removeManeuverLossLinesAndInfoOverlayForManeuver(key);
}
private void removeManeuverLossLinesAndInfoOverlayForManeuver(Triple<String, Date, ManeuverType> key) {
if (maneuverLossLinesMap.get(key) != null) {
for (Polyline p : maneuverLossLinesMap.get(key)) {
p.setMap((MapWidget) null);
}
maneuverLossLinesMap.remove(key);
}
final SmallTransparentInfoOverlay overlay;
if ((overlay=maneuverLossInfoOverlayMap.remove(key)) != null) {
overlay.removeFromMap();
}
}
/**
* Creates the ManeuverLoss Lines as calculated in {@link ManeuverDetectorImpl#getManeuverLossInMeters()} and
* {@link SmallTransparentInfoOverlay}s attached to the {@link Polyline}s visualizing the maneuver. Stores them in
* {@link #maneuverLossInfoOverlayMap} and {@link #maneuverLossLinesMap} with the identifier
* Triple<competitor.getIdAsString(), maneuver.getTimePoint(), maneuver.getType()>.
*/
private void createManeuverLossLinesAndInfoOverlays(ManeuverDTO maneuver, CompetitorDTO competitor) {
final Set<Polyline> maneuverLossLines = new HashSet<>();
Bearing bearingBefore = maneuver.getManeuverLoss().getSpeedWithBearingBefore().getBearing();
Bearing middleManeuverAngle = new DegreeBearingImpl(maneuver.getManeuverLoss().getMiddleManeuverAngle());
Distance extrapolationOfManeuverStartPoint =
maneuver.getManeuverLoss().getSpeedWithBearingBefore().travel(maneuver.getManeuverLoss().getManeuverDuration());
Position extrapolatedManeuverStartPosition = maneuver.getManeuverLoss().getManeuverStartPosition()
.translateRhumb(bearingBefore, extrapolationOfManeuverStartPoint);
Position intersectionMiddleManeuverAngleWithExtrapolationOfManeuverStartPoint = maneuver.getManeuverLoss().getManeuverStartPosition()
.getIntersection(bearingBefore, maneuver.getPosition(), middleManeuverAngle);
Position projectedExtrapolatedManeuverStartPosition = extrapolatedManeuverStartPosition.projectToLineThrough(
intersectionMiddleManeuverAngleWithExtrapolationOfManeuverStartPoint, middleManeuverAngle);
Position projectedManeuverEndPosition = maneuver.getManeuverLoss().getManeuverEndPosition().projectToLineThrough(
intersectionMiddleManeuverAngleWithExtrapolationOfManeuverStartPoint, middleManeuverAngle);
Position startOfBearingAtManeuverEndPositionLineOnMiddleManeuverAngleLine = maneuver.getManeuverLoss()
.getManeuverEndPosition()
.getIntersection(new DegreeBearingImpl(maneuver.getSpeedWithBearingAfter().bearingInDegrees),
intersectionMiddleManeuverAngleWithExtrapolationOfManeuverStartPoint, middleManeuverAngle);
maneuverLossLines.add(raceMap.showOrRemoveOrUpdateLine(null, true,
startOfBearingAtManeuverEndPositionLineOnMiddleManeuverAngleLine,
maneuver.getManeuverLoss().getManeuverEndPosition(), bearingAtManeuverEndPositionLineInfoProvider,
MANEUVERLOSSLINES_EXTRAPOLATEDLINES_COLOR, RaceMap.STANDARD_LINE_STROKEWEIGHT, LOWLIGHTED_LINE_OPACITY));
String color = maneuver.getManeuverLoss().getDistanceLost().compareTo(Distance.NULL) > 0
? color = MANEUVERLOSSLINES_RED : MANEUVERLOSSLINES_GREEN;
maneuverLossLines.add(raceMap.showOrRemoveOrUpdateLine(null, true,
intersectionMiddleManeuverAngleWithExtrapolationOfManeuverStartPoint, projectedManeuverEndPosition,
middleManeuverAngleLineInfoProvider, MANEUVERLOSSLINES_EXTRAPOLATEDLINES_COLOR,
RaceMap.STANDARD_LINE_STROKEWEIGHT, LOWLIGHTED_LINE_OPACITY));
maneuverLossLines.add(raceMap.showOrRemoveOrUpdateLine(null, true,
maneuver.getManeuverLoss().getManeuverStartPosition(), extrapolatedManeuverStartPosition,
extrapolatedLineInfoProvider, MANEUVERLOSSLINES_EXTRAPOLATEDLINES_COLOR, RaceMap.STANDARD_LINE_STROKEWEIGHT,
LOWLIGHTED_LINE_OPACITY));
maneuverLossLines.add(raceMap.showOrRemoveOrUpdateLine(null, true, extrapolatedManeuverStartPosition,
projectedExtrapolatedManeuverStartPosition, projectedExtrapolatedLineInfoProvider,
MANEUVERLOSSLINES_PROJECTEDLINES_COLOR, RaceMap.STANDARD_LINE_STROKEWEIGHT, RaceMap.LOWLIGHTED_TAIL_OPACITY));
maneuverLossLines.add(raceMap.showOrRemoveOrUpdateLine(null, true, maneuver.getManeuverLoss().getManeuverEndPosition(),
projectedManeuverEndPosition, projectedManeuverEndLineInfoProvider,
MANEUVERLOSSLINES_PROJECTEDLINES_COLOR, RaceMap.STANDARD_LINE_STROKEWEIGHT, RaceMap.LOWLIGHTED_TAIL_OPACITY));
maneuverLossLines.add(raceMap.showOrRemoveOrUpdateLine(null, true, projectedExtrapolatedManeuverStartPosition,
projectedManeuverEndPosition, maneuverLossLineInfoProvider, color, HIGHLIGHTED_LINE_STROKEWEIGHT,
RaceMap.STANDARD_LINE_OPACITY));
final Triple<String, Date, ManeuverType> key = createManeuverKey(maneuver, competitor);
maneuverLossLinesMap.put(key, maneuverLossLines);
StringBuilder sb = new StringBuilder();
sb.append(stringMessages.maneuverLoss() + ": " + numberFormatOneDecimal.format(maneuver.getManeuverLoss().getDistanceLost().getMeters())
+ stringMessages.metersUnit() + '\n');
if (maneuver.getType() == ManeuverType.TACK) {
sb.append(stringMessages.tackAngle() + ": ");
} else if (maneuver.getType() == ManeuverType.JIBE) {
sb.append(stringMessages.jibeAngle() + ": ");
} else {
sb.append(stringMessages.maneuverAngle() + ": ");
}
sb.append(numberFormatOneDecimal.format(Math.abs(maneuver.getDirectionChangeInDegrees()))
+ stringMessages.degreesUnit());
CssColor greyWithTransparency = CssColor.make("rgba(255,255,255,0.5)");
SmallTransparentInfoOverlay maneuverLossInfoOverlay = new SmallTransparentInfoOverlay(raceMap.getMap(),
RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, sb.toString(), raceMap.getCoordinateSystem(), greyWithTransparency);
maneuverLossInfoOverlay.setPosition(projectedManeuverEndPosition.translateGreatCircle(middleManeuverAngle,
maneuver.getManeuverLoss().getDistanceLost().scale(0.5)), /* transition time */ -1);
maneuverLossInfoOverlay.draw();
maneuverLossInfoOverlayMap.put(key, maneuverLossInfoOverlay);
}
Widget getInfoWindowContent(ManeuverDTO maneuver, CompetitorDTO competitor) {
SpeedWithBearingDTO before = maneuver.getSpeedWithBearingBefore();
SpeedWithBearingDTO after = maneuver.getSpeedWithBearingAfter();
VerticalPanel vPanel = new VerticalPanel();
vPanel.add(raceMap.createInfoWindowLabelAndValue(stringMessages.maneuverType(),
ManeuverTypeFormatter.format(maneuver.getType(), stringMessages)));
vPanel.add(raceMap.createInfoWindowLabelAndValue(stringMessages.time(),
DateTimeFormat.getFormat(PredefinedFormat.TIME_FULL).format(maneuver.getTimePoint())));
vPanel.add(raceMap.createInfoWindowLabelAndValue(stringMessages.directionChange(),
((int) Math.round(maneuver.getDirectionChangeInDegrees())) + " " + stringMessages.degreesShort() + " ("
+ ((int) Math.round(before.bearingInDegrees)) + " " + stringMessages.degreesShort() + " -> "
+ ((int) Math.round(after.bearingInDegrees)) + " " + stringMessages.degreesShort() + ")"));
vPanel.add(raceMap.createInfoWindowLabelAndValue(stringMessages.speedChange(),
NumberFormat.getDecimalFormat().format(after.speedInKnots - before.speedInKnots) + " "
+ stringMessages.knotsUnit() + " ("
+ NumberFormat.getDecimalFormat().format(before.speedInKnots) + " " + stringMessages.knotsUnit()
+ " -> " + NumberFormat.getDecimalFormat().format(after.speedInKnots) + " "
+ stringMessages.knotsUnit() + ")"));
vPanel.add(raceMap.createInfoWindowLabelAndValue(stringMessages.maxTurningRate(),
NumberFormat.getDecimalFormat().format(maneuver.getMaxTurningRateInDegreesPerSecond()) + " "
+ stringMessages.degreesPerSecondUnit()));
vPanel.add(raceMap.createInfoWindowLabelAndValue(stringMessages.avgTurningRate(),
NumberFormat.getDecimalFormat().format(maneuver.getAvgTurningRateInDegreesPerSecond()) + " "
+ stringMessages.degreesPerSecondUnit()));
if (maneuver.getType() != ManeuverType.BEAR_AWAY && maneuver.getType() != ManeuverType.HEAD_UP) {
vPanel.add(raceMap.createInfoWindowLabelAndValue(stringMessages.lowestSpeed(),
NumberFormat.getDecimalFormat().format(maneuver.getLowestSpeedInKnots()) + " "
+ stringMessages.knotsUnit()));
}
if (maneuver.getManeuverLoss() != null) {
Widget maneuverLossWidget = raceMap.createInfoWindowLabelAndValue(stringMessages.maneuverLoss(),
numberFormatOneDecimal.format(maneuver.getManeuverLoss().getDistanceLost().getMeters()) + " " + stringMessages.metersUnit());
CheckBox maneuverLossLinesCheckBox = new CheckBox(stringMessages.show());
Triple<String, Date, ManeuverType> t = createManeuverKey(maneuver, competitor);
maneuverLossLinesCheckBox.setValue(maneuverLossLinesMap.containsKey(t));
maneuverLossLinesCheckBox.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@Override
public void onValueChange(ValueChangeEvent<Boolean> event) {
if (event.getValue()) {
createManeuverLossLinesAndInfoOverlays(maneuver, competitor);
} else {
removeManeuverLossLinesAndInfoOverlayForManeuver(maneuver, competitor);
}
}
});
HorizontalPanel hPanel = new HorizontalPanel();
hPanel.add(maneuverLossWidget);
hPanel.add(maneuverLossLinesCheckBox);
vPanel.add(hPanel);
}
if (maneuver.getMarkPassingTimePoint() != null) {
vPanel.add(
raceMap.createInfoWindowLabelAndValue(stringMessages.markPassing(),
DateTimeFormat.getFormat(PredefinedFormat.TIME_FULL).format(maneuver.getMarkPassingTimePoint())
+ (maneuver.getMarkPassingSide() == null ? ""
: " (" + (maneuver.getMarkPassingSide() == NauticalSide.PORT
? stringMessages.portSide() : stringMessages.starboardSide())
+ ")")));
}
return vPanel;
}
public void updateManeuverMarkersAfterSettingsChanged() {
// TODO is it really necessary to remove them all first...?
if (lastManeuverResult != null) {
removeAllManeuverMarkers();
showManeuvers(lastManeuverResult);
}
}
/**
* Removes all maneuver displays from the map and also clears the structure holding the maneuvers
* last received from the server so that upon the next request to show maneuvers without a previous
* update from the server no maneuvers will be displayed.<p>
*
* @see #getAndShowManeuvers(RegattaAndRaceIdentifier, Map)
* @see #removeAllManeuverMarkers()
*/
void clearAllManeuverMarkers() {
removeAllManeuverMarkers();
lastManeuverResult = null;
}
}
@@ -23,8 +23,6 @@ import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.DateTimeFormat.PredefinedFormat;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.maps.client.LoadApi;
import com.google.gwt.maps.client.LoadApi.LoadLibrary;
@@ -74,7 +72,6 @@ import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.Bounds;
import com.sap.sailing.domain.common.ManeuverType;
import com.sap.sailing.domain.common.NauticalSide;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.WindSource;
@@ -95,7 +92,6 @@ import com.sap.sailing.gwt.ui.actions.GetWindInfoAction;
import com.sap.sailing.gwt.ui.client.ClientResources;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider;
import com.sap.sailing.gwt.ui.client.ManeuverTypeFormatter;
import com.sap.sailing.gwt.ui.client.NumberFormatterFactory;
import com.sap.sailing.gwt.ui.client.RaceCompetitorSelectionProvider;
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProviderListener;
@@ -115,7 +111,6 @@ import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTOWithSpeedWindTackAndLegType;
import com.sap.sailing.gwt.ui.shared.LegInfoDTO;
import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
import com.sap.sailing.gwt.ui.shared.MarkDTO;
import com.sap.sailing.gwt.ui.shared.QuickRankDTO;
import com.sap.sailing.gwt.ui.shared.RaceCourseDTO;
@@ -162,13 +157,18 @@ import com.sap.sse.gwt.client.shared.settings.ComponentContext;
public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> implements TimeListener, CompetitorSelectionChangeListener,
RaceTimesInfoProviderListener, TailFactory, RequiresDataInitialization, RequiresResize, QuickRankProvider {
private static final String ADVANTAGE_LINE_COLOR = "#ff9900"; // orange
private static final String FINISH_LINE_COLOR = "#000000";
private static final Color LOWLIGHTED_TAIL_COLOR = new RGBColor(200, 200, 200);
/* Line colors */
static final String ADVANTAGE_LINE_COLOR = "#ff9900"; // orange
static final String START_LINE_COLOR = "#ffffff";
static final String FINISH_LINE_COLOR = "#000000";
static final Color LOWLIGHTED_TAIL_COLOR = new RGBColor(200, 200, 200);
/* Line opacities and stroke weights */
static final double LOWLIGHTED_TAIL_OPACITY = 0.3;
static final double STANDARD_LINE_OPACITY = 1.0;
static final int STANDARD_LINE_STROKEWEIGHT = 1;
public static final String GET_RACE_MAP_DATA_CATEGORY = "getRaceMapData";
public static final String GET_WIND_DATA_CATEGORY = "getWindData";
private static final double LOWLIGHTED_TAIL_OPACITY = 0.3;
private static final String COMPACT_HEADER_STYLE = "compactHeader";
public static final Color WATER_COLOR = new RGBColor(0, 67, 125);
@@ -314,14 +314,6 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
*/
protected Set<Marker> douglasMarkers;
/**
* markers displayed in response to
* {@link SailingServiceAsync#getDouglasPoints(String, String, Map, Map, double, AsyncCallback)}
*/
private Set<Marker> maneuverMarkers;
private Map<CompetitorDTO, List<ManeuverDTO>> lastManeuverResult;
private Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> lastDouglasPeuckerResult;
private final RaceCompetitorSelectionProvider competitorSelection;
@@ -353,7 +345,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
*/
private int startedProcessingRequestID;
private RaceMapImageManager raceMapImageManager;
private final RaceMapImageManager raceMapImageManager;
private RaceMapSettings settings;
private final RaceMapLifecycle raceMapLifecycle;
@@ -425,8 +417,8 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
*/
private boolean orientationChangeInProgress;
private final NumberFormat numberFormatOneDecimal = NumberFormat.getFormat("0.0");
private final NumberFormat numberFormatNoDecimal = NumberFormat.getFormat("0");
private final NumberFormat numberFormatOneDecimal = NumberFormatterFactory.getDecimalFormat(1);
private final NumberFormat numberFormatNoDecimal = NumberFormatterFactory.getDecimalFormat(0);
/**
* The competitor for which the advantage line is currently showing. Should this competitor's quick rank change, or
@@ -441,6 +433,8 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
/** Callback to set the visibility of the wind chart. */
private final Consumer<WindSource> showWindChartForProvider;
private ManagedInfoWindow managedInfoWindow;
private final ManeuverMarkersAndLossIndicators maneuverMarkersAndLossIndicators;
private class AdvantageLineUpdater implements QuickRanksListener {
@Override
@@ -471,6 +465,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
StringMessages stringMessages, RegattaAndRaceIdentifier raceIdentifier,
RaceMapResources raceMapResources, boolean showHeaderPanel, QuickRanksDTOProvider quickRanksDTOProvider, Consumer<WindSource> showWindChartForProvider) {
super(parent, context);
this.maneuverMarkersAndLossIndicators = new ManeuverMarkersAndLossIndicators(this, sailingService, errorReporter, stringMessages);
this.showHeaderPanel = showHeaderPanel;
this.quickRanksDTOProvider = quickRanksDTOProvider;
this.raceMapLifecycle = raceMapLifecycle;
@@ -526,6 +521,14 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
this.setSize("100%", "100%");
}
ManagedInfoWindow getManagedInfoWindow() {
return managedInfoWindow;
}
RaceMapImageManager getRaceMapImageManager() {
return raceMapImageManager;
}
public void setQuickRanksDTOProvider(QuickRanksDTOProvider newQuickRanksDTOProvider) {
if (this.quickRanksDTOProvider != null) {
this.quickRanksDTOProvider.moveListernersTo(newQuickRanksDTOProvider);
@@ -1059,10 +1062,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
if (douglasMarkers != null) {
removeAllMarkDouglasPeuckerpoints();
}
if (maneuverMarkers != null) {
removeAllManeuverMarkers();
}
maneuverMarkersAndLossIndicators.clearAllManeuverMarkers();
if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) {
updateCoordinateSystemFromSettings();
}
@@ -1093,7 +1093,6 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
}
zoomMapToNewBounds(zoomToBounds);
mapFirstZoomDone = true;
updateEstimatedDuration(raceMapDataDTO.estimatedDuration);
}
} else {
@@ -1625,12 +1624,12 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
(settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE))
&& startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1,
windwardStartLinePosition, firstMarkPosition, windwardStartLineMarkToFirstMarkLineInfoProvider,
"grey");
"grey", STANDARD_LINE_STROKEWEIGHT, STANDARD_LINE_OPACITY);
leewardStartLineMarkToFirstMarkLine = showOrRemoveOrUpdateLine(leewardStartLineMarkToFirstMarkLine, /* showLine */
(settings.getHelpLinesSettings().isVisible(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE))
&& startMarkPositions.size() > 1 && courseDTO.waypointPositions.size() > 1,
leewardStartLinePosition, firstMarkPosition, leewardStartLineMarkToFirstMarkLineInfoProvider,
"grey");
"grey", STANDARD_LINE_STROKEWEIGHT, STANDARD_LINE_OPACITY);
}
}
@@ -1687,8 +1686,9 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
(settings.getHelpLinesSettings().isVisible(HelpLineTypes.FINISHLINE) && showFinishLineBasedOnCurrentLeg) ||
(settings.getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY) &&
(!startLineEqualsFinishLine(courseDTO) || showFinishLineBasedOnCurrentLeg));
startLine = showOrRemoveOrUpdateLine(startLine, reallyShowStartLine,
startLineLeftPosition, startLineRightPosition, startLineInfoProvider, "#ffffff");
startLine = showOrRemoveOrUpdateLine(startLine, reallyShowStartLine, startLineLeftPosition,
startLineRightPosition, startLineInfoProvider, START_LINE_COLOR, STANDARD_LINE_STROKEWEIGHT,
STANDARD_LINE_OPACITY);
// draw the finish line
final Position finishLineLeftPosition = numberOfFinishWaypointMarks == 0 ? null : courseDTO.getFinishMarkPositions().get(0);
final Position finishLineRightPosition = numberOfFinishWaypointMarks < 2 ? null : courseDTO.getFinishMarkPositions().get(1);
@@ -1701,8 +1701,9 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
} else {
finishLineAdvantageText.delete(0, finishLineAdvantageText.length());
}
finishLine = showOrRemoveOrUpdateLine(finishLine, reallyShowFinishLine,
finishLineLeftPosition, finishLineRightPosition, finishLineInfoProvider, FINISH_LINE_COLOR);
finishLine = showOrRemoveOrUpdateLine(finishLine, reallyShowFinishLine, finishLineLeftPosition,
finishLineRightPosition, finishLineInfoProvider, FINISH_LINE_COLOR, STANDARD_LINE_STROKEWEIGHT,
STANDARD_LINE_STROKEWEIGHT);
// the control point pairs for which we already decided whether or not
// to show a course middle line for; values tell whether to show the line and for which zero-based
// start waypoint index to do so; when for an equal control point pair multiple decisions with different
@@ -1787,13 +1788,10 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
return sb.toString();
}
};
return showOrRemoveOrUpdateLine(lineToShowOrRemoveOrUpdate, showLine, position1DTO, position2DTO, lineInfoProvider, "#2268a0");
return showOrRemoveOrUpdateLine(lineToShowOrRemoveOrUpdate, showLine, position1DTO, position2DTO,
lineInfoProvider, "#2268a0", STANDARD_LINE_STROKEWEIGHT, STANDARD_LINE_OPACITY);
}
private interface LineInfoProvider {
String getLineInfo();
}
/**
* @param showLine
* tells whether or not to show the line; if the <code>lineToShowOrRemoveOrUpdate</code> references a
@@ -1803,8 +1801,9 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
* @return <code>null</code> if the line is not shown; the polyline object representing the line being displayed
* otherwise
*/
private Polyline showOrRemoveOrUpdateLine(Polyline lineToShowOrRemoveOrUpdate, final boolean showLine,
final Position position1DTO, final Position position2DTO, final LineInfoProvider lineInfoProvider, String lineColorRGB) {
Polyline showOrRemoveOrUpdateLine(Polyline lineToShowOrRemoveOrUpdate, final boolean showLine,
final Position position1DTO, final Position position2DTO, final LineInfoProvider lineInfoProvider,
String lineColorRGB, int strokeWeight, double strokeOpacity) {
if (position1DTO != null && position2DTO != null) {
if (showLine) {
LatLng courseMiddleLinePoint1 = coordinateSystem.toLatLng(position1DTO);
@@ -1815,8 +1814,8 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
options.setClickable(true);
options.setGeodesic(true);
options.setStrokeColor(lineColorRGB);
options.setStrokeWeight(1);
options.setStrokeOpacity(1.0);
options.setStrokeWeight(strokeWeight);
options.setStrokeOpacity(strokeOpacity);
pointsAsArray = MVCArray.newInstance();
lineToShowOrRemoveOrUpdate = Polyline.newInstance(options);
lineToShowOrRemoveOrUpdate.setPath(pointsAsArray);
@@ -1862,22 +1861,25 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
private void adjustInfoOverlayForVisibleLine(Polyline lineToShowOrRemoveOrUpdate, final Position position1DTO,
final Position position2DTO, final LineInfoProvider lineInfoProvider) {
SmallTransparentInfoOverlay infoOverlay = infoOverlaysForLinesForCourseGeometry.get(lineToShowOrRemoveOrUpdate);
if (getSettings().getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY)) {
if (infoOverlay == null) {
infoOverlay = new SmallTransparentInfoOverlay(map, RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, lineInfoProvider.getLineInfo(), coordinateSystem);
infoOverlaysForLinesForCourseGeometry.put(lineToShowOrRemoveOrUpdate, infoOverlay);
infoOverlay.addToMap();
if (lineInfoProvider.isShowInfoOverlayWithHelplines()) {
SmallTransparentInfoOverlay infoOverlay = infoOverlaysForLinesForCourseGeometry.get(lineToShowOrRemoveOrUpdate);
if (getSettings().getHelpLinesSettings().isVisible(HelpLineTypes.COURSEGEOMETRY)) {
if (infoOverlay == null) {
infoOverlay = new SmallTransparentInfoOverlay(map, RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX,
lineInfoProvider.getLineInfo(), coordinateSystem);
infoOverlaysForLinesForCourseGeometry.put(lineToShowOrRemoveOrUpdate, infoOverlay);
infoOverlay.addToMap();
} else {
infoOverlay.setInfoText(lineInfoProvider.getLineInfo());
}
infoOverlay.setPosition(position1DTO.translateGreatCircle(position1DTO.getBearingGreatCircle(position2DTO),
position1DTO.getDistance(position2DTO).scale(0.5)), /* transition time */ -1);
infoOverlay.draw();
} else {
infoOverlay.setInfoText(lineInfoProvider.getLineInfo());
}
infoOverlay.setPosition(position1DTO.translateGreatCircle(position1DTO.getBearingGreatCircle(position2DTO),
position1DTO.getDistance(position2DTO).scale(0.5)), /* transition time */ -1);
infoOverlay.draw();
} else {
if (infoOverlay != null) {
infoOverlay.removeFromMap();
infoOverlaysForLinesForCourseGeometry.remove(lineToShowOrRemoveOrUpdate);
if (infoOverlay != null) {
infoOverlay.removeFromMap();
infoOverlaysForLinesForCourseGeometry.remove(lineToShowOrRemoveOrUpdate);
}
}
}
}
@@ -2143,7 +2145,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
}
}
private Widget createInfoWindowLabelAndValue(String labelName, String value) {
Widget createInfoWindowLabelAndValue(String labelName, String value) {
Label valueLabel = new Label(value);
valueLabel.setWordWrap(false);
return createInfoWindowLabelWithWidget(labelName, valueLabel);
@@ -2171,51 +2173,6 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
return vPanel;
}
private Widget getInfoWindowContent(ManeuverDTO maneuver) {
SpeedWithBearingDTO before = maneuver.speedWithBearingBefore;
SpeedWithBearingDTO after = maneuver.speedWithBearingAfter;
VerticalPanel vPanel = new VerticalPanel();
vPanel.add(createInfoWindowLabelAndValue(stringMessages.maneuverType(),
ManeuverTypeFormatter.format(maneuver.type, stringMessages)));
vPanel.add(createInfoWindowLabelAndValue(stringMessages.time(),
DateTimeFormat.getFormat(PredefinedFormat.TIME_FULL).format(maneuver.timePoint)));
vPanel.add(createInfoWindowLabelAndValue(stringMessages.directionChange(),
((int) Math.round(maneuver.directionChangeInDegrees)) + " " + stringMessages.degreesShort() + " ("
+ ((int) Math.round(before.bearingInDegrees)) + " " + stringMessages.degreesShort() + " -> "
+ ((int) Math.round(after.bearingInDegrees)) + " " + stringMessages.degreesShort() + ")"));
vPanel.add(createInfoWindowLabelAndValue(stringMessages.speedChange(),
NumberFormat.getDecimalFormat().format(after.speedInKnots - before.speedInKnots) + " "
+ stringMessages.knotsUnit() + " ("
+ NumberFormat.getDecimalFormat().format(before.speedInKnots) + " " + stringMessages.knotsUnit()
+ " -> " + NumberFormat.getDecimalFormat().format(after.speedInKnots) + " "
+ stringMessages.knotsUnit() + ")"));
vPanel.add(createInfoWindowLabelAndValue(stringMessages.maxTurningRate(),
NumberFormat.getDecimalFormat().format(maneuver.maxTurningRateInDegreesPerSecond) + " "
+ stringMessages.degreesPerSecondUnit()));
vPanel.add(createInfoWindowLabelAndValue(stringMessages.avgTurningRate(),
NumberFormat.getDecimalFormat().format(maneuver.avgTurningRateInDegreesPerSecond) + " "
+ stringMessages.degreesPerSecondUnit()));
if (maneuver.type != ManeuverType.BEAR_AWAY && maneuver.type != ManeuverType.HEAD_UP) {
vPanel.add(createInfoWindowLabelAndValue(stringMessages.lowestSpeed(),
NumberFormat.getDecimalFormat().format(maneuver.lowestSpeedInKnots) + " "
+ stringMessages.knotsUnit()));
}
if (maneuver.maneuverLossInMeters != null) {
vPanel.add(createInfoWindowLabelAndValue(stringMessages.maneuverLoss(),
numberFormatOneDecimal.format(maneuver.maneuverLossInMeters) + " " + stringMessages.metersUnit()));
}
if (maneuver.markPassingTimePoint != null) {
vPanel.add(
createInfoWindowLabelAndValue(stringMessages.markPassing(),
DateTimeFormat.getFormat(PredefinedFormat.TIME_FULL).format(maneuver.markPassingTimePoint)
+ (maneuver.markPassingSide == null ? ""
: " (" + (maneuver.markPassingSide == NauticalSide.PORT
? stringMessages.portSide() : stringMessages.starboardSide())
+ ")")));
}
return vPanel;
}
private Widget getInfoWindowContent(final WindSource windSource, WindTrackInfoDTO windTrackInfoDTO) {
WindDTO windDTO = windTrackInfoDTO.windFixes.get(0);
final VerticalPanel vPanel = new VerticalPanel();
@@ -2276,7 +2233,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
speedWithBearing = new SpeedWithBearingDTO(0, 0);
}
vPanel.add(createInfoWindowLabelAndValue(stringMessages.speed(),
NumberFormatterFactory.getDecimalFormat(1).format(speedWithBearing.speedInKnots) + " "+stringMessages.knotsUnit()));
numberFormatOneDecimal.format(speedWithBearing.speedInKnots) + " "+stringMessages.knotsUnit()));
vPanel.add(createInfoWindowLabelAndValue(stringMessages.bearing(), (int) speedWithBearing.bearingInDegrees + " "+stringMessages.degreesShort()));
if (lastFix.degreesBoatToTheWind != null) {
vPanel.add(createInfoWindowLabelAndValue(stringMessages.degreesBoatToTheWind(),
@@ -2310,24 +2267,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
}
}
});
sailingService.getManeuvers(race, timeRange, new AsyncCallback<Map<CompetitorDTO, List<ManeuverDTO>>>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error obtaining maneuvers: " + caught.getMessage(), true /*silentMode */);
}
@Override
public void onSuccess(Map<CompetitorDTO, List<ManeuverDTO>> result) {
lastManeuverResult = result;
if (maneuverMarkers != null) {
removeAllManeuverMarkers();
}
if (!(timer.getPlayState() == PlayStates.Playing)) {
showManeuvers(result);
}
}
});
maneuverMarkersAndLossIndicators.getAndShowManeuvers(race, timeRange);
}
}
return vPanel;
@@ -2370,15 +2310,6 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
douglasMarkers = null;
}
protected void removeAllManeuverMarkers() {
if (maneuverMarkers != null) {
for (Marker marker : maneuverMarkers) {
marker.setMap((MapWidget) null);
}
maneuverMarkers = null;
}
}
private void showMarkDouglasPeuckerPoints(Map<CompetitorDTO, List<GPSFixDTOWithSpeedWindTackAndLegType>> gpsFixPointMapForCompetitors) {
douglasMarkers = new HashSet<Marker>();
if (map != null && gpsFixPointMapForCompetitors != null) {
@@ -2399,42 +2330,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
}
}
}
private void showManeuvers(Map<CompetitorDTO, List<ManeuverDTO>> maneuvers) {
maneuverMarkers = new HashSet<Marker>();
if (map != null && maneuvers != null) {
Set<CompetitorDTO> keySet = maneuvers.keySet();
Iterator<CompetitorDTO> iter = keySet.iterator();
while (iter.hasNext()) {
CompetitorDTO competitorDTO = iter.next();
List<ManeuverDTO> maneuversForCompetitor = maneuvers.get(competitorDTO);
for (ManeuverDTO maneuver : maneuversForCompetitor) {
if (settings.isShowManeuverType(maneuver.type)) {
createAndAddMarkerOfManeuver(maneuver);
}
}
}
}
}
private void createAndAddMarkerOfManeuver(ManeuverDTO maneuver) {
LatLng latLng = coordinateSystem.toLatLng(maneuver.position);
Marker maneuverMarker = raceMapImageManager.maneuverIconsForTypeAndDirectionIndicatingColor
.get(new Pair<ManeuverType, ManeuverColor>(maneuver.type, ManeuverColor.getManeuverColor(maneuver)));
MarkerOptions options = MarkerOptions.newInstance();
options.setIcon(maneuverMarker.getIcon_MarkerImage());
Marker marker = Marker.newInstance(options);
marker.setPosition(latLng);
marker.setTitle(ManeuverTypeFormatter.format(maneuver.type, stringMessages));
marker.addClickHandler(event -> {
LatLng where = getCoordinateSystem().toLatLng(maneuver.position);
Widget content = getInfoWindowContent(maneuver);
managedInfoWindow.openAtPosition(content, where);
});
maneuverMarkers.add(marker);
marker.setMap(map);
}
/**
* @param date
* the point in time for which to determine the competitor's boat position; approximated by using the fix
@@ -2626,9 +2522,13 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
@Override
public void updateSettings(RaceMapSettings newSettings) {
boolean maneuverTypeSelectionChanged = false;
boolean showManeuverLossChanged = false;
boolean requiresRedraw = false;
boolean requiresUpdateCoordinateSystem = false;
if (newSettings.isShowManeuverLossVisualization() != settings.isShowManeuverLossVisualization()) {
showManeuverLossChanged = true;
}
for (ManeuverType maneuverType : ManeuverType.values()) {
if (newSettings.isShowManeuverType(maneuverType) != settings.isShowManeuverType(maneuverType)) {
maneuverTypeSelectionChanged = true;
@@ -2690,14 +2590,11 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
requiresRedraw = true;
}
this.settings = newSettings;
if (maneuverTypeSelectionChanged) {
if (!(timer.getPlayState() == PlayStates.Playing) && lastManeuverResult != null) {
removeAllManeuverMarkers();
showManeuvers(lastManeuverResult);
if (maneuverTypeSelectionChanged || showManeuverLossChanged) {
if (timer.getPlayState() != PlayStates.Playing) {
maneuverMarkersAndLossIndicators.updateManeuverMarkersAfterSettingsChanged();
}
}
if (requiresUpdateCoordinateSystem) {
updateCoordinateSystemFromSettings();
}
@@ -25,19 +25,19 @@ public class RaceMapImageManager {
/**
* An arrow showing the combined wind depending on the wind direction
*/
protected ImageTransformer combinedWindIconTransformer;
private final ImageTransformer combinedWindIconTransformer;
/**
* An arrow showing the true north direction
*/
protected ImageTransformer trueNorthIndicatorIconTransformer;
private final ImageTransformer trueNorthIndicatorIconTransformer;
/**
* An arrow showing the wind provided by a wind sensor on a boat
*/
protected ImageTransformer windSensorIconTransformer;
private final ImageTransformer windSensorIconTransformer;
protected Map<Util.Pair<ManeuverType, ManeuverColor>, Marker> maneuverIconsForTypeAndDirectionIndicatingColor;
private final Map<Util.Pair<ManeuverType, ManeuverColor>, Marker> maneuverIconsForTypeAndDirectionIndicatingColor;
private final RaceMapResources resources;
@@ -61,11 +61,11 @@ public class RaceMapImageManager {
}
Marker icon;
for (ManeuverType maneuver : maneuvers) {
icon = createMarker(resources.maneuverMarkerRed());
maneuverIconsForTypeAndDirectionIndicatingColor
icon = createMarker(getResources().maneuverMarkerRed());
getManeuverIconsForTypeAndDirectionIndicatingColor()
.put(new Util.Pair<ManeuverType, ManeuverColor>(maneuver, ManeuverColor.RED), icon);
icon = createMarker(resources.maneuverMarkerGreen());
maneuverIconsForTypeAndDirectionIndicatingColor
icon = createMarker(getResources().maneuverMarkerGreen());
getManeuverIconsForTypeAndDirectionIndicatingColor()
.put(new Util.Pair<ManeuverType, ManeuverColor>(maneuver, ManeuverColor.GREEN), icon);
}
}
@@ -81,19 +81,27 @@ public class RaceMapImageManager {
return marker;
}
public ImageTransformer getTrueNorthIndicatorIconTransformer() {
ImageTransformer getTrueNorthIndicatorIconTransformer() {
return trueNorthIndicatorIconTransformer;
}
public ImageTransformer getCombinedWindIconTransformer() {
ImageTransformer getCombinedWindIconTransformer() {
return combinedWindIconTransformer;
}
public ImageTransformer getWindSensorIconTransformer() {
ImageTransformer getWindSensorIconTransformer() {
return windSensorIconTransformer;
}
public TextResource getWindFinderLogo() {
return resources.getWindFinderLogo();
TextResource getWindFinderLogo() {
return getResources().getWindFinderLogo();
}
Map<Util.Pair<ManeuverType, ManeuverColor>, Marker> getManeuverIconsForTypeAndDirectionIndicatingColor() {
return maneuverIconsForTypeAndDirectionIndicatingColor;
}
private RaceMapResources getResources() {
return resources;
}
}
@@ -53,7 +53,8 @@ public class RaceMapLifecycle implements ComponentLifecycle<RaceMapSettings> {
settings.isShowOnlySelectedCompetitors(), settings.isShowSelectedCompetitorsInfo(),
settings.isShowWindStreamletColors(), settings.isShowWindStreamletOverlay(),
settings.isShowSimulationOverlay(), settings.isShowMapControls(), settings.getManeuverTypesToShow(),
settings.isShowDouglasPeuckerPoints(), settings.isShowEstimatedDuration(), settings.getStartCountDownFontSizeScaling());
settings.isShowDouglasPeuckerPoints(), settings.isShowEstimatedDuration(),
settings.getStartCountDownFontSizeScaling(), settings.isShowManeuverLossVisualization());
}
@Override
@@ -65,6 +66,7 @@ public class RaceMapLifecycle implements ComponentLifecycle<RaceMapSettings> {
settings.isShowOnlySelectedCompetitors(), settings.isShowSelectedCompetitorsInfo(),
settings.isShowWindStreamletColors(), settings.isShowWindStreamletOverlay(),
settings.isShowSimulationOverlay(), settings.isShowMapControls(), settings.getManeuverTypesToShow(),
settings.isShowDouglasPeuckerPoints(), settings.isShowEstimatedDuration(), settings.getStartCountDownFontSizeScaling());
settings.isShowDouglasPeuckerPoints(), settings.isShowEstimatedDuration(),
settings.getStartCountDownFontSizeScaling(), settings.isShowManeuverLossVisualization());
}
}
@@ -60,6 +60,8 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings {
private BooleanSetting showMapControls;
private BooleanSetting showManeuverLossVisualization;
/**
* If <code>true</code>, all map contents will be transformed to a water-only environment, rotating all directions /
* bearings / headings so that an assumed average wind direction for the race is coming from the top of the map
@@ -97,6 +99,7 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings {
showDouglasPeuckerPoints = new BooleanSetting("showDouglasPeuckerPoints", this, false);
showEstimatedDuration = new BooleanSetting("showEstimatedDuration", this, false);
startCountDownFontSizeScaling = new DoubleSetting("startCountDownFontSizeScaling", this, 1.0);
showManeuverLossVisualization = new BooleanSetting("showManeuverLossVisualization", this, false);
}
public RaceMapSettings() {
@@ -107,7 +110,7 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings {
Distance buoyZoneRadius, Boolean showOnlySelectedCompetitors, Boolean showSelectedCompetitorsInfo,
Boolean showWindStreamletColors, Boolean showWindStreamletOverlay, Boolean showSimulationOverlay,
Boolean showMapControls, Collection<ManeuverType> maneuverTypesToShow, Boolean showDouglasPeuckerPoints,
Boolean showEstimatedDuration, Double startCountDownFontSizeScaling) {
Boolean showEstimatedDuration, Double startCountDownFontSizeScaling, Boolean showManeuverLossVisualization) {
this.zoomSettings.init(zoomSettings);
this.helpLinesSettings.init(helpLinesSettings);
this.transparentHoverlines.setValue(transparentHoverlines);
@@ -125,6 +128,7 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings {
this.showDouglasPeuckerPoints.setValue(showDouglasPeuckerPoints);
this.showEstimatedDuration.setValue(showEstimatedDuration);
this.startCountDownFontSizeScaling.setValue(startCountDownFontSizeScaling);
this.showManeuverLossVisualization.setValue(showManeuverLossVisualization);
}
public static RaceMapSettings getDefaultWithShowMapControls(boolean showMapControlls) {
@@ -155,7 +159,8 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings {
/* maneuverTypesToShow */ getDefaultManeuvers(),
/* showDouglasPeuckerPoints */ false,
/* showEstimatedDuration*/ false,
/* startCountDownFontSizeScaling */ 1.0);
/* startCountDownFontSizeScaling */ 1.0,
/* showManeuverLossVisualization */ false);
}
private static Set<HelpLineTypes> createHelpLineSettings(boolean showCourseGeometry) {
@@ -197,7 +202,8 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings {
/* maneuverTypesToShow */ settings.getManeuverTypesToShow(),
/* showDouglasPeuckerPoints */ settings.isShowDouglasPeuckerPoints(),
/* showEstimatedDuration */ settings.isShowEstimatedDuration(),
/* startCountDownFontSizeScaling */ settings.getStartCountDownFontSizeScaling());
/* startCountDownFontSizeScaling */ settings.getStartCountDownFontSizeScaling(),
/* showManeuverLossLineVisualization */ settings.isShowManeuverLossVisualization());
}
/**
@@ -271,6 +277,10 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings {
return startCountDownFontSizeScaling.getValue();
}
public boolean isShowManeuverLossVisualization() {
return showManeuverLossVisualization.getValue();
}
public static RaceMapSettings createSettingsWithNewBuoyZoneRadius(RaceMapSettings settings, Distance newDefaultBuoyZoneRadius) {
final RaceMapSettings newRaceMapSettings = new RaceMapSettings(
settings.getZoomSettings(), settings.getHelpLinesSettings(),
@@ -284,7 +294,8 @@ public class RaceMapSettings extends AbstractGenericSerializableSettings {
settings.isShowSimulationOverlay(), settings.isShowMapControls(),
settings.getManeuverTypesToShow(),
settings.isShowDouglasPeuckerPoints(),settings.isShowEstimatedDuration(),
settings.getStartCountDownFontSizeScaling());
settings.getStartCountDownFontSizeScaling(),
settings.isShowManeuverLossVisualization());
return newRaceMapSettings;
}
@@ -54,6 +54,7 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R
private CheckBox transparentHoverlines;
private IntegerBox hoverlineStrokeWeight;
private DoubleBox startCountDownFontSizeScalingBox;
private CheckBox maneuverLossVisualizationCheckBox;
private boolean isSimulationEnabled;
@@ -233,6 +234,10 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R
vp.add(createHelpLineCheckBox(dialog, HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE));
vp.add(createHelpLineCheckBox(dialog, HelpLineTypes.COURSEGEOMETRY));
maneuverLossVisualizationCheckBox = dialog.createCheckbox(stringMessages.maneuverLoss());
maneuverLossVisualizationCheckBox.setValue(initialSettings.isShowManeuverLossVisualization());
vp.add(maneuverLossVisualizationCheckBox);
transparentHoverlines = dialog.createCheckbox(stringMessages.transparentBufferLineOnHover());
transparentHoverlines.ensureDebugId("transparentHoverlinesCheckBox");
transparentHoverlines.setValue(initialSettings.getTransparentHoverlines());
@@ -304,7 +309,7 @@ public class RaceMapSettingsDialogComponent implements SettingsDialogComponent<R
buoyZoneRadius, showOnlySelectedCompetitorsCheckBox.getValue(), showSelectedCompetitorsInfoCheckBox.getValue(),
showWindStreamletColorsCheckbox.getValue(), showWindStreamletOverlayCheckbox.getValue(), showSimulationOverlay,
initialSettings.isShowMapControls(), maneuverTypesToShow, showDouglasPeuckerPointsCheckBox.getValue(),estimatedDuration,
startCountDownFontSizeScalingBox.getValue());
startCountDownFontSizeScalingBox.getValue(), maneuverLossVisualizationCheckBox.getValue());
}
private RaceMapZoomSettings getZoomSettings() {
@@ -35,12 +35,25 @@ public class SmallTransparentInfoOverlay extends CanvasOverlayV3 {
private double cornerRadius;
private final double fontScalingFactor;
/* Background colors */
private static final CssColor GREYTRANSPARENT_COLOR = CssColor.make("rgba(255,255,255,0.75)");
private final CssColor greyWithCustomTransparency;
public SmallTransparentInfoOverlay(MapWidget map, int zIndex, String infoText, CoordinateSystem coordinateSystem) {
this(map, zIndex, infoText, coordinateSystem, /* fontScalingFactor */ 1.0);
this(map, zIndex, infoText, coordinateSystem, /* fontScalingFactor */ 1.0, GREYTRANSPARENT_COLOR);
}
public SmallTransparentInfoOverlay(MapWidget map, int zIndex, String infoText, CoordinateSystem coordinateSystem, double fontScalingFactor) {
this(map, zIndex, infoText, coordinateSystem, fontScalingFactor, GREYTRANSPARENT_COLOR);
}
public SmallTransparentInfoOverlay(MapWidget map, int zIndex, String infoText, CoordinateSystem coordinateSystem, CssColor cssColor) {
this(map, zIndex, infoText, coordinateSystem, /* fontScalingFactor */ 1.0, cssColor);
}
public SmallTransparentInfoOverlay(MapWidget map, int zIndex, String infoText, CoordinateSystem coordinateSystem,
double fontScalingFactor, CssColor cssColor) {
super(map, zIndex, coordinateSystem);
this.infoText = infoText;
this.fontScalingFactor = fontScalingFactor;
@@ -55,6 +68,7 @@ public class SmallTransparentInfoOverlay extends CanvasOverlayV3 {
getCanvas().setCoordinateSpaceWidth(canvasWidth);
getCanvas().setCoordinateSpaceHeight(canvasHeight);
}
greyWithCustomTransparency = cssColor;
}
@Override
@@ -67,7 +81,6 @@ public class SmallTransparentInfoOverlay extends CanvasOverlayV3 {
if (mapProjection != null && position != null) {
LatLng latLngPosition = coordinateSystem.toLatLng(position);
Context2d context2d = getCanvas().getContext2d();
CssColor grayTransparentColor = CssColor.make("rgba(255,255,255,0.75)");
final int fontSizeInPx = (int) (fontScalingFactor*(12+2*Math.max(0, map.getZoom()-15)));
final int LINE_HEIGHT = fontSizeInPx+LINE_SPACING;
context2d.setFont(""+fontSizeInPx+"px Roboto, Arial, sans-serif");
@@ -88,12 +101,12 @@ public class SmallTransparentInfoOverlay extends CanvasOverlayV3 {
getCanvas().setHeight(String.valueOf(canvasHeight));
getCanvas().setCoordinateSpaceHeight(canvasHeight);
// Change origin and dimensions to match true size (a stroke makes the shape a bit larger)
context2d.setFillStyle(grayTransparentColor);
context2d.setFillStyle(greyWithCustomTransparency);
drawRoundedRect(context2d, cornerRadius / 2, cornerRadius / 2, infoBoxWidth - cornerRadius, infoBoxHeight
- cornerRadius, cornerRadius);
// this translation is important for drawing lines with a real line width of 1 pixel
context2d.translate(-0.5, -0.5);
context2d.setStrokeStyle("gray");
context2d.setStrokeStyle(greyWithCustomTransparency);
context2d.setLineWidth(1.0);
context2d.beginPath();
context2d.moveTo(cornerRadius / 2, infoBoxHeight / 2);
@@ -54,7 +54,7 @@ public class TrueNorthIndicatorPanel extends FlowPanel {
oldRaceMapSettings.isShowWindStreamletOverlay(), oldRaceMapSettings.isShowSimulationOverlay(),
oldRaceMapSettings.isShowMapControls(), oldRaceMapSettings.getManeuverTypesToShow(),
oldRaceMapSettings.isShowDouglasPeuckerPoints(), oldRaceMapSettings.isShowEstimatedDuration(),
oldRaceMapSettings.getStartCountDownFontSizeScaling());
oldRaceMapSettings.getStartCountDownFontSizeScaling(), oldRaceMapSettings.isShowManeuverLossVisualization());
map.updateSettings(newRaceMapSettings);
}
});
@@ -20,23 +20,23 @@ class ManeuverTableData {
private final double lowestSpeedInKnots;
private final double maximumTurningRate;
private final double averageTurningRate;
private final Double maneuverLoss;
private final Double maneuverLossInMeters;
private final double directionChange;
private final boolean markPassing;
ManeuverTableData(final CompetitorDTO competitor, final ManeuverDTO maneuver) {
this.competitorName = competitor.getName();
this.timePoint = maneuver.timePoint;
this.timePointBefore = maneuver.timePointBefore;
this.maneuverType = maneuver.type;
this.speedBeforeInKnots = maneuver.speedWithBearingBefore.speedInKnots;
this.speedAfterInKnots = maneuver.speedWithBearingAfter.speedInKnots;
this.lowestSpeedInKnots = maneuver.lowestSpeedInKnots;
this.maneuverLoss = maneuver.maneuverLossInMeters;
this.directionChange = maneuver.directionChangeInDegrees;
this.maximumTurningRate = maneuver.maxTurningRateInDegreesPerSecond;
this.averageTurningRate = maneuver.avgTurningRateInDegreesPerSecond;
this.markPassing = maneuver.markPassingTimePoint != null && maneuver.markPassingSide != null;
this.timePoint = maneuver.getTimePoint();
this.timePointBefore = maneuver.getTimePointBefore();
this.maneuverType = maneuver.getType();
this.speedBeforeInKnots = maneuver.getSpeedWithBearingBefore().speedInKnots;
this.speedAfterInKnots = maneuver.getSpeedWithBearingAfter().speedInKnots;
this.lowestSpeedInKnots = maneuver.getLowestSpeedInKnots();
this.maneuverLossInMeters = maneuver.getManeuverLoss() == null ? 0.0 : maneuver.getManeuverLoss().getDistanceLost().getMeters();
this.directionChange = maneuver.getDirectionChangeInDegrees();
this.maximumTurningRate = maneuver.getMaxTurningRateInDegreesPerSecond();
this.averageTurningRate = maneuver.getAvgTurningRateInDegreesPerSecond();
this.markPassing = maneuver.getMarkPassingTimePoint() != null && maneuver.getMarkPassingSide() != null;
}
public String getCompetitorName() {
@@ -83,8 +83,8 @@ class ManeuverTableData {
return averageTurningRate;
}
public Double getManeuverLoss() {
return maneuverLoss;
public Double getManeuverLossInMeters() {
return maneuverLossInMeters;
}
public double getDirectionChange() {
@@ -153,7 +153,7 @@ public class ManeuverTablePanel extends AbstractCompositeComponent<ManeuverTable
this.stringMessages.maxTurningRate(), this.stringMessages.degreesPerSecondUnit()));
this.maneuverCellTable.addColumn(createSortableMinMaxColumn(ManeuverTableData::getAverageTurningRate,
this.stringMessages.avgTurningRate(), this.stringMessages.degreesPerSecondUnit()));
this.maneuverCellTable.addColumn(createSortableMinMaxColumn(ManeuverTableData::getManeuverLoss,
this.maneuverCellTable.addColumn(createSortableMinMaxColumn(ManeuverTableData::getManeuverLossInMeters,
this.stringMessages.maneuverLoss(), stringMessages.metersUnit()));
this.maneuverCellTable.addColumn(createSortableAbsMinMaxColumn(ManeuverTableData::getDirectionChange,
stringMessages.directionChange(), this.stringMessages.degreesShort()));
@@ -320,7 +320,7 @@ public class ManeuverTablePanel extends AbstractCompositeComponent<ManeuverTable
final Map<CompetitorDTO, Iterable<ManeuverDTO>> cachedData = competitorDataProvider.getCachedData();
for (final Entry<CompetitorDTO, Iterable<ManeuverDTO>> entry : cachedData.entrySet()) {
for (ManeuverDTO maneuver : entry.getValue()) {
if (settings.getSelectedManeuverTypes().contains(maneuver.type)) {
if (settings.getSelectedManeuverTypes().contains(maneuver.getType())) {
data.add(new ManeuverTableData(entry.getKey(), maneuver));
}
}
@@ -435,7 +435,7 @@ public class ManeuverTablePanel extends AbstractCompositeComponent<ManeuverTable
private CachedManeuverTableDataProvider(final TimeRangeProvider timeRangeProvider, final Timer timer,
final RegattaAndRaceIdentifier raceIdentifier, final SailingServiceAsync sailingService,
final AsyncActionsExecutor asyncActionsExecutor) {
super(timeRangeProvider, timer, m -> m.timePoint, LOADING_OFFSET_TO_NEXT_MANEUVER_PROVIDER, true);
super(timeRangeProvider, timer, m -> m.getTimePoint(), LOADING_OFFSET_TO_NEXT_MANEUVER_PROVIDER, true);
this.asyncActionsExecutor = asyncActionsExecutor;
this.raceIdentifier = raceIdentifier;
this.sailingService = sailingService;
@@ -121,7 +121,8 @@ public class EmbeddedMapAndWindChartEntryPoint extends AbstractSailingEntryPoint
defaultRaceMapSettings.isShowWindStreamletOverlay(), defaultRaceMapSettings.isShowSimulationOverlay(),
defaultRaceMapSettings.isShowMapControls(), defaultRaceMapSettings.getManeuverTypesToShow(),
defaultRaceMapSettings.isShowDouglasPeuckerPoints(), true,
defaultRaceMapSettings.getStartCountDownFontSizeScaling());
defaultRaceMapSettings.getStartCountDownFontSizeScaling(),
defaultRaceMapSettings.isShowManeuverLossVisualization());
getSailingService().getRaceIdentifier(regattaLikeName, raceColumnName, fleetName, new AsyncCallback<RegattaAndRaceIdentifier>() {
@Override
@@ -81,7 +81,8 @@ public class StartAnalysisMode extends RaceBoardModeWithPerRaceCompetitors {
defaultSettings.getManeuverTypesToShow(),
defaultSettings.isShowDouglasPeuckerPoints(),
defaultSettings.isShowEstimatedDuration(),
defaultSettings.getStartCountDownFontSizeScaling());
defaultSettings.getStartCountDownFontSizeScaling(),
defaultSettings.isShowManeuverLossVisualization());
((RaceBoardComponentContext) raceMap.getComponentContext()).addModesPatching(raceMap, additiveSettings, new OnSettingsReloadedCallback<RaceMapSettings>() {
@Override
public void onSettingsReloaded(RaceMapSettings patchedSettings) {
@@ -77,7 +77,8 @@ public class WinningLanesMode extends RaceBoardModeWithPerRaceCompetitors {
defaultSettings.getManeuverTypesToShow(),
defaultSettings.isShowDouglasPeuckerPoints(),
defaultSettings.isShowEstimatedDuration(),
defaultSettings.getStartCountDownFontSizeScaling());
defaultSettings.getStartCountDownFontSizeScaling(),
defaultSettings.isShowManeuverLossVisualization());
((RaceBoardComponentContext) raceMap.getComponentContext()).addModesPatching(raceMap, additiveSettings, new OnSettingsReloadedCallback<RaceMapSettings>() {
@@ -397,6 +397,7 @@ import com.sap.sailing.gwt.ui.shared.LeaderboardGroupBaseDTO;
import com.sap.sailing.gwt.ui.shared.LeaderboardGroupDTO;
import com.sap.sailing.gwt.ui.shared.LegInfoDTO;
import com.sap.sailing.gwt.ui.shared.ManeuverDTO;
import com.sap.sailing.gwt.ui.shared.ManeuverLossDTO;
import com.sap.sailing.gwt.ui.shared.MarkDTO;
import com.sap.sailing.gwt.ui.shared.MarkPassingTimesDTO;
import com.sap.sailing.gwt.ui.shared.PathDTO;
@@ -3625,16 +3626,22 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
final SpeedWithBearingDTO speedBefore = createSpeedWithBearingDTO(maneuver.getSpeedWithBearingBefore());
final SpeedWithBearingDTO speedAfter = createSpeedWithBearingDTO(maneuver.getSpeedWithBearingAfter());
final double directionChangeInDegrees = maneuver.getDirectionChangeInDegrees();
final Double maneuverLossInMeters = maneuver.getManeuverLoss() == null ? null : maneuver.getManeuverLoss().getMeters();
final double maxTurningRateInDegreesPerSecond = maneuver.getMaxTurningRateInDegreesPerSecond();
final double averageTurningRateInDegreesPerSecond = maneuver.getAvgTurningRateInDegreesPerSecond();
final double lowestSpeedInKnots = maneuver.getLowestSpeed().getKnots();
final Date markPassingTimePoint = maneuver.isMarkPassing()
? maneuver.getMarkPassing().getTimePoint().asDate() : null;
final NauticalSide markPassingSide = maneuver.isMarkPassing() ? maneuver.getToSide() : null;
result.add(new ManeuverDTO(type, newTack, position, timepoint, timePointBefore, speedBefore, speedAfter,
directionChangeInDegrees, maneuverLossInMeters, maxTurningRateInDegreesPerSecond,
averageTurningRateInDegreesPerSecond, lowestSpeedInKnots, markPassingTimePoint, markPassingSide));
final SpeedWithBearing speedWithBearingBeforeManeuverLoss = maneuver.getManeuverLoss() == null ? null
: maneuver.getManeuverLoss().getSpeedWithBearingBefore();
final Double middleManeuverAngle = maneuver.getManeuverLoss() == null ? null : maneuver.getManeuverLoss().getMiddleManeuverAngle().getDegrees();
final ManeuverLossDTO maneuverLoss = maneuver.getManeuverLoss() == null ? null
: new ManeuverLossDTO(maneuver.getManeuverLoss().getManeuverStartPosition(),
maneuver.getManeuverLoss().getManeuverEndPosition(), speedWithBearingBeforeManeuverLoss, middleManeuverAngle,
maneuver.getManeuverLoss().getManeuverDuration(), maneuver.getManeuverLoss().getProjectedDistanceLost());
result.add(new ManeuverDTO(type, newTack, position, timepoint, timePointBefore, speedBefore,
speedAfter, directionChangeInDegrees, maxTurningRateInDegreesPerSecond,
averageTurningRateInDegreesPerSecond, lowestSpeedInKnots, markPassingTimePoint, markPassingSide, maneuverLoss));
}
return result;
}
@@ -13,41 +13,42 @@ import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.gwt.ui.client.StringMessages;
public class ManeuverDTO implements IsSerializable {
public ManeuverType type;
private ManeuverType type;
public Tack newTack;
private Tack newTack;
public Position position;
private Position position;
public Date timePoint;
private Date timePoint;
public Date timePointBefore;
private Date timePointBefore;
private SpeedWithBearingDTO speedWithBearingBefore;
public SpeedWithBearingDTO speedWithBearingBefore;
private SpeedWithBearingDTO speedWithBearingAfter;
public SpeedWithBearingDTO speedWithBearingAfter;
private double directionChangeInDegrees;
public double directionChangeInDegrees;
private double maxTurningRateInDegreesPerSecond;
public Double maneuverLossInMeters;
private double avgTurningRateInDegreesPerSecond;
public double maxTurningRateInDegreesPerSecond;
private double lowestSpeedInKnots;
public double avgTurningRateInDegreesPerSecond;
private Date markPassingTimePoint;
public double lowestSpeedInKnots;
private NauticalSide markPassingSide;
private ManeuverLossDTO maneuverLoss;
public Date markPassingTimePoint;
public NauticalSide markPassingSide;
public ManeuverDTO() {}
@Deprecated // for GWT serialization only
ManeuverDTO() {}
public ManeuverDTO(ManeuverType type, Tack newTack, Position position, Date timePoint, Date timePointBefore,
SpeedWithBearingDTO speedWithBearingBefore, SpeedWithBearingDTO speedWithBearingAfter,
double directionChangeInDegrees, Double maneuverLossInMeters, double maxTurningRateInDegreesPerSecond,
double directionChangeInDegrees, double maxTurningRateInDegreesPerSecond,
double avgTurningRateInDegreesPerSecond, double lowestSpeedInKnots, Date markPassingTimePoint,
NauticalSide markPassingSide) {
NauticalSide markPassingSide, ManeuverLossDTO maneuverLoss) {
this.type = type;
this.newTack = newTack;
this.position = position;
@@ -56,23 +57,23 @@ public class ManeuverDTO implements IsSerializable {
this.speedWithBearingBefore = speedWithBearingBefore;
this.speedWithBearingAfter = speedWithBearingAfter;
this.directionChangeInDegrees = directionChangeInDegrees;
this.maneuverLossInMeters = maneuverLossInMeters;
this.maxTurningRateInDegreesPerSecond = maxTurningRateInDegreesPerSecond;
this.avgTurningRateInDegreesPerSecond = avgTurningRateInDegreesPerSecond;
this.lowestSpeedInKnots = lowestSpeedInKnots;
this.markPassingTimePoint = markPassingTimePoint;
this.markPassingSide = markPassingSide;
this.maneuverLoss = maneuverLoss;
}
public String toString(StringMessages stringMessages) {
SpeedWithBearingDTO before = this.speedWithBearingBefore;
SpeedWithBearingDTO after = this.speedWithBearingAfter;
SpeedWithBearingDTO before = this.getSpeedWithBearingBefore();
SpeedWithBearingDTO after = this.getSpeedWithBearingAfter();
final DateTimeFormat dateTimeFormat = DateTimeFormat.getFormat(PredefinedFormat.TIME_FULL);
String timeAndManeuver = dateTimeFormat.format(this.timePoint) + ": " + this.type.name();
String timePointBefore = " (started: " + dateTimeFormat.format(this.timePointBefore) + ")";
String timeAndManeuver = dateTimeFormat.format(this.getTimePoint()) + ": " + this.getType().name();
String timePointBefore = " (started: " + dateTimeFormat.format(this.getTimePointBefore()) + ")";
String directionChange = stringMessages.directionChange() + ": "
+ ((int) Math.round(this.directionChangeInDegrees)) + " " + stringMessages.degreesShort() + " ("
+ ((int) Math.round(this.getDirectionChangeInDegrees())) + " " + stringMessages.degreesShort() + " ("
+ ((int) Math.round(before.bearingInDegrees)) + " " + stringMessages.degreesShort() + " -> "
+ ((int) Math.round(after.bearingInDegrees)) + " " + stringMessages.degreesShort() + ")";
String speedChange = stringMessages.speedChange() + ": "
@@ -81,25 +82,81 @@ public class ManeuverDTO implements IsSerializable {
+ stringMessages.knotsUnit() + " -> " + NumberFormat.getDecimalFormat().format(after.speedInKnots) + " "
+ stringMessages.knotsUnit() + ")";
String maxTurningRate = stringMessages.maxTurningRate() + ": "
+ NumberFormat.getDecimalFormat().format(this.maxTurningRateInDegreesPerSecond) + " "
+ NumberFormat.getDecimalFormat().format(this.getMaxTurningRateInDegreesPerSecond()) + " "
+ stringMessages.degreesPerSecondUnit();
String avgTurningRate = stringMessages.avgTurningRate() + ": "
+ NumberFormat.getDecimalFormat().format(this.avgTurningRateInDegreesPerSecond) + " "
+ NumberFormat.getDecimalFormat().format(this.getAvgTurningRateInDegreesPerSecond()) + " "
+ stringMessages.degreesPerSecondUnit();
String lowestSpeed = stringMessages.lowestSpeed() + ": "
+ NumberFormat.getDecimalFormat().format(this.lowestSpeedInKnots) + " " + stringMessages.knotsUnit();
String maneuverLoss = this.maneuverLossInMeters == null ? ""
+ NumberFormat.getDecimalFormat().format(this.getLowestSpeedInKnots()) + " " + stringMessages.knotsUnit();
String maneuverLoss = (this.getManeuverLoss() == null || this.getManeuverLoss().getDistanceLost() == null) ? ""
: ("; " + stringMessages.maneuverLoss() + ": "
+ NumberFormat.getDecimalFormat().format(this.maneuverLossInMeters) + " "
+ NumberFormat.getDecimalFormat().format(this.getManeuverLoss().getDistanceLost().getMeters()) + " "
+ stringMessages.metersUnit());
String markPassing = markPassingTimePoint == null ? ""
String markPassing = getMarkPassingTimePoint() == null ? ""
: "; " + stringMessages.markPassedToAt(
this.markPassingSide == null ? ""
: this.markPassingSide == NauticalSide.PORT ? stringMessages.portSide()
this.getMarkPassingSide() == null ? ""
: this.getMarkPassingSide() == NauticalSide.PORT ? stringMessages.portSide()
: stringMessages.starboardSide(),
DateTimeFormat.getFormat(PredefinedFormat.TIME_FULL).format(this.markPassingTimePoint));
DateTimeFormat.getFormat(PredefinedFormat.TIME_FULL).format(this.getMarkPassingTimePoint()));
String maneuverTitle = timeAndManeuver + timePointBefore + "; " + directionChange + "; " + speedChange + "; "
+ maxTurningRate + "; " + avgTurningRate + "; " + lowestSpeed + maneuverLoss + markPassing;
return maneuverTitle;
}
public ManeuverType getType() {
return type;
}
public Tack getNewTack() {
return newTack;
}
public Position getPosition() {
return position;
}
public Date getTimePoint() {
return timePoint;
}
public Date getTimePointBefore() {
return timePointBefore;
}
public SpeedWithBearingDTO getSpeedWithBearingBefore() {
return speedWithBearingBefore;
}
public SpeedWithBearingDTO getSpeedWithBearingAfter() {
return speedWithBearingAfter;
}
public double getDirectionChangeInDegrees() {
return directionChangeInDegrees;
}
public double getMaxTurningRateInDegreesPerSecond() {
return maxTurningRateInDegreesPerSecond;
}
public double getAvgTurningRateInDegreesPerSecond() {
return avgTurningRateInDegreesPerSecond;
}
public double getLowestSpeedInKnots() {
return lowestSpeedInKnots;
}
public Date getMarkPassingTimePoint() {
return markPassingTimePoint;
}
public NauticalSide getMarkPassingSide() {
return markPassingSide;
}
public ManeuverLossDTO getManeuverLoss() {
return maneuverLoss;
}
}
@@ -0,0 +1,49 @@
package com.sap.sailing.gwt.ui.shared;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sse.common.Distance;
import com.sap.sse.common.Duration;
public class ManeuverLossDTO implements IsSerializable {
private Position maneuverStartPosition;
private Position maneuverEndPosition;
private SpeedWithBearing speedWithBearingBefore;
private Double middleManeuverAngle;
private Duration maneuverDuration;
private Distance projectedDistanceLost;
@Deprecated // for GWT serialization only
ManeuverLossDTO() {
}
public ManeuverLossDTO(Position maneuverStartPosition, Position maneuverEndPosition,
SpeedWithBearing speedWithBearingBefore, Double middleManeuverAngle, Duration maneuverDuration, Distance projectedDistanceLost) {
this.maneuverStartPosition = maneuverStartPosition;
this.maneuverEndPosition = maneuverEndPosition;
this.speedWithBearingBefore = speedWithBearingBefore;
this.middleManeuverAngle = middleManeuverAngle;
this.maneuverDuration = maneuverDuration;
this.projectedDistanceLost = projectedDistanceLost;
}
public Duration getManeuverDuration() {
return maneuverDuration;
}
public Position getManeuverStartPosition() {
return maneuverStartPosition;
}
public Position getManeuverEndPosition() {
return maneuverEndPosition;
}
public Double getMiddleManeuverAngle() {
return middleManeuverAngle;
}
public SpeedWithBearing getSpeedWithBearingBefore() {
return speedWithBearingBefore;
}
public Distance getDistanceLost() {
return projectedDistanceLost;
}
}
@@ -181,7 +181,7 @@ public class ManeuverBasedWindEstimationTrackImpl extends WindTrackImpl {
private final double maneuverAngleDeg;
private final SpeedWithBearing speedAtManeuverStart;
private final Bearing middleManeuverCourse;
private final Distance maneuverLoss;
private final Distance maneuverLossDistanceLost;
private Pair<Double, SpeedWithBearingWithConfidence<Void>>[] likelihoodAndTWSBasedOnSpeedAndAngleCache;
private ScalableBearingAndScalableDouble scalableMiddleManeuverCourseAndManeuverAngleDegCache;
@@ -195,7 +195,7 @@ public class ManeuverBasedWindEstimationTrackImpl extends WindTrackImpl {
this.speedAtManeuverStart = maneuver.getSpeedWithBearingBefore();
this.middleManeuverCourse = maneuver.getSpeedWithBearingBefore().getBearing()
.middle(maneuver.getSpeedWithBearingAfter().getBearing());
this.maneuverLoss = maneuver.getManeuverLoss();
this.maneuverLossDistanceLost = maneuver.getManeuverLoss() == null ? null : maneuver.getManeuverLoss().getProjectedDistanceLost();
@SuppressWarnings("unchecked")
Pair<Double, SpeedWithBearingWithConfidence<Void>>[] myLikelihoodAndTWSBasedOnSpeedAndAngleCache = (Pair<Double, SpeedWithBearingWithConfidence<Void>>[]) new Pair<?, ?>[ManeuverType
.values().length];
@@ -269,8 +269,8 @@ public class ManeuverBasedWindEstimationTrackImpl extends WindTrackImpl {
return result;
}
public Distance getManeuverLoss() {
return maneuverLoss;
public Distance getManeuverLossDistanceLost() {
return maneuverLossDistanceLost;
}
/**
@@ -322,7 +322,7 @@ public class ManeuverBasedWindEstimationTrackImpl extends WindTrackImpl {
result.append("\t");
result.append(mc.getMiddleManeuverCourse().getDegrees());
result.append("\t");
result.append(mc.getManeuverLoss() == null ? 0.0 : mc.getManeuverLoss().getMeters());
result.append(mc.getManeuverLossDistanceLost() == null ? 0.0 : mc.getManeuverLossDistanceLost().getMeters());
result.append("\t");
result.append(mc.getLikelihoodAndTWSBasedOnSpeedAndAngle(ManeuverType.TACK).getA());
result.append("\t");
@@ -44,7 +44,7 @@ public class ManeuverJsonSerializer implements JsonSerializer<Maneuver> {
: maneuver.getSpeedWithBearingAfter().getBearing().getDegrees());
result.put(DIRECTION_CHANGE_IN_DEGREES, maneuver.getDirectionChangeInDegrees());
result.put(MANEUVER_LOSS,
maneuver.getManeuverLoss() == null ? null : distanceSerializer.serialize(maneuver.getManeuverLoss()));
maneuver.getManeuverLoss() == null ? null : distanceSerializer.serialize(maneuver.getManeuverLoss().getProjectedDistanceLost()));
result.put(POSITION_AND_TIME, gpsFixSerializer.serialize(maneuver));
result.put(MAX_TURNING_RATE_IN_DEGREES_PER_SECOND, maneuver.getMaxTurningRateInDegreesPerSecond());
result.put(AVG_TURNING_RATE_IN_DEGREES_PER_SECOND, maneuver.getAvgTurningRateInDegreesPerSecond());
@@ -796,8 +796,8 @@ public class LeaderboardData extends ExportAction {
addNamedElementWithValue(maneuverInformation, "type", man.getType().toString());
addNamedElementWithValue(maneuverInformation, "position", maneuverCounter);
addNamedElementWithValue(maneuverInformation, "direction_change_in_degrees", man.getDirectionChangeInDegrees());
Distance maneuverLoss = man.getManeuverLoss();
addNamedElementWithValue(maneuverInformation, "loss_in_meters", maneuverLoss != null ? maneuverLoss.getMeters() : 0.0);
addNamedElementWithValue(maneuverInformation, "loss_in_meters",
man.getManeuverLoss() != null ? man.getManeuverLoss().getProjectedDistanceLost().getMeters() : 0.0);
addNamedElementWithValue(maneuverInformation, "speed_before_in_knots", man.getSpeedWithBearingBefore().getKnots());
addNamedElementWithValue(maneuverInformation, "speed_after_in_knots", man.getSpeedWithBearingAfter().getKnots());
maneuversElement.addContent(maneuverInformation);