mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-28 14:57:12 +00:00
Merge remote-tracking branch 'origin/master' into bug2610
This commit is contained in:
commit
caae9a587a
14 files changed
+422
-158
No files matched your search
+47
@@ -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;
|
||||
}
|
||||
}
|
||||
+43
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+10
-22
@@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+6
@@ -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);
|
||||
|
||||
}
|
||||
+6
@@ -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);
|
||||
|
||||
}
|
||||
+78
-27
@@ -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);
|
||||
|
||||
+5
-1
@@ -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() {
|
||||
|
||||
+164
-75
@@ -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();
|
||||
|
||||
+18
-4
@@ -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();
|
||||
|
||||
+31
@@ -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;
|
||||
}
|
||||
}
|
||||
+4
-18
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user