diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java old mode 100755 new mode 100644 diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/AdvantageLineAnimator.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/AdvantageLineAnimator.java new file mode 100644 index 00000000000..098fbadf186 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/AdvantageLineAnimator.java @@ -0,0 +1,103 @@ +package com.sap.sailing.gwt.ui.client.shared.racemap; + +import com.google.gwt.maps.client.base.LatLng; +import com.google.gwt.maps.client.mvc.MVCArray; +import com.google.gwt.maps.client.overlays.Polyline; +import com.google.gwt.user.client.Timer; + +/** + * Moves a {@link Polyline} from its current position to a target position in a given duration with a fixed + * {@link #ANIMATION_PERIOD_MILLIS animation interval}. Since the timer may be triggered at non-exact intervals, the + * offset per animation tick is calculated based on the current time for each iteration. + *

+ * + * If an animation is still going on, it is permissible for clients to invoke + * {@link #setNextPositionAndTransitionMillis(MVCArray, long)} which will have the effect that from its current position + * reached so far the {@link Polyline} will then be animated to the next target position in the specified time interval. + * + * @author Jonas Dann + * + */ +public class AdvantageLineAnimator extends Timer { + /** + * Time between two animation steps + */ + private static final int ANIMATION_PERIOD_MILLIS = 100; + + /** + * The polyline to animate by updating its {@link Polyline#getPath() path} + */ + private final Polyline advantageLine; + + /** + * The last time the coordinates in the {@link #advantageLine}'s path were updated + */ + private long lastTime; + + /** + * The remaining time for the animation to move the {@link #advantageLine} to its {@link #nextPosition target + * position}. If -1, the {@link #advantageLine} will be moved to its {@link #nextPosition next target + * position} in one step. + */ + private long durationMillis = -1; + + /** + * The "to-be" / target position to which to move the {@link #advantageLine}'s path gradually during the remaining + * {@link #durationMillis}. + */ + private MVCArray nextPosition; + + public AdvantageLineAnimator(Polyline advantageLine) { + if (advantageLine == null) { + throw new NullPointerException("Advantage line must not be null"); + } + this.advantageLine = advantageLine; + } + + /** + * @param timeForPositionTransitionMillis if -1 then no animation is requested and the {@link #advantageLine} will + * be moved to its {@link #nextPosition next target position} in one step. + * + * @param nextPosition the next target position for the {@link #advantageLine}; if null, no animation will happen at all + */ + public void setNextPositionAndTransitionMillis(MVCArray nextPosition, long timeForPositionTransitionMillis) { + if (nextPosition.getLength() != advantageLine.getPath().getLength()) { + throw new IllegalArgumentException("The next animation position for a polyline with "+advantageLine.getPath().getLength()+ + " elements must have the same number of positions but did have "+nextPosition.getLength()); + } + this.nextPosition = nextPosition; + this.lastTime = System.currentTimeMillis(); + this.durationMillis = timeForPositionTransitionMillis; + this.scheduleRepeating(ANIMATION_PERIOD_MILLIS); + } + + @Override + public void run() { + final long time = System.currentTimeMillis(); + final double moveFactor; // how much of the delta to the nextPosition shall this tick move the advantage line? + if (durationMillis > 0) { + long deltaTime = time - lastTime; + moveFactor = Math.min(1.0, (double) deltaTime / (double) durationMillis); // don't overshoot + durationMillis -= deltaTime; + } else { + moveFactor = 1; + } + if (nextPosition != null) { + final MVCArray currentPosition = this.advantageLine.getPath(); + for (int i = 0; i < nextPosition.getLength(); i++) { + final LatLng currentLatLng = currentPosition.get(i); + final LatLng nextLatLng = nextPosition.get(i); + currentPosition.setAt(i, LatLng.newInstance(currentLatLng.getLatitude() + (nextLatLng.getLatitude() - currentLatLng.getLatitude()) * moveFactor, + currentLatLng.getLongitude() + (nextLatLng.getLongitude() - currentLatLng.getLongitude()) * moveFactor)); + } + } + if (durationMillis <= 0) { + cancel(); + } + lastTime = time; + } + + public void removeAnimation() { + this.durationMillis = -1; + } +} \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java index a6d094c52a8..0c22d286786 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/RaceMap.java @@ -178,6 +178,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe */ private Polyline advantageLine; + private AdvantageLineAnimator advantageTimer; + /** * The windward of two Polylines representing a triangle between startline and first mark. */ @@ -711,6 +713,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe for (CompetitorInfoOverlay infoOverlay : competitorInfoOverlays.values()) { infoOverlay.removeCanvasPositionAndRotationTransition(); } + // remove the advantage line animation + if (advantageTimer != null) { + advantageTimer.removeAnimation(); + } } public void redraw() { @@ -767,6 +773,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe final Iterable competitorsToShow = getCompetitorsToShow(); if (race != null) { + final long transitionTimeInMillis = calculateTimeForPositionTransitionInMillis(newTime, oldTime); final com.sap.sse.common.Util.Triple, Map, Map> fromAndToAndOverlap = fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds()); int requestID = ++boatPositionRequestIDCounter; @@ -776,7 +783,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping = getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(fromAndToAndOverlap, race, newTime); if (getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping != null) { asyncActionsExecutor.execute(getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping, GET_RACE_MAP_DATA_CATEGORY, - getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); + getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); requestID = ++boatPositionRequestIDCounter; } // next, do the full thing; being the later call, if request throttling kicks in, the later call @@ -784,7 +791,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe GetRaceMapDataAction getRaceMapDataAction = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(), race, useNullAsTimePoint() ? null : newTime, fromAndToAndOverlap.getA(), fromAndToAndOverlap.getB(), /* extrapolate */ true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)); asyncActionsExecutor.execute(getRaceMapDataAction, GET_RACE_MAP_DATA_CATEGORY, - getRaceMapDataCallback(oldTime, newTime, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); + getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, requestID)); // draw the wind into the map, get the combined wind List windSourceTypeNames = new ArrayList(); @@ -803,7 +810,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe List> windSourcesToShow = new ArrayList>(); if (windInfo != null) { lastCombinedWindTrackInfoDTO = windInfo; - showAdvantageLine(competitorsToShow, newTime); + showAdvantageLine(competitorsToShow, newTime, transitionTimeInMillis); for (WindSource windSource : windInfo.windTrackInfoByWindSource.keySet()) { WindTrackInfoDTO windTrackInfoDTO = windInfo.windTrackInfoByWindSource.get(windSource); switch (windSource.getType()) { @@ -870,8 +877,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } private AsyncCallback getRaceMapDataCallback( - final Date oldTime, final Date newTime, + final long transitionTimeInMillis, final Map hasTailOverlapForCompetitor, final Iterable competitorsToShow, final int requestID) { return new AsyncCallback() { @@ -895,10 +902,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe startedProcessingRequestID = requestID; // Do boat specific actions Map> boatData = raceMapDataDTO.boatPositions; - long timeForPositionTransitionMillis = calculateTimeForPositionTransition(newTime, oldTime); - fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, timeForPositionTransitionMillis); - showBoatsOnMap(newTime, timeForPositionTransitionMillis, getCompetitorsToShow()); - showCompetitorInfoOnMap(newTime, timeForPositionTransitionMillis, competitorSelection.getSelectedFilteredCompetitors()); + fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); + showBoatsOnMap(newTime, transitionTimeInMillis, getCompetitorsToShow()); + showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors()); if (douglasMarkers != null) { removeAllMarkDouglasPeuckerpoints(); } @@ -916,7 +922,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe showStartLineToFirstMarkTriangle(raceMapDataDTO.coursePositions); // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to // adjust to new boat positions - showAdvantageLine(competitorsToShow, newTime); + showAdvantageLine(competitorsToShow, newTime, transitionTimeInMillis); // Rezoom the map LatLngBounds zoomToBounds = null; @@ -1135,7 +1141,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } } - private long calculateTimeForPositionTransition(final Date newTime, final Date oldTime) { + private long calculateTimeForPositionTransitionInMillis(final Date newTime, final Date oldTime) { final long timeForPositionTransitionMillis; boolean hasTimeJumped = oldTime != null && Math.abs(oldTime.getTime() - newTime.getTime()) > 3*timer.getRefreshInterval(); if (timer.getPlayState() == PlayStates.Playing && !hasTimeJumped) { @@ -1222,7 +1228,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe return null; } - private void showAdvantageLine(Iterable competitorsToShow, Date date) { + private void showAdvantageLine(Iterable competitorsToShow, Date date, long timeForPositionTransitionMillis) { if (map != null && lastRaceTimesInfo != null && quickRanks != null && lastCombinedWindTrackInfoDTO != null) { boolean drawAdvantageLine = false; if (settings.getHelpLinesSettings().isVisible(HelpLineTypes.ADVANTAGELINE)) { @@ -1288,6 +1294,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } break; } + MVCArray nextPath = MVCArray.newInstance(); LatLng advantageLinePos1 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, coordinateSystem.mapDegreeBearing(rotatedBearingDeg1), advantageLineLengthInKm / 2.0); LatLng advantageLinePos2 = calculatePositionAlongRhumbline(posAheadOfFirstBoat, @@ -1301,6 +1308,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe options.setStrokeOpacity(0.5); advantageLine = Polyline.newInstance(options); + advantageTimer = new AdvantageLineAnimator(advantageLine); MVCArray pointsAsArray = MVCArray.newInstance(); pointsAsArray.insertAt(0, advantageLinePos1); pointsAsArray.insertAt(1, advantageLinePos2); @@ -1318,10 +1326,9 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } }); } else { - advantageLine.getPath().removeAt(1); - advantageLine.getPath().removeAt(0); - advantageLine.getPath().insertAt(0, advantageLinePos1); - advantageLine.getPath().insertAt(1, advantageLinePos2); + nextPath.push(advantageLinePos1); + nextPath.push(advantageLinePos2); + advantageTimer.setNextPositionAndTransitionMillis(nextPath, timeForPositionTransitionMillis); advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg); advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint)); } @@ -1332,6 +1339,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (advantageLine != null) { advantageLine.setMap(null); advantageLine = null; + advantageTimer = null; } } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html index 69c32d47aea..6c0d43b7814 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html +++ b/java/com.sap.sailing.gwt.ui/src/main/resources/com/sap/sailing/gwt/home/solutions/SailingAnalyticsNotes.html @@ -7,12 +7,14 @@

December 2015