Merge remote-tracking branch 'origin/master' into bug2610

This commit is contained in:
fmittag committed 2015-12-18 16:13:58 +01:00
commit caae9a587a
14 files changed
+422 -158

No files matched your search

@@ -0,0 +1,47 @@
package com.sap.sailing.gwt.ui.actions;
import java.util.Date;
import java.util.Map;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sse.gwt.client.async.AsyncAction;
public abstract class AbstractGetMapRelatedDataAction<T> implements AsyncAction<T> {
private final SailingServiceAsync sailingService;
private final RegattaAndRaceIdentifier raceIdentifier;
private final Map<CompetitorDTO, Date> from;
private final Map<CompetitorDTO, Date> to;
private final boolean extrapolate;
public AbstractGetMapRelatedDataAction(SailingServiceAsync sailingService,
RegattaAndRaceIdentifier raceIdentifier, Map<CompetitorDTO, Date> from,
Map<CompetitorDTO, Date> to, boolean extrapolate) {
this.sailingService = sailingService;
this.raceIdentifier = raceIdentifier;
this.from = from;
this.to = to;
this.extrapolate = extrapolate;
}
protected SailingServiceAsync getSailingService() {
return sailingService;
}
protected RegattaAndRaceIdentifier getRaceIdentifier() {
return raceIdentifier;
}
protected Map<CompetitorDTO, Date> getFrom() {
return from;
}
protected Map<CompetitorDTO, Date> getTo() {
return to;
}
protected boolean isExtrapolate() {
return extrapolate;
}
}
@@ -0,0 +1,43 @@
package com.sap.sailing.gwt.ui.actions;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO;
public class GetBoatPositionsAction extends AbstractGetMapRelatedDataAction<CompactBoatPositionsDTO> {
public GetBoatPositionsAction(SailingServiceAsync sailingService,
RegattaAndRaceIdentifier raceIdentifier, Map<CompetitorDTO, Date> from,
Map<CompetitorDTO, Date> to, boolean extrapolate) {
super(sailingService, raceIdentifier, from, to, extrapolate);
}
@Override
public void execute(final AsyncCallback<CompactBoatPositionsDTO> callback) {
Map<String, Date> fromByCompetitorIdAsString = new HashMap<String, Date>();
for (Map.Entry<CompetitorDTO, Date> fromEntry : getFrom().entrySet()) {
fromByCompetitorIdAsString.put(fromEntry.getKey().getIdAsString(), fromEntry.getValue());
}
Map<String, Date> toByCompetitorIdAsString = new HashMap<String, Date>();
for (Map.Entry<CompetitorDTO, Date> toEntry : getTo().entrySet()) {
toByCompetitorIdAsString.put(toEntry.getKey().getIdAsString(), toEntry.getValue());
}
getSailingService().getBoatPositions(getRaceIdentifier(), fromByCompetitorIdAsString, toByCompetitorIdAsString,
isExtrapolate(), new AsyncCallback<CompactBoatPositionsDTO>() {
@Override
public void onFailure(Throwable caught) {
callback.onFailure(caught);
}
@Override
public void onSuccess(CompactBoatPositionsDTO result) {
callback.onSuccess(result);
}
});
}
}
@@ -11,45 +11,33 @@ import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO;
import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO;
import com.sap.sse.gwt.client.async.AsyncAction;
public class GetRaceMapDataAction implements AsyncAction<RaceMapDataDTO> {
private final SailingServiceAsync sailingService;
private final Iterable<CompetitorDTO> allCompetitors;
private final RegattaAndRaceIdentifier raceIdentifier;
private final Map<CompetitorDTO, Date> from;
private final Map<CompetitorDTO, Date> to;
private final boolean extrapolate;
public class GetRaceMapDataAction extends AbstractGetMapRelatedDataAction<RaceMapDataDTO> {
private final Map<String, CompetitorDTO> competitorsByIdAsString;
private final Date date;
private final LegIdentifier simulationLegIdentifier;
public GetRaceMapDataAction(SailingServiceAsync sailingService, Iterable<CompetitorDTO> allCompetitors,
public GetRaceMapDataAction(SailingServiceAsync sailingService, Map<String, CompetitorDTO> competitorsByIdAsString,
RegattaAndRaceIdentifier raceIdentifier, Date date, Map<CompetitorDTO, Date> from,
Map<CompetitorDTO, Date> to, boolean extrapolate, LegIdentifier simulationLegIdentifier) {
this.allCompetitors = allCompetitors;
this.sailingService = sailingService;
this.raceIdentifier = raceIdentifier;
super(sailingService, raceIdentifier, from, to, extrapolate);
this.competitorsByIdAsString = competitorsByIdAsString;
this.date = date;
this.from = from;
this.to = to;
this.extrapolate = extrapolate;
this.simulationLegIdentifier = simulationLegIdentifier;
}
@Override
public void execute(final AsyncCallback<RaceMapDataDTO> callback) {
Map<String, Date> fromByCompetitorIdAsString = new HashMap<String, Date>();
for (Map.Entry<CompetitorDTO, Date> fromEntry : from.entrySet()) {
for (Map.Entry<CompetitorDTO, Date> fromEntry : getFrom().entrySet()) {
fromByCompetitorIdAsString.put(fromEntry.getKey().getIdAsString(), fromEntry.getValue());
}
Map<String, Date> toByCompetitorIdAsString = new HashMap<String, Date>();
for (Map.Entry<CompetitorDTO, Date> toEntry : to.entrySet()) {
for (Map.Entry<CompetitorDTO, Date> toEntry : getTo().entrySet()) {
toByCompetitorIdAsString.put(toEntry.getKey().getIdAsString(), toEntry.getValue());
}
sailingService.getRaceMapData(raceIdentifier, date, fromByCompetitorIdAsString, toByCompetitorIdAsString,
extrapolate, simulationLegIdentifier, new AsyncCallback<CompactRaceMapDataDTO>() {
getSailingService().getRaceMapData(getRaceIdentifier(), date, fromByCompetitorIdAsString, toByCompetitorIdAsString,
isExtrapolate(), simulationLegIdentifier, new AsyncCallback<CompactRaceMapDataDTO>() {
@Override
public void onFailure(Throwable caught) {
callback.onFailure(caught);
@@ -57,7 +45,7 @@ public class GetRaceMapDataAction implements AsyncAction<RaceMapDataDTO> {
@Override
public void onSuccess(CompactRaceMapDataDTO result) {
callback.onSuccess(result.getRaceMapDataDTO(allCompetitors));
callback.onSuccess(result.getRaceMapDataDTO(competitorsByIdAsString));
}
});
}
@@ -45,6 +45,7 @@ import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.gwt.ui.adminconsole.RaceLogSetTrackingTimesDTO;
import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO;
import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO;
import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO;
import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO;
import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
@@ -158,6 +159,10 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
CompactRaceMapDataDTO getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date, Map<String, Date> fromPerCompetitorIdAsString,
Map<String, Date> toPerCompetitorIdAsString, boolean extrapolate, LegIdentifier simulationLegIdentifier) throws NoWindException;
CompactBoatPositionsDTO getBoatPositions(RegattaAndRaceIdentifier raceIdentifier,
Map<String, Date> fromPerCompetitorIdAsString, Map<String, Date> toPerCompetitorIdAsString,
boolean extrapolate) throws NoWindException;
RaceTimesInfoDTO getRaceTimesInfo(RegattaAndRaceIdentifier raceIdentifier);
List<RaceTimesInfoDTO> getRaceTimesInfos(Collection<RegattaAndRaceIdentifier> raceIdentifiers);
@@ -612,4 +617,5 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS
*/
Collection<CompetitorDTO> getCompetitorRegistrationsOnRaceLog(String leaderboardName, String raceColumnName,
String fleetName);
}
@@ -37,6 +37,7 @@ import com.sap.sailing.domain.common.racelog.RacingProcedureType;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.gwt.ui.adminconsole.RaceLogSetTrackingTimesDTO;
import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO;
import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO;
import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO;
import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO;
import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
@@ -368,6 +369,10 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
Map<String, Date> fromPerCompetitorIdAsString, Map<String, Date> toPerCompetitorIdAsString,
boolean extrapolate, LegIdentifier simulationLegIdentifier, AsyncCallback<CompactRaceMapDataDTO> callback);
void getBoatPositions(RegattaAndRaceIdentifier raceIdentifier, Map<String, Date> fromPerCompetitorIdAsString,
Map<String, Date> toPerCompetitorIdAsString, boolean extrapolate,
AsyncCallback<CompactBoatPositionsDTO> callback);
void getReplicaInfo(AsyncCallback<ReplicationStateDTO> callback);
void startReplicatingFromMaster(String messagingHost, String masterName, String exchangeName, int servletPort,
@@ -753,4 +758,5 @@ public interface SailingServiceAsync extends ServerInfoRetriever, FileStorageMan
void copyCourseToOtherRaceLogs(Triple<String, String, String> fromTriple,
Set<Triple<String, String, String>> toTriples, AsyncCallback<Void> callback);
}
@@ -12,6 +12,7 @@ import java.util.Map;
import java.util.Set;
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;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -47,22 +48,23 @@ public class FixesAndTails {
private final Map<CompetitorDTO, List<GPSFixDTO>> fixes;
/**
* Tails of competitors currently displayed as overlays on the map.
* Tails of competitors currently displayed as overlays on the map. A tail may have an {@link MVCArray#getLength()
* empty} {@link Polyline#getPath()}. In this case, {@link #firstShownFix} and {@link #lastShownFix} will hold
* <code>-1</code> for that competitor key.
*/
private final Map<CompetitorDTO, Polyline> tails;
/**
* Key set is equal to that of {@link #tails} and tells what the index in in {@link #fixes} of the first fix shown
* Key set is equal to that of {@link #tails} and tells what the index in {@link #fixes} of the first fix shown
* in {@link #tails} is. If a key is contained in this map, it is also contained in {@link #lastShownFix} and vice
* versa.
* versa. If a tail is present but has an empty path, this map contains <code>-1</code> for that competitor.
*/
private final Map<CompetitorDTO, Integer> firstShownFix;
/**
* Key set is equal to that of {@link #tails} and tells what the index in in {@link #fixes} of the last fix shown in
* Key set is equal to that of {@link #tails} and tells what the index in {@link #fixes} of the last fix shown in
* {@link #tails} is. If a key is contained in this map, it is also contained in {@link #firstShownFix} and vice
* versa.
* versa. If a tail is present but has an empty path, this map contains <code>-1</code> for that competitor.
*/
private final Map<CompetitorDTO, Integer> lastShownFix;
@@ -129,7 +131,9 @@ public class FixesAndTails {
* Creates a polyline for the competitor represented by <code>competitorDTO</code>, taking the fixes from
* {@link #fixes fixes.get(competitorDTO)} and using the fixes starting at time point <code>from</code> (inclusive)
* up to the last fix with time point before <code>to</code>. The polyline is returned. Updates are applied to
* {@link #lastShownFix}, {@link #firstShownFix} and {@link #tails}.
* {@link #lastShownFix}, {@link #firstShownFix} and {@link #tails}.<p>
*
* Precondition: <code>tails.containsKey(competitorDTO) == false</code>
*/
protected Polyline createTailAndUpdateIndices(final CompetitorDTO competitorDTO, Date from, Date to, TailFactory tailFactory) {
List<LatLng> points = new ArrayList<LatLng>();
@@ -173,22 +177,45 @@ public class FixesAndTails {
}
/**
* Adds the fixes received in <code>result</code> to {@link #fixes} and ensures they are still contiguous for each
* competitor. If <code>overlapsWithKnownFixes</code> indicates that the fixes received in <code>result</code>
* overlap with those already known, the fixes are merged into the list of already known fixes for the competitor.
* Otherwise, the fixes received in <code>result</code> 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 <code>fixesForCompetitors</code> to {@link #fixes} and ensures they are still
* contiguous for each competitor. If <code>overlapsWithKnownFixes</code> indicates that the fixes received in
* <code>result</code> overlap with those already known, the fixes are merged into the list of already known fixes
* for the competitor. Otherwise, the fixes received in <code>result</code> 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
* @param overlapsWithKnownFixes
* 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.
* @return
*
* @return a {@link Runnable} for each competitor that the caller must invoke before starting to create or update
* the key competitor's tail. The object returned may be a no-op, but it may also clear an existing tail if
* necessary. The reason why this is not executed immediately is that there may be a transition-based delay
* with which the tails are updated. In this case we would be producing severe flicker. By delaying the
* command to the point when the update is applied should avoid such flicker.
*/
protected void updateFixes(Map<CompetitorDTO, List<GPSFixDTO>> fixesForCompetitors,
protected Map<CompetitorDTO, Runnable> updateFixes(Map<CompetitorDTO, List<GPSFixDTO>> fixesForCompetitors,
Map<CompetitorDTO, Boolean> overlapsWithKnownFixes, TailFactory tailFactory, long timeForPositionTransitionMillis) {
for (Map.Entry<CompetitorDTO, List<GPSFixDTO>> e : fixesForCompetitors.entrySet()) {
final Map<CompetitorDTO, Runnable> result = new HashMap<>();
final Runnable noOp = new Runnable() { @Override public void run() {} };
for (final Map.Entry<CompetitorDTO, List<GPSFixDTO>> e : fixesForCompetitors.entrySet()) {
if (e.getValue() != null && !e.getValue().isEmpty()) {
List<GPSFixDTO> fixesForCompetitor = fixes.get(e.getKey());
if (fixesForCompetitor == null) {
@@ -200,18 +227,18 @@ 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());
result.put(e.getKey(), new Runnable() { @Override public void run() { clearTail(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);
result.put(e.getKey(), noOp);
}
} else {
result.put(e.getKey(), noOp);
}
}
return result;
}
/**
@@ -289,6 +316,7 @@ public class FixesAndTails {
} else {
intoThis.add(intoThisIndex, mergeThisFix);
if (tail != null && intoThisIndex >= indexOfFirstShownFix && intoThisIndex <= indexOfLastShownFix) {
// fix inserted at a position currently visualized by tail
tail.getPath().insertAt(intoThisIndex - indexOfFirstShownFix, coordinateSystem.toLatLng(mergeThisFix.position));
}
if (intoThisIndex < indexOfFirstShownFix) {
@@ -354,12 +382,19 @@ public class FixesAndTails {
* @param delayForTailChangeInMillis
* the time in milliseconds after which to actually draw the tail update, or <code>-1</code> to perform
* the update immediately
* @param tailPreparer
* will be {@link Runnable#run} just before the updates are applied to the <code>tail</code>. In
* particular, if a transition is active, the effect of running this preparer will be held back until the
* transition applies. May be <code>null</code> in which case simply no preparer is run.
*/
protected void updateTail(final Polyline tail, final CompetitorDTO competitorDTO,
final Date from, final Date to, final int delayForTailChangeInMillis) {
final Date from, final Date to, final int delayForTailChangeInMillis, final Runnable tailPreparer) {
Timer delayedOrImmediateExecutor = new Timer() {
@Override
public void run() {
if (tailPreparer != null) {
tailPreparer.run();
}
int vertexCount = tail.getPath().getLength();
final List<GPSFixDTO> fixesForCompetitor = getFixes(competitorDTO);
int indexOfFirstShownFix = firstShownFix.get(competitorDTO) == null ? -1 : firstShownFix
@@ -381,7 +416,7 @@ public class FixesAndTails {
tail.getPath().insertAt(0, coordinateSystem.toLatLng(fix.position));
vertexCount++;
}
// now adjust the polylines tail: remove excess vertices that are after "to"
// now adjust the polyline's tail: remove excess vertices that are after "to"
int indexOfLastShownFix = lastShownFix.get(competitorDTO) == null ? -1 : lastShownFix
.get(competitorDTO);
while (indexOfLastShownFix != -1 && vertexCount > 0
@@ -400,6 +435,9 @@ public class FixesAndTails {
indexOfLastShownFix++;
GPSFixDTO fix = fixesForCompetitor.get(indexOfLastShownFix);
tail.getPath().insertAt(vertexCount++, coordinateSystem.toLatLng(fix.position));
if (indexOfFirstShownFix < 0) { // empty tail before?
indexOfFirstShownFix = indexOfLastShownFix; // set to the first vertex inserted into tail
}
}
firstShownFix.put(competitorDTO, indexOfFirstShownFix);
lastShownFix.put(competitorDTO, indexOfLastShownFix);
@@ -428,6 +466,19 @@ public class FixesAndTails {
firstShownFix.remove(competitor);
lastShownFix.remove(competitor);
}
/**
* Leaves the tail on the map and empties its path {@link Polyline#getPath()}. Correspondingly, the
* {@link #firstShownFix} and {@link #lastShownFix} entries for <code>competitor</code> are set to <code>-1</code>.
*/
private void clearTail(CompetitorDTO competitor) {
Polyline tail = tails.get(competitor);
if (tail != null) {
tail.getPath().clear();
firstShownFix.put(competitor, -1);
lastShownFix.put(competitor, -1);
}
}
/**
* From {@link #fixes} as well as the selection of {@link #getCompetitorsToShow competitors to show}, computes the
@@ -441,7 +492,7 @@ public class FixesAndTails {
* @return a triple whose {@link Triple#getA() first} component contains the "from", and whose {@link Triple#getB()
* second} component contains the "to" times for the competitors whose trails / positions to show; the
* {@link Triple#getC() third} component tells whether the existing fixes can remain and be augmented by
* those requested or need to be replaced
* those requested (<code>true</code>) or need to be replaced (<code>false</code>)
*/
protected Util.Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> computeFromAndTo(
Date upTo, Iterable<CompetitorDTO> competitorsToShow, long effectiveTailLengthInMilliseconds) {
@@ -476,7 +527,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);
@@ -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()); // if the zindex is not set, this line throws an exception in dev mode
} 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() {
@@ -83,6 +83,7 @@ import com.sap.sailing.domain.common.impl.DegreeBearingImpl;
import com.sap.sailing.domain.common.impl.DegreePosition;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableBearing;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition;
import com.sap.sailing.gwt.ui.actions.GetBoatPositionsAction;
import com.sap.sailing.gwt.ui.actions.GetPolarAction;
import com.sap.sailing.gwt.ui.actions.GetRaceMapDataAction;
import com.sap.sailing.gwt.ui.actions.GetWindInfoAction;
@@ -100,6 +101,7 @@ import com.sap.sailing.gwt.ui.client.shared.filter.QuickRankProvider;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes;
import com.sap.sailing.gwt.ui.common.client.DateAndTimeFormatterUtil;
import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO;
import com.sap.sailing.gwt.ui.shared.ControlPointDTO;
import com.sap.sailing.gwt.ui.shared.CoursePositionsDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
@@ -771,28 +773,12 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
if (selectedRaces != null && !selectedRaces.isEmpty()) {
RegattaAndRaceIdentifier race = selectedRaces.get(selectedRaces.size() - 1);
final Iterable<CompetitorDTO> competitorsToShow = getCompetitorsToShow();
if (race != null) {
final long transitionTimeInMillis = calculateTimeForPositionTransitionInMillis(newTime, oldTime);
final com.sap.sse.common.Util.Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap =
fixesAndTails.computeFromAndTo(newTime, competitorsToShow, settings.getEffectiveTailLengthInMilliseconds());
int requestID = ++boatPositionRequestIDCounter;
// For those competitors for which the tails don't overlap (and therefore will be replaced by the new tail coming from the server)
// we expect some potential delay in computing the full tail. Therefore, in those cases we fire two requests: one fetching only the
// boat positions at newTime with zero tail length; and another one fetching everything else.
GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping = getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(fromAndToAndOverlap, race, newTime);
if (getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping != null) {
asyncActionsExecutor.execute(getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping, GET_RACE_MAP_DATA_CATEGORY,
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
// supersedes the earlier call which may get dropped then
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(newTime, transitionTimeInMillis, fromAndToAndOverlap.getC(), competitorsToShow, requestID));
// 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<String> windSourceTypeNames = new ArrayList<String>();
windSourceTypeNames.add(WindSourceType.EXPEDITION.name());
@@ -844,36 +830,84 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
}
/**
* We assume that overlapping segments usually don't require a lot of loading time as the most typical case will be to update a longer
* tail with a few new fixes that were received since the last time tick. Non-overlapping position requests typically occur for the
* first request when no fix at all is known for the competitor yet, and when the user has radically moved the time slider to some
* other time such that given the current tail length setting the new tail segment does not overlap with the old one, requiring a full
* load of the entire tail data for that competitor.<p>
* Requests updates for map data and, when received, updates the map structures accordingly.
* <p>
*
* For the non-overlapping requests, this method creates a separate request which only loads boat positions, quick ranks, sidelines and
* mark positions for the zero-length interval at <code>newTime</code>, assuming that this will work fairly fast and in particular in
* O(1) time regardless of tail length, compared to fetching the entire tail for all competitors.
* The update can happen in one or two round trips. We assume that overlapping segments usually don't require a lot
* of loading time as the most typical case will be to update a longer tail with a few new fixes that were received
* since the last time tick. These supposedly fast requests are handled by a {@link GetRaceMapDataAction} which also
* requests updates for mark positions, sidelines and quick ranks. The same request also loads boat positions for
* the zero-length interval at <code>newTime</code> for the non-overlapping tails assuming that this will work
* fairly fast and in particular in O(1) time regardless of tail length, compared to fetching the entire tail for
* all competitors. This will at least provide quick feedback about those competitors' positions even if loading
* their entire tail may take a little longer. The {@link GetRaceMapDataAction} therefore is intended to be a rather
* quick call.
* <p>
*
* Non-overlapping position requests typically occur for the first request when no fix at all is known for the
* competitor yet, or when the user has radically moved the time slider to some other time such that given the
* current tail length setting the new tail segment does not overlap with the old one, requiring a full load of the
* entire tail data for that competitor. For these non-overlapping requests, this method creates a
* {@link GetBoatPositionsAction} request loading the entire tail required, but not quick ranks, sidelines and mark
* positions. Updating the results of this call is done in {@link #updateBoatPositions(Date, long, Map, Iterable, Map, boolean)}.
* <p>
*/
private GetRaceMapDataAction getRaceMapDataForAllOverlappingAndTipsOfNonOverlapping(
Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap,
RegattaAndRaceIdentifier race, Date newTime) {
Map<CompetitorDTO, Date> fromTimes = new HashMap<>();
Map<CompetitorDTO, Date> toTimes = new HashMap<>();
private void callGetRaceMapDataForAllOverlappingAndTipsOfNonOverlappingAndGetBoatPositionsForAllOthers(
final Triple<Map<CompetitorDTO, Date>, Map<CompetitorDTO, Date>, Map<CompetitorDTO, Boolean>> fromAndToAndOverlap,
RegattaAndRaceIdentifier race, final Date newTime, final long transitionTimeInMillis, final Iterable<CompetitorDTO> competitorsToShow) {
final Map<CompetitorDTO, Date> fromTimesForQuickCall = new HashMap<>();
final Map<CompetitorDTO, Date> toTimesForQuickCall = new HashMap<>();
final Map<CompetitorDTO, Date> fromTimesForNonOverlappingTailsCall = new HashMap<>();
final Map<CompetitorDTO, Date> toTimesForNonOverlappingTailsCall = new HashMap<>();
for (Map.Entry<CompetitorDTO, Boolean> e : fromAndToAndOverlap.getC().entrySet()) {
if (!e.getValue()) {
// no overlap; add competitor to request
fromTimes.put(e.getKey(), newTime);
toTimes.put(e.getKey(), newTime);
if (e.getValue()) {
// overlap: expect a quick response; add original request interval for the competitor
fromTimesForQuickCall.put(e.getKey(), fromAndToAndOverlap.getA().get(e.getKey()));
toTimesForQuickCall.put(e.getKey(), fromAndToAndOverlap.getB().get(e.getKey()));
} else {
// no overlap; add competitor to request with a zero-length interval asking only position at newTime, not the entire tail
fromTimesForQuickCall.put(e.getKey(), newTime);
toTimesForQuickCall.put(e.getKey(), newTime);
fromTimesForNonOverlappingTailsCall.put(e.getKey(), fromAndToAndOverlap.getA().get(e.getKey()));
toTimesForNonOverlappingTailsCall.put(e.getKey(), fromAndToAndOverlap.getB().get(e.getKey()));
}
}
final GetRaceMapDataAction result;
if (!fromTimes.isEmpty()) {
result = new GetRaceMapDataAction(sailingService, competitorSelection.getAllCompetitors(),
race, useNullAsTimePoint() ? null : newTime, fromTimes, toTimes, /* extrapolate */true, (settings.isShowSimulationOverlay() ? simulationOverlay.getLegIdentifier() : null));
} else {
result = null;
final Map<String, CompetitorDTO> competitorsByIdAsString = new HashMap<String, CompetitorDTO>();
for (CompetitorDTO competitor : competitorSelection.getAllCompetitors()) {
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
if (!fromTimesForNonOverlappingTailsCall.keySet().isEmpty()) {
asyncActionsExecutor.execute(new GetBoatPositionsAction(sailingService, race, fromTimesForNonOverlappingTailsCall, toTimesForNonOverlappingTailsCall,
/* extrapolate */ true), GET_RACE_MAP_DATA_CATEGORY,
new MarkedAsyncCallback<>(new AsyncCallback<CompactBoatPositionsDTO>() {
@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);
}
}));
}
return result;
}
private AsyncCallback<RaceMapDataDTO> getRaceMapDataCallback(
@@ -881,7 +915,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
final long transitionTimeInMillis,
final Map<CompetitorDTO, Boolean> hasTailOverlapForCompetitor,
final Iterable<CompetitorDTO> competitorsToShow, final int requestID) {
return new AsyncCallback<RaceMapDataDTO>() {
return new MarkedAsyncCallback<>(new AsyncCallback<RaceMapDataDTO>() {
@Override
public void onFailure(Throwable caught) {
errorReporter.reportError("Error obtaining racemap data: " + caught.getMessage(), true /*silentMode */);
@@ -890,21 +924,19 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
@Override
public void onSuccess(RaceMapDataDTO raceMapDataDTO) {
if (map != null && raceMapDataDTO != null) {
quickRanks = raceMapDataDTO.quickRanks;
competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber =
raceMapDataDTO.competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber;
if (showViewSimulation && settings.isShowSimulationOverlay()) {
lastLegNumber = raceMapDataDTO.coursePositions.currentLegNumber;
simulationOverlay.updateLeg(Math.max(lastLegNumber,1), /* clearCanvas */ false, raceMapDataDTO.simulationResultVersion);
}
// process response only if not received out of order
if (startedProcessingRequestID < requestID) {
startedProcessingRequestID = requestID;
quickRanks = raceMapDataDTO.quickRanks;
competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber =
raceMapDataDTO.competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber;
if (showViewSimulation && settings.isShowSimulationOverlay()) {
lastLegNumber = raceMapDataDTO.coursePositions.currentLegNumber;
simulationOverlay.updateLeg(Math.max(lastLegNumber, 1), /* clearCanvas */ false, raceMapDataDTO.simulationResultVersion);
}
// Do boat specific actions
Map<CompetitorDTO, List<GPSFixDTO>> boatData = raceMapDataDTO.boatPositions;
fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis);
showBoatsOnMap(newTime, transitionTimeInMillis, getCompetitorsToShow());
showCompetitorInfoOnMap(newTime, transitionTimeInMillis, competitorSelection.getSelectedFilteredCompetitors());
updateBoatPositions(newTime, transitionTimeInMillis, hasTailOverlapForCompetitor, competitorsToShow, boatData, /* updateTailsOnly */ false);
if (douglasMarkers != null) {
removeAllMarkDouglasPeuckerpoints();
}
@@ -912,17 +944,14 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
removeAllManeuverMarkers();
}
// Do mark specific actions
showCourseMarksOnMap(raceMapDataDTO.coursePositions);
if (requiresCoordinateSystemUpdateWhenCoursePositionAndWindDirectionIsKnown) {
updateCoordinateSystemFromSettings();
}
showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines);
// Do mark specific actions
showCourseMarksOnMap(raceMapDataDTO.coursePositions);
showCourseSidelinesOnMap(raceMapDataDTO.courseSidelines);
showStartAndFinishAndCourseMiddleLines(raceMapDataDTO.coursePositions);
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, transitionTimeInMillis);
// Rezoom the map
LatLngBounds zoomToBounds = null;
@@ -950,7 +979,44 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
lastTimeChangeBeforeInitialization = newTime;
}
}
};
});
}
/**
* @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
* 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
* competitor info bubbles moved. This assumes that the tips of these tails are loaded in a separate call
* which <em>does</em> update those structures. In particular, tails that do not appear in
* <code>boatData</code> are not removed from the map in case <code>updateTailsOnly</code> is
* <code>true</code>.
*/
private void updateBoatPositions(final Date newTime, final long transitionTimeInMillis,
final Map<CompetitorDTO, Boolean> hasTailOverlapForCompetitor,
final Iterable<CompetitorDTO> competitorsToShow, Map<CompetitorDTO, List<GPSFixDTO>> boatData, boolean updateTailsOnly) {
final Map<CompetitorDTO, Runnable> tailPreparersPerCompetitor =
fixesAndTails.updateFixes(boatData, hasTailOverlapForCompetitor, RaceMap.this, transitionTimeInMillis);
showBoatsOnMap(newTime, transitionTimeInMillis, competitorsToShow, updateTailsOnly, tailPreparersPerCompetitor);
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);
}
}
private void showCourseSidelinesOnMap(List<SidelineDTO> sidelinesDTOs) {
@@ -1154,37 +1220,56 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
return timeForPositionTransitionMillis;
}
private void showBoatsOnMap(final Date newTime, final long timeForPositionTransitionMillis, final Iterable<CompetitorDTO> competitorsToShow) {
/**
* @param updateTailsOnly
* if <code>false</code>, tails of competitors not in <code>competitorsToShow</code> are removed from the
* map
* @param tailPreparersPerCompetitor
* executed before creating or updating the respective competitor's tail
*/
private void showBoatsOnMap(final Date newTime, final long timeForPositionTransitionMillis,
final Iterable<CompetitorDTO> competitorsToShow, boolean updateTailsOnly,
Map<CompetitorDTO, Runnable> tailPreparersPerCompetitor) {
if (map != null) {
Date tailsFromTime = new Date(newTime.getTime() - settings.getEffectiveTailLengthInMilliseconds());
Date tailsToTime = newTime;
Set<CompetitorDTO> competitorDTOsOfUnusedTails = new HashSet<CompetitorDTO>(fixesAndTails.getCompetitorsWithTails());
Set<CompetitorDTO> competitorDTOsOfUnusedBoatCanvases = new HashSet<CompetitorDTO>(boatOverlays.keySet());
Set<CompetitorDTO> competitorDTOsOfUnusedTails = new HashSet<CompetitorDTO>();
Set<CompetitorDTO> competitorDTOsOfUnusedBoatCanvases = new HashSet<CompetitorDTO>();
if (!updateTailsOnly) {
competitorDTOsOfUnusedTails.addAll(fixesAndTails.getCompetitorsWithTails());
competitorDTOsOfUnusedBoatCanvases.addAll(boatOverlays.keySet());
}
for (CompetitorDTO competitorDTO : competitorsToShow) {
Runnable tailPreparer = tailPreparersPerCompetitor.get(competitorDTO);
if (fixesAndTails.hasFixesFor(competitorDTO)) {
Polyline tail = fixesAndTails.getTail(competitorDTO);
if (tail == null) {
tailPreparer.run(); // presumably a no-op, but you never know...
tail = fixesAndTails.createTailAndUpdateIndices(competitorDTO, tailsFromTime, tailsToTime, this);
} else {
fixesAndTails.updateTail(tail, competitorDTO, tailsFromTime, tailsToTime,
(int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2));
competitorDTOsOfUnusedTails.remove(competitorDTO);
(int) (timeForPositionTransitionMillis==-1?-1:timeForPositionTransitionMillis/2), tailPreparer);
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);
}
}
}
for (CompetitorDTO unusedBoatCanvasCompetitorDTO : competitorDTOsOfUnusedBoatCanvases) {
BoatOverlay boatCanvas = boatOverlays.get(unusedBoatCanvasCompetitorDTO);
boatCanvas.removeFromMap();
boatOverlays.remove(unusedBoatCanvasCompetitorDTO);
}
for (CompetitorDTO unusedTailCompetitorDTO : competitorDTOsOfUnusedTails) {
fixesAndTails.removeTail(unusedTailCompetitorDTO);
if (!updateTailsOnly) {
for (CompetitorDTO unusedBoatCanvasCompetitorDTO : competitorDTOsOfUnusedBoatCanvases) {
BoatOverlay boatCanvas = boatOverlays.get(unusedBoatCanvasCompetitorDTO);
boatCanvas.removeFromMap();
boatOverlays.remove(unusedBoatCanvasCompetitorDTO);
}
for (CompetitorDTO unusedTailCompetitorDTO : competitorDTOsOfUnusedTails) {
fixesAndTails.removeTail(unusedTailCompetitorDTO);
}
}
}
}
@@ -1329,8 +1414,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;
}
@@ -2001,6 +2088,8 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
return vPanel;
}
// FIXME bug3378: only fetch those competitors relevant to the race displayed by this RaceMap; see http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=3378#c20
// could be a method on CompetitorSelectionProvider, parameterized by a RaceIdentifier, and if nothing is known about the race association, all competitors are returned as a safe default
private Iterable<CompetitorDTO> getCompetitorsToShow() {
Iterable<CompetitorDTO> result;
Iterable<CompetitorDTO> selection = competitorSelection.getSelectedCompetitors();
@@ -291,6 +291,7 @@ import com.sap.sailing.domain.tractracadapter.TracTracConnectionConstants;
import com.sap.sailing.gwt.ui.adminconsole.RaceLogSetTrackingTimesDTO;
import com.sap.sailing.gwt.ui.client.SailingService;
import com.sap.sailing.gwt.ui.shared.BulkScoreCorrectionDTO;
import com.sap.sailing.gwt.ui.shared.CompactBoatPositionsDTO;
import com.sap.sailing.gwt.ui.shared.CompactRaceMapDataDTO;
import com.sap.sailing.gwt.ui.shared.CompetitorRaceDataDTO;
import com.sap.sailing.gwt.ui.shared.CompetitorsRaceDataDTO;
@@ -1640,7 +1641,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
public CompactRaceMapDataDTO getRaceMapData(RegattaAndRaceIdentifier raceIdentifier, Date date,
Map<String, Date> fromPerCompetitorIdAsString, Map<String, Date> toPerCompetitorIdAsString,
boolean extrapolate, LegIdentifier simulationLegIdentifier) throws NoWindException {
final Map<CompetitorDTO, List<GPSFixDTO>> boatPositions = getBoatPositions(raceIdentifier,
final Map<CompetitorDTO, List<GPSFixDTO>> boatPositions = getBoatPositionsInternal(raceIdentifier,
fromPerCompetitorIdAsString, toPerCompetitorIdAsString, extrapolate);
final CoursePositionsDTO coursePositions = getCoursePositions(raceIdentifier, date);
final List<SidelineDTO> courseSidelines = getCourseSidelines(raceIdentifier, date);
@@ -1670,6 +1671,14 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
return competitorBoats;
}
@Override
public CompactBoatPositionsDTO getBoatPositions(RegattaAndRaceIdentifier raceIdentifier,
Map<String, Date> fromPerCompetitorIdAsString, Map<String, Date> toPerCompetitorIdAsString,
boolean extrapolate) throws NoWindException {
return new CompactBoatPositionsDTO(getBoatPositionsInternal(raceIdentifier, fromPerCompetitorIdAsString, toPerCompetitorIdAsString, extrapolate));
}
/**
* {@link LegType}s are cached within the method with a resolution of one minute. The cache key is a pair of
* {@link TrackedLegOfCompetitor} and {@link TimePoint}.
@@ -1688,7 +1697,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
* @return a map where for each competitor participating in the race the list of GPS fixes in increasing
* chronological order is provided. The last one is the last position at or before <code>date</code>.
*/
private Map<CompetitorDTO, List<GPSFixDTO>> getBoatPositions(RegattaAndRaceIdentifier raceIdentifier,
private Map<CompetitorDTO, List<GPSFixDTO>> getBoatPositionsInternal(RegattaAndRaceIdentifier raceIdentifier,
Map<String, Date> fromPerCompetitorIdAsString, Map<String, Date> toPerCompetitorIdAsString,
boolean extrapolate)
throws NoWindException {
@@ -1724,6 +1733,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
} finally {
track.unlockAfterRead();
}
final Set<GPSFixMoving> extrapolatedFixes;
if (fixes.isEmpty()) {
// then there was no (smoothened) fix between fromTimePoint and toTimePointExcluding; estimate...
TimePoint middle = new MillisecondsTimePoint((toTimePointExcluding.asMillis()+fromTimePoint.asMillis())/2);
@@ -1735,7 +1745,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
logger.finest(""+competitor.getName()+": " + estimatedFix+" (estimated)");
}
fixes.add(estimatedFix);
extrapolatedFixes = Collections.singleton(estimatedFix);
} else {
extrapolatedFixes = Collections.emptySet();
}
} else {
extrapolatedFixes = Collections.emptySet();
}
Iterator<GPSFixMoving> fixIter = fixes.iterator();
if (fixIter.hasNext()) {
@@ -1765,8 +1780,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
legType = null;
}
WindDTO windDTO = wind == null ? null : createWindDTOFromAlreadyAveraged(wind, toTimePointExcluding);
GPSFixDTO fixDTO = createGPSFixDTO(fix, estimatedSpeed, windDTO, tack, legType, /* extrapolate */
false);
GPSFixDTO fixDTO = createGPSFixDTO(fix, estimatedSpeed, windDTO, tack, legType, /* extrapolate */ extrapolatedFixes.contains(fix));
fixesForCompetitor.add(fixDTO);
if (fixIter.hasNext()) {
fix = fixIter.next();
@@ -0,0 +1,31 @@
package com.sap.sailing.gwt.ui.shared;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import com.google.gwt.user.client.rpc.IsSerializable;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
public class CompactBoatPositionsDTO implements IsSerializable {
private Map<String, List<GPSFixDTO>> boatPositions;
CompactBoatPositionsDTO() {} // for GWT serialization only
public CompactBoatPositionsDTO(Map<CompetitorDTO, List<GPSFixDTO>> boatPositions) {
super();
this.boatPositions = new HashMap<>();
for (final Entry<CompetitorDTO, List<GPSFixDTO>> e : boatPositions.entrySet()) {
this.boatPositions.put(e.getKey().getIdAsString(), e.getValue());
}
}
public Map<CompetitorDTO, List<GPSFixDTO>> getBoatPositionsForCompetitors(Map<String, CompetitorDTO> competitorsByIdsAsStrings) {
final Map<CompetitorDTO, List<GPSFixDTO>> result = new HashMap<>();
for (final Entry<String, List<GPSFixDTO>> e : boatPositions.entrySet()) {
result.put(competitorsByIdsAsStrings.get(e.getKey()), e.getValue());
}
return result;
}
}
@@ -23,7 +23,7 @@ import com.sap.sse.common.Util;
*
*/
public class CompactRaceMapDataDTO implements IsSerializable {
private Map<String, List<GPSFixDTO>> boatPositionsByCompetitorIdAsString;
private CompactBoatPositionsDTO boatPositionsByCompetitorIdAsString;
private CoursePositionsDTO coursePositions;
private List<SidelineDTO> courseSidelines;
@@ -35,10 +35,7 @@ public class CompactRaceMapDataDTO implements IsSerializable {
public CompactRaceMapDataDTO(Map<CompetitorDTO, List<GPSFixDTO>> boatPositions, CoursePositionsDTO coursePositions,
List<SidelineDTO> courseSidelines, QuickRanksDTO quickRanks, long simulationResultVersion) {
this.boatPositionsByCompetitorIdAsString = new HashMap<String, List<GPSFixDTO>>();
for (Map.Entry<CompetitorDTO, List<GPSFixDTO>> e : boatPositions.entrySet()) {
this.boatPositionsByCompetitorIdAsString.put(e.getKey().getIdAsString(), e.getValue());
}
this.boatPositionsByCompetitorIdAsString = new CompactBoatPositionsDTO(boatPositions);
this.quickRanks = new ArrayList<CompactQuickRankDTO>(quickRanks == null ? 0 : Util.size(quickRanks.getQuickRanks()));
this.competitorsInOrderOfWindwardDistanceTraveledFarthestFirstIdAsStringAndOneBasedLegNumber = new LinkedHashMap<>();
Map<String, Integer> competitorIdAsStringToOneBasedLegNumber = new HashMap<>();
@@ -59,11 +56,7 @@ public class CompactRaceMapDataDTO implements IsSerializable {
this.simulationResultVersion = simulationResultVersion;
}
public RaceMapDataDTO getRaceMapDataDTO(Iterable<CompetitorDTO> competitors) {
Map<String, CompetitorDTO> competitorsByIdAsString = new HashMap<String, CompetitorDTO>();
for (CompetitorDTO competitor : competitors) {
competitorsByIdAsString.put(competitor.getIdAsString(), competitor);
}
public RaceMapDataDTO getRaceMapDataDTO(Map<String, CompetitorDTO> competitorsByIdAsString) {
RaceMapDataDTO result = new RaceMapDataDTO();
result.quickRanks = new LinkedHashMap<CompetitorDTO, QuickRankDTO>(this.quickRanks.size());
for (CompactQuickRankDTO compactQuickRank : this.quickRanks) {
@@ -74,14 +67,7 @@ public class CompactRaceMapDataDTO implements IsSerializable {
}
result.courseSidelines = courseSidelines;
result.coursePositions = coursePositions;
result.boatPositions = new HashMap<CompetitorDTO, List<GPSFixDTO>>();
for (Map.Entry<String, List<GPSFixDTO>> e : boatPositionsByCompetitorIdAsString.entrySet()) {
final CompetitorDTO competitor = competitorsByIdAsString.get(e.getKey());
if (competitor != null) {
// maybe null in case the competitor was added, e.g., by unsuppressing, while this call was underway
result.boatPositions.put(competitor, e.getValue());
}
}
result.boatPositions = boatPositionsByCompetitorIdAsString.getBoatPositionsForCompetitors(competitorsByIdAsString);
result.competitorsInOrderOfWindwardDistanceTraveledWithOneBasedLegNumber = new LinkedHashMap<>();
for (Entry<String, Integer> i : competitorsInOrderOfWindwardDistanceTraveledFarthestFirstIdAsStringAndOneBasedLegNumber.entrySet()) {
CompetitorDTO c = competitorsByIdAsString.get(i.getKey());
@@ -19,9 +19,9 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
*/
public class AsyncActionsExecutor {
private class ExecutionJob<T> implements AsyncCallback<T> {
private AsyncAction<T> action;
private String category;
private AsyncCallback<T> callback;
private final AsyncAction<T> action;
private final String category;
private final AsyncCallback<T> callback;
public ExecutionJob(AsyncAction<T> action, String category, AsyncCallback<T> callback) {
this.action = action;
@@ -51,8 +51,7 @@ public final class MarkedAsyncCallback<T> implements AsyncCallback<T> {
public MarkedAsyncCallback(AsyncCallback<T> callback, String category) {
this.callback = callback;
this.category = category;
if(DebugInfo.isDebugIdEnabled()) {
if (DebugInfo.isDebugIdEnabled()) {
PendingAjaxCallMarker.incrementPendingAjaxCalls(this.category);
}
}
@@ -70,7 +69,7 @@ public final class MarkedAsyncCallback<T> implements AsyncCallback<T> {
try {
this.callback.onFailure(cause);
} finally {
if(DebugInfo.isDebugIdEnabled()) {
if (DebugInfo.isDebugIdEnabled()) {
PendingAjaxCallMarker.decrementPendingAjaxCalls(this.category);
}
}
@@ -89,7 +88,7 @@ public final class MarkedAsyncCallback<T> implements AsyncCallback<T> {
try {
this.callback.onSuccess(result);
} finally {
if(DebugInfo.isDebugIdEnabled()) {
if (DebugInfo.isDebugIdEnabled()) {
PendingAjaxCallMarker.decrementPendingAjaxCalls(this.category);
}
}