Merge remote-tracking branch 'origin/master' into 60_inch

This commit is contained in:
Papick Garcia Taboada committed 2017-06-20 15:46:36 +02:00
commit 8da357fb9c
15 files changed
+377 -711

No files matched your search

@@ -65,6 +65,7 @@ import com.tractrac.subscription.lib.api.ISubscriberFactory;
import com.tractrac.subscription.lib.api.SubscriberInitializationException;
import com.tractrac.subscription.lib.api.SubscriptionLocator;
import com.tractrac.subscription.lib.api.competitor.ICompetitorsListener;
import com.tractrac.subscription.lib.api.control.IControlsListener;
import com.tractrac.subscription.lib.api.event.IConnectionStatusListener;
import com.tractrac.subscription.lib.api.event.ILiveDataEvent;
import com.tractrac.subscription.lib.api.event.IStoredDataEvent;
@@ -194,6 +195,9 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
private final com.sap.sailing.domain.base.Regatta regatta;
private final IEventSubscriber eventSubscriber;
private final IRaceSubscriber raceSubscriber;
private final IRacesListener racesListener;
private final IControlsListener controlsListener;
private final ICompetitorsListener competitorsListener;
private final Set<Receiver> receivers;
private final DomainFactory domainFactory;
private final WindStore windStore;
@@ -331,7 +335,16 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
// Initialize data controller using live and stored data sources
ISubscriberFactory subscriberFactory = SubscriptionLocator.getSusbcriberFactory();
eventSubscriber = subscriberFactory.createEventSubscriber(tractracEvent, liveURI, effectifeStoredURI);
eventSubscriber.subscribeCompetitors(new ICompetitorsListener() {
controlsListener = new IControlsListener() {
@Override
public void updateControl(IControl control) {}
@Override
public void deleteControl(UUID controlId) {}
@Override
public void addControl(IControl control) {}
};
eventSubscriber.subscribeControls(controlsListener);
competitorsListener = new ICompetitorsListener() {
@Override
public void updateCompetitor(ICompetitor competitor) {
if (!competitor.isNonCompeting()) {
@@ -349,8 +362,9 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
@Override
public void addCompetitor(ICompetitor competitor) {
}
});
eventSubscriber.subscribeRaces(new IRacesListener() {
};
eventSubscriber.subscribeCompetitors(competitorsListener);
racesListener = new IRacesListener() {
@Override public void abandonRace(UUID raceId) {}
@Override public void addRace(IRace race) {}
@Override public void deleteRace(UUID raceId) {}
@@ -369,7 +383,8 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
}
}
}
});
};
eventSubscriber.subscribeRaces(racesListener);
// Start live and stored data streams
final Regatta effectiveRegatta;
raceSubscriber = subscriberFactory.createRaceSubscriber(tractracRace, liveURI, effectifeStoredURI);
@@ -532,6 +547,9 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
protected void onStop(boolean stopReceiversPreemtively) throws InterruptedException {
if (!stopped) {
stopped = true;
eventSubscriber.unsubscribeRaces(racesListener);
eventSubscriber.unsubscribeCompetitors(competitorsListener);
eventSubscriber.unsubscribeControls(controlsListener);
raceSubscriber.stop();
eventSubscriber.stop();
raceSubscriber.unsubscribeConnectionStatus(this);
@@ -453,7 +453,12 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
} else {
Distance distanceToNextMark = getTrackedRace().getTrackedLeg(leg)
.getAbsoluteWindwardDistance(currentPosition, nextMarkPosition, timePoint, windPositionMode, cache);
result = new MeterDistance(result.getMeters() + distanceToNextMark.getMeters());
if (distanceToNextMark != null) {
result = new MeterDistance(result.getMeters() + distanceToNextMark.getMeters());
} else {
result = null;
break;
}
}
currentPosition = nextMarkPosition;
} else {
@@ -4,6 +4,61 @@
<div id="mainContent">
<h4 class="articleHeadline">What's New - SAP Sailing Analytics</h4>
<div class="innerContent">
<h5 class="articleSubheadline">May 2017</h5>
<ul class="bulletList">
<li>It's now possible to show nationality flags in addition to competitor images in leaderboards if both are available.
If no competitor images are available, this won't have any effect.
You can activate this option through the leaderboard settings dialog to be found in various views.
</li>
</ul>
<h5 class="articleSubheadline">April 2017</h5>
<ul class="bulletList">
<li>Several pages are now enabled to save user settings. In settings dialogs there is a "Make default"
button in the lower-left corner which is displayed when saving of settings is enabled. A click on that
button causes the system to use the new settings as default values for the same page for any other context opened, e.g. another event.
A click on OK button causes the system to store current settings for the current context and restore the same settings
when the user opens the page with the same context again unless there are specific parameters given by the URL. Settings saved this way are
stored locally in the browser's local storage and in the user profile if you are logged in.
Some settings are only valid in a specific context (e.g. the selected race columns to show are stored for a specific leaderboard only).
This feature required changing several URL parameters, which means that previously saved bookmarks might work slightly different.
Affected pages:
<ul>
<li><code>/gwt/Leaderboard.html</code></li>
<li><code>/gwt/RegattaOverview.html</code></li>
<li>Leaderboards embedded in <code>/gwt/Home.html</code></li>
</ul>
</li>
<li>The maneuver loss calculation for penalty circles has been improved. Previously, the end of the
penalty circle maneuver was considered to be at the last of the two adjacent tack/gybe maneuvers.
This, usually, is earlier than when approaching the course over ground at the beginning of the
penalty circle again. With this, the extrapolation of the speed/course at the beginning of
the maneuver did not last long enough. Furthermore, penalties with strong accelerations during
the gybing phase could have led to an incorrect inference of the speed and course at the
beginning of the maneuver. Both of these issues have now been fixed, leading to good results
also in very specific and unusual speed developments during a penalty circle maneuver.
</li>
<li>The advantage line display has been fixed for legs changing their type (upwind/downwind/reach)
while the race is going on. It now uses the leg type at the same current time point that the
leaderboard uses for calculating the competitor ranking.
</li>
<li>Images in the overall image gallery ("Photographers love Sailing") are now linked with the
events to which they belong. Clicking the image navigates to the event page.
</li>
</ul>
<h5 class="articleSubheadline">March 2017</h5>
<ul class="bulletList">
<li>The SAP Sailing Analytics are now also available in French, Portugese, as well as Spanish.
Find the language chooser in the lower right corner of the footer, or when signed in, go to your user
profile and set your preferred language.
</li>
<li>The application's locale now switches based on the client's "Accept-Language" HTTP header field.
This means that the application in all its parts will show in the user's default language if
supported, defaulting to English otherwise.
</li>
</ul>
<h5 class="articleSubheadline">February 2017</h5>
<ul class="bulletList">
<li>The definition of "ride height" for foiling sensors has been changed. Instead of showing
@@ -38,7 +38,7 @@ public class BoatClassVectorGraphicsResolver {
BoatClassMasterdata.D_35, BoatClassMasterdata.SKUD_18, BoatClassMasterdata.WETA);
BoatClassVectorGraphics gc32 = new GC32VectorGraphics(BoatClassMasterdata.GC_32, BoatClassMasterdata.M32);
BoatClassVectorGraphics smallMultihull = new SmallMultihullVectorGraphics(BoatClassMasterdata.NACRA_17,
BoatClassMasterdata.F_16, BoatClassMasterdata.F_18, BoatClassMasterdata.HOBIE_WILD_CAT,
BoatClassMasterdata.F_16, BoatClassMasterdata.F_18, BoatClassMasterdata.HOBIE_WILD_CAT, BoatClassMasterdata.HOBIE_16,
BoatClassMasterdata.HOBIE_TIGER, BoatClassMasterdata.A_CAT, BoatClassMasterdata.TORNADO,
BoatClassMasterdata.FLYING_PHANTOM);
BoatClassVectorGraphics keelBoatWithGennaker = new KeelBoatWithGennakerVectorGraphics(BoatClassMasterdata.J70,
@@ -0,0 +1,178 @@
package com.sap.sailing.gwt.ui.client.shared.racemap;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.NumberFormatterFactory;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.racemap.QuickRanksDTOProvider.QuickRanksListener;
import com.sap.sailing.gwt.ui.shared.GPSFixDTOWithSpeedWindTackAndLegType;
import com.sap.sailing.gwt.ui.shared.QuickRankDTO;
/**
* Manages the collection of all {@link CompetitorInfoOverlay} objects shown in the {@link RaceMap}. In particular, it
* keeps track of the information used for the text in the overlays and allows for incremental updates, e.g., if a
* competitor's rank has been updated. It can act as a {@link QuickRanksListener} and as such be subscribed for rank
* changes, e.g., with a {@link QuickRanksDTOProvider}. It will track all rank updates for any competitor whose info
* overlay is managed by this instance.
* <p>
*
* Competitor positions are expected to be explicitly announced by calling
* {@link #updatePosition(CompetitorDTO, GPSFixDTOWithSpeedWindTackAndLegType, long)} in case new position data is
* known.
*
* @author Axel Uhl (d043530)
*
*/
public class CompetitorInfoOverlays implements QuickRanksListener {
private final RaceMap raceMap;
/**
* html5 canvases used for competitor info display on the map; keys are the competitor IDs as strings
*/
private final Map<String, CompetitorInfoOverlay> competitorInfoOverlays;
/**
* keys are the competitor IDs as strings
*/
private final Map<String, Integer> ranks;
/**
* keys are the competitor IDs as strings
*/
private final Map<String, GPSFixDTOWithSpeedWindTackAndLegType> lastPositions;
private final StringMessages stringMessages;
public CompetitorInfoOverlays(RaceMap raceMap, final StringMessages stringMessages) {
competitorInfoOverlays = new HashMap<>();
ranks = new HashMap<>();
lastPositions = new HashMap<>();
this.stringMessages = stringMessages;
this.raceMap = raceMap;
}
public CompetitorInfoOverlay createCompetitorInfoOverlay(int zIndex, final CompetitorDTO competitorDTO, GPSFixDTOWithSpeedWindTackAndLegType gpsFixDTO, Integer rank, long timeForPositionTransitionMillis) {
CompetitorInfoOverlay result = new CompetitorInfoOverlay(raceMap.getMap(), zIndex,
raceMap.getCompetitorSelection().getColor(competitorDTO, raceMap.getRaceIdentifier()),
/* info text */ "", raceMap.getCoordinateSystem());
competitorInfoOverlays.put(competitorDTO.getIdAsString(), result);
updatePosition(competitorDTO, gpsFixDTO, timeForPositionTransitionMillis);
result.setInfoText(createInfoText(competitorDTO));
updateRank(competitorDTO, rank);
return result;
}
/**
* Removes the {@link CompetitorInfoOverlay} for the competitor specified as {@code competitorDTO} from the map and
* from this structure.
*/
public void remove(final CompetitorDTO competitorDTO) {
remove(competitorDTO.getIdAsString());
}
public void remove(final String competitorIdAsString) {
CompetitorInfoOverlay overlay = competitorInfoOverlays.remove(competitorIdAsString);
if (overlay != null) {
overlay.removeFromMap();
}
ranks.remove(competitorIdAsString);
lastPositions.remove(competitorIdAsString);
}
public CompetitorInfoOverlay get(final CompetitorDTO competitorDTO) {
return competitorInfoOverlays.get(competitorDTO.getIdAsString());
}
public Iterable<CompetitorInfoOverlay> getCompetitorInfoOverlays() {
return competitorInfoOverlays.values();
}
/**
* Updates {@link #ranks} and re-draws the corresponding overlay
*/
public void updateRank(CompetitorDTO competitorDTO, Integer rank) {
CompetitorInfoOverlay overlay = competitorInfoOverlays.get(competitorDTO.getIdAsString());
if (overlay != null) {
ranks.put(competitorDTO.getIdAsString(), rank);
overlay.setInfoText(createInfoText(competitorDTO));
overlay.draw();
}
}
private GPSFixDTOWithSpeedWindTackAndLegType getLastPosition(CompetitorDTO competitorDTO) {
return lastPositions.get(competitorDTO.getIdAsString());
}
public void removeTransitions() {
for (CompetitorInfoOverlay infoOverlay : getCompetitorInfoOverlays()) {
infoOverlay.removeCanvasPositionAndRotationTransition();
}
}
/**
* Updates the competitor's position data and and, if the competitor has an overlay managed by this instance,
* adjusts the overlay's position on the map using the transition timeout specified by
* {@code timeForPositionTransitionMillis}.
*/
public void updatePosition(CompetitorDTO competitorDTO, GPSFixDTOWithSpeedWindTackAndLegType gpsFixDTO, long timeForPositionTransitionMillis) {
lastPositions.put(competitorDTO.getIdAsString(), gpsFixDTO);
CompetitorInfoOverlay overlay = competitorInfoOverlays.get(competitorDTO.getIdAsString());
if (overlay != null) {
overlay.setPosition(gpsFixDTO.position, timeForPositionTransitionMillis);
overlay.draw();
}
}
/**
* Produces a text for the competitor info window for competitor {@code competitorDTO} based on the contents
* of {@link #ranks} and {@link #lastPositions}. If no {@link #ranks} entry exists, no rank is shown. However,
* a {@link #lastPosition} is required in order to produce the speed/course information.
*/
private String createInfoText(CompetitorDTO competitorDTO) {
StringBuilder infoText = new StringBuilder();
infoText.append(competitorDTO.getSailID()).append("\n");
infoText.append(NumberFormatterFactory.getDecimalFormat(1).format(getLastPosition(competitorDTO).speedWithBearing.speedInKnots))
.append(" ").append(stringMessages.knotsUnit()).append("\n");
final Integer rank = ranks.get(competitorDTO.getIdAsString());
if (rank != null && rank != 0) {
infoText.append(stringMessages.rank()).append(" : ").append(rank);
}
return infoText.toString();
}
public Iterable<String> getCompetitorIdsAsStrings() {
return Collections.unmodifiableCollection(competitorInfoOverlays.keySet());
}
/**
* Removes all overlays managed by this structure from the map and from this structure
*/
public void clear() {
for (final Iterator<Entry<String, CompetitorInfoOverlay>> i=competitorInfoOverlays.entrySet().iterator(); i.hasNext(); ) {
final Entry<String, CompetitorInfoOverlay> e = i.next();
e.getValue().removeFromMap();
i.remove();
}
lastPositions.clear();
ranks.clear();
}
/**
* @param quickRanks
* uses the {@link QuickRankDTO#oneBasedRank} and {@link QuickRankDTO#competitor} fields
*/
@Override
public void rankChanged(String competitorIdAsString, QuickRankDTO quickRanks) {
final CompetitorInfoOverlay competitorInfoOverlay = competitorInfoOverlays.get(competitorIdAsString);
ranks.put(competitorIdAsString, quickRanks.oneBasedRank);
if (competitorInfoOverlay != null) {
competitorInfoOverlay.setInfoText(createInfoText(quickRanks.competitor));
competitorInfoOverlay.draw();
}
}
}
@@ -1,15 +1,20 @@
package com.sap.sailing.gwt.ui.client.shared.racemap;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import com.sap.sailing.gwt.ui.raceboard.AbstractQuickRanksDTOProvider;
import com.sap.sailing.gwt.ui.shared.QuickRankDTO;
public class DefaultQuickRanksDTOProvider implements QuickRanksDTOProvider {
public class DefaultQuickRanksDTOProvider extends AbstractQuickRanksDTOProvider {
private LinkedHashMap<String, QuickRankDTO> quickRanksFromServer;
@Override
public void quickRanksReceivedFromServer(LinkedHashMap<String, QuickRankDTO> quickRanksFromServer) {
this.quickRanksFromServer = quickRanksFromServer;
for (final Entry<String, QuickRankDTO> e : quickRanksFromServer.entrySet()) {
notifyListeners(e.getKey(), e.getValue());
}
}
@Override
@@ -23,6 +23,14 @@ import com.sap.sailing.gwt.ui.shared.RaceMapDataDTO;
*
*/
public interface QuickRanksDTOProvider {
public interface QuickRanksListener {
void rankChanged(String competitorIdAsString, QuickRankDTO quickRanks);
}
void addQuickRanksListener(QuickRanksListener listener);
void removeQuickRanksListener(QuickRanksListener listener);
/**
* The strategy may or may not use this quick ranks information provided directly from the server. This information
* may have been provided with some delay and may be slightly inconsistent with a {@link LeaderboardDTO leaderboard}
@@ -269,7 +269,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
/**
* html5 canvases used for competitor info display on the map
*/
private final Map<CompetitorDTO, CompetitorInfoOverlay> competitorInfoOverlays;
private final CompetitorInfoOverlays competitorInfoOverlays;
private SmallTransparentInfoOverlay countDownOverlay;
@@ -431,7 +431,8 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
courseMiddleLines = new HashMap<>();
infoOverlaysForLinesForCourseGeometry = new HashMap<>();
boatOverlays = new HashMap<CompetitorDTO, BoatOverlay>();
competitorInfoOverlays = new HashMap<CompetitorDTO, CompetitorInfoOverlay>();
competitorInfoOverlays = new CompetitorInfoOverlays(this, stringMessages);
quickRanksDTOProvider.addQuickRanksListener(competitorInfoOverlays);
windSensorOverlays = new HashMap<WindSource, WindSensorOverlay>();
courseMarkOverlays = new HashMap<String, CourseMarkOverlay>();
courseMarkClickHandlers = new HashMap<String, HandlerRegistration>();
@@ -741,9 +742,7 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
boatOverlay.removeCanvasPositionAndRotationTransition();
}
// remove the canvas animations for the info overlays of the selected boats
for (CompetitorInfoOverlay infoOverlay : competitorInfoOverlays.values()) {
infoOverlay.removeCanvasPositionAndRotationTransition();
}
competitorInfoOverlays.removeTransitions();
for (CourseMarkOverlay markOverlay : courseMarkOverlays.values()) {
markOverlay.removeCanvasPositionAndRotationTransition();
}
@@ -761,6 +760,18 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
return Collections.unmodifiableMap(boatOverlays);
}
protected CompetitorSelectionProvider getCompetitorSelection() {
return competitorSelection;
}
protected Timer getTimer() {
return timer;
}
protected RegattaAndRaceIdentifier getRaceIdentifier() {
return raceIdentifier;
}
public MapWidget getMap() {
return map;
}
@@ -1215,38 +1226,30 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
private void showCompetitorInfoOnMap(final Date newTime, final long timeForPositionTransitionMillis, final Iterable<CompetitorDTO> competitorsToShow) {
if (map != null) {
if (settings.isShowSelectedCompetitorsInfo()) {
Set<CompetitorDTO> toRemoveCompetorInfoOverlays = new HashSet<CompetitorDTO>(
competitorInfoOverlays.keySet());
Set<String> toRemoveCompetorInfoOverlaysIdsAsStrings = new HashSet<>();
Util.addAll(competitorInfoOverlays.getCompetitorIdsAsStrings(), toRemoveCompetorInfoOverlaysIdsAsStrings);
for (CompetitorDTO competitorDTO : competitorsToShow) {
if (fixesAndTails.hasFixesFor(competitorDTO)) {
GPSFixDTOWithSpeedWindTackAndLegType lastBoatFix = getBoatFix(competitorDTO, newTime);
if (lastBoatFix != null) {
CompetitorInfoOverlay competitorInfoOverlay = competitorInfoOverlays.get(competitorDTO);
final Integer rank = getRank(competitorDTO);
if (competitorInfoOverlay == null) {
competitorInfoOverlay = createCompetitorInfoOverlay(RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, competitorDTO);
competitorInfoOverlays.put(competitorDTO, competitorInfoOverlay);
competitorInfoOverlay.setInfoText(createInfoText(competitorDTO, lastBoatFix));
competitorInfoOverlay.setPosition(lastBoatFix.position, timeForPositionTransitionMillis);
competitorInfoOverlay = competitorInfoOverlays.createCompetitorInfoOverlay(RaceMapOverlaysZIndexes.INFO_OVERLAY_ZINDEX, competitorDTO,
lastBoatFix, rank, timeForPositionTransitionMillis);
competitorInfoOverlay.addToMap();
} else {
competitorInfoOverlay.setInfoText(createInfoText(competitorDTO, lastBoatFix));
competitorInfoOverlay.setPosition(lastBoatFix.position, timeForPositionTransitionMillis);
competitorInfoOverlay.draw();
competitorInfoOverlays.updatePosition(competitorDTO, lastBoatFix, timeForPositionTransitionMillis);
}
toRemoveCompetorInfoOverlays.remove(competitorDTO);
toRemoveCompetorInfoOverlaysIdsAsStrings.remove(competitorDTO.getIdAsString());
}
}
}
for (CompetitorDTO toRemoveCompetorDTO : toRemoveCompetorInfoOverlays) {
CompetitorInfoOverlay competitorInfoOverlay = competitorInfoOverlays.get(toRemoveCompetorDTO);
competitorInfoOverlay.removeFromMap();
competitorInfoOverlays.remove(toRemoveCompetorDTO);
for (String toRemoveCompetitorOverlayIdAsString : toRemoveCompetorInfoOverlaysIdsAsStrings) {
competitorInfoOverlays.remove(toRemoveCompetitorOverlayIdAsString);
}
} else {
// remove all overlays
for (CompetitorInfoOverlay competitorInfoOverlay : competitorInfoOverlays.values()) {
competitorInfoOverlay.removeFromMap();
}
competitorInfoOverlays.clear();
}
}
@@ -1966,23 +1969,6 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
}
}
private CompetitorInfoOverlay createCompetitorInfoOverlay(int zIndex, final CompetitorDTO competitorDTO) {
GPSFixDTOWithSpeedWindTackAndLegType gpsFixDTO = getBoatFix(competitorDTO, timer.getTime());
return new CompetitorInfoOverlay(map, zIndex, competitorSelection.getColor(competitorDTO, raceIdentifier), createInfoText(competitorDTO, gpsFixDTO), coordinateSystem);
}
private String createInfoText(CompetitorDTO competitorDTO, GPSFixDTOWithSpeedWindTackAndLegType gpsFixDTO) {
StringBuilder infoText = new StringBuilder();
infoText.append(competitorDTO.getSailID()).append("\n");
infoText.append(NumberFormatterFactory.getDecimalFormat(1).format(gpsFixDTO.speedWithBearing.speedInKnots))
.append(" ").append(stringMessages.knotsUnit()).append("\n");
final Integer rank = getRank(competitorDTO);
if (rank != null) {
infoText.append(stringMessages.rank()).append(" : ").append(rank);
}
return infoText.toString();
}
private BoatOverlay createBoatOverlay(int zIndex, final CompetitorDTO competitorDTO, boolean highlighted) {
final BoatOverlay boatCanvas = new BoatOverlay(map, zIndex, competitorDTO, competitorSelection.getColor(competitorDTO, raceIdentifier), coordinateSystem);
boatCanvas.setSelected(highlighted);
@@ -2779,11 +2765,10 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
@Override
public Integer getRank(CompetitorDTO competitor) {
// TODO bug4175: obtain from raceBoardPanel.leaderboardPanel.leaderboard instead of QuickRanks
final Integer result;
QuickRankDTO quickRank = quickRanksDTOProvider.getQuickRanks().get(competitor.getIdAsString());
if (quickRank != null) {
result = quickRank.rank;
result = quickRank.oneBasedRank;
} else {
result = null;
}
@@ -0,0 +1,31 @@
package com.sap.sailing.gwt.ui.raceboard;
import java.util.HashSet;
import java.util.Set;
import com.sap.sailing.gwt.ui.client.shared.racemap.QuickRanksDTOProvider;
import com.sap.sailing.gwt.ui.shared.QuickRankDTO;
public abstract class AbstractQuickRanksDTOProvider implements QuickRanksDTOProvider {
private final Set<QuickRanksListener> listeners;
public AbstractQuickRanksDTOProvider() {
listeners = new HashSet<>();
}
@Override
public void addQuickRanksListener(QuickRanksListener listener) {
listeners.add(listener);
}
@Override
public void removeQuickRanksListener(QuickRanksListener listener) {
listeners.remove(listener);
}
protected void notifyListeners(String competitorIdAsString, QuickRankDTO quickRank) {
for (QuickRanksListener listener : listeners) {
listener.rankChanged(competitorIdAsString, quickRank);
}
}
}
@@ -10,7 +10,6 @@ import com.sap.sailing.domain.common.dto.LeaderboardDTO;
import com.sap.sailing.domain.common.dto.LeaderboardEntryDTO;
import com.sap.sailing.domain.common.dto.LeaderboardRowDTO;
import com.sap.sailing.domain.common.dto.RaceColumnDTO;
import com.sap.sailing.gwt.ui.client.shared.racemap.QuickRanksDTOProvider;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceCompetitorSet;
import com.sap.sailing.gwt.ui.shared.QuickRankDTO;
import com.sap.sse.common.Util;
@@ -25,7 +24,7 @@ import com.sap.sse.common.Util;
* @author Axel Uhl (d043530)
*
*/
public class QuickRanksDTOFromLeaderboardDTOProvider implements QuickRanksDTOProvider {
public class QuickRanksDTOFromLeaderboardDTOProvider extends AbstractQuickRanksDTOProvider {
private LinkedHashMap<String, QuickRankDTO> quickRanks;
private final RaceCompetitorSet raceCompetitorSet;
private final RaceIdentifier selectedRace;
@@ -43,6 +42,7 @@ public class QuickRanksDTOFromLeaderboardDTOProvider implements QuickRanksDTOPro
quickRanks = new LinkedHashMap<>();
for (final Entry<String, QuickRankDTO> e : quickRanksFromServer.entrySet()) {
quickRanks.put(e.getKey(), e.getValue());
notifyListeners(e.getKey(), e.getValue());
}
} else if (!lastLeaderboardProvidedLegNumbers) {
// extract at least the leg numbers and update existing quick ranks accordingly in place
@@ -65,33 +65,44 @@ public class QuickRanksDTOFromLeaderboardDTOProvider implements QuickRanksDTOPro
}
if (raceColumnName != null) {
final List<CompetitorDTO> competitorsFromBestToWorst = leaderboard.getCompetitorsFromBestToWorst(raceColumnName);
int rank = 1;
for (final CompetitorDTO c : competitorsFromBestToWorst) {
if (Util.contains(raceCompetitorSet.getIdsOfCompetitorsParticipatingInRaceAsStrings(), c.getIdAsString())) {
final LeaderboardRowDTO row = leaderboard.rows.get(c);
final int oneBasedLegNumber;
if (row != null) {
final LeaderboardEntryDTO raceEntryForCompetitor = row.fieldsByRaceColumnName.get(raceColumnName);
if (raceEntryForCompetitor != null && raceEntryForCompetitor.legDetails != null) {
oneBasedLegNumber = raceEntryForCompetitor.getOneBasedCurrentLegNumber();
lastLeaderboardProvidedLegNumbers = true;
if (competitorsFromBestToWorst.isEmpty()) {
for (CompetitorDTO c : leaderboard.competitors) {
final QuickRankDTO quickRank = new QuickRankDTO(c, /* oneBasedRank */ 0, /* leg number ignored */ 0);
quickRanks.put(c.getIdAsString(), quickRank);
notifyListeners(c.getIdAsString(), quickRank);
}
} else {
int oneBasedRank = 1;
for (final CompetitorDTO c : competitorsFromBestToWorst) {
if (Util.contains(raceCompetitorSet.getIdsOfCompetitorsParticipatingInRaceAsStrings(), c.getIdAsString())) {
final LeaderboardRowDTO row = leaderboard.rows.get(c);
final int oneBasedLegNumber;
if (row != null) {
final LeaderboardEntryDTO raceEntryForCompetitor = row.fieldsByRaceColumnName.get(raceColumnName);
if (raceEntryForCompetitor != null && raceEntryForCompetitor.legDetails != null) {
oneBasedLegNumber = raceEntryForCompetitor.getOneBasedCurrentLegNumber();
lastLeaderboardProvidedLegNumbers = true;
} else {
oneBasedLegNumber = 0;
lastLeaderboardProvidedLegNumbers = false;
}
} else {
oneBasedLegNumber = 0;
lastLeaderboardProvidedLegNumbers = false;
}
} else {
oneBasedLegNumber = 0;
}
QuickRankDTO quickRankToUpdate = quickRanks.get(c.getIdAsString());
if (quickRankToUpdate == null) {
quickRanks.put(c.getIdAsString(), new QuickRankDTO(c, rank, oneBasedLegNumber));
} else {
quickRankToUpdate.rank = rank;
if (lastLeaderboardProvidedLegNumbers) {
quickRankToUpdate.legNumberOneBased = oneBasedLegNumber;
QuickRankDTO quickRankToUpdate = quickRanks.get(c.getIdAsString());
if (quickRankToUpdate == null) {
final QuickRankDTO quickRankDTO = new QuickRankDTO(c, oneBasedRank, oneBasedLegNumber);
quickRanks.put(c.getIdAsString(), quickRankDTO);
notifyListeners(c.getIdAsString(), quickRankDTO);
} else {
quickRankToUpdate.oneBasedRank = oneBasedRank;
if (lastLeaderboardProvidedLegNumbers) {
quickRankToUpdate.legNumberOneBased = oneBasedLegNumber;
}
notifyListeners(c.getIdAsString(), quickRankToUpdate);
}
oneBasedRank++;
}
rank++;
}
}
}
@@ -110,5 +121,4 @@ public class QuickRanksDTOFromLeaderboardDTOProvider implements QuickRanksDTOPro
public LinkedHashMap<String, QuickRankDTO> getQuickRanks() {
return quickRanks;
}
}
@@ -2229,17 +2229,17 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S
actualTimePoint = timePoint;
}
final RaceDefinition race = trackedRace.getRace();
int rank = 1;
int oneBasedRank = 1;
final List<Competitor> competitorsFromBestToWorst = trackedRace.getCompetitorsFromBestToWorst(actualTimePoint);
for (Competitor competitor : competitorsFromBestToWorst) {
TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, actualTimePoint);
if (trackedLeg != null) {
int legNumberOneBased = race.getCourse().getLegs().indexOf(trackedLeg.getLeg()) + 1;
QuickRankDTO quickRankDTO = new QuickRankDTO(baseDomainFactory.convertToCompetitorDTO(competitor),
rank, legNumberOneBased);
oneBasedRank, legNumberOneBased);
result.add(quickRankDTO);
}
rank++;
oneBasedRank++;
}
}
return new QuickRanksDTO(result);
@@ -28,7 +28,7 @@ public class CompactQuickRankDTO implements IsSerializable {
return competitorIdAsString;
}
public int getRank() {
public int getOneBasedRank() {
return rank;
}
@@ -47,7 +47,7 @@ public class CompactRaceMapDataDTO implements IsSerializable {
Map<String, Integer> competitorIdAsStringToOneBasedLegNumber = new HashMap<>();
if (quickRanks != null) {
for (QuickRankDTO quickRank : quickRanks.getQuickRanks()) {
this.quickRanks.add(new CompactQuickRankDTO(quickRank.competitor.getIdAsString(), quickRank.rank,
this.quickRanks.add(new CompactQuickRankDTO(quickRank.competitor.getIdAsString(), quickRank.oneBasedRank,
quickRank.legNumberOneBased));
competitorIdAsStringToOneBasedLegNumber.put(quickRank.competitor.getIdAsString(), quickRank.legNumberOneBased);
}
@@ -63,7 +63,7 @@ public class CompactRaceMapDataDTO implements IsSerializable {
for (CompactQuickRankDTO compactQuickRank : this.quickRanks) {
final CompetitorDTO competitorDTO = competitorsByIdAsString.get(compactQuickRank.getCompetitorIdAsString());
if (competitorDTO != null) {
result.quickRanks.put(compactQuickRank.getCompetitorIdAsString(), new QuickRankDTO(competitorDTO, compactQuickRank.getRank(), compactQuickRank.getLegNumber()));
result.quickRanks.put(compactQuickRank.getCompetitorIdAsString(), new QuickRankDTO(competitorDTO, compactQuickRank.getOneBasedRank(), compactQuickRank.getLegNumber()));
}
}
result.courseSidelines = courseSidelines;
@@ -5,7 +5,8 @@ import com.sap.sailing.domain.common.dto.CompetitorDTO;
public class QuickRankDTO implements IsSerializable {
public CompetitorDTO competitor;
public int rank;
public int oneBasedRank;
/**
* The {@link #competitor}'s leg number, starting with 1 for the first leg
@@ -16,7 +17,7 @@ public class QuickRankDTO implements IsSerializable {
public QuickRankDTO(CompetitorDTO competitorDTO, int rank, int legNumberOneBased) {
this.competitor = competitorDTO;
this.rank = rank;
this.oneBasedRank = rank;
this.legNumberOneBased = legNumberOneBased;
}
}
@@ -1,630 +0,0 @@
<!DOCTYPE html>
<html>
<body>
<div id="mainContent">
<h4 class="articleHeadline">What's New - SAP Sailing Analytics</h4>
<div class="innerContent">
<h5 class="articleSubheadline">May 2017</h5>
<ul class="bulletList">
<li>It's now possible to show nationality flags in addition to competitor images in leaderboards if both are available.
If no competitor images are available, this won't have any effect.
You can activate this option through the leaderboard settings dialog to be found in various views.
</li>
</ul>
<h5 class="articleSubheadline">April 2017</h5>
<ul class="bulletList">
<li>Several pages are now enabled to save user settings. In settings dialogs there is a "Make default"
button in the lower-left corner which is displayed when saving of settings is enabled. A click on that
button causes the system to use the new settings as default values for the same page for any other context opened, e.g. another event.
A click on OK button causes the system to store current settings for the current context and restore the same settings
when the user opens the page with the same context again unless there are specific parameters given by the URL. Settings saved this way are
stored locally in the browser's local storage and in the user profile if you are logged in.
Some settings are only valid in a specific context (e.g. the selected race columns to show are stored for a specific leaderboard only).
This feature required changing several URL parameters, which means that previously saved bookmarks might work slightly different.
Affected pages:
<ul>
<li><code>/gwt/Leaderboard.html</code></li>
<li><code>/gwt/RegattaOverview.html</code></li>
<li>Leaderboards embedded in <code>/gwt/Home.html</code></li>
</ul>
</li>
<li>The maneuver loss calculation for penalty circles has been improved. Previously, the end of the
penalty circle maneuver was considered to be at the last of the two adjacent tack/gybe maneuvers.
This, usually, is earlier than when approaching the course over ground at the beginning of the
penalty circle again. With this, the extrapolation of the speed/course at the beginning of
the maneuver did not last long enough. Furthermore, penalties with strong accelerations during
the gybing phase could have led to an incorrect inference of the speed and course at the
beginning of the maneuver. Both of these issues have now been fixed, leading to good results
also in very specific and unusual speed developments during a penalty circle maneuver.
</li>
<li>The advantage line display has been fixed for legs changing their type (upwind/downwind/reach)
while the race is going on. It now uses the leg type at the same current time point that the
leaderboard uses for calculating the competitor ranking.
</li>
<li>Images in the overall image gallery ("Photographers love Sailing") are now linked with the
events to which they belong. Clicking the image navigates to the event page.
</li>
</ul>
<h5 class="articleSubheadline">March 2017</h5>
<ul class="bulletList">
<li>The SAP Sailing Analytics are now also available in French, Portugese, as well as Spanish.
Find the language chooser in the lower right corner of the footer, or when signed in, go to your user
profile and set your preferred language.
</li>
<li>The application's locale now switches based on the client's "Accept-Language" HTTP header field.
This means that the application in all its parts will show in the user's default language if
supported, defaulting to English otherwise.
</li>
</ul>
<h5 class="articleSubheadline">February 2017</h5>
<ul class="bulletList">
<li>The definition of "ride height" for foiling sensors has been changed. Instead of showing
the ride height calculated by the sensor we are now showing the minimum of the port and
starboard ride heights. Therefore, this new definition of ride height can be seen as the
distance of lowest hull part from the water.
</li>
</ul>
<h5 class="articleSubheadline">January 2017</h5>
<ul class="bulletList">
<li>An issue with wind-up map rotation which sometimes left the map centered over the African continent has been fixed.
</li>
</ul>
<h5 class="articleSubheadline">October 2016</h5>
<ul class="bulletList">
<li>An issue with live re-calculation of the maximum speed over ground (SOG) values has been fixed.
In some circumstances it was possible that new live data did not properly update the MaxSOG figures.
Since this depended on the timing of read and write access it could have led to different values
for different server instances, giving confusing results. The fix provided now ensures that
the MaxSOG values are properly evaluated when new data has arrived.
</li>
<li>The buoy zone radius is now determined by a configurable regatta property. By default, it is
set to two hull lengths, and a regatta administrator may choose to change it to any other factor
such as 3 or 2.5. This determines the default used in the race viewer (RaceBoard.html). Users can
still change the radius in the map settings to any arbitrary number, deviating from what has
"officially" been set for the regatta.
</li>
</ul>
<h5 class="articleSubheadline">August 2016</h5>
<ul class="bulletList">
<li>In order to prepare user-specific configuration support in additional parts of the application,
various modules now require a user account to be accessed. Please sign up and log in to use their
full functionality as usual. The following modules are affected:
<ul>
<li><tt>/gwt/Simulator.html</tt></li>
<li><tt>/gwt/DataMining.html</tt></li>
</ul>
</li>
</ul>
<h5 class="articleSubheadline">July 2016</h5>
<ul class="bulletList">
<li>The Wind arrow in the top left corner of the race map / player now serves as a toggle button
for the wind streamlet overlay which can still also be reached through the map settings.
</li>
<li>If you want to analyze a race, the navigation slightly changed. The button, which is shown for tracked
races won't lead you to the race board directly, but provides a small menu. There you can choose from
several pre-defined analysis, which will launch the race board with respective configurations:
<ul>
<li>Replay/Watch live: Launches race board in play mode starting at race start, if race is already
finished (Replay) or at the actual time, if the race is currently live (Watch live)
</li>
<li>Race analysis: Shows the race results by setting the timer to the end of the race.
<i>This option is only available, if the race is already finished.</i>
</li>
<li>Start analysis: Shows the race start 10 sec after start and shows the speed over ground (SOG) of
the best starters within the competitor chart (zoomed to start).
<i>This option is only available, if the race is already started.</i>
</li>
<li>Winning lanes: Shows the full lanes of the best competitor, if race is already finished or the of
leading competitors if the race is currently live.
</li>
</ul>
</li>
<li>On the user profile page you now can maintain a list of you favorite boat classes and competitors.
You can also opt in for notification mails about your favorites to stay informed.
A requirement for notification mails to work for you is that you entered a valid mail address
to your user profile and verified the address by clicking the link that has been sent via email.
</li>
<li>Users now may choose their preferred language on the user profile page.
Currently only the mail notification mechanism will accept this setting to send mails in the preferred
language. This also prepares additional language sensitive feature, e.g. automatically switching the
website to your preferred language when logged in.
</li>
<li>The leaderboard now has a new measure value called "Ride height", which shows the ride height on
foils of a competitor, if respective data is available (currently ESS only). This information can
also be shown in the competitor chart on the RaceBaord page.
</li>
<li>To visualize multiple information at once, the competitor chart on the RaceBaord page can now be
configured to show two Y-axes.
</li>
</ul>
<h5 class="articleSubheadline">May 2016</h5>
<ul class="bulletList">
<li>It is now possible to follow races on the RaceBoard, even if there's no wind data available.</li>
</ul>
<h5 class="articleSubheadline">April 2016</h5>
<ul class="bulletList">
<li>The <tt>/gwt/RaceBoard.html</tt> entry point now accepts an optional parameter "t" in the form
<tt>[[hh:]mm:]ss</tt>, for example <tt>t=12:34</tt> to place the timer so much after the race start
if the viewer comes up in Replay mode. You may also use just seconds, as in <tt>t=1234</tt> which
makes specifying a time <em>before</em> the start somewhat easier (<tt>t=-123</tt> instead of
<tt>t=-02:-03</tt>).
</li>
</ul>
<h5 class="articleSubheadline">February 2016</h5>
<ul class="bulletList">
<li>The website now features a user registration and authentication interface.
User accounts are currently used to grant special permissions to administrators and moderators.
New user specific features (e.g. preferences) will be available in the next couple of months.
</li>
<li>The RaceBoard URL Parameter "canReplayDuringLiveRaces" has been removed.
This parameter was used to allow users to trigger autoplay for live races on non-live points in time.
The permission "CAN_REPLAY_DURING_LIVE_RACES" and role "moderator" now grant this functionality to users.
</li>
<li>Mark positions are no longer interpolated between fixes. Instead, when a mark's track is asked for the
mark's position, the last known position at or before the time point requested will be used. The
"jumping" of marks in the UI that may otherwise be caused by this is ameliorated by introducing
the same CSS animation for the marks that is already used for the boats.
</li>
</ul>
<h5 class="articleSubheadline">January 2016</h5>
<ul class="bulletList">
<li>Show maneuver markers on the race board by default
(tacks, jibes and penalty circles) with a smaller icon, which
shows an info bubble when the marker is clicked.
The icon turns green if the direction change is to starboard
and red if to port.
</li>
</ul>
<h5 class="articleSubheadline">December 2015</h5>
<ul class="bulletList">
<li>The advantage line is now animated and moves smoothly with the boats.
</li>
<li>New line hover behavior in race map. When the mouse is moved onto a line, for example the start line, a transparent,
wider line is activated behind it that acts as a buffer for the thin line, where the text displayed by the hover is still shown.
This makes it easier to hover over the lines. These wider lines can be made semi transparent instead of transparent
by unticking the corresponding checkbox in the settings of the race map. Furthermore the stroke weight of the wider lines can be
configured in the same settings dialog.
</li>
<li>The race viewer (RaceBoard.html) now intelligently chooses a layout that suits the screen size.
When only little horizontal space is available, only the sail number and nationality but not the
competitor's long name is displayed by default. When vertical space is scarce, competitor and wind
charts are disabled and more horizontal space can be given to the time slider at the bottom. The
leaderboard section can then be scaled to smaller widths and will reduce the column widths better
than in the past.
</li>
<li>With the changes described above, the RaceBoard is now also accessible from the races page on mobile devices.
</li>
<li>For overall / series leaderboards such as a national sailing league or the Extreme Sailing Series, the
live status of the overall leaderboard is now indicated. As long as a live race is ongoing, the respective
column in the overall leaderboard is highlighted, and a comment is displayed that indicates which regatta
is currently still live.
</li>
</ul>
<h5 class="articleSubheadline">November 2015</h5>
<ul class="bulletList">
<li>The smartphone page provides some structural navigation items for several pages in the upper right burger menu.
You will see links to e.g. events, regattas and series when navigating on event and series pages.
</li>
<li>It is now possible to filter for competitors in the race list of events/regattas.
If competitor information is available, there is a filter box in the upper right corner of the races page.
The filter is searching for competitors by sail ID as well as the competitor's name.
</li>
<li>Regattas can now be filtered by boat category in the smartphone page.
The filter will be shown for events with multiple regattas if there is more than on boat category associated with the event.
</li>
</ul>
<h5 class="articleSubheadline">October 2015</h5>
<ul class="bulletList">
<li>A variety of tablets will now show the desktop page by default.
The mobile version of the website is primarily designed to be used on smartphones,
but you can manually switch back to the mobile version by clicking the corresponding link in the page footer.
</li>
<li>There is some amount of caching on the web page to reduce the consumed traffic for mobile users.
Contents aren't loaded again on each page switch. This improves the traffic consumption and loading speed on fast page switches.
</li>
<li>The fullscreen image viewer (start page and event media page) can automatically slide through the images.
This can be turned on by clicking the corresponding icon in the upper right corner of the fullscreen viewer.
</li>
<li>There is an optimized search and results page for mobile devices now.
</li>
</ul>
<h5 class="articleSubheadline">September 2015</h5>
<ul class="bulletList">
<li>Races in the event/regatta race list can now be shown in 'competition format'.
In this format the races and flights are shown in a structural view.
In contrast to the default list view there is no grouping by live and finished states and all
(including upcoming) races/flights are shown in one structural view.
</li>
<li>The mobile page now provides regatta overview pages for events having multiple regattas.
This page shows live races, an excerpt of the current results as well as statistics for the regatta.
</li>
</ul>
<h5 class="articleSubheadline">August 2015</h5>
<ul class="bulletList">
<li>Series events appear as only one entry in the events list, with a marker that
this is only one of a certain number of events in a series. This way, the many series events
don't produce clutter in the events list.
</li>
<li>Bug fix: memory leaks for distance cache have been solved. The issue could
also lead to endless recursions, ultimately leading to server lock-ups.
</li>
<li>Bug fix: master data import detached race logs and regatta logs from database.
Log entries added after import on the importing server were not written to the
database and therefore were lost upon server restart.
</li>
<li>There is a smartphone-optimized version of the "solutions" page known from the desktop.
</li>
<li>The smartphone event history now has a slider that allows swiping through the events of a year.
</li>
</ul>
<h5 class="articleSubheadline">July 2015</h5>
<ul class="bulletList">
<li>Major improvements to the web page, with specific enhancements for smartphone
users. Race lists are now sortable by various criteria by clicking on the column
headers. A new fancy progress indicator now also provides visual feedback for
multi-fleet regattas. A news ticker box has been implemented, and the event
overview page can hold a video player on its stage. The event media gallery section
has been greatly improved.
</li>
<li>Fixed a bug in the wind streamlet visualization. Now the wind streamlets
correctly show the wind at the location selected also in wind-up mode.
</li>
<li>Added the "Course Geometry" option to the map display. With this option selected,
the map will show an info text for each leg and the start and finish line (only
for one of start/finish line if they are identical), telling the length of the leg
or line, respectively, and its angle to the wind. For the start and finish line
the advantage of the favored side is shown in meters.
</li>
<li>The SAP Sailing Analytics start page is now available in the smartphone version.
This page features some events that are currently interesting as well as showing a
quickfinder for a variety of recent and upcoming events.
Previously, the start page redirected to the event history.
</li>
</ul>
<h5 class="articleSubheadline">May 2015</h5>
<ul class="bulletList">
<li>Competitors can have a separate flag image, such as a club flag to be displayed instead of
the nationality flag usually displayed with the sail number. This enables national league events
to display the club flags consistently when all nation flags would be the same anyway.
</li>
<li>The wind estimation from the tracked courses over ground has been improved. It now takes into
account any polar data available for the boat class concerned. In particular, this helps to rule
out very unlikely gybing angles in downwind clusters that otherwise could have led to an incorrect
split of the downwind fleet into two clusters where actually all boats were on the same tack.
</li>
<li>Some significant improvements have been applied to the simulator feature which is embedded in the
race viewer since some time now. The simulator now copes with very shifty wind conditions and finds
solutions in scenarios where an upwind leg flips to a reach or even into a downwind leg over time.
</li>
</ul>
<h5 class="articleSubheadline">April 2015</h5>
<ul class="bulletList">
<li>There is a new map setting "Wind-Up (show wind from top of map)". Selecting it will
align the map's top with the combined true wind average direction around the time currently
selected in the time slider. In this new view there are limitations as to how far you can zoom
out. Furthermore, you will not be able to use the Satellite view with this setting. Tip:
if you want to re-align to wind-up after a major wind shift, un-check the setting, confirm
the setting change, then set it again. A compass shows the true north orientation. Clicking
the compass will toggle between wind-up and north-up display.
</li>
<li>Bug fixes around addition of the "Regatta Rank" column in the leaderboard which could have
distorted the leaderboard's column layout under certain conditions.
</li>
<li>Bug fix for cumulative points in the leaderboard ("Show added scores for each entry in leaderboard").
With this setting, scores of competitors not tracked in the current race were not correctly represented
and may have stayed blank.
</li>
<li>Bug fix for the leg counter in the regatta page: when the first boat has finished, the leg counter went
to zero (e.g., "0 of 8"). This has now been fixed, and it will show, e.g., "8 of 8" upon the first boat
crossing the finish line.
</li>
</ul>
<h5 class="articleSubheadline">March 2015</h5>
<ul class="bulletList">
<li>Added competitor chart type for Course Over Ground (COG)
</li>
<li>Added leaderboard column and competitor chart entry for distances considering
gate starts. With gate starts, late starters sail a shorter distance in their first
leg. To still make distances comparable, a new metric has been introduced that for a gate
start adds the distance between the port side of the start line and the position where
the competitor started to the distance sailed in the first leg.
</li>
<li>The weights of the wind measurement units, the wind estimation and manual wind inputs by the
race committee are now used properly for the wind streamlet visualization. This fixes issues
with exaggerated weights for an estimated wind direction of very low confidence.
</li>
<li>The expand/collapse toggle button in the race viewer's time panel has correct arrows now.
</li>
</ul>
<h5 class="articleSubheadline">February 2015</h5>
<ul class="bulletList">
<li>The leaderboard offers a new column: "Start delay." It shows the number of seconds that passed
since the race start and the time the competitor passes the start mark / start line. As usual,
you can sort for this column. For line starts, this is a fairly direct measure of start quality
except for port starts which may have to be considered differently. For gate starts, you can use
this measure to find out about early and late starters and dig into possible correlations between
results and start time point.
</li>
</ul>
<h5 class="articleSubheadline">January 2015</h5>
<ul class="bulletList">
<li>The race viewer ("RaceBoard.html") now offers a new option in the map settings:
"Show simulation overlay". Checking this feature will simulate the current leader's leg
using different strategies and using the wind as it has been measured and estimated.
This feature becomes particularly interesting when combined with the "Show wind streamlets overlay"
feature that visualizes the spatial distribution of the wind field over the course.
</li>
<li>The race viewer is now more resilient against exceptional conditions such as the missing
of all wind information and the missing of a course with its waypoints. If no wind information
is available at all (no wind direction indicator is then shown in the upper left corner of
the map) then all ranking and distances will be based on a projection onto the course instead
of onto the wind direction, even for upwind and downwind legs.
</li>
</ul>
<h5 class="articleSubheadline">September 2014</h5>
<ul class="bulletList">
<li>The search feature now also includes results from currently ongoing events that may not yet be
included in our archive. Search keywords are now properly used in constructing the query.
</li>
</ul>
<h5 class="articleSubheadline">August 2014</h5>
<ul class="bulletList">
<li>The 2D race viewer, also known as the "Race Board," now comes with a fresh, new design. The orange
checkbox toggle buttons that open or close views such as the leaderboard or the charts for wind
and competitor comparison, have been replaced by tabs with "&gt;&gt;" symbols. Clicking on those
will expand or collapse the respective view. The views can now be resized using the handles attached
to the dividers separating the views. They work much better on mobile and touch devices than the splitters
used previously did. The settings buttons that used to be integrated with the orange toggle
checkbox buttons have now been moved into the views that they control. For example, the map settings
button is now displayed on the map, below the zoom control on the right map border.
</li>
<li>The leaderboard now has an improved search and filter control. Above the table displaying the
competitors and their figures is a new field with a text input that you can use to filter for
competitors, by their sail number or their name. While filtered, use the checkboxes in front of
the table row to add the competitor to or remove it from the selection. Use the "X" button to the
right of the search input field to clear the search. To the left of the search input field is a
filter symbol that you can use to select and create advanced filtering rules, such as filtering
by rank or nationality. Left of the filter button is the leaderboard settings button.
</li>
<li>The leaderboard has a new first column: a selection check box. We hope this makes it easier in
particular for users on mobile or other touch devices to select more than one competitor at a time.
The old-style multi-selection using the Control and Shift keys, as known well from typical desktop
file explorers, still work like they did before. Note that it is possible to sort for the checkbox
column, putting the selected competitors at the top of the leaderboard.
</li>
<li>When using the auto-zoom feature from the map settings, boats and wind streamlets will now
continue to move smoothly as long as the map only needs panning. Please also note that auto-zooming
to "tails" may slow down the display considerable, particularly when tracking at high frequency.
</li>
<li>When viewing a single race, the leaderboard now by default does no longer show the regatta rank as
the first column (and not as the second one either, given that the first column now usually shows
the checkboxes used for selecting items). If you'd like to get the regatta rank column back, also when
looking at a single race, please choose it from the leaderboard settings dialog where you can find it
in the "Overall Details" section. It may still be a nice column to have when following the last race
in a regatta. If you're just interested in the relative regatta rank ordering you may as well sort by
the right-most total points column which (surprising to some but welcome by most) sorts by the regatta
rank and not by the plain points sum (taking into account medal race and gold/silver/... fleet participations).
</li>
</ul>
<h5 class="articleSubheadline">July 2014</h5>
<ul class="bulletList">
<li>The advantage line now uses the wind at the position in the middle of the leg that the
competitor to which the line is attached is currently sailing in. Before, the "global" wind
average was used which is still displayed in the top left corner of the map. You can understand
the details by turning on the "Streamlet" wind visualization in the Map Settings.
</li>
<li>Faster wind streamlet loading: when you activate the streamlets for wind visualization, they now
display much faster.
</li>
<li>Faster boat position display: We found that what you're probably the most interested in when
you first open a race map or move the time slider to some very different point in time is the
position of the boats at that point in time; and you want to know it fast.
The tails which show how the boats got there are also interesting, but you won't blame us as much
if it takes a little bit to update them. So we've separated the placement of the boats from the
drawing of their tails. Now you may blame us for the tails temporarily not being attached to the
boats anymore ;-).
</li>
</ul>
<h5 class="articleSubheadline">June 2014</h5>
<ul class="bulletList">
<li>In the "Maps" settings in the RaceBoard page there is a new checkbox named
"Show wind streamlets overlay." If you select it and wait for about a minute,
a complete wind field will be calculated and visualized as an overlay on top
of the race course display. It reacts to you moving the time slider and shows
how the wind differs at different locations on the course. Stronger and faster-moving
streamlets mean stronger breeze.
</li>
<li>Wind is now calculated location-based. At locations closer to a sensor, that sensor
has a higher influence on the local wind field than sensors further away. The wind
estimations based on the courses sailed are assigned a location that corresponds with
the "center of gravity" of the field that was used for the estimation. For example,
if the fleet is approaching the windward mark and the wind sensor in on the committee
boat on the leeward side of the course, the course-based estimation influences the
assumed wind field more on the windward side whereas the sensor counts more for
the leeward side.
</li>
<li>Switched to the latest version of the Highcharts charting framework. This leads to improvements
especially on mobile devices. Charts can be zoomed using pinch gestures now.
</li>
<li>To support the use of the SAP Sailing Analytics also for live video streaming formats, we
introduced a new URL parameter <tt>viewShowMapControls</tt> for the <tt>Spectator.html</tt>
and the <tt>RaceBoard.html</tt> pages. Setting this parameter to "false"
will suppress the zoom, pan and map type selection
controls on the map display. Example:
<a href="http://danishleague2014.sapsailing.com/gwt/Spectator.html?leaderboardGroupName=Danish%20League%202014&showRaceDetails=true&viewShowMapControls=false">http://danishleague2014.sapsailing.com/gwt/Spectator.html?leaderboardGroupName=Danish%20League%202014&showRaceDetails=true&viewShowMapControls=false</a>.
When used for the <tt>Spectator.html</tt> URL, this will apply
to all <tt>RaceBoard.html</tt> pages (all races) reached from there. Of course, the map
can still be panned and zoomed using the usual mouse gestures. A left double click zooms
in, a right double click zooms out. Zoom can also be controlled using the mouse wheel.
And panning works by clicking on the map and dragging the mouse. Without the map type
control, switching to satellite view is not possible (keep this in mind when tracking
races in China, see below for Google's random coordinate transformations there).
</li>
</ul>
<h5 class="articleSubheadline">May 2014</h5>
<ul class="bulletList">
<li>Fixed an issue with the wind speed charts. Charts that only had a single, unchanged
wind speed crashed the browser. The solution scales the right hand side Y-axis denoting
the wind speed such that it always starts at 0kts.
</li>
<li>Analysis of tacks and jibes around mark passings has now been improved. Before,
a jibe followed by a tack that turns in the same direction (e.g., to port) so far
was always called a penalty maneuver. Now, if a mark passing separates such a tack
from such a jibe, no penalty is called anymore, and the two maneuvers are identified
correctly.
</li>
<li>Another note on maneuver analysis: we received questions regarding the maneuvers
involved in clearing an OCS. The SAP Sailing Analytics treat a clearing of
an OCS, going back behind the start line and crossing it again, as if the competitor
started again. In particular this means that all maneuvers prior to this new start
do not count for the metrics; neither does the distance sailed between the first (OCS)
start and the clear start.
</li>
<li>A hint regarding the "distance to start line" metrics: please keep in mind that currently this
measurement is calculated as the distance of the GPS tracker from the line. Unless the tracker
is mounted near the bow (or at the bow sprit, if any), the distance calculated this way will
be greater than what counts for crossing the line. Ordering, comparing and aggregating over this
figure still makes sense as long as you remember this detail.
</li>
<li>Introduced a new leaderboard entry "XTE +/-" featuring a signed cross-track error which shows
for a leg and also aggregated for an entire race on which side of the leg a competitor stayed on
average. Currently, the "side" is defined as viewing in the leg's direction, so "left" for a downwind
leg is the same side as "right" for the corresponding upwind leg.
</li>
</ul>
<h5 class="articleSubheadline">April 2014</h5>
<ul class="bulletList">
<li>China races, such as in Qingdao, may have the map background displayed at slightly wrong locations.
This is due to an intentional random transformation that Google applies to GPS locations for
China. You can circumvent this issue by switching to satellite view where the map background is displayed
correctly. <img src="/images/mapsatellite.png"/>
</li>
<li>Fixed a bug that in some cases has caused the wind chart, the advantage line, the wind indicator on the map
and the leaderboard to use slightly different wind values. The problem was related to different averaging and
damping methods used for these values which has now been aligned.
</li>
<li>Fixed a bug that caused the time spent in a leg, race or regatta to always show the time at the end of the leg
if the leg has been started by the competitor at the time point queried, e.g., by the time slider. While this
did not have any serious effects in live mode, for replay races this left the leg time and race time constant
at the total time spent so far up to the end of the last leg the competitor had started at that time.
</li>
</ul>
<h5 class="articleSubheadline">March 2014</h5>
<ul class="bulletList">
<li>Added a new setting for a leaderboard called "Show added scores". Using this feature one
can display added scores for each leaderboard entry. An added score is the sum over all scores
from the first column to the current column. One can describe it as an intermediate overall score.
This feature can be used to know the score until a specific race while another race is live and
constantly changes the overall score.</li>
<li>Added tooltips for leaderboard settings.</li>
<li>Fixed HighPointOverall scheme to also break a tie by looking at the ranks of the last regatta.</li>
<li>Progress indicator when expanding a race in the RaceBoard view</li>
<li>Leaderboard column "GPS Lag" shows how far a competitor's GPS signal lags behind the average
tracking quality. A background color bar classifies into good/ok/bad (green/yellow/red).</li>
</ul>
<h5 class="articleSubheadline">February 2014</h5>
<ul class="bulletList">
<li>Fixed an issue with boat rotation on Safari browsers</li>
<li>Maneuver analysis now counts tacks and jibes based on wind
at boat position and not based on leg type; subsequently,
penalty circle analysis is now based on a tack and a jibe
in close succession instead of a full circle.</li>
<li>Regatta rank and overall rank in competitor chart of leaderboard panel
has been fixed; it did erroneously display the rank of the particular
race, not the regatta/overall rank after that race.</li>
<li>Regatta overview now also displays all regatta leaderboards</li>
<li>Boat colors match tail colors</li>
<li>When selecting boats in leaderboard, de-selected boats' tails turn grey</li>
</ul>
<h5 class="articleSubheadline">January 2014</h5>
<ul class="bulletList">
<li>Boat movement animated smoothly</li>
<li>Start and Finish line show line parameters (length, angle to wind,
advantage) when mouse hovers over them</li>
<li>Start line has a count-down indicator displayed next to it
before the start. The count-down is also displayed underneath the time
slider</li>
<li>Competitor and Wind charts show pre-start data where possible</li>
<li>Competitors can be assigned a fixed color which they will keep
for all races in a regatta</li>
<li>By default, videos are now shown in a moveable, resizable box embedded in
the web page. For a two screen setup, the embedded box can be popped out to a
dedicated browser window.</li>
<li>Official score corrections for historic races are only applied after
the respective competitor finished the race. Therefore, in-between race
scores now correspond with tracking, regardless of final corrected score.</li>
<li>By using negative playback speed (e.g., -1), replays can be run backwards.</li>
</ul>
</div>
</div>
</body>
</html>