diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java index fcd439a6dcd..ebbdbae1171 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/FixesAndTails.java @@ -174,28 +174,37 @@ public class FixesAndTails { } /** - * Adds the fixes received in result to {@link #fixes} and ensures they are still contiguous for each - * competitor. If overlapsWithKnownFixes indicates that the fixes received in result - * overlap with those already known, the fixes are merged into the list of already known fixes for the competitor. - * Otherwise, the fixes received in result replace those known so far for the respective competitor. - * The {@link #tails} affected by these fixes are updated accordingly when modifications fall inside the interval - * shown by the tail, as defined by {@link #firstShownFix} and {@link #lastShownFix}. The tails are, however, not - * trimmed according to the specification for the tail length. This has to happen elsewhere (see also - * {@link #updateTail}). + * Adds the fixes received in fixesForCompetitors to {@link #fixes} and ensures they are still + * contiguous for each competitor. If overlapsWithKnownFixes indicates that the fixes received in + * result overlap with those already known, the fixes are merged into the list of already known fixes + * for the competitor. Otherwise, the fixes received in result replace those known so far for the + * respective competitor. The {@link #tails} affected by these fixes are updated accordingly when modifications fall + * inside the interval shown by the tail, as defined by {@link #firstShownFix} and {@link #lastShownFix}. The tails + * are, however, not trimmed according to the specification for the tail length. This has to happen elsewhere (see + * also {@link #updateTail}). * * @param fixesForCompetitors * For each list the invariant must hold that an {@link GPSFixDTO#extrapolated extrapolated} fix must be * the last one in the list - * - * @return the competitors for which new fixes have been added; this will not include competitors appearing as keys - * in fixesForCompetitor whose fixes list is null or empty + * @param overlapsWithKnownFixes + * if for a competitor whose fixes are provided in fixesForCompetitors this holds + * false, any fixes previously stored for that competitor are removed, and the tail is + * deleted from the map (see {@link #removeTail(CompetitorDTO)}); the new fixes are then added to the + * {@link #fixes} map, and a new tail will have to be constructed as needed (does not happen here). If + * this map holds true, {@link #mergeFixes(CompetitorDTO, List, long)} is used to merge the + * new fixes from fixesForCompetitors into the {@link #fixes} collection, and the tail is + * left unchanged. NOTE: When a non-overlapping set of fixes is updated (false), this + * map's record for the competitor is UPDATED to true after the tail deletion and + * {@link #fixes} replacement has taken place. This helps in cases where this update is only one of two + * into which an original request was split (one quick update of the tail's head and another one for the + * longer tail itself), such that the second request that uses the same map will be considered + * having an overlap now, not leading to a replacement of the previous update originating from the same + * request. */ - protected Iterable updateFixes(Map> fixesForCompetitors, + protected void updateFixes(Map> fixesForCompetitors, Map overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) { - final Set competitorsWithNewFixes = new HashSet<>(); for (Map.Entry> e : fixesForCompetitors.entrySet()) { if (e.getValue() != null && !e.getValue().isEmpty()) { - competitorsWithNewFixes.add(e.getKey()); List fixesForCompetitor = fixes.get(e.getKey()); if (fixesForCompetitor == null) { fixesForCompetitor = new ArrayList(); @@ -206,19 +215,14 @@ public class FixesAndTails { fixesForCompetitor.clear(); // to re-establish the invariants for tails, firstShownFix and lastShownFix, we now need to remove // all points from the competitor's polyline and clear the entries in firstShownFix and lastShownFix - if (tails.containsKey(e.getKey())) { - Polyline removedTail = tails.remove(e.getKey()); - removedTail.setMap(null); - } - firstShownFix.remove(e.getKey()); - lastShownFix.remove(e.getKey()); + removeTail(e.getKey()); fixesForCompetitor.addAll(e.getValue()); + overlapsWithKnownFixes.put(e.getKey(), true); // In case this was only one part of a split request, the next request *does* have an overlap } else { mergeFixes(e.getKey(), e.getValue(), timeForPositionTransitionMillis); } } } - return competitorsWithNewFixes; } /** @@ -483,7 +487,7 @@ public class FixesAndTails { toDate = upTo; } // only request something for the competitor if we're missing information at all - if (fromDate.before(toDate) || fromDate.equals(toDate)) { + if (!fromDate.after(toDate)) { from.put(competitor, fromDate); to.put(competitor, toDate); overlapWithKnownFixes.put(competitor, overlap); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java index ffb6f83106f..d6287d5192c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/racemap/Hoverline.java @@ -23,7 +23,11 @@ public class Hoverline { this.options.setMap(polyline.getMap()); this.options.setPath(polyline.getPath()); this.options.setStrokeColor(polylineOptions.getStrokeColor()); - this.options.setZindex(polylineOptions.getZindex()); + try { + this.options.setZindex(polylineOptions.getZindex()); + } catch (Exception e) { + // the Z-index of polylineOptions most likely was undefined and therefore cannot be copied (GWT DevMode problem, mostly) + } this.hoverline = Polyline.newInstance(this.options); this.hoverline.setVisible(false); polyline.addMouseOverHandler(new MouseOverMapHandler() { 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 7a86f54b0e9..e0c21ec2231 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 @@ -773,14 +773,12 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (selectedRaces != null && !selectedRaces.isEmpty()) { RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1); 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()); // Request map data update, possibly in two calls; see method details callGetRaceMapDataForAllOverlappingAndTipsOfNonOverlappingAndGetBoatPositionsForAllOthers(fromAndToAndOverlap, race, newTime, transitionTimeInMillis, competitorsToShow); - // draw the wind into the map, get the combined wind List windSourceTypeNames = new ArrayList(); windSourceTypeNames.add(WindSourceType.EXPEDITION.name()); @@ -879,25 +877,37 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe competitorsByIdAsString.put(competitor.getIdAsString(), competitor); } + // only update the tails for these competitors + // Note: the fromAndToAndOverlap.getC() map will be UPDATED by the call to updateBoatPositions happening inside + // the callback provided by getRaceMapDataCallback(...) for those + // entries that are considered not overlapping; subsequently, fromAndToOverlap.getC() will contain true for + // all its entries so that the other response received for GetBoatPositionsAction will consider this an + // overlap if it happens after this update. asyncActionsExecutor.execute(new GetRaceMapDataAction(sailingService, competitorsByIdAsString, race, useNullAsTimePoint() ? null : newTime, fromTimesForQuickCall, toTimesForQuickCall, /* extrapolate */true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)), GET_RACE_MAP_DATA_CATEGORY, getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, ++boatPositionRequestIDCounter)); // next, if necessary, do the full thing; the two calls have different action classes, so throttling should not drop one for the other - asyncActionsExecutor.execute(new GetBoatPositionsAction(sailingService, race, fromTimesForNonOverlappingTailsCall, toTimesForNonOverlappingTailsCall, - /* extrapolate */ true), GET_RACE_MAP_DATA_CATEGORY, - new MarkedAsyncCallback<>(new AsyncCallback() { - @Override - public void onFailure(Throwable t) { - errorReporter.reportError("Error obtaining racemap data: " + t.getMessage(), true /*silentMode */); - } - - @Override - public void onSuccess(CompactBoatPositionsDTO result) { - updateBoatPositions(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, result.getBoatPositionsForCompetitors(competitorsByIdAsString), - /* updateTailsOnly */ true); - } - })); + if (!fromTimesForNonOverlappingTailsCall.keySet().isEmpty()) { + asyncActionsExecutor.execute(new GetBoatPositionsAction(sailingService, race, fromTimesForNonOverlappingTailsCall, toTimesForNonOverlappingTailsCall, + /* extrapolate */ true), GET_RACE_MAP_DATA_CATEGORY, + new MarkedAsyncCallback<>(new AsyncCallback() { + @Override + public void onFailure(Throwable t) { + errorReporter.reportError("Error obtaining racemap data: " + t.getMessage(), true /*silentMode */); + } + + @Override + public void onSuccess(CompactBoatPositionsDTO result) { + // Note: the fromAndToAndOverlap.getC() map will be UPDATED by the call to updateBoatPositions for those + // entries that are considered not overlapping; subsequently, fromAndToOverlap.getC() will contain true for + // all its entries so that the other response received for GetRaceMapDataAction will consider this an + // overlap if it happens after this update. + updateBoatPositions(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, + result.getBoatPositionsForCompetitors(competitorsByIdAsString), /* updateTailsOnly */ true); + } + })); + } } private AsyncCallback getRaceMapDataCallback( @@ -973,6 +983,20 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } /** + * @param hasTailOverlapForCompetitor + * if for a competitor whose fixes are provided in fixesForCompetitors this holds + * false, any fixes previously stored for that competitor are removed, and the tail is + * deleted from the map (see {@link #removeTail(CompetitorDTO)}); the new fixes are then added to the + * {@link #fixes} map, and a new tail will have to be constructed as needed (does not happen here). If + * this map holds true, {@link #mergeFixes(CompetitorDTO, List, long)} is used to merge the + * new fixes from fixesForCompetitors into the {@link #fixes} collection, and the tail is + * left unchanged. NOTE: When a non-overlapping set of fixes is updated (false), this + * map's record for the competitor is UPDATED to true after the tail deletion and + * {@link #fixes} replacement has taken place. This helps in cases where this update is only one of two + * into which an original request was split (one quick update of the tail's head and another one for the + * longer tail itself), such that the second request that uses the same map will be considered + * having an overlap now, not leading to a replacement of the previous update originating from the same + * request. See also {@link FixesAndTails#updateFixes(Map, Map, TailFactory, long)}. * @param updateTailsOnly * if true, only the tails are updated according to boatData and * hasTailOverlapForCompetitor, but the advantage line is not updated, and neither are the @@ -985,16 +1009,12 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe final Map hasTailOverlapForCompetitor, final Iterable competitorsToShow, Map> boatData, boolean updateTailsOnly) { fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); - Iterable competitorsForWhichNewFixesWereReveiced = - fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); - if (!Util.isEmpty(competitorsForWhichNewFixesWereReveiced)) { - showBoatsOnMap(newTime, transitionTimeInMillis, competitorsForWhichNewFixesWereReveiced, updateTailsOnly); - if (!updateTailsOnly) { - showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors()); - // 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, transitionTimeInMillis); - } + showBoatsOnMap(newTime, transitionTimeInMillis, competitorsToShow, updateTailsOnly); + if (!updateTailsOnly) { + showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors()); + // 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, transitionTimeInMillis); } } @@ -1206,8 +1226,12 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe if (map != null) { Date tailsFromTime = new Date(newTime.getTime() - settings.getEffectiveTailLengthInMilliseconds()); Date tailsToTime = newTime; - Set competitorDTOsOfUnusedTails = new HashSet(fixesAndTails.getCompetitorsWithTails()); - Set competitorDTOsOfUnusedBoatCanvases = new HashSet(boatOverlays.keySet()); + Set competitorDTOsOfUnusedTails = new HashSet(); + Set competitorDTOsOfUnusedBoatCanvases = new HashSet(); + if (!updateTailsOnly) { + competitorDTOsOfUnusedTails.addAll(fixesAndTails.getCompetitorsWithTails()); + competitorDTOsOfUnusedBoatCanvases.addAll(boatOverlays.keySet()); + } for (CompetitorDTO competitorDTO : competitorsToShow) { if (fixesAndTails.hasFixesFor(competitorDTO)) { Polyline tail = fixesAndTails.getTail(competitorDTO); @@ -1216,12 +1240,14 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe } else { fixesAndTails.updateTail(tail, competitorDTO, tailsFromTime, tailsToTime, (int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2)); - competitorDTOsOfUnusedTails.remove(competitorDTO); + if (!updateTailsOnly) { + competitorDTOsOfUnusedTails.remove(competitorDTO); + } PolylineOptions newOptions = createTailStyle(competitorDTO, displayHighlighted(competitorDTO)); tail.setOptions(newOptions); } boolean usedExistingBoatCanvas = updateBoatCanvasForCompetitor(competitorDTO, newTime, timeForPositionTransitionMillis); - if (usedExistingBoatCanvas) { + if (usedExistingBoatCanvas && !updateTailsOnly) { competitorDTOsOfUnusedBoatCanvases.remove(competitorDTO); } } @@ -1379,8 +1405,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe nextPath.push(advantageLinePos1); nextPath.push(advantageLinePos2); advantageTimer.setNextPositionAndTransitionMillis(nextPath, timeForPositionTransitionMillis); - advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg); - advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint)); + if (advantageLineMouseOverHandler != null) { + advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg); + advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint)); + } } drawAdvantageLine = true; }