bug3378: fixing display by marking the second of two split calls as overlap=true, avoiding the incorrect removal of fixes/tails for the second update

Change-Id: Ib1cdeb7b58455745a572c85fab954cfd0f14c31e
This commit is contained in:
Axel Uhl committed 2015-12-15 11:57:58 +01:00
1 parent a2076240a3
commit a292d85224
3 files changed
+91 -55

No files matched your search

@@ -174,28 +174,37 @@ public class FixesAndTails {
} }
/** /**
* Adds the fixes received in <code>result</code> to {@link #fixes} and ensures they are still contiguous for each * Adds the fixes received in <code>fixesForCompetitors</code> to {@link #fixes} and ensures they are still
* competitor. If <code>overlapsWithKnownFixes</code> indicates that the fixes received in <code>result</code> * contiguous for each competitor. If <code>overlapsWithKnownFixes</code> indicates that the fixes received in
* overlap with those already known, the fixes are merged into the list of already known fixes for the competitor. * <code>result</code> overlap with those already known, the fixes are merged into the list of already known fixes
* Otherwise, the fixes received in <code>result</code> replace those known so far for the respective competitor. * for the competitor. Otherwise, the fixes received in <code>result</code> replace those known so far for the
* The {@link #tails} affected by these fixes are updated accordingly when modifications fall inside the interval * respective competitor. The {@link #tails} affected by these fixes are updated accordingly when modifications fall
* shown by the tail, as defined by {@link #firstShownFix} and {@link #lastShownFix}. The tails are, however, not * inside the interval shown by the tail, as defined by {@link #firstShownFix} and {@link #lastShownFix}. The tails
* trimmed according to the specification for the tail length. This has to happen elsewhere (see also * are, however, not trimmed according to the specification for the tail length. This has to happen elsewhere (see
* {@link #updateTail}). * also {@link #updateTail}).
* *
* @param fixesForCompetitors * @param fixesForCompetitors
* For each list the invariant must hold that an {@link GPSFixDTO#extrapolated extrapolated} fix must be * For each list the invariant must hold that an {@link GPSFixDTO#extrapolated extrapolated} fix must be
* the last one in the list * the last one in the list
* * @param overlapsWithKnownFixes
* @return the competitors for which new fixes have been added; this will not include competitors appearing as keys * if for a competitor whose fixes are provided in <code>fixesForCompetitors</code> this holds
* in <code>fixesForCompetitor</code> whose fixes list is <code>null</code> or empty * <code>false</code>, 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 <code>true</code>, {@link #mergeFixes(CompetitorDTO, List, long)} is used to merge the
* new fixes from <code>fixesForCompetitors</code> into the {@link #fixes} collection, and the tail is
* left unchanged. <b>NOTE:</b> When a non-overlapping set of fixes is updated (<code>false</code>), this
* map's record for the competitor is <b>UPDATED</b> to <code>true</code> 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 <em>same</em> map will be considered
* having an overlap now, not leading to a replacement of the previous update originating from the same
* request.
*/ */
protected Iterable<CompetitorDTO> updateFixes(Map<CompetitorDTO, List<GPSFixDTO>> fixesForCompetitors, protected void updateFixes(Map<CompetitorDTO, List<GPSFixDTO>> fixesForCompetitors,
Map<CompetitorDTO, Boolean> overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) { Map<CompetitorDTO, Boolean> overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) {
final Set<CompetitorDTO> competitorsWithNewFixes = new HashSet<>();
for (Map.Entry<CompetitorDTO, List<GPSFixDTO>> e : fixesForCompetitors.entrySet()) { for (Map.Entry<CompetitorDTO, List<GPSFixDTO>> e : fixesForCompetitors.entrySet()) {
if (e.getValue() != null && !e.getValue().isEmpty()) { if (e.getValue() != null && !e.getValue().isEmpty()) {
competitorsWithNewFixes.add(e.getKey());
List<GPSFixDTO> fixesForCompetitor = fixes.get(e.getKey()); List<GPSFixDTO> fixesForCompetitor = fixes.get(e.getKey());
if (fixesForCompetitor == null) { if (fixesForCompetitor == null) {
fixesForCompetitor = new ArrayList<GPSFixDTO>(); fixesForCompetitor = new ArrayList<GPSFixDTO>();
@@ -206,19 +215,14 @@ public class FixesAndTails {
fixesForCompetitor.clear(); fixesForCompetitor.clear();
// to re-establish the invariants for tails, firstShownFix and lastShownFix, we now need to remove // 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 // all points from the competitor's polyline and clear the entries in firstShownFix and lastShownFix
if (tails.containsKey(e.getKey())) { removeTail(e.getKey());
Polyline removedTail = tails.remove(e.getKey());
removedTail.setMap(null);
}
firstShownFix.remove(e.getKey());
lastShownFix.remove(e.getKey());
fixesForCompetitor.addAll(e.getValue()); 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 { } else {
mergeFixes(e.getKey(), e.getValue(), timeForPositionTransitionMillis); mergeFixes(e.getKey(), e.getValue(), timeForPositionTransitionMillis);
} }
} }
} }
return competitorsWithNewFixes;
} }
/** /**
@@ -483,7 +487,7 @@ public class FixesAndTails {
toDate = upTo; toDate = upTo;
} }
// only request something for the competitor if we're missing information at all // 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); from.put(competitor, fromDate);
to.put(competitor, toDate); to.put(competitor, toDate);
overlapWithKnownFixes.put(competitor, overlap); overlapWithKnownFixes.put(competitor, overlap);
@@ -23,7 +23,11 @@ public class Hoverline {
this.options.setMap(polyline.getMap()); this.options.setMap(polyline.getMap());
this.options.setPath(polyline.getPath()); this.options.setPath(polyline.getPath());
this.options.setStrokeColor(polylineOptions.getStrokeColor()); 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 = Polyline.newInstance(this.options);
this.hoverline.setVisible(false); this.hoverline.setVisible(false);
polyline.addMouseOverHandler(new MouseOverMapHandler() { polyline.addMouseOverHandler(new MouseOverMapHandler() {
@@ -773,14 +773,12 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
if (selectedRaces != null && !selectedRaces.isEmpty()) { if (selectedRaces != null && !selectedRaces.isEmpty()) {
RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1); RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1);
final Iterable<CompetitorDTO> competitorsToShow = getCompetitorsToShow(); final Iterable<CompetitorDTO> competitorsToShow = getCompetitorsToShow();
if (race != null) { if (race != null) {
final long transitionTimeInMillis = calculateTimeForPositionTransitionInMillis(newTime, oldTime); final long transitionTimeInMillis = calculateTimeForPositionTransitionInMillis(newTime, oldTime);
final com.sap.sse.common.Util.Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap = final com.sap.sse.common.Util.Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap =
fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds()); fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds());
// Request map data update, possibly in two calls; see method details // Request map data update, possibly in two calls; see method details
callGetRaceMapDataForAllOverlappingAndTipsOfNonOverlappingAndGetBoatPositionsForAllOthers(fromAndToAndOverlap, race, newTime, transitionTimeInMillis, competitorsToShow); callGetRaceMapDataForAllOverlappingAndTipsOfNonOverlappingAndGetBoatPositionsForAllOthers(fromAndToAndOverlap, race, newTime, transitionTimeInMillis, competitorsToShow);
// draw the wind into the map, get the combined wind // draw the wind into the map, get the combined wind
List<String> windSourceTypeNames = new ArrayList<String>(); List<String> windSourceTypeNames = new ArrayList<String>();
windSourceTypeNames.add(WindSourceType.EXPEDITION.name()); windSourceTypeNames.add(WindSourceType.EXPEDITION.name());
@@ -879,25 +877,37 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
competitorsByIdAsString.put(competitor.getIdAsString(), competitor); 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, asyncActionsExecutor.execute(new GetRaceMapDataAction(sailingService, competitorsByIdAsString,
race, useNullAsTimePoint() ? null : newTime, fromTimesForQuickCall, toTimesForQuickCall, /* extrapolate */true, race, useNullAsTimePoint() ? null : newTime, fromTimesForQuickCall, toTimesForQuickCall, /* extrapolate */true,
(settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)), GET_RACE_MAP_DATA_CATEGORY, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null)), GET_RACE_MAP_DATA_CATEGORY,
getRaceMapDataCallback(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, ++boatPositionRequestIDCounter)); 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 // 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, if (!fromTimesForNonOverlappingTailsCall.keySet().isEmpty()) {
/* extrapolate */ true), GET_RACE_MAP_DATA_CATEGORY, asyncActionsExecutor.execute(new GetBoatPositionsAction(sailingService, race, fromTimesForNonOverlappingTailsCall, toTimesForNonOverlappingTailsCall,
new MarkedAsyncCallback<>(new AsyncCallback<CompactBoatPositionsDTO>() { /* extrapolate */ true), GET_RACE_MAP_DATA_CATEGORY,
@Override new MarkedAsyncCallback<>(new AsyncCallback<CompactBoatPositionsDTO>() {
public void onFailure(Throwable t) { @Override
errorReporter.reportError("Error obtaining racemap data: " + t.getMessage(), true /*silentMode */); public void onFailure(Throwable t) {
} errorReporter.reportError("Error obtaining racemap data: " + t.getMessage(), true /*silentMode */);
}
@Override
public void onSuccess(CompactBoatPositionsDTO result) { @Override
updateBoatPositions(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, result.getBoatPositionsForCompetitors(competitorsByIdAsString), public void onSuccess(CompactBoatPositionsDTO result) {
/* updateTailsOnly */ true); // 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<RaceMapDataDTO> getRaceMapDataCallback( private AsyncCallback<RaceMapDataDTO> getRaceMapDataCallback(
@@ -973,6 +983,20 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
} }
/** /**
* @param hasTailOverlapForCompetitor
* if for a competitor whose fixes are provided in <code>fixesForCompetitors</code> this holds
* <code>false</code>, 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 <code>true</code>, {@link #mergeFixes(CompetitorDTO, List, long)} is used to merge the
* new fixes from <code>fixesForCompetitors</code> into the {@link #fixes} collection, and the tail is
* left unchanged. <b>NOTE:</b> When a non-overlapping set of fixes is updated (<code>false</code>), this
* map's record for the competitor is <b>UPDATED</b> to <code>true</code> 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 <em>same</em> 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 * @param updateTailsOnly
* if <code>true</code>, only the tails are updated according to <code>boatData</code> and * if <code>true</code>, only the tails are updated according to <code>boatData</code> and
* <code>hasTailOverlapForCompetitor</code>, but the advantage line is not updated, and neither are the * <code>hasTailOverlapForCompetitor</code>, 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<CompetitorDTO, Boolean> hasTailOverlapForCompetitor, final Map<CompetitorDTO, Boolean> hasTailOverlapForCompetitor,
final Iterable<CompetitorDTO> competitorsToShow, Map<CompetitorDTO, List<GPSFixDTO>> boatData, boolean updateTailsOnly) { final Iterable<CompetitorDTO> competitorsToShow, Map<CompetitorDTO, List<GPSFixDTO>> boatData, boolean updateTailsOnly) {
fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis);
Iterable<CompetitorDTO> competitorsForWhichNewFixesWereReveiced = showBoatsOnMap(newTime, transitionTimeInMillis, competitorsToShow, updateTailsOnly);
fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis); if (!updateTailsOnly) {
if (!Util.isEmpty(competitorsForWhichNewFixesWereReveiced)) { showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors());
showBoatsOnMap(newTime, transitionTimeInMillis, competitorsForWhichNewFixesWereReveiced, updateTailsOnly); // even though the wind data is retrieved by a separate call, re-draw the advantage line because it needs to
if (!updateTailsOnly) { // adjust to new boat positions
showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors()); showAdvantageLine(competitorsToShow, newTime, transitionTimeInMillis);
// 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) { if (map != null) {
Date tailsFromTime = new Date(newTime.getTime() - settings.getEffectiveTailLengthInMilliseconds()); Date tailsFromTime = new Date(newTime.getTime() - settings.getEffectiveTailLengthInMilliseconds());
Date tailsToTime = newTime; Date tailsToTime = newTime;
Set<CompetitorDTO> competitorDTOsOfUnusedTails = new HashSet<CompetitorDTO>(fixesAndTails.getCompetitorsWithTails()); Set<CompetitorDTO> competitorDTOsOfUnusedTails = new HashSet<CompetitorDTO>();
Set<CompetitorDTO> competitorDTOsOfUnusedBoatCanvases = new HashSet<CompetitorDTO>(boatOverlays.keySet()); Set<CompetitorDTO> competitorDTOsOfUnusedBoatCanvases = new HashSet<CompetitorDTO>();
if (!updateTailsOnly) {
competitorDTOsOfUnusedTails.addAll(fixesAndTails.getCompetitorsWithTails());
competitorDTOsOfUnusedBoatCanvases.addAll(boatOverlays.keySet());
}
for (CompetitorDTO competitorDTO : competitorsToShow) { for (CompetitorDTO competitorDTO : competitorsToShow) {
if (fixesAndTails.hasFixesFor(competitorDTO)) { if (fixesAndTails.hasFixesFor(competitorDTO)) {
Polyline tail = fixesAndTails.getTail(competitorDTO); Polyline tail = fixesAndTails.getTail(competitorDTO);
@@ -1216,12 +1240,14 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
} else { } else {
fixesAndTails.updateTail(tail, competitorDTO, tailsFromTime, tailsToTime, fixesAndTails.updateTail(tail, competitorDTO, tailsFromTime, tailsToTime,
(int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2)); (int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2));
competitorDTOsOfUnusedTails.remove(competitorDTO); if (!updateTailsOnly) {
competitorDTOsOfUnusedTails.remove(competitorDTO);
}
PolylineOptions newOptions = createTailStyle(competitorDTO, displayHighlighted(competitorDTO)); PolylineOptions newOptions = createTailStyle(competitorDTO, displayHighlighted(competitorDTO));
tail.setOptions(newOptions); tail.setOptions(newOptions);
} }
boolean usedExistingBoatCanvas = updateBoatCanvasForCompetitor(competitorDTO, newTime, timeForPositionTransitionMillis); boolean usedExistingBoatCanvas = updateBoatCanvasForCompetitor(competitorDTO, newTime, timeForPositionTransitionMillis);
if (usedExistingBoatCanvas) { if (usedExistingBoatCanvas && !updateTailsOnly) {
competitorDTOsOfUnusedBoatCanvases.remove(competitorDTO); competitorDTOsOfUnusedBoatCanvases.remove(competitorDTO);
} }
} }
@@ -1379,8 +1405,10 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
nextPath.push(advantageLinePos1); nextPath.push(advantageLinePos1);
nextPath.push(advantageLinePos2); nextPath.push(advantageLinePos2);
advantageTimer.setNextPositionAndTransitionMillis(nextPath, timeForPositionTransitionMillis); advantageTimer.setNextPositionAndTransitionMillis(nextPath, timeForPositionTransitionMillis);
advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg); if (advantageLineMouseOverHandler != null) {
advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint)); advantageLineMouseOverHandler.setTrueWindBearing(bearingOfCombinedWindInDeg);
advantageLineMouseOverHandler.setDate(new Date(windFix.measureTimepoint));
}
} }
drawAdvantageLine = true; drawAdvantageLine = true;
} }