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
+410 -146

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);
@@ -429,6 +467,19 @@ public class FixesAndTails {
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
* from/to times for which to request GPS fixes from the server. No update is performed here to {@link #fixes}. 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);
}
}
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));
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 {
result = null;
// 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 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) {
// 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);
simulationOverlay.updateLeg(Math.max(lastLegNumber, 1), /* clearCanvas */ false, raceMapDataDTO.simulationResultVersion);
}
// process response only if not received out of order
if (startedProcessingRequestID < requestID) {
startedProcessingRequestID = requestID;
// 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();
}
// 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,30 +1220,48 @@ 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));
(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);
}
}
}
if (!updateTailsOnly) {
for (CompetitorDTO unusedBoatCanvasCompetitorDTO : competitorDTOsOfUnusedBoatCanvases) {
BoatOverlay boatCanvas = boatOverlays.get(unusedBoatCanvasCompetitorDTO);
boatCanvas.removeFromMap();
@@ -1188,6 +1272,7 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
}
}
}
}
/**
* This algorithm is limited to distances such that dlon < pi/2, i.e., those that extend around less than one
@@ -1329,9 +1414,11 @@ public class RaceMap extends AbsolutePanel implements TimeListener, CompetitorSe
nextPath.push(advantageLinePos1);
nextPath.push(advantageLinePos2);
advantageTimer.setNextPositionAndTransitionMillis(nextPath, timeForPositionTransitionMillis);
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);
}
}
+4 -4
View File
@@ -1,17 +1,17 @@
# How to work with this Wiki
### General Information
This Wiki is being stored into a GIT repository on branch master that is tracking remote branch master of origin at `/home/trac/git`. The physical location is `/home/gollum/wiki/gitwiki`. The changes here are pushed to origin on a more or less regular base (every week) by one of the admins.
This Wiki is being stored into a GIT repository on branch master that is tracking remote branch master of origin at `/home/trac/git`. The physical location is `/home/wiki/gitwiki`. The changes here are pushed to origin on a more or less regular base (every week) by one of the admins.
The script `/home/gollum/wiki/serve.sh` can be executed to either start or restart the process being responsible for this wiki. The process is a Ruby webserver (Rack) that uses the handler defined in `/home/gollum/wiki/app.rb`. This handler embeds Gollum wiki software into Rack and also makes sure that only authenticated access is possible (see `/home/gollum/wiki/users.yml`).
The script `/home/wiki/serve.sh` can be executed to either start or restart the process being responsible for this wiki. The process is a Ruby webserver (Rack) that uses the handler defined in `/home/wiki/app.rb`. This handler embeds Gollum wiki software into Rack and also makes sure that only authenticated access is possible (see `/home/wiki/users.yml`).
### Making websites public
Accessing this wiki is normally only allowed for authenticated users. If you want to share your work with others not having an account here then you can just request making a given url publicly available. On the server this is done by simply putting a new line to `/home/gollum/wiki/public.txt`.
Accessing this wiki is normally only allowed for authenticated users. If you want to share your work with others not having an account here then you can just request making a given url publicly available. On the server this is done by simply putting a new line to `/home/wiki/public.txt`.
### Creating a New Account
Send a request to Axel Uhl or Simon Marcel Pamies that includes the SHA1 hash of your desired password. Obtain such an SHA1 hash for your password here: http://www.sha1-online.com/.
A Wiki administrator can add a new account by adding a new entry to `/home/gollum/wiki/users.yml`. The password there is encrypted using sha1sum (`echo -n "password" | sha1sum`) or, for those who don't happen to have sha1sum available, using an online SHA1 generator such as [this one](http://www.sha1-online.com/).
A Wiki administrator can add a new account by adding a new entry to `/home/wiki/users.yml`. The password there is encrypted using sha1sum (`echo -n "password" | sha1sum`) or, for those who don't happen to have sha1sum available, using an online SHA1 generator such as [this one](http://www.sha1-online.com/).
### Editing
You can edit information here freely but do not leave any nonsense here.