Merge branch 'master' into ubilabs--buoy_positioning_app--develop

Conflicts:
	java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch
	java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch
	java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch
	java/com.sap.sailing.gwt.ui/SailingGWT.launch
	java/com.sap.sse.gwt.theme/SSETheme_GWT sdm.launch
	java/com.sap.sse.gwt.theme/SSETheme_GWT.launch
This commit is contained in:
Axel Uhl committed 2015-03-24 20:37:36 +01:00
commit ca254c72e3
334 files changed
+6485 -3189

No files matched your search

+9 -4
View File
@@ -24,9 +24,14 @@ if __name__ == '__main__':
# using options like re.IGNORECASE will render the search almost unusable
prefix_pattern = re.compile('^%s' % prefix)
for fix in wind_tracks.find({'REGATTA_NAME' : prefix_pattern}):
data = {}
try:
data = (fix['REGATTA_NAME'], str(fix['WIND']['LAT_DEG']),
str(fix['WIND']['LNG_DEG']), str(fix['WIND']['TIME_AS_MILLIS']), str(fix['WIND']['KNOT_SPEED']),
str(fix['WIND']['DEGREE_BEARING']))
except:
print 'Exception for dataset %s' % data
continue
wind_source_id = fix['WIND_SOURCE_NAME']
if hasattr(fix, 'WIND_SOURCE_ID'):
wind_source_id += '-'+fix['WIND_SOURCE_ID']
@@ -37,11 +42,10 @@ if __name__ == '__main__':
data_container.append(data)
if counter % 1000 == 0:
sys.stdout.write('.')
sys.stdout.flush()
if counter % 100000 == 0:
sys.stdout.write(str(counter))
sys.stdout.flush()
print '\nSorting %s fixes in memory for regattas %s' % (len(data_container), regattas)
print '\nFlushing to store, sorting %s fixes in memory for regattas %s' % (len(data_container), regattas)
sorted_fixes = sorted(data_container, key=lambda windfix: windfix[3], reverse=True)
counter = 0; last_timepoint = {}
print 'Starting export of %s wind fixes in interval %sms to %s' % (len(sorted_fixes), long(interval), exportfile)
@@ -54,7 +58,8 @@ if __name__ == '__main__':
if counter % 1000 == 0:
sys.stdout.write('#')
sys.stdout.flush()
# free memory
data_container = []
print '\nExported %s fix(es) that have a minimum time distance of %s milliseconds' % (counter, interval)
csv.close()
print 'Exported the following prefix-matching regattas: %s' % last_timepoint.keys()
+1 -1
View File
@@ -20,7 +20,7 @@ start_tmux() {
start_servers() {
NUMBER_OF_INSTANCES=`echo "$JAVA_START_INSTANCES" | wc -w`
MEMORY_PER_INSTANCE_IN_MB=`cat /proc/meminfo | grep MemTotal | awk '{printf("%i\n", ($2 / 1024 - 1500) / '$NUMBER_OF_INSTANCES' );}'`
MEMORY_PER_INSTANCE_IN_MB=`cat /proc/meminfo | grep MemTotal | awk '{printf("%i\n", ($2 / 1024 * 0.75 - 1500) / '$NUMBER_OF_INSTANCES' );}'`
echo "Using ${MEMORY_PER_INSTANCE_IN_MB}MB as default heap size per instance."
for conf in $JAVA_START_INSTANCES; do
echo "Checking for amazon update..."
@@ -70,22 +70,11 @@
line-height: 0pt;
}
.competitorEditButton {
.eventLogo {
position: absolute;
height: 23.5pt;
width: 23.5pt;
right: 10pt;
top: 17.5pt;
right: 12.5px;
bottom: 10px;
height: 22.5pt;
z-index: 1;
}
.competitorLabel{
position: absolute;
width: 200pt;
height: 23.5pt;
margin-top: 17.5pt;
right: 43.5pt;
font-size: 12pt;
font-color: black;
text-align: right;
line-height: 23.5pt;
}
@@ -1,15 +1,17 @@
<!doctype html>
<!-- The DOCTYPE declaration above will set the -->
<!-- browser's rendering engine into -->
<!-- "Standards Mode". Replacing this declaration -->
<!-- with a "Quirks Mode" doctype may lead to some -->
<!-- differences in layout. -->
<html>
<head>
<!--[if lt IE 9]>
<meta http-equiv="REFRESH" content="0; url=http://www.sapsailing.com/browser-info.html">
<![endif]-->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link href='src/main/resources/images/dashboardicon.png'
rel='apple-touch-startup-image'>
<link href='src/main/resources/images/dashboardicon.png' rel='apple-touch-icon'>
<link rel="shortcut icon" type="image/x-icon" href="src/main/resources/images/dashboardicon.png" />
<link href='images/dashboardicon.png' rel='apple-touch-icon'>
<title>ESS Dashboard</title>
@@ -19,7 +21,7 @@
<script type="text/javascript"
src="com.sap.sailing.dashboards.gwt.RibDashboard/com.sap.sailing.dashboards.gwt.RibDashboard.nocache.js"></script>
<!-- Disables vertical scrolling whole website over edge on apple browsers -->
<!-- Disables scrolling whole website vertically over edge on apple browsers -->
<script type="text/javascript">
document.ontouchmove = function(event){
event.preventDefault();
+9 -3
View File
@@ -4,7 +4,10 @@
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
<booleanAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT_AUTO" value="true"/>
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
</listAttribute>
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
@@ -19,6 +22,8 @@
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry containerPath=&quot;org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6&quot; path=&quot;1&quot; type=&quot;4&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.dashboards.gwt/src/main/resources&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
@@ -29,13 +34,14 @@
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-servlet.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.google.gwt.osgi/lib/gwt-user.jar&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.domain.common/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sailing.gwt.ui/src/main/java&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/org.moxieapps.gwt.highcharts/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
<listEntry value="&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; standalone=&quot;no&quot;?&gt;&#10;&lt;runtimeClasspathEntry internalArchive=&quot;/com.sap.sse.gwt.adminconsole/src&quot; path=&quot;3&quot; type=&quot;2&quot;/&gt;&#10;"/>
</listAttribute>
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-nosuperDevMode -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort auto com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -war &quot;${project_loc:com.sap.sailing.dashboards.gwt}&quot; -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI &quot;${gwt_remote_ui_server_port}:${unique_id}&quot; -logLevel INFO -codeServerPort 9998 com.sap.sailing.dashboards.gwt.RibDashboard"/>
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-XstartOnFirstThread -Xmx2048m"/>
</launchConfiguration>
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

@@ -0,0 +1,57 @@
package com.sap.sailing.dashboards.gwt.client;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Image;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.shared.EventDTO;
public class EventLogo extends Image {
private static final Logger logger = Logger.getLogger(RibDashboardPanel.class.getName());
protected EventLogo() {
super();
this.getElement().addClassName("eventLogo");
}
public static EventLogo getEventLogoFromEventId(SailingServiceAsync sailingServiceAsync, String eventId) {
final EventLogo eventLogo = new EventLogo();
eventLogo.getElement().setAttribute("src", "someURL");
try {
final UUID eventUUID = UUID.fromString(eventId);
sailingServiceAsync.getEventById(eventUUID, true, new AsyncCallback<EventDTO>() {
@Override
public void onSuccess(final EventDTO event) {
logger.log(Level.INFO, "GOT EVENT: ");
final String logoUrl = event.getLogoImageURL();
// Scheduler.get().scheduleDeferred(new ScheduledCommand() {
//
// @Override
// public void execute() {
if (logoUrl != null) {
eventLogo.getElement().setAttribute("src", logoUrl);
} else {
eventLogo.getElement().getStyle().setVisibility(Visibility.HIDDEN);
}
// }
// });
}
@Override
public void onFailure(Throwable caught) {
eventLogo.getElement().getStyle().setVisibility(Visibility.HIDDEN);
}
});
} catch (IllegalArgumentException e) {
eventLogo.getElement().getStyle().setVisibility(Visibility.HIDDEN);
}
return eventLogo;
}
}
@@ -5,26 +5,10 @@ import java.util.Collections;
import java.util.Date;
import java.util.List;
import com.google.gwt.dom.client.Document;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Cookies;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.sap.sailing.dashboards.gwt.client.actions.GetRibDashboardRaceInfoAction;
import com.sap.sailing.dashboards.gwt.client.bottomnotification.BottomNotification;
import com.sap.sailing.dashboards.gwt.client.device.Location;
import com.sap.sailing.dashboards.gwt.client.popups.RacingNotYetStartedPopup;
import com.sap.sailing.dashboards.gwt.client.popups.RacingNotYetStartedPopupListener;
import com.sap.sailing.dashboards.gwt.client.popups.competitorselection.CompetitorSelectionPopup;
import com.sap.sailing.dashboards.gwt.client.popups.competitorselection.CompetitorSelectionPopupListener;
import com.sap.sailing.dashboards.gwt.client.startanalysis.NewStartAnalysisListener;
import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener;
import com.sap.sailing.gwt.ui.client.RaceSelectionProvider;
@@ -39,126 +23,48 @@ import com.sap.sse.gwt.client.player.TimeListener;
* @author Alexander Ries
*
*/
public class RibDashboardDataRetriever implements RacingNotYetStartedPopupListener, CompetitorSelectionPopupListener,
TimeListener, RaceSelectionProvider {
public class RibDashboardDataRetriever implements TimeListener, RaceSelectionProvider {
private int numberOfCachedStartAnalysisDTOs;
private String leaderboardName;
private ArrayList<RibDashboardDataRetrieverListener> dataRetrieverListener;
private ArrayList<NewStartAnalysisListener> newStartAnalysisListeners;
private ArrayList<RaceSelectionChangeListener> raceSelectionChangeListener;
private String selectedCompetitorName;
private CompetitorSelectionPopup competitorSelectionPopup;
private RacingNotYetStartedPopup popupRacingNotYetStarted;
private BottomNotification bottomNotification;
private FocusPanel competitorEditButton;
private Label competitorLabel;
private boolean shouldReloadStartAnalysis;
private AsyncActionsExecutor asyncActionsExecutor;
private final RibDashboardServiceAsync ribDashboardService;
private final Object MUTEX;
private static RibDashboardDataRetriever INSTANCE = null;
private static final String PARAM_LEADERBOARD_NAME = "leaderboardName";
private static final String KEY_SLECTED_TEAM_COOKIE = "selectedTeam";
private static final int SLECTED_TEAM_COOKIE_EXPIRE_TIME_IN_MILLIS = 60 * 1000 * 60 * 5;
public RibDashboardDataRetriever(RibDashboardServiceAsync ribDashboardService) {
initNonFinalMemberVariablesWithNoArgumentConstructor();
initRacingNotYetStartedPopup();
MUTEX = new Object();
this.ribDashboardService = ribDashboardService;
this.leaderboardName = Window.Location.getParameter(PARAM_LEADERBOARD_NAME);
numberOfCachedStartAnalysisDTOs = 0;
competitorSelectionPopup = new CompetitorSelectionPopup();
selectedCompetitorName = Cookies.getCookie(KEY_SLECTED_TEAM_COOKIE);
asyncActionsExecutor = new AsyncActionsExecutor();
initCompetitorLabelAndAddToRootPanel();
initBottomNotification();
}
public static RibDashboardDataRetriever getInstance(RibDashboardServiceAsync ribDashboardService) {
synchronized (Location.class) {
if (INSTANCE == null) {
if (ribDashboardService == null) {
throw new RuntimeException("Can not initialize data retriever without reference to ribdashboard service");
}
INSTANCE = new RibDashboardDataRetriever(ribDashboardService);
}
}
return INSTANCE;
}
private void initRacingNotYetStartedPopup() {
popupRacingNotYetStarted = new RacingNotYetStartedPopup();
popupRacingNotYetStarted.addListener(this);
}
private void initBottomNotification() {
bottomNotification = new BottomNotification();
RootPanel.get().add(bottomNotification);
}
private void initNonFinalMemberVariablesWithNoArgumentConstructor() {
dataRetrieverListener = new ArrayList<RibDashboardDataRetrieverListener>();
newStartAnalysisListeners = new ArrayList<NewStartAnalysisListener>();
raceSelectionChangeListener = new ArrayList<RaceSelectionChangeListener>();
}
private void loadLiveRaceInfoFromRibDashboadService() {
GetRibDashboardRaceInfoAction getRibDashboardRaceInfoAction = new GetRibDashboardRaceInfoAction(ribDashboardService, leaderboardName, selectedCompetitorName);
GetRibDashboardRaceInfoAction getRibDashboardRaceInfoAction = new GetRibDashboardRaceInfoAction(ribDashboardService, leaderboardName);
asyncActionsExecutor.execute(getRibDashboardRaceInfoAction, new AsyncCallback<RibDashboardRaceInfoDTO>() {
@Override
public void onSuccess(RibDashboardRaceInfoDTO result) {
switch (result.responseMessage) {
case OK:
popupRacingNotYetStarted.hide(/* remove blur effect */true);
case RACE_LIVE:
List<RegattaAndRaceIdentifier> singletonList = Collections
.singletonList(result.idOfLastTrackedRace);
setSelection(singletonList);
if (result.startAnalysisDTOList != null) {
int numberOfReceivedStartAnalysisDTOs = result.startAnalysisDTOList.size();
if (numberOfCachedStartAnalysisDTOs != numberOfReceivedStartAnalysisDTOs || shouldReloadStartAnalysis == true) {
shouldReloadStartAnalysis = false;
numberOfCachedStartAnalysisDTOs = numberOfReceivedStartAnalysisDTOs;
notifyNewStartAnalysisListener(result.startAnalysisDTOList, selectedCompetitorName);
}
}
if(competitorEditButton == null){
initCompetitorChangeButtonAndAddToRootPanel();
setCompetitorLabelText(selectedCompetitorName);
}
if (result.competitorNamesFromLastTrackedRace != null
&& result.competitorNamesFromLastTrackedRace.size() > 0
&& !competitorSelectionPopup.isShown()) {
competitorSelectionPopup.setCompetitorList(result.competitorNamesFromLastTrackedRace);
competitorSelectionPopup.addListener(RibDashboardDataRetriever.this);
}
notifyDataObservers(result);
break;
case NO_COMPETITOR_SELECTED:
popupRacingNotYetStarted.hide(true);
if (result.competitorNamesFromLastTrackedRace != null
&& result.competitorNamesFromLastTrackedRace.size() > 0
&& !competitorSelectionPopup.isShown()) {
competitorSelectionPopup.setCompetitorList(result.competitorNamesFromLastTrackedRace);
competitorSelectionPopup.addListener(RibDashboardDataRetriever.this);
competitorSelectionPopup.show();
}
break;
case NO_RACE_LIVE:
if (numberOfCachedStartAnalysisDTOs == 0) {
popupRacingNotYetStarted.showWithMessageAndImageAndButtonText("Racing not yet started",
RibDashboardImageResources.INSTANCE.watch(), "Retry");
} else {
setSelection(null);
}
break;
default:
break;
@@ -195,68 +101,6 @@ public class RibDashboardDataRetriever implements RacingNotYetStartedPopupListen
}
}
public void addNewStartAnalysisListener(NewStartAnalysisListener o) {
synchronized (MUTEX) {
if (o != null && !newStartAnalysisListeners.contains(o)) {
this.newStartAnalysisListeners.add(o);
}
}
}
public void removeNewStartAnalysisListener(NewStartAnalysisListener o) {
synchronized (MUTEX) {
this.newStartAnalysisListeners.remove(o);
}
}
public void notifyNewStartAnalysisListener(List<StartAnalysisDTO> startAnalysisDTOs, String selectedCompetitor) {
synchronized (MUTEX) {
for (NewStartAnalysisListener newStartAnalysisListener : newStartAnalysisListeners) {
newStartAnalysisListener.addNewStartAnalysisCardForCompetitor(startAnalysisDTOs, selectedCompetitor);
}
}
}
private void initCompetitorChangeButtonAndAddToRootPanel() {
if (competitorEditButton == null) {
competitorEditButton = new FocusPanel();
competitorEditButton.addStyleName("competitorEditButton");
Image settingsImage = new Image();
settingsImage.setResource(RibDashboardImageResources.INSTANCE.settings());
competitorEditButton.add(settingsImage);
competitorEditButton.addDomHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
competitorSelectionPopup.show();
}
}, ClickEvent.getType());
RootPanel.get().add(competitorEditButton);
}
}
private void initCompetitorLabelAndAddToRootPanel() {
competitorLabel = new Label();
competitorLabel.addStyleName("competitorLabel");
Document.get().getBody().appendChild(competitorLabel.getElement());
}
private void setCompetitorLabelText(String slectedCompetitor) {
competitorLabel.setText(slectedCompetitor);
}
@Override
public void popupButtonClicked() {
}
@Override
public void didClickedOKWithCompetitorName(String competitorName) {
this.selectedCompetitorName = competitorName;
Cookies.setCookie(KEY_SLECTED_TEAM_COOKIE, competitorName, new Date(new Date().getTime()
+ SLECTED_TEAM_COOKIE_EXPIRE_TIME_IN_MILLIS));
initCompetitorChangeButtonAndAddToRootPanel();
setCompetitorLabelText(competitorName);
shouldReloadStartAnalysis = true;
}
@Override
public void timeChanged(Date newTime, Date oldTime) {
loadLiveRaceInfoFromRibDashboadService();
@@ -33,10 +33,10 @@ public class RibDashboardEntryPoint extends AbstractEntryPoint<StringMessages> {
RibDashboardServiceAsync ribdashboardService = GWT.create(RibDashboardService.class);
EntryPointHelper.registerASyncService((ServiceDefTarget) ribdashboardService, RemoteServiceMappingConstants.WEB_CONTEXT_PATH, RemoteServiceMappingConstants.ribdashboardServiceRemotePath);
RibDashboardDataRetriever dataRetriever = RibDashboardDataRetriever.getInstance(ribdashboardService);
RibDashboardDataRetriever dataRetriever = new RibDashboardDataRetriever(ribdashboardService);
applyMGWTSettings();
RibDashboardPanel ribDashboardPanel = new RibDashboardPanel(ribdashboardService);
RibDashboardPanel ribDashboardPanel = new RibDashboardPanel(ribdashboardService, sailingService, dataRetriever);
RootLayoutPanel.get().add(ribDashboardPanel);
WindBotDataRetriever windBotDataRetriever = new WindBotDataRetriever(sailingService);
windBotDataRetriever.addNumberOfWindBotsChangeListeners(ribDashboardPanel);
@@ -33,9 +33,6 @@ public interface RibDashboardImageResources extends ClientBundle {
@Source("images/rightarrow_disabled.png")
ImageResource rightdisabled();
@Source("images/windarrow.png")
ImageResource windarrow();
@Source("images/compass.png")
ImageResource compass();
@@ -10,18 +10,19 @@ import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.googlecode.mgwt.ui.client.widget.carousel.Carousel;
import com.sap.sailing.dashboards.gwt.client.bottomnotification.BottomNotification;
import com.sap.sailing.dashboards.gwt.client.startanalysis.StartlineAnalysisComponent;
import com.sap.sailing.dashboards.gwt.client.startlineadvantage.StartLineAdvantageComponent;
import com.sap.sailing.dashboards.gwt.client.windchart.WindBotComponent;
import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
/**
* This class contains all components of the Dashboard and the basic UI Elements of the Dashboard, like top and bottom
@@ -57,7 +58,7 @@ public class RibDashboardPanel extends Composite implements RibDashboardDataRetr
@UiField
HTMLPanel rightwindbotcontainer;
@UiField
@UiField(provided = true)
public StartLineAdvantageComponent startLineCAComponent;
@UiField
@@ -69,19 +70,34 @@ public class RibDashboardPanel extends Composite implements RibDashboardDataRetr
@UiField
public HTMLPanel bottombar;
@UiField
public HTMLPanel windcharthint;
@UiField(provided = true)
public StartlineAnalysisComponent startanalysisComponent;
private BottomNotification bottomNotification;
private List<WindBotComponent> windBotComponents;
private StringMessages stringConstants;
private final String EVENT_ID_PARAMETER = "eventId";
public RibDashboardPanel(RibDashboardServiceAsync ribDashboardService) {
public RibDashboardPanel(RibDashboardServiceAsync ribDashboardService, SailingServiceAsync sailingServiceAsync, RibDashboardDataRetriever ribDashboardDataRetriever) {
windBotComponents = new ArrayList<WindBotComponent>();
RibDashboardDataRetriever.getInstance(ribDashboardService).addDataObserver(this);
initBottomNotification();
startanalysisComponent = new StartlineAnalysisComponent(bottomNotification, ribDashboardService);
ribDashboardDataRetriever.addDataObserver(this);
startanalysisComponent = new StartlineAnalysisComponent(ribDashboardService, sailingServiceAsync);
startLineCAComponent = new StartLineAdvantageComponent(ribDashboardDataRetriever);
stringConstants = StringMessages.INSTANCE;
initWidget(uiBinder.createAndBindUi(this));
initLogos();
windcharthint.getElement().setInnerText(stringConstants.dashboardWindChartHint());
windloadinghintleft.setInnerText(stringConstants.dashboardWindBotLoading());
windloadinghintright.setInnerText(stringConstants.dashboardWindBotLoading());
String eventId = Window.Location.getParameter(EVENT_ID_PARAMETER);
EventLogo eventLogo = EventLogo.getEventLogoFromEventId(sailingServiceAsync, eventId);
if(eventLogo != null){
Document.get().getBody().appendChild(eventLogo.getElement());
}
}
private void initLogos() {
@@ -89,16 +105,6 @@ public class RibDashboardPanel extends Composite implements RibDashboardDataRetr
logo.setResource(RibDashboardImageResources.INSTANCE.logo_sap());
logo.getElement().addClassName(style.logo());
Document.get().getBody().appendChild(logo.getElement());
Image esslogo = new Image();
esslogo.setResource(RibDashboardImageResources.INSTANCE.logoess());
esslogo.getElement().addClassName(style.extremelogo());
Document.get().getBody().appendChild(esslogo.getElement());
}
private void initBottomNotification() {
bottomNotification = new BottomNotification();
RootPanel.get().add(bottomNotification);
}
private void initWindBotComponents(List<String> windBotIDs,
@@ -122,8 +128,8 @@ public class RibDashboardPanel extends Composite implements RibDashboardDataRetr
windBotAddedCounterTOConsiderForPanels++;
if (windloadinghintleft.getStyle().getOpacity() != "0.0"
|| windloadinghintright.getStyle().getOpacity() != "0.0") {
windloadinghintleft.setInnerHTML("Wind bot not available.");
windloadinghintright.setInnerHTML("Wind bot not available.");
windloadinghintleft.setInnerHTML(stringConstants.dashboardWindBotNotAvailable());
windloadinghintright.setInnerHTML(stringConstants.dashboardWindBotNotAvailable());
}
}
addWindBotComponentsAsDataRetrieverListener(windBotDataRetrieverProvider);
@@ -18,13 +18,13 @@
<mgwtcarousel:Carousel ui:field="carousel">
<mgwtpanel:Panel styleName="{style.page}">
<g:HTMLPanel ui:field="leftwindbotcontainer" styleName="{style.collumn} {style.left}">
<div class="{style.windloadinghint}"><div ui:field="windloadinghintleft" class="{style.windloadinghinttext}">Loading Wind Bot Data...</div></div>
<div class="{style.windloadinghint}"><div ui:field="windloadinghintleft" class="{style.windloadinghinttext}"></div></div>
</g:HTMLPanel>
<g:HTMLPanel ui:field="rightwindbotcontainer"
styleName="{style.collumn} {style.right}">
<div class="{style.windloadinghint}"><div ui:field="windloadinghintright" class="{style.windloadinghinttext}">Loading Wind Bot Data...</div></div>
<div class="{style.windloadinghint}"><div ui:field="windloadinghintright" class="{style.windloadinghinttext}"></div></div>
</g:HTMLPanel>
<g:HTMLPanel styleName="{style.windcharthint}">Tap on chart to change interval.</g:HTMLPanel>
<g:HTMLPanel ui:field="windcharthint" styleName="{style.windcharthint}"></g:HTMLPanel>
</mgwtpanel:Panel>
<mgwtpanel:Panel styleName="{style.page}">
<g:HTMLPanel styleName="{style.collumn} {style.startanalysiscollumn}">
@@ -4,6 +4,5 @@ import com.google.gwt.resources.client.CssResource;
public interface RibDashboardPanelStyle extends CssResource {
String logo();
String extremelogo();
String start();
}
@@ -1,9 +1,17 @@
package com.sap.sailing.dashboards.gwt.client;
import java.util.List;
import com.google.gwt.user.client.rpc.RemoteService;
import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
public interface RibDashboardService extends RemoteService {
RibDashboardRaceInfoDTO getLiveRaceInfo(String leaderboardName, String competitorName) throws NoWindException;
RibDashboardRaceInfoDTO getLiveRaceInfo(String leaderboardName) throws NoWindException;
List<StartAnalysisDTO> getStartAnalysisListForCompetitorIDAndLeaderboardName(String competitorIdAsString, String leaderboardName);
List<CompetitorDTO> getCompetitorsInLeaderboard(String leaderboardName);
}
@@ -1,10 +1,18 @@
package com.sap.sailing.dashboards.gwt.client;
import java.util.List;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
public interface RibDashboardServiceAsync {
void getLiveRaceInfo(String leaderboardName, String competitorName, AsyncCallback<RibDashboardRaceInfoDTO> callback);
void getLiveRaceInfo(String leaderboardName, AsyncCallback<RibDashboardRaceInfoDTO> callback);
void getStartAnalysisListForCompetitorIDAndLeaderboardName(String competitorIdAsString, String leaderboardName, AsyncCallback<List<StartAnalysisDTO>> callback);
void getCompetitorsInLeaderboard(String leaderboardName, AsyncCallback<List<CompetitorDTO>> callback);
}
@@ -34,17 +34,6 @@
z-index: 1;
}
.extremelogo {
position: absolute;
left: 100%;
top: 100%;
width: 90pt;
height: 22.5pt;
margin-top: -31pt;
margin-left: -100pt;
z-index: 1;
}
.collumn {
position: absolute;
top: 5pt;
@@ -161,32 +150,6 @@
bottom: 0pt;
}
.windloadinghint {
position: absolute;
background-color: white;
opacity: 0.4;
width: 100%;
height: 100%;
vertical-align: middle;
text-align: center;
z-index: 0;
}
.windloadinghinttext {
position: absolute;
bottom: 0pt;
height: 15pt;
line-height: 15pt;
margin-top: 0pt;
vertical-align: middle;
font-size: 12pt;
font-family: "Arial";
width: 100%;
text-align: center;
top: 50%;
margin-top: -7pt;
}
.leaderboardpanel {
position: absolute;
top: 30%;
@@ -329,3 +292,29 @@ windchartcontainer {
left: 260%;
background-color: #0DBFFF;
}
.windloadinghint {
position: absolute;
background-color: white;
opacity: 0.4;
width: 100%;
height: 100%;
vertical-align: middle;
text-align: center;
z-index: 0;
}
.windloadinghinttext {
position: absolute;
bottom: 0pt;
height: 15pt;
line-height: 15pt;
margin-top: 0pt;
vertical-align: middle;
font-size: 12pt;
font-family: "Arial";
width: 100%;
text-align: center;
top: 50%;
margin-top: -7pt;
}
@@ -9,16 +9,14 @@ public class GetRibDashboardRaceInfoAction implements AsyncAction<RibDashboardRa
private final RibDashboardServiceAsync ribDashboardService;
private final String leaderboardName;
private final String competitorName;
public GetRibDashboardRaceInfoAction(RibDashboardServiceAsync ribDashboardService, String leaderboardName, String competitorName) {
public GetRibDashboardRaceInfoAction(RibDashboardServiceAsync ribDashboardService, String leaderboardName) {
this.ribDashboardService = ribDashboardService;
this.leaderboardName = leaderboardName;
this.competitorName = competitorName;
}
@Override
public void execute(AsyncCallback<RibDashboardRaceInfoDTO> callback) {
ribDashboardService.getLiveRaceInfo(leaderboardName, competitorName, callback);
ribDashboardService.getLiveRaceInfo(leaderboardName, callback);
}
}
@@ -39,18 +39,14 @@ public class BottomNotification extends FocusPanel {
bottomNotificationClickListeners = new ArrayList<BottomNotificationClickListener>();
}
public void initClickHander() {
}
public void show(String message, String backgroundColorAsHex, String textcolorAsHex, boolean dismissAfter20Seconds) {
public void show(BottomNotificationType bottomNotificationType) {
shown = true;
this.removeStyleName("bottomnotificationhidden");
this.addStyleName("bottomnotificationshow");
this.getElement().setInnerHTML(message);
this.getElement().getStyle().setBackgroundColor(backgroundColorAsHex);
this.getElement().getStyle().setColor(textcolorAsHex);
if (dismissAfter20Seconds) {
this.getElement().setInnerHTML(bottomNotificationType.getMessage());
this.getElement().getStyle().setBackgroundColor(bottomNotificationType.getBackgroundColorAsHex());
this.getElement().getStyle().setColor(bottomNotificationType.getTextColorAsHex());
if (bottomNotificationType.isShouldDisappearAfter20Seconds()) {
Timer timer = new Timer() {
public void run() {
hide();
@@ -0,0 +1,36 @@
package com.sap.sailing.dashboards.gwt.client.bottomnotification;
import com.sap.sailing.gwt.ui.client.StringMessages;
public enum BottomNotificationType {
NEW_STARTANALYSIS_AVAILABLE(StringMessages.INSTANCE.dashboardNewStartAnalysisAvailable(), "#F0AB00", "#000000", true);
private String message;
private String backgroundColorAsHex;
private String textColorAsHex;
private boolean shouldDisappearAfter20Seconds;
private BottomNotificationType(String message, String backgroundColorAsHex, String textColorAsHex, boolean shouldDisappearAfter20Seconds) {
this.message = message;
this.backgroundColorAsHex = backgroundColorAsHex;
this.textColorAsHex = textColorAsHex;
this.shouldDisappearAfter20Seconds = shouldDisappearAfter20Seconds;
}
public String getMessage() {
return message;
}
public String getBackgroundColorAsHex() {
return backgroundColorAsHex;
}
public String getTextColorAsHex() {
return textColorAsHex;
}
public boolean isShouldDisappearAfter20Seconds() {
return shouldDisappearAfter20Seconds;
}
}
@@ -0,0 +1,8 @@
package com.sap.sailing.dashboards.gwt.client.popups.competitorselection;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
public interface CompetitorSelectionListener {
public void didClickOKWithSelectedCompetitor(CompetitorDTO competitor);
}
@@ -17,6 +17,7 @@ import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
public class CompetitorSelectionPopup extends Composite implements HasWidgets, CompetitorTableRowSelectionListener {
@@ -44,10 +45,10 @@ public class CompetitorSelectionPopup extends Composite implements HasWidgets, C
@UiField
Button button;
private String currentCompetitorSelected;
private CompetitorDTO currentCompetitorSelected;
private boolean isVisible;
private List<CompetitorSelectionPopupListener> competitorSelectionPopupListener;
private List<CompetitorSelectionListener> competitorSelectionPopupListener;
private static CompetitorSelectionPopupUiBinder uiBinder = GWT.create(CompetitorSelectionPopupUiBinder.class);
@@ -55,17 +56,14 @@ public class CompetitorSelectionPopup extends Composite implements HasWidgets, C
}
public CompetitorSelectionPopup() {
competitorSelectionPopupListener = new ArrayList<CompetitorSelectionPopupListener>();
competitorSelectionPopupListener = new ArrayList<CompetitorSelectionListener>();
competitortable = new CompetitorTable(this);
isVisible = false;
initWidget(uiBinder.createAndBindUi(this));
}
public void setCompetitorList(List<String> competitorList){
public void show(List<CompetitorDTO> competitorList) {
competitortable.setTableContent(competitorList);
}
public void show() {
RootPanel.get().add(this);
this.button.setText("OK");
this.getElement().addClassName(style.popupshow());
@@ -84,19 +82,19 @@ public class CompetitorSelectionPopup extends Composite implements HasWidgets, C
isVisible = false;
}
public void addListener(CompetitorSelectionPopupListener o) {
public void addListener(CompetitorSelectionListener o) {
if (o != null && !competitorSelectionPopupListener.contains(o)) {
this.competitorSelectionPopupListener.add(o);
}
}
public void removeListener(CompetitorSelectionPopupListener o) {
public void removeListener(CompetitorSelectionListener o) {
this.competitorSelectionPopupListener.remove(o);
}
public void notifyListenerAboutOKButtonClickedWithSelectedCompetitorName(String competitorName) {
for (CompetitorSelectionPopupListener newStartAnalysisListener : competitorSelectionPopupListener) {
newStartAnalysisListener.didClickedOKWithCompetitorName(competitorName);
public void notifyListenerAboutOKButtonClickedWithSelectedCompetitorName(CompetitorDTO competitor) {
for (CompetitorSelectionListener newStartAnalysisListener : competitorSelectionPopupListener) {
newStartAnalysisListener.didClickOKWithSelectedCompetitor(competitor);
}
hide();
}
@@ -113,6 +111,12 @@ public class CompetitorSelectionPopup extends Composite implements HasWidgets, C
}
}
@Override
public void didSelectedRowWithCompetitorName(CompetitorDTO competitor) {
button.getElement().addClassName(style.buttonshow());
currentCompetitorSelected = competitor;
}
@Override
public void add(Widget w) {
@@ -133,10 +137,4 @@ public class CompetitorSelectionPopup extends Composite implements HasWidgets, C
public boolean remove(Widget w) {
return false;
}
@Override
public void didSelectedRowWithCompetitorName(String competitorName) {
button.getElement().addClassName(style.buttonshow());
currentCompetitorSelected = competitorName;
}
}
@@ -1,6 +0,0 @@
package com.sap.sailing.dashboards.gwt.client.popups.competitorselection;
public interface CompetitorSelectionPopupListener {
public void didClickedOKWithCompetitorName(String competitorName);
}
@@ -10,45 +10,46 @@ import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SingleSelectionModel;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
public class CompetitorTable extends AbsolutePanel {
private CellTable<String> table;
private CellTable<CompetitorDTO> table;
private CompetitorTableRowSelectionListener competitorTableRowSelectionListener;
public CompetitorTable(CompetitorTableRowSelectionListener competitorTableRowSelectionListener) {
this.competitorTableRowSelectionListener = competitorTableRowSelectionListener;
}
public void setTableContent(List<String> competitorNames) {
public void setTableContent(List<CompetitorDTO> competitorNames) {
if (table == null) {
initTable(competitorNames);
}
}
private void initTable(List<String> competitorNames) {
private void initTable(List<CompetitorDTO> competitorNames) {
CellTable.Resources tableRes = GWT.create(CompetitorTableStyleResource.class);
table = new CellTable<String>(15, tableRes);
table = new CellTable<CompetitorDTO>(15, tableRes);
table.getElement().getStyle().setWidth(100, Unit.PCT);
table.setSkipRowHoverStyleUpdate(true);
table.setSkipRowHoverCheck(true);
table.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED);
final SingleSelectionModel<String> selectionModel = new SingleSelectionModel<String>();
final SingleSelectionModel<CompetitorDTO> selectionModel = new SingleSelectionModel<CompetitorDTO>();
table.setSelectionModel(selectionModel);
selectionModel.addSelectionChangeHandler(new SelectionChangeEvent.Handler() {
public void onSelectionChange(SelectionChangeEvent event) {
String selected = selectionModel.getSelectedObject();
CompetitorDTO selected = selectionModel.getSelectedObject();
if (selected != null) {
competitorTableRowSelectionListener.didSelectedRowWithCompetitorName(selected);
}
}
});
TextColumn<String> competitorNameCollumn = new TextColumn<String>() {
TextColumn<CompetitorDTO> competitorNameCollumn = new TextColumn<CompetitorDTO>() {
@Override
public String getValue(String competitorName) {
return competitorName;
public String getValue(CompetitorDTO competitor) {
return competitor.getName();
}
};
table.addColumn(competitorNameCollumn, "");
@@ -1,6 +1,8 @@
package com.sap.sailing.dashboards.gwt.client.popups.competitorselection;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
public interface CompetitorTableRowSelectionListener {
public void didSelectedRowWithCompetitorName(String competitorName);
public void didSelectedRowWithCompetitorName(CompetitorDTO competitor);
}
@@ -0,0 +1,30 @@
@CHARSET "UTF-8";
.settings_button {
position: absolute !important;
height: 23.5pt;
width: 23.5pt;
right: 10pt;
top: 17.5pt;
z-index: 2;
}
.settings_button_image {
position: absolute;
height: 23.5pt;
width: 23.5pt;
right: 10pt;
top: 17.5pt;
}
.settings_indication_label {
position: absolute;
width: 200pt;
height: 23.5pt;
margin-top: 17.5pt;
right: 43.5pt;
font-size: 12pt;
font-color: black;
text-align: right;
line-height: 23.5pt;
}
@@ -0,0 +1,51 @@
package com.sap.sailing.dashboards.gwt.client.popups.competitorselection;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.dashboards.gwt.client.RibDashboardImageResources;
public class SettingsButtonWithSelectionIndicationLabel extends Composite {
private static SettingsButtonWithSelectionIndicationLabelUiBinder uiBinder = GWT
.create(SettingsButtonWithSelectionIndicationLabelUiBinder.class);
interface SettingsButtonWithSelectionIndicationLabelUiBinder extends
UiBinder<Widget, SettingsButtonWithSelectionIndicationLabel> {
}
interface SettingsButtonWithSelectionIndicationLabelStyle extends CssResource {
}
@UiField
SettingsButtonWithSelectionIndicationLabelStyle style;
@UiField
FocusPanel settingsButton;
@UiField
Image settingsButtonImage;
@UiField
Label settingsIndicationLabel;
public SettingsButtonWithSelectionIndicationLabel() {
initWidget(uiBinder.createAndBindUi(this));
settingsButtonImage.setResource(RibDashboardImageResources.INSTANCE.settings());
}
public void addClickHandlerToSettingsButton(ClickHandler clickHandler){
settingsButton.addClickHandler(clickHandler);
}
public void setSelectionIndicationTextOnLabel(String selectionIndicationText) {
settingsIndicationLabel.setText(selectionIndicationText);
}
}
@@ -0,0 +1,12 @@
<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:style
type='com.sap.sailing.dashboards.gwt.client.popups.competitorselection.SettingsButtonWithSelectionIndicationLabel.SettingsButtonWithSelectionIndicationLabelStyle'
src='SettingsButtonWithSelectionIndicationLabel.css'></ui:style>
<g:HTMLPanel>
<g:FocusPanel ui:field="settingsButton" styleName="{style.settings_button}"></g:FocusPanel>
<g:Image ui:field="settingsButtonImage" styleName="{style.settings_button_image}"></g:Image>
<g:Label ui:field="settingsIndicationLabel" styleName="{style.settings_indication_label}"></g:Label>
</g:HTMLPanel>
</ui:UiBinder>
@@ -1,18 +0,0 @@
package com.sap.sailing.dashboards.gwt.client.startanalysis;
import java.util.List;
import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO;
/**
* classes implementing this interface get usually notified, if there is a new {@link StartAnalysisDTO}
* in {@link RibDashboardRaceInfoDTO} variable <code>startAnalysisDTOList</code>.
*
* @author Alexander Ries (D062114)
*
*/
public interface NewStartAnalysisListener {
public void addNewStartAnalysisCardForCompetitor(List<StartAnalysisDTO> startAnalysisDTOs, String selectedCompetitor);
}
@@ -0,0 +1,30 @@
@CHARSET "UTF-8";
.combinedWindPanel {
margin-top: 10pt;
margin-left: 10pt;
right: 0pt;
width: 45pt;
height: 45pt;
border-radius: 22.5pt;
background-color: white;
}
.combinedWindPanelTextLabel {
margin-top: 15pt;
width: 45pt;
height: 25pt;
background-color: white;
line-height: 25pt;
vertical-align: middle;
font-size: 11pt;
font-family: "Arial";
text-align: center;
}
.combinedWindPanelCanvas{
width: 35pt;
height: 35pt;
margin-top: 5pt;
margin-left: 5pt;
}
@@ -1,7 +1,9 @@
package com.sap.sailing.dashboards.gwt.client.startanalysis;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO;
public interface StartAnalysisPageChangeListener {
public void loadMapAndContent(int newPageIndex, String someDataDTO);
public void startAnalysisComponentPageChangedToIndexAndStartAnalysis(int newPageIndex, StartAnalysisDTO startAnalysisDTO);
}
@@ -0,0 +1,14 @@
package com.sap.sailing.dashboards.gwt.client.startanalysis;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
import com.sap.sailing.gwt.ui.client.shared.racemap.CombinedWindPanelStyle;
interface StartAnalysisResources extends ClientBundle {
@Source("com/sap/sailing/dashboards/gwt/client/images/windarrow.png")
ImageResource combinedWindIcon();
@Source("StartAnalysisCombinedWindPanel.css")
CombinedWindPanelStyle combinedWindPanelStyle();
}
@@ -1,326 +0,0 @@
package com.sap.sailing.dashboards.gwt.client.startanalysis;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.maps.client.LoadApi;
import com.google.gwt.maps.client.LoadApi.LoadLibrary;
import com.google.gwt.maps.client.MapOptions;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.base.LatLng;
import com.google.gwt.maps.client.base.LatLngBounds;
import com.google.gwt.maps.client.controls.ControlPosition;
import com.google.gwt.maps.client.controls.MapTypeStyle;
import com.google.gwt.maps.client.controls.ScaleControlOptions;
import com.google.gwt.maps.client.maptypes.MapTypeStyleFeatureType;
import com.google.gwt.maps.client.mvc.MVCArray;
import com.google.gwt.maps.client.overlays.Polyline;
import com.google.gwt.maps.client.overlays.PolylineOptions;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisCompetitorDTO;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.PositionDTO;
import com.sap.sailing.gwt.ui.client.shared.racemap.BoatOverlay;
import com.sap.sailing.gwt.ui.client.shared.racemap.CourseMarkOverlay;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapOverlaysZIndexes;
import com.sap.sailing.gwt.ui.client.shared.racemap.TailFactory;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.MarkDTO;
import com.sap.sailing.gwt.ui.shared.racemap.GoogleMapStyleHelper;
import com.sap.sse.common.impl.RGBColor;
public class StartAnalysisSimpleMap extends AbsolutePanel implements TailFactory {
private MapWidget map;
private StartAnalysisDTO startAnalysisDTO;
public StartAnalysisSimpleMap(int containerID, StartAnalysisDTO startAnalysisDTO) {
this.startAnalysisDTO = startAnalysisDTO;
this.getElement().getStyle().setPosition(Position.ABSOLUTE);
this.getElement().getStyle().setWidth(100, Unit.PCT);
this.getElement().getStyle().setHeight(100, Unit.PCT);
loadMapsAPIV3();
}
private void loadMapsAPIV3() {
boolean sensor = true;
// load all the libs for use in the maps
ArrayList<LoadLibrary> loadLibraries = new ArrayList<LoadApi.LoadLibrary>();
loadLibraries.add(LoadLibrary.DRAWING);
loadLibraries.add(LoadLibrary.GEOMETRY);
Runnable onLoad = new Runnable() {
@Override
public void run() {
MapOptions mapOptions = MapOptions.newInstance();
mapOptions.setScrollWheel(true);
mapOptions.setMapTypeControl(false);
mapOptions.setPanControl(false);
mapOptions.setZoomControl(false);
mapOptions.setScaleControl(true);
mapOptions.setDraggable(false);
mapOptions.setPanControl(false);
MapTypeStyle[] mapTypeStyles = new MapTypeStyle[4];
// hide all transit lines including ferry lines
mapTypeStyles[0] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.TRANSIT);
// hide points of interest
mapTypeStyles[1] = GoogleMapStyleHelper.createHiddenStyle(MapTypeStyleFeatureType.POI);
// simplify road display
mapTypeStyles[2] = GoogleMapStyleHelper.createSimplifiedStyle(MapTypeStyleFeatureType.ROAD);
// set water color
mapTypeStyles[3] = GoogleMapStyleHelper.createColorStyle(MapTypeStyleFeatureType.WATER, new RGBColor(0,
136, 255), -35, -34);
mapOptions.setMapTypeStyles(mapTypeStyles);
ScaleControlOptions scaleControlOptions = ScaleControlOptions.newInstance();
scaleControlOptions.setPosition(ControlPosition.BOTTOM_RIGHT);
mapOptions.setScaleControlOptions(scaleControlOptions);
mapOptions.setStreetViewControl(false);
map = new MapWidget(mapOptions);
StartAnalysisSimpleMap.this.add(map, 0, 0);
map.setSize("100%", "100%");
map.getElement().getStyle().setBackgroundColor("#4293DB");
map.triggerResize();
drawStartLine(startAnalysisDTO.startLineMarks);
drawLinesFromStartLineToFirstMark(startAnalysisDTO.startLineMarks, startAnalysisDTO.firstMark);
drawStartlineMarks(startAnalysisDTO.startLineMarks);
drawFirstMark(startAnalysisDTO.firstMark);
drawTails(createGPSFixesForCompetitors(startAnalysisDTO));
drawBoats(createGPSPositionForCompetitors(startAnalysisDTO));
zoomMapToNewBounds(calculateNewBounds(startAnalysisDTO.startLineMarkPositions));
}
};
LoadApi.go(onLoad, loadLibraries, sensor);
}
private Map<CompetitorDTO, List<GPSFixDTO>> createGPSFixesForCompetitors(StartAnalysisDTO startAnalysisDTO) {
Map<CompetitorDTO, List<GPSFixDTO>> gpsFixesForCompetitors = new HashMap<CompetitorDTO, List<GPSFixDTO>>();
for (StartAnalysisCompetitorDTO startAnalysisCompetitorDTO : startAnalysisDTO.startAnalysisCompetitorDTOs) {
gpsFixesForCompetitors.put(startAnalysisCompetitorDTO.competitorDTO,
startAnalysisCompetitorDTO.gpsFixDTOs);
}
return gpsFixesForCompetitors;
}
private Map<CompetitorDTO, GPSFixDTO> createGPSPositionForCompetitors(StartAnalysisDTO startAnalysisDTO) {
Map<CompetitorDTO, GPSFixDTO> gpsPositionForCompetitors = new HashMap<CompetitorDTO, GPSFixDTO>();
for (StartAnalysisCompetitorDTO startAnalysisCompetitorDTO : startAnalysisDTO.startAnalysisCompetitorDTOs) {
gpsPositionForCompetitors.put(startAnalysisCompetitorDTO.competitorDTO,
startAnalysisCompetitorDTO.gpsFixDTOs.get(startAnalysisCompetitorDTO.gpsFixDTOs.size()-1));
}
return gpsPositionForCompetitors;
}
private void drawStartLine(List<MarkDTO> startlineMarks) {
if (map != null && startlineMarks != null) {
if (startlineMarks.size() == 2) {
LatLng startLinePoint1 = LatLng.newInstance(startlineMarks.get(0).position.latDeg,
startlineMarks.get(0).position.lngDeg);
LatLng startLinePoint2 = LatLng.newInstance(startlineMarks.get(1).position.latDeg,
startlineMarks.get(1).position.lngDeg);
PolylineOptions options = PolylineOptions.newInstance();
options.setGeodesic(true);
options.setStrokeColor("#FFFFFF");
options.setStrokeWeight(2);
options.setStrokeOpacity(1.0);
MVCArray<LatLng> pointsAsArray = MVCArray.newInstance();
pointsAsArray.insertAt(0, startLinePoint1);
pointsAsArray.insertAt(1, startLinePoint2);
Polyline startLine = Polyline.newInstance(options);
startLine.setPath(pointsAsArray);
startLine.setMap(map);
}
}
}
private void drawLinesFromStartLineToFirstMark(List<MarkDTO> startlineMarks, MarkDTO firstMark){
if (map != null && startlineMarks != null && firstMark != null) {
if (startlineMarks.size() == 2) {
LatLng startLinePoint1 = LatLng.newInstance(startlineMarks.get(0).position.latDeg,
startlineMarks.get(0).position.lngDeg);
LatLng startLinePoint2 = LatLng.newInstance(startlineMarks.get(1).position.latDeg,
startlineMarks.get(1).position.lngDeg);
LatLng firstMarkPoint = LatLng.newInstance(firstMark.position.latDeg,
firstMark.position.lngDeg);
PolylineOptions options = PolylineOptions.newInstance();
options.setGeodesic(true);
options.setStrokeColor("#A3A3A3");
options.setStrokeWeight(2);
options.setStrokeOpacity(1.0);
MVCArray<LatLng> pointsAsArray1 = MVCArray.newInstance();
pointsAsArray1.insertAt(0, startLinePoint1);
pointsAsArray1.insertAt(1, firstMarkPoint);
MVCArray<LatLng> pointsAsArray2 = MVCArray.newInstance();
pointsAsArray2.insertAt(0, startLinePoint2);
pointsAsArray2.insertAt(1, firstMarkPoint);
Polyline startMarkFirstMark1 = Polyline.newInstance(options);
Polyline startMarkFirstMark2 = Polyline.newInstance(options);
startMarkFirstMark1.setPath(pointsAsArray1);
startMarkFirstMark2.setPath(pointsAsArray2);
startMarkFirstMark1.setMap(map);
startMarkFirstMark2.setMap(map);
}
}
}
private void drawStartlineMarks(List<MarkDTO> startlineMarks) {
if (map != null) {
for (MarkDTO markDTO : startlineMarks) {
CourseMarkOverlay courseMarkOverlay = createCourseMarkOverlay(
RaceMapOverlaysZIndexes.COURSEMARK_ZINDEX, markDTO);
courseMarkOverlay.addToMap();
}
}
}
private void drawFirstMark(MarkDTO firstMark){
CourseMarkOverlay courseMarkOverlay = createCourseMarkOverlay(
RaceMapOverlaysZIndexes.COURSEMARK_ZINDEX, firstMark);
courseMarkOverlay.addToMap();
}
private CourseMarkOverlay createCourseMarkOverlay(int zIndex, final MarkDTO markDTO) {
markDTO.position.latDeg = markDTO.position.latDeg;
final CourseMarkOverlay courseMarkOverlay = new CourseMarkOverlay(map, zIndex, markDTO);
return courseMarkOverlay;
}
private LatLngBounds calculateNewBounds(List<PositionDTO> startLinePositions) {
LatLngBounds newBounds = null;
List<PositionDTO> newStartLinePositionDTOs = new ArrayList<PositionDTO>(startLinePositions);
Iterable<PositionDTO> positionsToZoom = newStartLinePositionDTOs;
if (positionsToZoom != null) {
for (PositionDTO position : positionsToZoom) {
PositionDTO positionMoovedToNorth = position;
positionMoovedToNorth.latDeg = positionMoovedToNorth.latDeg - 0.0009;
LatLng markLatLng = LatLng.newInstance(positionMoovedToNorth.latDeg, positionMoovedToNorth.lngDeg);
LatLngBounds bounds = LatLngBounds.newInstance(markLatLng, markLatLng);
if (newBounds == null) {
newBounds = bounds;
} else {
newBounds.extend(markLatLng);
}
markLatLng = null;
position = null;
positionMoovedToNorth.latDeg = positionMoovedToNorth.latDeg + 0.0009;
}
}
// }
positionsToZoom = null;
return newBounds;
}
private void zoomMapToNewBounds(LatLngBounds newBounds) {
if (newBounds != null) {
map.setCenter(newBounds.getCenter());
map.setZoom(17);
}
}
private void drawBoats(Map<CompetitorDTO, GPSFixDTO> competitorAndLastFix) {
Set<CompetitorDTO> competitors = competitorAndLastFix.keySet();
for (CompetitorDTO competitorDTO : competitors) {
BoatOverlay boatOverlay = createBoatOverlay(RaceMapOverlaysZIndexes.BOATS_ZINDEX, competitorDTO, false);
boatOverlay.setSelected(false);
boatOverlay.setBoatFix(competitorAndLastFix.get(competitorDTO), 10);
boatOverlay.addToMap();
}
}
private BoatOverlay createBoatOverlay(int zIndex, final CompetitorDTO competitorDTO, boolean highlighted) {
final BoatOverlay boatCanvas = new BoatOverlay(map, zIndex, competitorDTO, competitorDTO.getColor());
boatCanvas.setSelected(false);
return boatCanvas;
}
private void drawTails(Map<CompetitorDTO, List<GPSFixDTO>> competitorFixes) {
Set<CompetitorDTO> competitors = competitorFixes.keySet();
for (CompetitorDTO competitor : competitors) {
Polyline competitorPolyline = createPolylineFromGPSFixDTOs(competitor, competitorFixes.get(competitor));
competitorPolyline.setMap(map);
}
}
private Polyline createPolylineFromGPSFixDTOs(CompetitorDTO competitorDTO, List<GPSFixDTO> fixes) {
List<LatLng> points = new ArrayList<LatLng>();
int indexOfFirst = -1;
int indexOfLast = -1;
int i = 0;
for (Iterator<GPSFixDTO> fixIter = fixes.iterator(); fixIter.hasNext() && indexOfLast == -1;) {
GPSFixDTO fix = fixIter.next();
if (!fix.timepoint.before(fixes.get(fixes.size() - 1).timepoint)) {
indexOfLast = i - 1;
} else {
LatLng point = null;
if (indexOfFirst == -1) {
if (!fix.timepoint.before(fixes.get(0).timepoint)) {
indexOfFirst = i;
point = LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg);
}
} else {
point = LatLng.newInstance(fix.position.latDeg, fix.position.lngDeg);
}
if (point != null) {
points.add(point);
}
}
i++;
}
if (indexOfLast == -1) {
indexOfLast = i - 1;
}
Polyline result = createTail(competitorDTO, points);
return result;
}
@Override
public Polyline createTail(CompetitorDTO competitor, List<LatLng> points) {
PolylineOptions options = createTailStyle(competitor, false);
Polyline result = Polyline.newInstance(options);
MVCArray<LatLng> pointsAsArray = MVCArray.newInstance(points.toArray(new LatLng[0]));
result.setPath(pointsAsArray);
return result;
}
@Override
public PolylineOptions createTailStyle(CompetitorDTO competitor, boolean isHighlighted) {
PolylineOptions options = PolylineOptions.newInstance();
options.setClickable(true);
options.setGeodesic(true);
options.setStrokeOpacity(1.0);
options.setStrokeColor(competitor.getColor().getAsHtml());
options.setStrokeWeight(1);
options.setZindex(RaceMapOverlaysZIndexes.BOATTAILS_ZINDEX);
return options;
}
}
@@ -56,19 +56,12 @@
display: inline;
width: 80%;
height: 100%;
background-color: #4293DB;
margin-top: 1px; /*Zeigt sonst Linie am oberen Rand*/
/*#4293DB old water color*/
background-color: #004384;
margin-top: 1px; /*Shows otherwise small line at top edge*/
overflow: hidden;
}
.startanalysis_google_map {
position: absolute;
bottom: -20pt;
right: -20pt;
left: -20pt;
top: -20pt;
}
.startanalysis_card_table {
position: absolute;
bottom: 10pt;
@@ -87,30 +80,6 @@
z-index: 1;
}
.startanalysis_card_wind_direction_image {
position: absolute;
top: 0pt;
right: 0pt;
width: 45pt;
height: 45pt;
border-radius: 22.5pt;
background-color: white;
}
.startanalysis_card_wind_arrow {
width: 25pt;
height: 25pt;
margin-top: 10pt;
margin-left: 10pt;
}
.startanalysis_card_wind_canvas {
width: 25pt;
height: 25pt;
margin-top: 10pt;
margin-left: 10pt;
}
.startanalysis_card_data {
position: absolute;
right: 0pt;
@@ -124,14 +93,6 @@
text-align: center;
}
.startanalysis_card_wind_data_direction {
top: 55pt;
}
.startanalysis_card_wind_data_speed {
top: 85pt;
}
.startanalysis_card_line_advantage {
top: 0pt;
left: 55pt;
@@ -1,37 +1,45 @@
package com.sap.sailing.dashboards.gwt.client.startanalysis;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.dashboards.gwt.client.RibDashboardImageResources;
import com.sap.sailing.dashboards.gwt.client.startanalysis.rankingtable.StartAnalysisStartRankTable;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO;
import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel;
import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapHelpLinesSettings.HelpLineTypes;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings;
import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapZoomSettings.ZoomTypes;
import com.sap.sse.gwt.client.async.AsyncActionsExecutor;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
public class StartlineAnalysisCard extends Composite implements HasWidgets, StartAnalysisPageChangeListener {
private StartAnalysisSimpleMap simpleMap;
private static StartlineAnalysisCardUiBinder uiBinder = GWT.create(StartlineAnalysisCardUiBinder.class);
interface StartlineAnalysisCardUiBinder extends UiBinder<Widget, StartlineAnalysisCard> {
}
interface StartlineAnalysis extends CssResource {
public String startanalysis_card_wind_canvas();
public String startanalysis_card_wind_arrow();
}
@UiField
@@ -40,100 +48,135 @@ public class StartlineAnalysisCard extends Composite implements HasWidgets, Star
@UiField
HTMLPanel startanalysis_card;
@UiField
HTMLPanel winddirection;
@UiField
HTMLPanel startanalysis_card_table;
@UiField
DivElement startanalysis_card_wind_data_direction;
@UiField
DivElement startanalysis_card_wind_data_speed;
@UiField
DivElement startanalysis_card_line_advantage;
Canvas winddirectionContainer;
Image windarrow;
private int cardid;
private boolean containsMap = false;
private StartAnalysisDTO startAnalysisDTO;
private RaceMap raceMap;
public StartlineAnalysisCard(double leftCSSProperty, int cardID, StartAnalysisDTO startAnalysisDTO) {
private SailingServiceAsync sailingServiceAsync;
private StringMessages stringMessages;
private final CompetitorSelectionModel competitorSelectionModel;
private final double WIND_LINE_ADVANTAGE_DIV_WIDTH_IN_PT = 185;
private final double GEOMETRIC_LINE_ADVANTAGE_DIV_WIDTH_IN_PT = 210;
private final int TAIL_LENGHT_IN_MILLISECONDS = 900000;
private static StartAnalysisResources resources = GWT.create(StartAnalysisResources.class);
public StartlineAnalysisCard(double leftCSSProperty, int cardId, StartAnalysisDTO startAnalysisDTO,
SailingServiceAsync sailingServiceAsync) {
stringMessages = StringMessages.INSTANCE;
resources.combinedWindPanelStyle().ensureInjected();
this.sailingServiceAsync = sailingServiceAsync;
competitorSelectionModel = new CompetitorSelectionModel(/* hasMultiSelection */true);
initWidget(uiBinder.createAndBindUi(this));
startanalysis_card.getElement().getStyle().setLeft(leftCSSProperty, Unit.PCT);
winddirectionContainer = Canvas.createIfSupported();
this.startAnalysisDTO = startAnalysisDTO;
winddirectionContainer.setStyleName(style.startanalysis_card_wind_canvas());
windarrow = new Image();
windarrow.setResource(RibDashboardImageResources.INSTANCE.windarrow());
windarrow.setStyleName(style.startanalysis_card_wind_arrow());
winddirection.add(windarrow);
cardid = cardID;
if (cardID == 0) {
addMap(cardID);
}
this.cardid = cardId;
startanalysis_card_table.add(new StartAnalysisStartRankTable(startAnalysisDTO.startAnalysisCompetitorDTOs));
fillWindAndStartLineData(this.startAnalysisDTO);
this.onLoad();
}
private void fillWindAndStartLineData(StartAnalysisDTO startAnalysisDTO) {
if (startAnalysisDTO.startAnalysisWindLineInfoDTO != null) {
String winddirectionformatted = NumberFormat.getFormat("#0").format(
startAnalysisDTO.startAnalysisWindLineInfoDTO.windDirectionInDegrees-90);
windarrow.getElement().getStyle()
.setProperty("transform", "rotate(" + winddirectionformatted + "deg)");
windarrow.getElement().getStyle().setProperty("webkitTransform", "rotate(" + winddirectionformatted + "deg)");
startanalysis_card_wind_data_direction
.setInnerHTML(NumberFormat.getFormat("#0.0").format(startAnalysisDTO.startAnalysisWindLineInfoDTO.windDirectionInDegrees) + "°");
startanalysis_card_wind_data_speed
.setInnerHTML(NumberFormat.getFormat("#0.0").format(startAnalysisDTO.startAnalysisWindLineInfoDTO.windSpeedInKnots) + " kts");
startanalysis_card_line_advantage.setInnerHTML(startAnalysisDTO.startAnalysisWindLineInfoDTO.startLineAdvantage.startLineAdvatageType.getDisplayName()+": "+NumberFormat.getFormat("#0.0").format(startAnalysisDTO.startAnalysisWindLineInfoDTO.startLineAdvantage.startLineAdvantage)+" m");
setLineAdvantageDivWidth(startAnalysisDTO.startAnalysisWindLineInfoDTO.startLineAdvantage.startLineAdvatageType);
String startLineAdvantageType;
if (startAnalysisDTO.startAnalysisWindLineInfoDTO.startLineAdvantage.startLineAdvatageType
.equals(StartlineAdvantageType.GEOMETRIC)) {
startLineAdvantageType = stringMessages.dashboardStartlineAdvantageByGeometry();
}else{
startLineAdvantageType = stringMessages.dashboardStartlineAdvantageByWind();
}
startanalysis_card_line_advantage
.setInnerHTML(startLineAdvantageType
+ ": "
+ NumberFormat
.getFormat("#0.0")
.format(startAnalysisDTO.startAnalysisWindLineInfoDTO.startLineAdvantage.startLineAdvantage)
+ " m");
}
}
private void addMap(final int cardID) {
Timer timer = new Timer()
{
private void setLineAdvantageDivWidth(StartlineAdvantageType startlineAdvantageType) {
if (startAnalysisDTO.startAnalysisWindLineInfoDTO.startLineAdvantage.startLineAdvatageType
.equals(StartlineAdvantageType.GEOMETRIC)) {
startanalysis_card_line_advantage.getStyle().setWidth(GEOMETRIC_LINE_ADVANTAGE_DIV_WIDTH_IN_PT, Unit.PT);
} else {
startanalysis_card_line_advantage.getStyle().setWidth(WIND_LINE_ADVANTAGE_DIV_WIDTH_IN_PT, Unit.PT);
}
}
private void addMap(final int cardID, final StartAnalysisDTO startAnalysisDTO) {
AsyncActionsExecutor asyncActionsExecutor = new AsyncActionsExecutor();
com.sap.sse.gwt.client.player.Timer timer = new com.sap.sse.gwt.client.player.Timer(PlayModes.Live, 1000l);
timer.setTime(startAnalysisDTO.timeOfStartInMilliSeconds);
timer.pause();
RaceTimesInfoProvider raceTimesInfoProvider = new RaceTimesInfoProvider(sailingServiceAsync,
asyncActionsExecutor, null, Collections.singletonList(startAnalysisDTO.regattaAndRaceIdentifier), 5000l /* requestInterval */);
raceMap = new RaceMap(sailingServiceAsync, asyncActionsExecutor, null, timer, competitorSelectionModel,
StringMessages.INSTANCE, false, false, false, startAnalysisDTO.regattaAndRaceIdentifier,
resources.combinedWindPanelStyle());
competitorSelectionModel.setCompetitors(startAnalysisDTO.getCompetitorDTOsFromStartAnaylsisCompetitorDTOs(),
raceMap);
raceMap.onRaceSelectionChange(Collections.singletonList(startAnalysisDTO.regattaAndRaceIdentifier));
ArrayList<ZoomTypes> zoomTypes = new ArrayList<ZoomTypes>();
zoomTypes.add(ZoomTypes.TAILS);
raceMap.getSettings().setZoomSettings(new RaceMapZoomSettings(zoomTypes, false));
raceMap.getSettings().setHelpLinesSettings(getHelpLineSettings());
raceMap.getSettings().setTailLengthInMilliseconds(TAIL_LENGHT_IN_MILLISECONDS);
raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap);
startanalysis_card.add(raceMap);
/**
* Executes onResize() after the reflow of the DOM. Otherwise it has no effect.
* Needs to resize the map because google maps are not shown loaded fully when they are hidden.
* */
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void run()
{
simpleMap = new StartAnalysisSimpleMap(cardID, startAnalysisDTO);
startanalysis_card.add(simpleMap);
public void execute() {
raceMap.onResize();
}
});
}
};
timer.schedule(800);
containsMap = true;
private RaceMapHelpLinesSettings getHelpLineSettings() {
HashSet<HelpLineTypes> visibleHelpLines = new HashSet<HelpLineTypes>();
visibleHelpLines.add(HelpLineTypes.STARTLINE);
visibleHelpLines.add(HelpLineTypes.BOATTAILS);
visibleHelpLines.add(HelpLineTypes.STARTLINETOFIRSTMARKTRIANGLE);
return new RaceMapHelpLinesSettings(visibleHelpLines);
}
private void removeMap() {
if(simpleMap.isAttached()){
startanalysis_card.remove(simpleMap);
containsMap = false;
if (raceMap != null && raceMap.isAttached() == true) {
startanalysis_card.remove(raceMap);
}
}
@Override
public void loadMapAndContent(int newPageIndex, String someDataDTO) {
if (cardid == newPageIndex && containsMap == false) {
addMap(newPageIndex);
} else if (containsMap == true) {
public void startAnalysisComponentPageChangedToIndexAndStartAnalysis(final int newPageIndex,
final StartAnalysisDTO startAnalysisDTO) {
removeMap();
if (cardid == newPageIndex) {
/**
* Prevents UI to animate startanalysis card animation with small interruptions.
* The map gets added just after the DOM manipulation was executed.
* */
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
addMap(newPageIndex, startAnalysisDTO);
}
});
}
}
@@ -7,15 +7,7 @@
src='StartlineAnalysis.css'></ui:style>
<g:HTMLPanel ui:field="startanalysis_card" styleName="{style.startanalysis_card}">
<!-- <maps:MapWidget></maps:MapWidget> -->
<div class="{style.startanalysis_card_wind_line_data_container}">
<g:HTMLPanel ui:field="winddirection"
styleName="{style.startanalysis_card_wind_direction_image}">
</g:HTMLPanel>
<div ui:field="startanalysis_card_wind_data_direction"
class="{style.startanalysis_card_data} {style.startanalysis_card_wind_data_direction}"></div>
<div ui:field="startanalysis_card_wind_data_speed"
class="{style.startanalysis_card_data} {style.startanalysis_card_wind_data_speed}"></div>
<div ui:field="startanalysis_card_line_advantage"
class="{style.startanalysis_card_data} {style.startanalysis_card_line_advantage}"></div>
</div>
@@ -1,6 +1,7 @@
package com.sap.sailing.dashboards.gwt.client.startanalysis;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
@@ -11,18 +12,30 @@ import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Cookies;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.dashboards.gwt.client.RibDashboardDataRetriever;
import com.sap.sailing.dashboards.gwt.client.RibDashboardImageResources;
import com.sap.sailing.dashboards.gwt.client.RibDashboardServiceAsync;
import com.sap.sailing.dashboards.gwt.client.bottomnotification.BottomNotification;
import com.sap.sailing.dashboards.gwt.client.bottomnotification.BottomNotificationClickListener;
import com.sap.sailing.dashboards.gwt.client.bottomnotification.BottomNotificationType;
import com.sap.sailing.dashboards.gwt.client.popups.competitorselection.CompetitorSelectionListener;
import com.sap.sailing.dashboards.gwt.client.popups.competitorselection.CompetitorSelectionPopup;
import com.sap.sailing.dashboards.gwt.client.popups.competitorselection.SettingsButtonWithSelectionIndicationLabel;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.client.SailingServiceAsync;
import com.sap.sse.gwt.client.player.TimeListener;
import com.sap.sse.gwt.client.player.Timer;
import com.sap.sse.gwt.client.player.Timer.PlayModes;
/**
* The class contains an collection of {@link StartlineAnalysisCard}s that are displayed in horizontal aligned pages. It
@@ -34,8 +47,7 @@ import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO;
* @author Alexander Ries (D062114)
*
*/
public class StartlineAnalysisComponent extends Composite implements HasWidgets, NewStartAnalysisListener,
BottomNotificationClickListener {
public class StartlineAnalysisComponent extends Composite implements HasWidgets {
private static StartlineAnalysisComponentUiBinder uiBinder = GWT.create(StartlineAnalysisComponentUiBinder.class);
@@ -71,7 +83,7 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets,
HTMLPanel startanalysis_card_container;
/**
* The left property from the {@link #startanalysis_card_container}.
* The CSS "left" property from the {@link #startanalysis_card_container}.
* */
private double currentScrollPosition = 0;
@@ -87,22 +99,42 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets,
private boolean displaysCards;
private List<StartAnalysisDTO> starts;
private List<StartAnalysisPageChangeListener> pageChangeListener;
private List<StartlineAnalysisCard> pageChangeListener;
private BottomNotification bottomNotification;
private String selectedCompetitor;
private Timer timer;
private RibDashboardServiceAsync ribDashboardServiceAsync;
private SailingServiceAsync sailingServiceAsync;
private static final int RERFRESH_INTERVAL = 10000;
private CompetitorSelectionPopup competitorSelectionPopup;
private SettingsButtonWithSelectionIndicationLabel settingsButtonWithSelectionIndicationLabel;
private String leaderboardName;
private static final String PARAM_LEADERBOARD_NAME = "leaderboardName";
private static final String SELECTED_COMPETITOR_ID_COOKIE_KEY = "selectedCompetitorId";
private static final int SELECTED_COMPETITOR_ID_COOKIE_KEY_EXPIRE_TIME_IN_MILLIS = 60 * 1000 * 60 * 5;
private static final int SCROLL_OFFSET_STARTANALYSIS_CARDS = 83;
/**
* The class requires a {@link BottomNotification} to inform the user when a new {@link StartlineAnalysisCard} gets
* added.
* Component that contains handles, displays and loads startanalysis cards.
* */
public StartlineAnalysisComponent(BottomNotification bottomNotification, RibDashboardServiceAsync ribDashboardService) {
this.bottomNotification = bottomNotification;
pageChangeListener = new ArrayList<StartAnalysisPageChangeListener>();
bottomNotification.addBottomNotificationClickListener(this);
RibDashboardDataRetriever.getInstance(ribDashboardService).addNewStartAnalysisListener(this);
public StartlineAnalysisComponent(RibDashboardServiceAsync ribDashboardServiceAsync, SailingServiceAsync sailingServiceAsync) {
this.ribDashboardServiceAsync = ribDashboardServiceAsync;
this.sailingServiceAsync = sailingServiceAsync;
pageChangeListener = new ArrayList<StartlineAnalysisCard>();
starts = new ArrayList<StartAnalysisDTO>();
this.leaderboardName = Window.Location.getParameter(PARAM_LEADERBOARD_NAME);
initWidget(uiBinder.createAndBindUi(this));
initCompetitorSelectionPopupAndAddCompetitorSelectionListener();
initLeftRightButtons();
initTimer();
initAndAddBottomNotification();
getCachedSelectedCompetitorOrAskForWithPopup();
initAndAddSettingsButtonWithSelectionIndicationLabel();
}
private void initLeftRightButtons() {
@@ -122,6 +154,114 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets,
});
}
private void initTimer() {
timer = new Timer(PlayModes.Live);
timer.setRefreshInterval(RERFRESH_INTERVAL);
timer.addTimeListener(new TimeListener() {
@Override
public void timeChanged(Date newTime, Date oldTime) {
String selectedCompetitorId = Cookies.getCookie(SELECTED_COMPETITOR_ID_COOKIE_KEY);
if (selectedCompetitorId != null) {
loadStartAnalysisDTOsForCompetitorID(selectedCompetitorId);
} else {
loadCompetitorsAndShowCompetitorSelectionPopup();
}
}
});
}
private void loadStartAnalysisDTOsForCompetitorID(String competitorIdAsString){
ribDashboardServiceAsync.getStartAnalysisListForCompetitorIDAndLeaderboardName(
competitorIdAsString, leaderboardName, new AsyncCallback<List<StartAnalysisDTO>>() {
@Override
public void onSuccess(List<StartAnalysisDTO> result) {
if (!result.isEmpty()) {
if (displayedStartAnalysisCompetitorDifferentToRequestedOne()) {
removeAllStartAnalysisCards();
}
if (result.size() != starts.size()) {
showNotificationForNewStartAnalysis();
}
addNewStartAnalysisCards(result);
settingsButtonWithSelectionIndicationLabel
.setSelectionIndicationTextOnLabel(result.get(0).competitor.getName());
}
}
@Override
public void onFailure(Throwable caught) {
}
});
}
private void initAndAddBottomNotification() {
bottomNotification = new BottomNotification();
bottomNotification.addBottomNotificationClickListener(new BottomNotificationClickListener() {
@Override
public void bottomNotificationClicked() {
scrollToLast();
}
});
RootPanel.get().add(bottomNotification);
}
private void initCompetitorSelectionPopupAndAddCompetitorSelectionListener() {
competitorSelectionPopup = new CompetitorSelectionPopup();
competitorSelectionPopup.addListener(new CompetitorSelectionListener() {
@Override
public void didClickOKWithSelectedCompetitor(CompetitorDTO competitor) {
if (competitor != null) {
Cookies.removeCookie(SELECTED_COMPETITOR_ID_COOKIE_KEY);
Cookies.setCookie(SELECTED_COMPETITOR_ID_COOKIE_KEY, competitor.getIdAsString(), new Date(
new Date().getTime() + SELECTED_COMPETITOR_ID_COOKIE_KEY_EXPIRE_TIME_IN_MILLIS));
if (settingsButtonWithSelectionIndicationLabel != null) {
settingsButtonWithSelectionIndicationLabel.setSelectionIndicationTextOnLabel(competitor
.getName());
}
loadStartAnalysisDTOsForCompetitorID(competitor.getIdAsString());
}
}
});
}
private void getCachedSelectedCompetitorOrAskForWithPopup() {
String selectedCompetitorIdFromCookie = Cookies.getCookie(SELECTED_COMPETITOR_ID_COOKIE_KEY);
if (selectedCompetitorIdFromCookie == null) {
loadCompetitorsAndShowCompetitorSelectionPopup();
}
}
private void loadCompetitorsAndShowCompetitorSelectionPopup() {
ribDashboardServiceAsync.getCompetitorsInLeaderboard(leaderboardName, new AsyncCallback<List<CompetitorDTO>>() {
@Override
public void onSuccess(List<CompetitorDTO> result) {
if (!competitorSelectionPopup.isShown())
competitorSelectionPopup.show(result);
}
@Override
public void onFailure(Throwable caught) {
}
});
}
private void initAndAddSettingsButtonWithSelectionIndicationLabel() {
settingsButtonWithSelectionIndicationLabel = new SettingsButtonWithSelectionIndicationLabel();
settingsButtonWithSelectionIndicationLabel.addClickHandlerToSettingsButton(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
loadCompetitorsAndShowCompetitorSelectionPopup();
}
});
RootPanel.get().add(settingsButtonWithSelectionIndicationLabel);
}
/**
* <param>clickedLeft</param> if clicked left is true, the left arrow button was pressed, a false represent the
* right button. The buttons are represented by the member variables {@link #left_focus_panel} and
@@ -131,7 +271,7 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets,
if (displaysCards == true) {
if (clickedLeft) {
if (page != 0) {
currentScrollPosition = currentScrollPosition + 83;
currentScrollPosition = currentScrollPosition + SCROLL_OFFSET_STARTANALYSIS_CARDS;
page--;
startanalysis_card_container.getElement().getStyle().setLeft(currentScrollPosition, Unit.PCT);
notifyStartAnalysisPageChangeListener(page);
@@ -148,7 +288,7 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets,
}
} else {
if (page != numberOfStartAnalysisCards - 1) {
currentScrollPosition -= 83;
currentScrollPosition -= SCROLL_OFFSET_STARTANALYSIS_CARDS;
page++;
startanalysis_card_container.getElement().getStyle().setLeft(currentScrollPosition, Unit.PCT);
notifyStartAnalysisPageChangeListener(page);
@@ -169,7 +309,8 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets,
private void scrollToLast() {
if (numberOfStartAnalysisCards > 1) {
currentScrollPosition = currentScrollPosition - (numberOfStartAnalysisCards - page - 1) * 83;
currentScrollPosition = currentScrollPosition - (numberOfStartAnalysisCards - page - 1)
* SCROLL_OFFSET_STARTANALYSIS_CARDS;
page = numberOfStartAnalysisCards - 1;
startanalysis_card_container.getElement().getStyle().setLeft(currentScrollPosition, Unit.PCT);
header.getElement().setInnerHTML("Start " + starts.get(page).raceName);
@@ -181,7 +322,7 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets,
}
}
private void addStartAnalysisCard(StartAnalysisDTO startAnalysisDTO) {
private void addStartAnalysisCard(final StartAnalysisDTO startAnalysisDTO) {
if (displaysCards == false) {
displaysCards = true;
@@ -190,20 +331,26 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets,
if (numberOfStartAnalysisCards > 0) {
rightButton.setResource(RibDashboardImageResources.INSTANCE.right());
}
StartlineAnalysisCard sac = new StartlineAnalysisCard(numberOfStartAnalysisCards * 83 + 10,
numberOfStartAnalysisCards, startAnalysisDTO);
startanalysis_card_container.add(sac);
registerPageChangeListener(sac);
final StartlineAnalysisCard startlineAnalysisCard = new StartlineAnalysisCard(numberOfStartAnalysisCards
* SCROLL_OFFSET_STARTANALYSIS_CARDS + 10, numberOfStartAnalysisCards, startAnalysisDTO,
sailingServiceAsync);
startanalysis_card_container.add(startlineAnalysisCard);
startlineAnalysisCard.startAnalysisComponentPageChangedToIndexAndStartAnalysis(page, startAnalysisDTO);
registerPageChangeListener(startlineAnalysisCard);
numberOfStartAnalysisCards++;
}
private void removeAllStartAnalysisCards() {
startanalysis_card_container.clear();
numberOfStartAnalysisCards = 0;
starts.clear();
}
public void registerPageChangeListener(StartAnalysisPageChangeListener s) {
private void showNotificationForNewStartAnalysis() {
this.bottomNotification.show(BottomNotificationType.NEW_STARTANALYSIS_AVAILABLE);
}
public void registerPageChangeListener(StartlineAnalysisCard s) {
pageChangeListener.add(s);
}
@@ -213,26 +360,37 @@ public class StartlineAnalysisComponent extends Composite implements HasWidgets,
public void notifyStartAnalysisPageChangeListener(int newPageIndex) {
for (StartAnalysisPageChangeListener sO : pageChangeListener) {
sO.loadMapAndContent(newPageIndex, "someData");
if(starts.get(newPageIndex) != null)
sO.startAnalysisComponentPageChangedToIndexAndStartAnalysis(newPageIndex, starts.get(newPageIndex));
}
}
@Override
public void addNewStartAnalysisCardForCompetitor(List<StartAnalysisDTO> startAnalysisDTOs, String selectedCompetitor) {
this.bottomNotification.show("New Start Analysis available.", "#F0AB00", "#000000", true);
if (!selectedCompetitor.equals(this.selectedCompetitor)) {
removeAllStartAnalysisCards();
}
private void addNewStartAnalysisCards(List<StartAnalysisDTO> startAnalysisDTOs) {
for (int i = numberOfStartAnalysisCards; i <= startAnalysisDTOs.size() - 1; i++) {
addStartAnalysisCard(startAnalysisDTOs.get(i));
starts.add(startAnalysisDTOs.get(i));
}
this.selectedCompetitor = selectedCompetitor;
}
@Override
public void bottomNotificationClicked() {
scrollToLast();
private String getCompetitorIdAsStringFromDisplayedStartAnalysisDTOs() {
String result = "";
if (starts != null && starts.size() > 0) {
CompetitorDTO competitor = starts.get(0).competitor;
if (competitor != null)
result = starts.get(0).competitor.getIdAsString();
}
return result;
}
private boolean displayedStartAnalysisCompetitorDifferentToRequestedOne() {
String competitorIDFromDispalyedStartAnalysisDTOs = getCompetitorIdAsStringFromDisplayedStartAnalysisDTOs();
if (competitorIDFromDispalyedStartAnalysisDTOs != null
&& !(competitorIDFromDispalyedStartAnalysisDTOs.equals(Cookies
.getCookie(SELECTED_COMPETITOR_ID_COOKIE_KEY)))) {
return true;
} else {
return false;
}
}
@Override
@@ -10,14 +10,16 @@ import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSe
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.client.ui.AbsolutePanel;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisCompetitorDTO;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sse.common.Util.Pair;
public class StartAnalysisStartRankTable extends AbsolutePanel {
CellTable<StartAnalysisCompetitorDTO> table;
private CellTable<StartAnalysisCompetitorDTO> table;
private StringMessages stringConstants;
public StartAnalysisStartRankTable(List<StartAnalysisCompetitorDTO> saCompetitors) {
stringConstants = StringMessages.INSTANCE;
initTable(saCompetitors);
}
@@ -59,13 +61,13 @@ public class StartAnalysisStartRankTable extends AbsolutePanel {
}
};
table.addColumn(rankColumn, "Rank at first mark");
table.addColumn(rankColumn, stringConstants.dashboardRankAtFirstMark());
table.setColumnWidth(rankColumn, 18.0, Unit.PCT);
table.addColumn(teamColumn, "Team");
table.addColumn(teamColumn, stringConstants.dashboardTeam());
table.setColumnWidth(teamColumn, 42.0, Unit.PCT);
table.addColumn(speedColumn, "Speed in kts");
table.addColumn(speedColumn, stringConstants.dashboardSpeedInKts());
table.setColumnWidth(speedColumn, 15.0, Unit.PCT);
table.addColumn(distanceColumn, "Distance to line in m");
table.addColumn(distanceColumn, stringConstants.dashboardDistanceToLineInM());
table.setColumnWidth(distanceColumn, 25.0, Unit.PCT);
table.setRowCount(4, true);
@@ -9,7 +9,6 @@ import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiConstructor;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HTMLPanel;
@@ -17,6 +16,7 @@ import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.dashboards.gwt.client.windchart.VerticalWindChartClickListener;
import com.sap.sailing.dashboards.gwt.client.windchart.WindBotComponent;
import com.sap.sailing.gwt.ui.client.StringMessages;
/**
* The purpose of the class is to display a live and an average value of a continuously updating data source.
@@ -66,8 +66,7 @@ public class LiveAverageComponent extends Composite implements HasWidgets, Verti
@UiField
HTMLPanel averagePanel;
int sizeForUnit;
private StringMessages stringConstants;
public LiveAverageComponent() {
initWidget(uiBinder.createAndBindUi(this));
}
@@ -79,7 +78,8 @@ public class LiveAverageComponent extends Composite implements HasWidgets, Verti
* sets a special size
*
* */
public @UiConstructor LiveAverageComponent(String header, String unit) {
public LiveAverageComponent(String header, String unit) {
stringConstants = StringMessages.INSTANCE;
initWidget(uiBinder.createAndBindUi(this));
liveAveragePanel.getElement().getStyle().setProperty("backgroundColor", "white");
this.header.setInnerText(header);
@@ -94,8 +94,8 @@ public class LiveAverageComponent extends Composite implements HasWidgets, Verti
this.liveUnit.getStyle().setPosition(Position.ABSOLUTE);
this.averageUnit.getStyle().setPosition(Position.ABSOLUTE);
}
this.liveLabel.setInnerHTML("live");
this.averageLabel.setInnerHTML("average<br>(15 minutes)");
this.liveLabel.setInnerHTML(stringConstants.dashboardLiveWind());
this.averageLabel.setInnerHTML(stringConstants.dashboardAverageWind()+"<br>"+stringConstants.dashboardAverageWindMinutes(15));
}
public void updateValues(String liveValue, String averageValue) {
@@ -125,10 +125,6 @@ public class LiveAverageComponent extends Composite implements HasWidgets, Verti
@Override
public void clickedWindChartWithNewIntervalChangeInMillis(int windChartIntervallInMillis) {
if(windChartIntervallInMillis > 15){
this.averageLabel.setInnerHTML("average<br>(1 hour)");
}else{
this.averageLabel.setInnerHTML("average<br>(15 minutes)");
}
this.averageLabel.setInnerHTML(stringConstants.dashboardAverageWind()+"<br>"+stringConstants.dashboardAverageWindMinutes(windChartIntervallInMillis));
}
}
@@ -5,6 +5,7 @@ import com.sap.sailing.dashboards.gwt.client.RibDashboardDataRetriever;
import com.sap.sailing.dashboards.gwt.client.RibDashboardDataRetrieverListener;
import com.sap.sailing.dashboards.gwt.client.startanalysis.StartlineAdvantageType;
import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO;
import com.sap.sailing.gwt.ui.client.StringMessages;
/**
* The purpose of the class is to display the live and average start line advantage either by wind or by geometry. There
@@ -21,15 +22,14 @@ public class StartLineAdvantageComponent extends LiveAverageComponent implements
private StartLineAdvantageComponentState startLineAdvantageComponentState;
private StartLineAdvantageComponentStateShowsAdvantageByWind startLineAdvantageComponentStateShowsAdvantageByWind;
private StartLineAdvantageComponentStateShowsAdvantageByGeometry startLineAdvantageComponentStateShowsAdvantageByGeometry;
private StringMessages stringConstants;
private static final String HEADER_ADVANTAGE_BY_WIND = "Startline advantage by wind";
private static final String HEADER_ADVANTAGE_BY_GEOMETRY = "Startline advantage by geometry";
public StartLineAdvantageComponent() {
public StartLineAdvantageComponent(RibDashboardDataRetriever ribDashboardDataRetriever) {
super();
stringConstants = StringMessages.INSTANCE;
initAndSetStartLineAdvantageStates();
StartLineAdvantageComponentRessources.INSTANCE.css().ensureInjected();
this.header.setInnerText(HEADER_ADVANTAGE_BY_WIND);
this.header.setInnerText(stringConstants.dashboardStartlineAdvantageByWind());
this.header.addClassName(StartLineAdvantageComponentRessources.INSTANCE.css()
.startLineAdvantageComponent_header());
liveAveragePanel.getElement().addClassName(
@@ -40,9 +40,9 @@ public class StartLineAdvantageComponent extends LiveAverageComponent implements
StartLineAdvantageComponentRessources.INSTANCE.css().startLineAdvantageComponent_middleLine());
averagePanel.getElement().addClassName(
StartLineAdvantageComponentRessources.INSTANCE.css().startLineAdvantageComponent_averagePanel());
this.liveLabel.setInnerHTML("live");
this.averageLabel.setInnerHTML("average<br>(1 hour)");
RibDashboardDataRetriever.getInstance(null).addDataObserver(this);
this.liveLabel.setInnerHTML(stringConstants.dashboardLiveWind());
this.averageLabel.setInnerHTML(stringConstants.dashboardAverageWind()+"<br>"+stringConstants.dashboardAverageWindMinutes(60));
ribDashboardDataRetriever.addDataObserver(this);
}
private void initAndSetStartLineAdvantageStates() {
@@ -116,7 +116,7 @@ public class StartLineAdvantageComponent extends LiveAverageComponent implements
@Override
public void changeStartLineAdvatageComponentsStateToOtherState(
StartLineAdvantageComponent startLineAdvantageComponent) {
startLineAdvantageComponent.header.setInnerText(HEADER_ADVANTAGE_BY_GEOMETRY);
startLineAdvantageComponent.header.setInnerText(stringConstants.dashboardStartlineAdvantageByWind());
startLineAdvantageComponent.startLineAdvantageComponentState = startLineAdvantageComponent.startLineAdvantageComponentStateShowsAdvantageByGeometry;
startLineAdvantageComponent.liveNumber
.setInnerText(startLineAdvantageComponent.startLineAdvantageComponentStateShowsAdvantageByGeometry
@@ -149,7 +149,7 @@ public class StartLineAdvantageComponent extends LiveAverageComponent implements
@Override
public void changeStartLineAdvatageComponentsStateToOtherState(
StartLineAdvantageComponent startLineAdvantageComponent) {
startLineAdvantageComponent.header.setInnerText(HEADER_ADVANTAGE_BY_WIND);
startLineAdvantageComponent.header.setInnerText(stringConstants.dashboardStartlineAdvantageByGeometry());
startLineAdvantageComponent.startLineAdvantageComponentState = startLineAdvantageComponent.startLineAdvantageComponentStateShowsAdvantageByWind;
startLineAdvantageComponent.liveNumber
.setInnerText(startLineAdvantageComponent.startLineAdvantageComponentStateShowsAdvantageByWind
@@ -3,8 +3,6 @@ package com.sap.sailing.dashboards.gwt.client.windchart;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.moxieapps.gwt.highcharts.client.Axis.Type;
import org.moxieapps.gwt.highcharts.client.Chart;
@@ -38,7 +36,6 @@ import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;
import com.sap.sailing.dashboards.gwt.client.RibDashboardEntryPoint;
/**
* The class represents a wind chart that is displayed vertically. Because it is meant to be used to display wind fixes,
@@ -71,8 +68,6 @@ public class VerticalWindChart extends Composite implements HasWidgets {
private static final int MAX_SERIES_POINTS = 100000000;
private static final Logger logger = Logger.getLogger(RibDashboardEntryPoint.class.getName());
private static VerticalWindChartUiBinder uiBinder = GWT.create(VerticalWindChartUiBinder.class);
interface VerticalWindChartUiBinder extends UiBinder<Widget, VerticalWindChart> {
@@ -246,7 +241,6 @@ public class VerticalWindChart extends Composite implements HasWidgets {
} else {
pointRangeInMilliseconds = 0;
}
logger.log(Level.INFO, "Points Range " + pointRangeInMilliseconds);
return pointRangeInMilliseconds;
}
@@ -34,6 +34,32 @@
color: black;
}
.windloadinghint {
position: absolute;
background-color: white;
opacity: 0.4;
width: 100%;
height: 100%;
vertical-align: middle;
text-align: center;
z-index: 0;
}
.windloadinghinttext {
position: absolute;
bottom: 0pt;
height: 15pt;
line-height: 15pt;
margin-top: 0pt;
vertical-align: middle;
font-size: 12pt;
font-family: "Arial";
width: 100%;
text-align: center;
top: 50%;
margin-top: -7pt;
}
.clickHintMinutesSpan{
font-weight: bolder;
}
@@ -24,6 +24,7 @@ import com.sap.sailing.dashboards.gwt.shared.SinWave;
import com.sap.sailing.dashboards.gwt.shared.WindType;
import com.sap.sailing.domain.common.WindSource;
import com.sap.sailing.domain.common.WindSourceType;
import com.sap.sailing.gwt.ui.client.StringMessages;
import com.sap.sailing.gwt.ui.client.shared.charts.ChartPointRecalculator;
import com.sap.sailing.gwt.ui.shared.WindDTO;
import com.sap.sailing.gwt.ui.shared.WindInfoForRaceDTO;
@@ -57,6 +58,9 @@ public class WindBotComponent extends Composite implements HasWidgets, WindBotDa
interface WindBotComponentStyle extends CssResource {
}
@UiField
public HTMLPanel windBotComponent;
/**
* The header of the widget displaying the name of the wind bot.
* */
@@ -67,14 +71,14 @@ public class WindBotComponent extends Composite implements HasWidgets, WindBotDa
* One of two {@link LiveAverageComponent}s that display in big font the live and the average value of the measured
* wind speed.
* */
@UiField
@UiField(provided = true)
public LiveAverageComponent trueWindSpeedLiveAverageComponent;
/**
* One of two {@link LiveAverageComponent}s that display in big font the live and the average value of the measured
* wind direction.
* */
@UiField
@UiField(provided = true)
public LiveAverageComponent trueWindDirectionLiveAverageComponent;
/**
@@ -99,9 +103,11 @@ public class WindBotComponent extends Composite implements HasWidgets, WindBotDa
public LocationPointerCompass locationPointerCompass;
private String windBotId;
private StringMessages stringConstants;
public WindBotComponent(String windBotId) {
this.windBotId = windBotId;
stringConstants = StringMessages.INSTANCE;
String inSimulationModeParameter = Window.Location.getParameter(IN_SIMULATION_MODE);
if (inSimulationModeParameter != null && inSimulationModeParameter.equals("true")) {
inSimulationMode = true;
@@ -112,8 +118,10 @@ public class WindBotComponent extends Composite implements HasWidgets, WindBotDa
}
movingAverageSpeed = new MovingAverage(500);
movingAverageDirection = new MovingAverage(500);
trueWindSpeedLiveAverageComponent = new LiveAverageComponent(stringConstants.dashboardTrueWindSpeed(), stringConstants.dashboardTrueWindSpeedUnit());
trueWindDirectionLiveAverageComponent = new LiveAverageComponent(stringConstants.dashboardTrueWindDirection(), stringConstants.dashboardTrueWindDirectionUnit());
initWidget(uiBinder.createAndBindUi(this));
windBotNamePanel.getElement().setInnerText("Wind Bot " + windBotId);
windBotNamePanel.getElement().setInnerText(stringConstants.dashboardWindBot()+" "+ windBotId);
trueWindSpeedVerticalWindChart.addVerticalWindChartClickListener(trueWindSpeedLiveAverageComponent);
trueWindDirectionVerticalWindChart.addVerticalWindChartClickListener(trueWindDirectionLiveAverageComponent);
}
@@ -8,17 +8,17 @@
<ui:style type='com.sap.sailing.dashboards.gwt.client.windchart.WindBotComponent.WindBotComponentStyle'
src='WindBotComponentStyle.css'></ui:style>
<g:HTMLPanel styleName="windBotComponent">
<g:HTMLPanel ui:field="windBotNamePanel" styleName="{style.windBotNamePanel}">Wind Bot</g:HTMLPanel>
<g:HTMLPanel ui:field="windBotComponent" styleName="windBotComponent">
<g:HTMLPanel ui:field="windBotNamePanel" styleName="{style.windBotNamePanel}"></g:HTMLPanel>
<g:HTMLPanel
styleName="{style.wind_bot_component_live_average_container} {style.wind_bot_component_left}">
<startlineadvantage:LiveAverageComponent
ui:field="trueWindSpeedLiveAverageComponent" header="TWS" unit="kts"></startlineadvantage:LiveAverageComponent>
ui:field="trueWindSpeedLiveAverageComponent"></startlineadvantage:LiveAverageComponent>
</g:HTMLPanel>
<g:HTMLPanel
styleName="{style.wind_bot_component_live_average_container} {style.wind_bot_component_right}">
<startlineadvantage:LiveAverageComponent
ui:field="trueWindDirectionLiveAverageComponent" header="TWD" unit="°"></startlineadvantage:LiveAverageComponent>
ui:field="trueWindDirectionLiveAverageComponent"></startlineadvantage:LiveAverageComponent>
</g:HTMLPanel>
<g:HTMLPanel
styleName="{style.wind_bot_component_windchart_container} {style.wind_bot_component_left}">
@@ -3,9 +3,7 @@ package com.sap.sailing.dashboards.gwt.server;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -15,6 +13,7 @@ import org.osgi.util.tracker.ServiceTracker;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.sap.sailing.dashboards.gwt.client.RibDashboardService;
import com.sap.sailing.dashboards.gwt.client.startanalysis.StartlineAdvantageType;
import com.sap.sailing.dashboards.gwt.server.startanalysis.StartAnalysisCreationController;
import com.sap.sailing.dashboards.gwt.shared.MovingAverage;
import com.sap.sailing.dashboards.gwt.shared.ResponseMessage;
import com.sap.sailing.dashboards.gwt.shared.dto.RibDashboardRaceInfoDTO;
@@ -25,7 +24,7 @@ import com.sap.sailing.domain.base.Fleet;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedRace;
@@ -39,10 +38,14 @@ import com.sap.sse.common.impl.MillisecondsTimePoint;
* @author Alexander Ries (D062114)
*
*/
public class RibDashboardServiceImpl extends RemoteServiceServlet implements RibDashboardService,
StartAnalysisRacesStoreListener {
public class RibDashboardServiceImpl extends RemoteServiceServlet implements RibDashboardService {
private static final long serialVersionUID = 7710545186662632126L;
/**
*
*/
private static final long serialVersionUID = 1L;
private StartAnalysisCreationController startAnalysisCreationController;
/**
* Variable contains last {@link TrackedRace} that is or was live
@@ -63,30 +66,18 @@ public class RibDashboardServiceImpl extends RemoteServiceServlet implements Rib
* */
private MovingAverage averageStartLineAdvantageByGeometry;
/**
* The map values contain a Pair, whose first value is a {@link MovingAverage} for the true wind speed and the
* second one contains an average for the true wind direction for a wind bot.
* */
private Map<String, List<StartAnalysisDTO>> startAnalysisDTOsForCompetitor;
private StartAnalysisRacesStore startAnalysisRacesStore;
private final ServiceTracker<RacingEventService, RacingEventService> racingEventServiceTracker;
private final com.sap.sailing.domain.base.DomainFactory baseDomainFactory;
private final BundleContext context;
private static final Logger logger = Logger.getLogger(RibDashboardServiceImpl.class.getName());
public RibDashboardServiceImpl() {
context = Activator.getDefault();
racingEventServiceTracker = createAndOpenRacingEventServiceTracker(context);
baseDomainFactory = getRacingEventService().getBaseDomainFactory();
startAnalysisCreationController = new StartAnalysisCreationController(getRacingEventService());
startAnalysisRacesStore = new StartAnalysisRacesStore(getRacingEventService());
startAnalysisRacesStore.addStartAnalyisisRacesStoreListener(this);
startAnalysisDTOsForCompetitor = new HashMap<String, List<StartAnalysisDTO>>();
averageStartLineAdvantageByWind = new MovingAverage(400);
averageStartLineAdvantageByGeometry = new MovingAverage(400);
}
@@ -105,36 +96,26 @@ public class RibDashboardServiceImpl extends RemoteServiceServlet implements Rib
/**
* <param>leaderboardName</param> is used to retrieve the live running race with the method
* {@link #getLiveRaceFromLeaderboardName(String)}. <param>competitorName</param> If competitorName is null, the
* response contains only a list of competitors in the live race. Otherwise the parameter is used to return the
* right startanalysis for a specific competitor with in the returned {@link RibDashboardRaceInfoDTO}.
* {@link #getLiveRaceFromLeaderboardName(String)}.
*
* @throws NoWindException
* */
@Override
public RibDashboardRaceInfoDTO getLiveRaceInfo(String leaderboardName, String competitorName) throws NoWindException {
public RibDashboardRaceInfoDTO getLiveRaceInfo(String leaderboardName) throws NoWindException {
RibDashboardRaceInfoDTO lRInfo = new RibDashboardRaceInfoDTO();
if (leaderboardName != null) {
TimePoint timePointOfRequest = MillisecondsTimePoint.now();
if (checkIfRaceIsStillRunning(timePointOfRequest, leaderboardName)) {
if (competitorName == null) {
fillLiveRaceInfoDTOWithRaceData(lRInfo, timePointOfRequest);
logger.log(Level.INFO, "No Competitor selected");
lRInfo.responseMessage = ResponseMessage.NO_COMPETITOR_SELECTED;
return lRInfo;
} else {
fillLiveRaceInfoDTOWithRaceData(lRInfo, timePointOfRequest);
fillLiveRaceInfoDTOWithStartLineAdavantageData(lRInfo, timePointOfRequest);
fillLiveRaceInfoDTOWithStartAnalysisData(lRInfo, competitorName);
lRInfo.responseMessage = ResponseMessage.OK;
lRInfo.responseMessage = ResponseMessage.RACE_LIVE;
} else {
lRInfo.responseMessage = ResponseMessage.NO_RACE_LIVE;
}
} else {
lRInfo.responseMessage = ResponseMessage.NO_RACE_LIVE;
}
return lRInfo;
} else {
lRInfo.responseMessage = ResponseMessage.NO_RACE_LIVE;
return lRInfo;
}
}
private void fillLiveRaceInfoDTOWithRaceData(RibDashboardRaceInfoDTO lRInfo, TimePoint now) {
@@ -150,10 +131,6 @@ public class RibDashboardServiceImpl extends RemoteServiceServlet implements Rib
}
}
private void fillLiveRaceInfoDTOWithStartAnalysisData(RibDashboardRaceInfoDTO lRInfo, String competiorName) {
lRInfo.startAnalysisDTOList = startAnalysisDTOsForCompetitor.get(competiorName);
}
// returns true if race is still live
private boolean checkIfRaceIsStillRunning(TimePoint now, String leaderboardName) {
if (runningRace == null || !(runningRace.isLive(now))) {
@@ -161,7 +138,6 @@ public class RibDashboardServiceImpl extends RemoteServiceServlet implements Rib
if (runningRace == null) {
return false;
} else {
startAnalysisRacesStore.addAsListener(runningRace);
return true;
}
} else {
@@ -185,7 +161,24 @@ public class RibDashboardServiceImpl extends RemoteServiceServlet implements Rib
return result;
}
private void fillLiveRaceInfoDTOWithStartLineAdavantageData(RibDashboardRaceInfoDTO lRInfo, TimePoint timePoint) throws NoWindException {
private List<TrackedRace> getTrackedRacesFromLeaderboard(String leaderboardName) {
List<TrackedRace> result = new ArrayList<TrackedRace>();
Leaderboard lb = getRacingEventService().getLeaderboardByName(leaderboardName);
if (lb != null) {
for (RaceColumn column : lb.getRaceColumns()) {
for (Fleet fleet : column.getFleets()) {
TrackedRace race = column.getTrackedRace(fleet);
if (race != null) {
result.add(race);
}
}
}
}
return result;
}
private void fillLiveRaceInfoDTOWithStartLineAdavantageData(RibDashboardRaceInfoDTO lRInfo, TimePoint timePoint)
throws NoWindException {
if (runningRace != null && timePoint != null) {
StartLineAdvantageDTO startLineAdvantageDTO = new StartLineAdvantageDTO();
StartlineAdvantageType startlineAdvantageType = getStartlineAdvantageType(runningRace, timePoint);
@@ -224,7 +217,8 @@ public class RibDashboardServiceImpl extends RemoteServiceServlet implements Rib
}
}
private LegType getFirstLegTypeOfTrackedRaceAtTimePoint(TrackedRace trackedRace, TimePoint timePoint) throws NoWindException{
private LegType getFirstLegTypeOfTrackedRaceAtTimePoint(TrackedRace trackedRace, TimePoint timePoint)
throws NoWindException {
Iterable<TrackedLeg> trackedLegs = trackedRace.getTrackedLegs();
if (trackedLegs != null && trackedLegs.iterator().hasNext()) {
TrackedLeg firstLegInTrackedRace = trackedLegs.iterator().next();
@@ -238,12 +232,26 @@ public class RibDashboardServiceImpl extends RemoteServiceServlet implements Rib
return baseDomainFactory;
}
public TrackedRace getExistingTrackedRace(RegattaAndRaceIdentifier regattaNameAndRaceName) {
return null;
@Override
public List<StartAnalysisDTO> getStartAnalysisListForCompetitorIDAndLeaderboardName(String competitorIdAsString,
String leaderboardName) {
List<StartAnalysisDTO> startAnalysisDTOs = new ArrayList<StartAnalysisDTO>();
Competitor competitor = baseDomainFactory.getCompetitorStore().getExistingCompetitorByIdAsString(
competitorIdAsString);
List<TrackedRace> trackedRacesForLeaderBoardName = getTrackedRacesFromLeaderboard(leaderboardName);
for (TrackedRace trackedRace : trackedRacesForLeaderBoardName) {
StartAnalysisDTO startAnalysisDTO = startAnalysisCreationController.checkStartAnalysisForCompetitorInTrackedRace(competitor, trackedRace);
if (startAnalysisDTO != null)
startAnalysisDTOs.add(startAnalysisDTO);
}
return startAnalysisDTOs;
}
@Override
public void startAnalyisisRacesChanged(Map<String, List<StartAnalysisDTO>> startAnalysisDTOCompetitorMap) {
startAnalysisDTOsForCompetitor = startAnalysisDTOCompetitorMap;
public List<CompetitorDTO> getCompetitorsInLeaderboard(String leaderboardName) {
logger.log(Level.INFO, "getCompetitorsInLeaderboard(...) Request.");
Leaderboard lb = getRacingEventService().getLeaderboardByName(leaderboardName);
return baseDomainFactory.getCompetitorDTOList(lb.getCompetitorsFromBestToWorst(new MillisecondsTimePoint(
new Date())));
}
}
@@ -1,484 +0,0 @@
package com.sap.sailing.dashboards.gwt.server;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.dashboards.gwt.client.RibDashboardEntryPoint;
import com.sap.sailing.dashboards.gwt.client.startanalysis.StartlineAdvantageType;
import com.sap.sailing.dashboards.gwt.client.startanalysis.rankingtable.StartAnalysisStartRankTable;
import com.sap.sailing.dashboards.gwt.shared.dto.StartLineAdvantageDTO;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisCompetitorDTO;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisRankingTableEntryDTO;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.WindAndAdvantagesInfoForStartLineDTO;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Mark;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.domain.common.dto.PositionDTO;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.LineDetails;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
import com.sap.sailing.gwt.ui.shared.MarkDTO;
import com.sap.sailing.gwt.ui.shared.SpeedWithBearingDTO;
import com.sap.sailing.gwt.ui.shared.WindDTO;
import com.sap.sailing.server.RacingEventService;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
/**
* The task of StartAnalyisisRacesStore is to cache StartAnalysisDTOs for Competitors at one day. At initialization it
* generates StartAnalysisDTOs for Competitors from races started before initialization time and generates
* StartAnalysisDTOs for Competitors from races that are starting when the server is running. It achieves this basically
* by registering as RaceChangeListener and receiving so markRoundings for competitors.
*/
public class StartAnalysisRacesStore {
private final com.sap.sailing.domain.base.DomainFactory baseDomainFactory;
/**
* {@link StartAnalyisisRacesStoreListener} get notified at after initialization and after the change of
* {@link #startAnalysisDTOListForCompetitorName}
* */
private List<StartAnalysisRacesStoreListener> listeners;
/**
* Caches a list of chronologically sorted {@link StartAnalysisDTO} for names from Competitors. After any change of
* this field {@link #notifyListernersAboutStartAnalysisDTOsChanges()} get called.
* */
private Map<String, List<StartAnalysisDTO>> startAnalysisDTOListForCompetitorName;
/**
* Contains a ranking list of competitor at mark one for every race
* */
private Map<TrackedRace, List<Competitor>> rankingListAtMarkOneForTrackedRace;
/**
* Like {@link startAnalysisCompetitorDTOsForCompetitorNameForTrackedRace} gpsFixesForCompetitorNameForTracedRace
* contains the list of gps fixes for a competitor name at from 6 seconds at the start. Caching
* {@link gpsFixesForCompetitorNameForTracedRace}s brings the advantage of faster code at creating finished
* {@link #startAnalysisDTOListForCompetitorName}.
* */
private Map<TrackedRace, Map<String, List<GPSFixDTO>>> gpsFixesPathBeforeStartForCompetitor;
private static final Logger logger = Logger.getLogger(RibDashboardEntryPoint.class.getName());
/**
* Minimum number of competitors in {@link StartAnalysisStartRankTable}
* */
private final int MINIMUM_NUMBER_OF_COMPETITORS_IN_STARTANALYSISRANKINGTABLE = 3;
private final int TAILLENGHT_IN_MILLISECONDS = 200000;
/**
* Calls {@link #generateStartAnalysisDTOsForCompetitorNamesFromRacesStartedBeforeClassInitialization()} to create
* startanalysisDTO for races that started before class initialization.
*/
public StartAnalysisRacesStore(RacingEventService racingEventService) {
baseDomainFactory = racingEventService.getBaseDomainFactory();
this.listeners = new ArrayList<StartAnalysisRacesStoreListener>();
startAnalysisDTOListForCompetitorName = new HashMap<String, List<StartAnalysisDTO>>();
rankingListAtMarkOneForTrackedRace = new HashMap<TrackedRace, List<Competitor>>();
gpsFixesPathBeforeStartForCompetitor = new HashMap<TrackedRace, Map<String, List<GPSFixDTO>>>();
}
/**
* Returns the Index of the Mark in the Race. Is used in
* {@link #markPassingReceived(Competitor, Map, Iterable, TrackedRace)} to identify just markpassings at mark one,
* because the position of a boat at mark one defines the performance/startingrank of the race
*/
private int getIndexOfMarkPassingMark(Iterable<MarkPassing> markPassings, TrackedRace trackedRace) {
List<MarkPassing> markPassingsList = listFromIterator(markPassings.iterator());
return trackedRace.getRace().getCourse()
.getIndexOfWaypoint(markPassingsList.get(markPassingsList.size() - 1).getWaypoint());
}
private List<Competitor> getOrCreateAndAddRankingListAtMarkOneForTrackedRace(TrackedRace trackedRace) {
if (!rankingListAtMarkOneForTrackedRace.containsKey(trackedRace)) {
List<Competitor> rankingListAtMarkOneForRace = new ArrayList<Competitor>();
rankingListAtMarkOneForTrackedRace.put(trackedRace, rankingListAtMarkOneForRace);
}
return rankingListAtMarkOneForTrackedRace.get(trackedRace);
}
private List<StartAnalysisDTO> getOrCreateAndAddStartAnalysisDTOListForCompetitorName(String competitorName) {
if (!startAnalysisDTOListForCompetitorName.containsKey(competitorName)) {
List<StartAnalysisDTO> startAnalysisDTOList = new ArrayList<StartAnalysisDTO>();
startAnalysisDTOListForCompetitorName.put(competitorName, startAnalysisDTOList);
}
return startAnalysisDTOListForCompetitorName.get(competitorName);
}
private StartAnalysisDTO createStartAnalysisDTOForCompetitor(Competitor competitor, TrackedRace trackedRace) {
StartAnalysisDTO startAnalysisDTO = createStartAnalysisDTOWithOnlyStaticData(trackedRace);
List<StartAnalysisCompetitorDTO> competitors = new ArrayList<StartAnalysisCompetitorDTO>();
List<Competitor> rankingList = getOrCreateAndAddRankingListAtMarkOneForTrackedRace(trackedRace);
boolean isCompetitorBetweenFirstThree = false;
for (int i = 0; i < MINIMUM_NUMBER_OF_COMPETITORS_IN_STARTANALYSISRANKINGTABLE; i++) {
competitors.add(createStartAnalysisCompetitorDTO(trackedRace, i + 1,
getOrCreateAndAddRankingListAtMarkOneForTrackedRace(trackedRace).get(i)));
if (rankingList.get(i).equals(competitor)) {
isCompetitorBetweenFirstThree = true;
}
}
if (!isCompetitorBetweenFirstThree) {
competitors.add(createStartAnalysisCompetitorDTO(trackedRace,
getOrCreateAndAddRankingListAtMarkOneForTrackedRace(trackedRace).size(), competitor));
}
startAnalysisDTO.startAnalysisCompetitorDTOs = competitors;
return startAnalysisDTO;
}
private List<MarkPassing> listFromIterator(Iterator<MarkPassing> iterator) {
List<MarkPassing> result = new ArrayList<MarkPassing>();
while (iterator.hasNext()) {
result.add(iterator.next());
}
return result;
}
private StartAnalysisDTO createStartAnalysisDTOWithOnlyStaticData(TrackedRace trackedRace) {
StartAnalysisDTO startAnalysisDTO = new StartAnalysisDTO();
startAnalysisDTO.raceName = trackedRace.getRace().getName();
startAnalysisDTO.startLineMarkPositions = getStartLineBoyPositions(trackedRace);
startAnalysisDTO.firstMark = getMarksOfWayPoint(trackedRace, getFirstMarkWayPoint(trackedRace)).get(0);
startAnalysisDTO.startAnalysisWindLineInfoDTO = createStartAnalysisWindAndLineData(trackedRace);
startAnalysisDTO.startLineMarks = getMarksOfWayPoint(trackedRace,
trackedRace.getStartLine(trackedRace.getStartTimeReceived()).getWaypoint());
return startAnalysisDTO;
}
private Map<String, Date> createPerCompetitorIdAsString(TrackedRace currentRace, Date fiveSecondsBeforeStart) {
Map<String, Date> fromPerCompetitorIdAsString = new HashMap<String, Date>();
for (Competitor competitor : currentRace.getCompetitorsFromBestToWorst(new MillisecondsTimePoint(new Date()
.getTime()))) {
fromPerCompetitorIdAsString.put(competitor.getId().toString(), fiveSecondsBeforeStart);
}
return fromPerCompetitorIdAsString;
}
private StartAnalysisRankingTableEntryDTO createRankTableEntry(TrackedRace trackedRace, int rank,
Competitor competitor) {
StartAnalysisRankingTableEntryDTO startAnalysisRankTableEntryDTO = createStartAnalysisRankTableEntryDTOWithRankAndStartTimepoint(
competitor, rank, trackedRace);
return startAnalysisRankTableEntryDTO;
}
private StartAnalysisRankingTableEntryDTO createStartAnalysisRankTableEntryDTOWithRankAndStartTimepoint(
Competitor competitor, int rank, TrackedRace trackedRace) {
StartAnalysisRankingTableEntryDTO tableentry = new StartAnalysisRankingTableEntryDTO();
tableentry.rankAtFirstMark = rank;
tableentry.teamName = competitor.getName();
tableentry.speedAtStartTime = trackedRace.getSpeed(competitor, 1).getKnots();
tableentry.distanceToLineAtStartTime = trackedRace.getDistanceToStartLine(competitor, 1).getMeters();
tableentry.tailColor = competitor.getColor().getAsHtml();
return tableentry;
}
private List<PositionDTO> getStartLineBoyPositions(TrackedRace trackedRace) {
return getMarkPositionDTOs(trackedRace.getStartTimeReceived(), trackedRace, trackedRace.getRace().getCourse()
.getFirstWaypoint());
}
private Waypoint getFirstMarkWayPoint(TrackedRace trackedRace) {
Waypoint firstMarkWayPoint = null;
Iterator<Waypoint> waypoints = trackedRace.getRace().getCourse().getWaypoints().iterator();
int wayPointCounter = 0;
while (waypoints.hasNext()) {
Waypoint wayPoint = waypoints.next();
if (wayPointCounter == 1) {
return wayPoint;
}
wayPointCounter++;
}
return firstMarkWayPoint;
}
private List<PositionDTO> getMarkPositionDTOs(TimePoint timePoint, TrackedRace trackedRace, Waypoint waypoint) {
List<PositionDTO> markPositionDTOs = new ArrayList<PositionDTO>();
for (Mark startMark : waypoint.getMarks()) {
final Position estimatedMarkPosition = trackedRace.getOrCreateTrack(startMark).getEstimatedPosition(
timePoint, /* extrapolate */false);
if (estimatedMarkPosition != null) {
markPositionDTOs.add(new PositionDTO(estimatedMarkPosition.getLatDeg(), estimatedMarkPosition
.getLngDeg()));
}
}
return markPositionDTOs;
}
private List<MarkDTO> getMarksOfWayPoint(TrackedRace trackedRace, Waypoint wayPoint) {
List<MarkDTO> startLineMarks = new ArrayList<MarkDTO>();
for (Mark startMark : wayPoint.getMarks()) {
final Position estimatedMarkPosition = trackedRace.getOrCreateTrack(startMark).getEstimatedPosition(
trackedRace.getStartTimeReceived(), /* extrapolate */false);
if (estimatedMarkPosition != null) {
startLineMarks.add(convertToMarkDTO(startMark, estimatedMarkPosition));
}
}
return startLineMarks;
}
private MarkDTO convertToMarkDTO(Mark mark, Position position) {
MarkDTO markDTO;
if (position != null) {
markDTO = new MarkDTO(mark.getId().toString(), mark.getName(), position.getLatDeg(), position.getLngDeg());
} else {
markDTO = new MarkDTO(mark.getId().toString(), mark.getName());
}
markDTO.color = mark.getColor();
markDTO.shape = mark.getShape();
markDTO.pattern = mark.getPattern();
markDTO.type = mark.getType();
return markDTO;
}
public void addStartAnalyisisRacesStoreListener(StartAnalysisRacesStoreListener startAnalyisisRacesStoreListener) {
listeners.add(startAnalyisisRacesStoreListener);
}
private WindAndAdvantagesInfoForStartLineDTO createStartAnalysisWindAndLineData(TrackedRace trackedRace) {
WindAndAdvantagesInfoForStartLineDTO startAnalysisWindLineInfoDTO = new WindAndAdvantagesInfoForStartLineDTO();
LineDetails startline = trackedRace.getStartLine(trackedRace.getStartTimeReceived());
StartLineAdvantageDTO startLineAdvantageDTO = new StartLineAdvantageDTO();
startLineAdvantageDTO.startLineAdvatageType = getStartlineAdvantageType(trackedRace, new MillisecondsTimePoint(new Date()));
startLineAdvantageDTO.startLineAdvantage = startline.getAdvantage().getMeters();
startAnalysisWindLineInfoDTO.startLineAdvantage = startLineAdvantageDTO;
Position portMarkPosition = trackedRace.getOrCreateTrack(
trackedRace.getStartLine(trackedRace.getStartTimeReceived()).getStarboardMarkWhileApproachingLine())
.getEstimatedPosition(trackedRace.getStartTimeReceived(), /* extrapolate */
false);
Wind windAtStart = trackedRace.getWind(portMarkPosition, trackedRace.getStartTimeReceived());
startAnalysisWindLineInfoDTO.windDirectionInDegrees = windAtStart.getBearing().getDegrees();
startAnalysisWindLineInfoDTO.windSpeedInKnots = windAtStart.getKnots();
return startAnalysisWindLineInfoDTO;
}
private StartlineAdvantageType getStartlineAdvantageType(TrackedRace trackedRace, TimePoint timePoint) {
try {
LegType typeOfFirstLeg;
typeOfFirstLeg = getFirstLegTypeOfTrackedRaceAtTimePoint(trackedRace, timePoint);
switch (typeOfFirstLeg) {
case UPWIND:
return StartlineAdvantageType.WIND;
case REACHING:
return StartlineAdvantageType.GEOMETRIC;
default:
return StartlineAdvantageType.WIND;
}
} catch (NoWindException e) {
e.printStackTrace();
return null;
}
}
public void notifyListernersAboutStartAnalysisDTOChange() {
for (StartAnalysisRacesStoreListener startAnalyisisRacesStoreListener : listeners) {
startAnalyisisRacesStoreListener.startAnalyisisRacesChanged(startAnalysisDTOListForCompetitorName);
}
}
/**
* returns a List of GPSFixDTOs for every competitor name in tracked race
* */
private Map<String, List<GPSFixDTO>> getGPSFixDTOListForCompetitorName(TrackedRace trackedRace,
Map<String, Date> fromPerCompetitorIdAsString, Map<String, Date> toPerCompetitorIdAsString,
boolean extrapolate) throws NoWindException {
Map<String, List<GPSFixDTO>> result = new HashMap<String, List<GPSFixDTO>>();
if (trackedRace != null) {
for (Competitor competitor : trackedRace.getRace().getCompetitors()) {
if (fromPerCompetitorIdAsString.containsKey(competitor.getId().toString())) {
CompetitorDTO competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor);
List<GPSFixDTO> fixesForCompetitor = new ArrayList<GPSFixDTO>();
result.put(competitorDTO.getName(), fixesForCompetitor);
GPSFixTrack<Competitor, GPSFixMoving> track = trackedRace.getTrack(competitor);
TimePoint fromTimePoint = new MillisecondsTimePoint(fromPerCompetitorIdAsString.get(competitorDTO
.getIdAsString()));
TimePoint toTimePoint = new MillisecondsTimePoint(toPerCompetitorIdAsString.get(competitorDTO
.getIdAsString()));
// copy the fixes into a list while holding the monitor;
// then release the monitor to avoid deadlocks
// during wind estimations required for tack determination
List<GPSFixMoving> fixes = new ArrayList<GPSFixMoving>();
track.lockForRead();
try {
Iterator<GPSFixMoving> fixIter = track.getFixesIterator(fromTimePoint, /* inclusive */
true);
while (fixIter.hasNext()) {
GPSFixMoving fix = fixIter.next();
if (fix.getTimePoint().after(fromTimePoint) && fix.getTimePoint().before(toTimePoint)) {
fixes.add(fix);
}
}
} finally {
track.unlockAfterRead();
}
Iterator<GPSFixMoving> fixIter = fixes.iterator();
while (fixIter.hasNext()) {
GPSFixMoving fix = fixIter.next();
Wind wind = trackedRace.getWind(fix.getPosition(), toTimePoint);
final SpeedWithBearing estimatedSpeed = track.getEstimatedSpeed(fix.getTimePoint());
Tack tack = wind == null ? null : trackedRace.getTack(estimatedSpeed, wind, fix.getTimePoint());
LegType legType = null;
WindDTO windDTO = wind == null ? null : createWindDTOFromAlreadyAveraged(wind, toTimePoint);
GPSFixDTO fixDTO = createGPSFixDTO(fix, estimatedSpeed, windDTO, tack, legType, /* extrapolate */
false);
fixesForCompetitor.add(fixDTO);
}
}
}
}
return result;
}
protected WindDTO createWindDTOFromAlreadyAveraged(Wind wind, TimePoint requestTimepoint) {
WindDTO windDTO = new WindDTO();
windDTO.requestTimepoint = requestTimepoint.asMillis();
windDTO.trueWindBearingDeg = wind.getBearing().getDegrees();
windDTO.trueWindFromDeg = wind.getBearing().reverse().getDegrees();
windDTO.trueWindSpeedInKnots = wind.getKnots();
windDTO.trueWindSpeedInMetersPerSecond = wind.getMetersPerSecond();
windDTO.dampenedTrueWindBearingDeg = wind.getBearing().getDegrees();
windDTO.dampenedTrueWindFromDeg = wind.getBearing().reverse().getDegrees();
windDTO.dampenedTrueWindSpeedInKnots = wind.getKnots();
windDTO.dampenedTrueWindSpeedInMetersPerSecond = wind.getMetersPerSecond();
if (wind.getPosition() != null) {
windDTO.position = new PositionDTO(wind.getPosition().getLatDeg(), wind.getPosition().getLngDeg());
}
if (wind.getTimePoint() != null) {
windDTO.measureTimepoint = wind.getTimePoint().asMillis();
}
return windDTO;
}
private SpeedWithBearingDTO createSpeedWithBearingDTO(SpeedWithBearing speedWithBearing) {
return new SpeedWithBearingDTO(speedWithBearing.getKnots(), speedWithBearing.getBearing().getDegrees());
}
private GPSFixDTO createGPSFixDTO(GPSFix fix, SpeedWithBearing speedWithBearing, WindDTO windDTO, Tack tack,
LegType legType, boolean extrapolated) {
return new GPSFixDTO(fix.getTimePoint().asDate(), fix.getPosition() == null ? null : new PositionDTO(fix
.getPosition().getLatDeg(), fix.getPosition().getLngDeg()), speedWithBearing == null ? null
: createSpeedWithBearingDTO(speedWithBearing), windDTO, tack, legType, extrapolated);
}
private LegType getFirstLegTypeOfTrackedRaceAtTimePoint(TrackedRace trackedRace, TimePoint timePoint)
throws NoWindException {
Iterable<TrackedLeg> trackedLegs = trackedRace.getTrackedLegs();
if (trackedLegs != null && trackedLegs.iterator().hasNext()) {
TrackedLeg firstLegInTrackedRace = trackedLegs.iterator().next();
return firstLegInTrackedRace.getLegType(timePoint);
} else {
return null;
}
}
private StartAnalysisCompetitorDTO createStartAnalysisCompetitorDTO(TrackedRace trackedRace, int rank,
Competitor competitor) {
StartAnalysisCompetitorDTO startAnalysisCompetitorDTOsForRace = new StartAnalysisCompetitorDTO();
startAnalysisCompetitorDTOsForRace.competitorDTO = baseDomainFactory.convertToCompetitorDTO(competitor);
startAnalysisCompetitorDTOsForRace.rankingTableEntryDTO = createRankTableEntry(trackedRace, rank, competitor);
List<GPSFixDTO> boatPositions = getOrCreateGPSFixDTOsForCompetitorForTrackedRace(trackedRace).get(
competitor.getName());
startAnalysisCompetitorDTOsForRace.gpsFixDTOs = boatPositions;
return startAnalysisCompetitorDTOsForRace;
}
private Map<String, List<GPSFixDTO>> getOrCreateGPSFixDTOsForCompetitorForTrackedRace(TrackedRace trackedRace) {
try {
if (!gpsFixesPathBeforeStartForCompetitor.containsKey(trackedRace)) {
gpsFixesPathBeforeStartForCompetitor.put(
trackedRace,
getGPSFixDTOListForCompetitorName(
trackedRace,
createPerCompetitorIdAsString(trackedRace, new Date(trackedRace.getStartTimeReceived()
.asMillis() - TAILLENGHT_IN_MILLISECONDS)),
createPerCompetitorIdAsString(trackedRace, new Date(trackedRace.getStartTimeReceived()
.asMillis())), true));
}
} catch (NoWindException e) {
e.printStackTrace();
}
return gpsFixesPathBeforeStartForCompetitor.get(trackedRace);
}
public static List<StartAnalysisDTO> cloneList(List<StartAnalysisDTO> list) {
List<StartAnalysisDTO> clone = new ArrayList<StartAnalysisDTO>(list.size());
for (StartAnalysisDTO item : list)
clone.add(item);
return clone;
}
public void addAsListener(TrackedRace trackedRace) {
trackedRace.addListener(new Listener(trackedRace));
}
private class Listener extends AbstractRaceChangeListener {
private final TrackedRace trackedRace;
protected Listener(TrackedRace trackedRace) {
this.trackedRace = trackedRace;
}
@Override
public void markPassingReceived(Competitor competitor, Map<Waypoint, MarkPassing> oldMarkPassings,
Iterable<MarkPassing> markPassings) {
logger.log(Level.INFO, "Received Mark Passing " + competitor.getName());
if (markPassings.iterator().hasNext()) {
if (getIndexOfMarkPassingMark(markPassings, trackedRace) == 1) {
logger.log(Level.INFO, "Received Mark Passing " + competitor.getName() + " Mark 1");
if (!getOrCreateAndAddRankingListAtMarkOneForTrackedRace(trackedRace).contains(competitor)) {
getOrCreateAndAddRankingListAtMarkOneForTrackedRace(trackedRace).add(competitor);
logger.log(Level.INFO, "Received Mark Passing " + competitor.getName() + " RankingList Size"
+ getOrCreateAndAddRankingListAtMarkOneForTrackedRace(trackedRace).size());
if (getOrCreateAndAddRankingListAtMarkOneForTrackedRace(trackedRace).size() == MINIMUM_NUMBER_OF_COMPETITORS_IN_STARTANALYSISRANKINGTABLE) {
for (Competitor competitorInRankingList : getOrCreateAndAddRankingListAtMarkOneForTrackedRace(trackedRace)) {
getOrCreateAndAddStartAnalysisDTOListForCompetitorName(
competitorInRankingList.getName()).add(
createStartAnalysisDTOForCompetitor(competitorInRankingList, trackedRace));
}
logger.log(Level.INFO, "Received Mark Passing "
+ competitor.getName()
+ " StartAnalysisDTOlist for competitor size: "
+ getOrCreateAndAddStartAnalysisDTOListForCompetitorName(competitor.getName())
.size());
notifyListernersAboutStartAnalysisDTOChange();
} else if (getOrCreateAndAddRankingListAtMarkOneForTrackedRace(trackedRace).size() > MINIMUM_NUMBER_OF_COMPETITORS_IN_STARTANALYSISRANKINGTABLE) {
getOrCreateAndAddStartAnalysisDTOListForCompetitorName(competitor.getName()).add(
createStartAnalysisDTOForCompetitor(competitor, trackedRace));
logger.log(Level.INFO, "Received Mark Passing "
+ competitor.getName()
+ " StartAnalysisDTOlist for competitor size: "
+ getOrCreateAndAddStartAnalysisDTOListForCompetitorName(competitor.getName())
.size());
notifyListernersAboutStartAnalysisDTOChange();
}
}
}
}
}
}
}
@@ -1,12 +0,0 @@
package com.sap.sailing.dashboards.gwt.server;
import java.util.List;
import java.util.Map;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO;
public interface StartAnalysisRacesStoreListener {
public void startAnalyisisRacesChanged(Map<String , List<StartAnalysisDTO>> startAnalysisDTOs);
}
@@ -0,0 +1,29 @@
package com.sap.sailing.dashboards.gwt.server.startanalysis;
import java.util.Iterator;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedRace;
public abstract class AbstractStartAnalysisCreationValidator {
private static int MINIMUM_NUMBER_COMPETITORS_FOR_STARTANALYSIS = 3;
protected boolean threeCompetitorsPassedSecondWayPoint(TrackedRace trackedRace){
Waypoint secondWaypoint = trackedRace.getRace().getCourse().getFirstLeg().getTo();
Iterator<MarkPassing> markPassingsInOrder = trackedRace.getMarkPassingsInOrder(secondWaypoint).iterator();
int counter = 0;
while (markPassingsInOrder.hasNext()) {
markPassingsInOrder.next();
counter ++;
}
return counter >= MINIMUM_NUMBER_COMPETITORS_FOR_STARTANALYSIS ? true : false;
}
protected boolean competitorPassedSecondWayPoint(Competitor competitor, TrackedRace trackedRace){
Waypoint secondWaypoint = trackedRace.getRace().getCourse().getFirstLeg().getTo();
return trackedRace.getMarkPassing(competitor, secondWaypoint) == null ? false : true;
}
}
@@ -0,0 +1,33 @@
package com.sap.sailing.dashboards.gwt.server.startanalysis;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.RacingEventService;
public class StartAnalysisCreationController extends AbstractStartAnalysisCreationValidator {
private StartAnalysisDTOFactory startAnalysisDTOFactory;
private static final Logger logger = Logger.getLogger(StartAnalysisCreationController.class.getName());
public StartAnalysisCreationController(RacingEventService racingEventService) {
startAnalysisDTOFactory = new StartAnalysisDTOFactory(racingEventService);
}
public StartAnalysisDTO checkStartAnalysisForCompetitorInTrackedRace(Competitor competitor, TrackedRace trackedRace) {
StartAnalysisDTO startAnalysisDTO;
if (threeCompetitorsPassedSecondWayPoint(trackedRace) &&
competitorPassedSecondWayPoint(competitor, trackedRace))
{
logger.log(Level.INFO, "Trigger StartAnalysisDTO creation");
startAnalysisDTO = startAnalysisDTOFactory.createStartAnalysisForCompetitorAndTrackedRace(competitor, trackedRace);
return startAnalysisDTO;
}else{
return null;
}
}
}
@@ -0,0 +1,168 @@
package com.sap.sailing.dashboards.gwt.server.startanalysis;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.dashboards.gwt.client.startanalysis.StartlineAdvantageType;
import com.sap.sailing.dashboards.gwt.shared.dto.StartLineAdvantageDTO;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisCompetitorDTO;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisDTO;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.StartAnalysisRankingTableEntryDTO;
import com.sap.sailing.dashboards.gwt.shared.dto.startanalysis.WindAndAdvantagesInfoForStartLineDTO;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.tracking.LineDetails;
import com.sap.sailing.domain.tracking.MarkPassing;
import com.sap.sailing.domain.tracking.TrackedLeg;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.server.RacingEventService;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class StartAnalysisDTOFactory extends AbstractStartAnalysisCreationValidator{
private final com.sap.sailing.domain.base.DomainFactory baseDomainFactory;
private static int MINIMUM_NUMBER_COMPETITORS_FOR_STARTANALYSIS = 3;
private static final Logger logger = Logger.getLogger(StartAnalysisDTOFactory.class.getName());
public StartAnalysisDTOFactory(RacingEventService racingEventService){
baseDomainFactory = racingEventService.getBaseDomainFactory();
}
public StartAnalysisDTO createStartAnalysisForCompetitorAndTrackedRace(Competitor competitor, TrackedRace trackedRace) {
StartAnalysisDTO startAnalysisDTO = new StartAnalysisDTO();
addStaticDataToStartAnalysisDTOFrom(startAnalysisDTO, trackedRace);
List<StartAnalysisCompetitorDTO> competitors = new ArrayList<StartAnalysisCompetitorDTO>();
Waypoint secondWaypoint = trackedRace.getRace().getCourse().getFirstLeg().getTo();
List<MarkPassing> markPassingsInOrder = convertMarkpPassingsIteratorToList(trackedRace.getMarkPassingsInOrder(secondWaypoint).iterator());
boolean isCompetitorOneOfFirstThree = false;
for (int i = 0; i < MINIMUM_NUMBER_COMPETITORS_FOR_STARTANALYSIS; i++) {
competitors.add(createStartAnalysisCompetitorDTO(trackedRace, i + 1,
markPassingsInOrder.get(i).getCompetitor()));
if (markPassingsInOrder.get(i).getCompetitor().equals(competitor)) {
isCompetitorOneOfFirstThree = true;
}
}
if (!isCompetitorOneOfFirstThree) {
int rankOfCompetitorWhilePassingSecondWaypoint = getRankOfCompetitorWhilePassingSecondWaypoint(competitor, trackedRace);
competitors.add(createStartAnalysisCompetitorDTO(trackedRace, rankOfCompetitorWhilePassingSecondWaypoint, competitor));
}
startAnalysisDTO.competitor = baseDomainFactory.getCompetitorStore().convertToCompetitorDTO(competitor);
startAnalysisDTO.startAnalysisCompetitorDTOs = competitors;
startAnalysisDTO.timeOfStartInMilliSeconds = trackedRace.getStartOfRace().asMillis();
startAnalysisDTO.regattaAndRaceIdentifier = trackedRace.getRaceIdentifier();
logger.log(Level.INFO, "Created StartAnalysis For Competitor "+competitor.getName()+" and "+trackedRace.getRace().getName());
return startAnalysisDTO;
}
private List<MarkPassing> convertMarkpPassingsIteratorToList(Iterator<MarkPassing> iterator){
List<MarkPassing> list = new ArrayList<MarkPassing>();
while(iterator.hasNext()){
list.add(iterator.next());
}
return list;
}
private int getRankOfCompetitorWhilePassingSecondWaypoint(Competitor competitor, TrackedRace trackedRace){
Waypoint secondWaypoint = trackedRace.getRace().getCourse().getFirstLeg().getTo();
Iterator<MarkPassing> markPassings = trackedRace.getMarkPassingsInOrder(secondWaypoint).iterator();
int counter = 0;
while(markPassings.hasNext()){
counter ++;
if(markPassings.next().getCompetitor().getId().equals(competitor.getId()))
return counter;
}
return 0;
}
private StartAnalysisDTO addStaticDataToStartAnalysisDTOFrom(StartAnalysisDTO startAnalysisDTO,TrackedRace trackedRace) {
startAnalysisDTO.raceName = trackedRace.getRace().getName();
startAnalysisDTO.startAnalysisWindLineInfoDTO = createStartAnalysisWindAndLineData(trackedRace);
return startAnalysisDTO;
}
private StartAnalysisRankingTableEntryDTO createRankTableEntry(TrackedRace trackedRace, int rank,
Competitor competitor) {
StartAnalysisRankingTableEntryDTO startAnalysisRankTableEntryDTO = createStartAnalysisRankTableEntryDTOWithRankAndStartTimepoint(
competitor, rank, trackedRace);
return startAnalysisRankTableEntryDTO;
}
private StartAnalysisRankingTableEntryDTO createStartAnalysisRankTableEntryDTOWithRankAndStartTimepoint(
Competitor competitor, int rank, TrackedRace trackedRace) {
StartAnalysisRankingTableEntryDTO tableentry = new StartAnalysisRankingTableEntryDTO();
tableentry.rankAtFirstMark = rank;
tableentry.teamName = competitor.getName();
tableentry.speedAtStartTime = trackedRace.getSpeed(competitor, 1).getKnots();
tableentry.distanceToLineAtStartTime = trackedRace.getDistanceToStartLine(competitor, 1).getMeters();
tableentry.tailColor = competitor.getColor().getAsHtml();
return tableentry;
}
private WindAndAdvantagesInfoForStartLineDTO createStartAnalysisWindAndLineData(TrackedRace trackedRace) {
WindAndAdvantagesInfoForStartLineDTO startAnalysisWindLineInfoDTO = new WindAndAdvantagesInfoForStartLineDTO();
LineDetails startline = trackedRace.getStartLine(trackedRace.getStartTimeReceived());
StartLineAdvantageDTO startLineAdvantageDTO = new StartLineAdvantageDTO();
startLineAdvantageDTO.startLineAdvatageType = getStartlineAdvantageType(trackedRace, new MillisecondsTimePoint(new Date()));
startLineAdvantageDTO.startLineAdvantage = startline.getAdvantage().getMeters();
startAnalysisWindLineInfoDTO.startLineAdvantage = startLineAdvantageDTO;
Position portMarkPosition = trackedRace.getOrCreateTrack(
trackedRace.getStartLine(trackedRace.getStartTimeReceived()).getStarboardMarkWhileApproachingLine())
.getEstimatedPosition(trackedRace.getStartTimeReceived(), /* extrapolate */
false);
Wind windAtStart = trackedRace.getWind(portMarkPosition, trackedRace.getStartTimeReceived());
startAnalysisWindLineInfoDTO.windDirectionInDegrees = windAtStart.getBearing().getDegrees();
startAnalysisWindLineInfoDTO.windSpeedInKnots = windAtStart.getKnots();
return startAnalysisWindLineInfoDTO;
}
private StartlineAdvantageType getStartlineAdvantageType(TrackedRace trackedRace, TimePoint timePoint) {
try {
LegType typeOfFirstLeg;
typeOfFirstLeg = getFirstLegTypeOfTrackedRaceAtTimePoint(trackedRace, timePoint);
switch (typeOfFirstLeg) {
case UPWIND:
return StartlineAdvantageType.WIND;
case REACHING:
return StartlineAdvantageType.GEOMETRIC;
default:
return StartlineAdvantageType.WIND;
}
} catch (NoWindException e) {
e.printStackTrace();
return null;
}
}
private LegType getFirstLegTypeOfTrackedRaceAtTimePoint(TrackedRace trackedRace, TimePoint timePoint)
throws NoWindException {
Iterable<TrackedLeg> trackedLegs = trackedRace.getTrackedLegs();
if (trackedLegs != null && trackedLegs.iterator().hasNext()) {
TrackedLeg firstLegInTrackedRace = trackedLegs.iterator().next();
return firstLegInTrackedRace.getLegType(timePoint);
} else {
return null;
}
}
private StartAnalysisCompetitorDTO createStartAnalysisCompetitorDTO(TrackedRace trackedRace, int rank,
Competitor competitor) {
StartAnalysisCompetitorDTO startAnalysisCompetitorDTOsForRace = new StartAnalysisCompetitorDTO();
startAnalysisCompetitorDTOsForRace.competitorDTO = baseDomainFactory.getCompetitorStore().convertToCompetitorDTO(competitor);
startAnalysisCompetitorDTOsForRace.rankingTableEntryDTO = createRankTableEntry(trackedRace, rank, competitor);
return startAnalysisCompetitorDTOsForRace;
}
}
@@ -9,9 +9,7 @@ package com.sap.sailing.dashboards.gwt.shared;
*/
public enum ResponseMessage
{
OK,
BETWEEN_RACES,
NO_RACE_LIVE,
NO_COMPETITOR_SELECTED
RACE_LIVE,
NO_RACE_LIVE
}
@@ -1,10 +1,8 @@
package com.sap.sailing.dashboards.gwt.shared.dto.startanalysis;
import java.io.Serializable;
import java.util.List;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
import com.sap.sailing.gwt.ui.shared.GPSFixDTO;
public class StartAnalysisCompetitorDTO implements Serializable{
@@ -12,5 +10,4 @@ public class StartAnalysisCompetitorDTO implements Serializable{
public CompetitorDTO competitorDTO;
public StartAnalysisRankingTableEntryDTO rankingTableEntryDTO;
public List<GPSFixDTO> gpsFixDTOs;
}
@@ -1,27 +1,36 @@
package com.sap.sailing.dashboards.gwt.shared.dto.startanalysis;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.sap.sailing.domain.common.dto.PositionDTO;
import com.sap.sailing.gwt.ui.shared.MarkDTO;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.dto.CompetitorDTO;
public class StartAnalysisDTO implements Serializable, Comparable<StartAnalysisDTO> {
private static final long serialVersionUID = -1325150193180234561L;
public String raceName;
public List<PositionDTO> startLineMarkPositions;
public List<MarkDTO> startLineMarks;
public MarkDTO firstMark;
public CompetitorDTO competitor;
public WindAndAdvantagesInfoForStartLineDTO startAnalysisWindLineInfoDTO;
public List<StartAnalysisCompetitorDTO> startAnalysisCompetitorDTOs;
public long timeOfStartInMilliSeconds;
public RegattaAndRaceIdentifier regattaAndRaceIdentifier;
public StartAnalysisDTO(){}
public StartAnalysisDTO() {
}
@Override
public int compareTo(StartAnalysisDTO o) {
return raceName.compareTo(o.raceName);
}
public List<CompetitorDTO> getCompetitorDTOsFromStartAnaylsisCompetitorDTOs() {
List<CompetitorDTO> competitorDTOs = new ArrayList<CompetitorDTO>();
for (StartAnalysisCompetitorDTO startAnalysisCompetitorDTO : startAnalysisCompetitorDTOs) {
competitorDTOs.add(startAnalysisCompetitorDTO.competitorDTO);
}
return competitorDTOs;
}
}
@@ -15,6 +15,7 @@ import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContext;
import com.sap.sailing.datamining.test.util.ConcurrencyTestsUtil;
import com.sap.sailing.datamining.test.util.NullProcessor;
import com.sap.sailing.domain.leaderboard.Leaderboard;
@@ -26,7 +27,7 @@ import com.sap.sse.datamining.components.Processor;
public class TestLeaderboardGroupRetrievalProcessor {
private RacingEventService service;
private Processor<RacingEventService, LeaderboardGroup> retriever;
private Processor<RacingEventService, LeaderboardGroupWithContext> retriever;
private Collection<LeaderboardGroup> retrievedGroups;
@@ -36,16 +37,16 @@ public class TestLeaderboardGroupRetrievalProcessor {
stub(service.getLeaderboardGroups()).toReturn(getGroupsInService());
retrievedGroups = new HashSet<>();
Processor<LeaderboardGroup, Void> receiver = new NullProcessor<LeaderboardGroup, Void>(LeaderboardGroup.class, Void.class) {
Processor<LeaderboardGroupWithContext, Void> receiver = new NullProcessor<LeaderboardGroupWithContext, Void>(LeaderboardGroupWithContext.class, Void.class) {
@Override
public void processElement(LeaderboardGroup element) {
retrievedGroups.add(element);
public void processElement(LeaderboardGroupWithContext element) {
retrievedGroups.add(element.getLeaderboardGroup());
}
};
Collection<Processor<LeaderboardGroup, ?>> resultReceivers = new ArrayList<>();
Collection<Processor<LeaderboardGroupWithContext, ?>> resultReceivers = new ArrayList<>();
resultReceivers.add(receiver);
retriever = new LeaderboardGroupRetrievalProcessor(ConcurrencyTestsUtil.getExecutor(), resultReceivers);
retriever = new LeaderboardGroupRetrievalProcessor(ConcurrencyTestsUtil.getExecutor(), resultReceivers, 0);
}
private Map<String, LeaderboardGroup> getGroupsInService() {
@@ -2,28 +2,20 @@ package com.sap.sailing.datamining.test.util;
import static org.junit.Assert.fail;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ExecutorService;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.impl.DataMiningExecutorService;
public class ConcurrencyTestsUtil {
private static final int THREAD_POOL_SIZE = Math.max(Runtime.getRuntime().availableProcessors(), 3);
private static ThreadPoolExecutor executor = createExecutor();
private static final ExecutorService executor = new DataMiningExecutorService(THREAD_POOL_SIZE);
public static ThreadPoolExecutor getExecutor() {
if (executor.isShutdown()) {
executor = createExecutor();
}
public static ExecutorService getExecutor() {
return executor;
}
private static ThreadPoolExecutor createExecutor() {
return new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
}
public static void sleepFor(long milliseconds) {
try {
Thread.sleep(milliseconds);
@@ -16,7 +16,7 @@ public class NullProcessor<InputType, ResultType> extends AbstractProcessor<Inpu
@Override
public void onFailure(Throwable failure) {
throw new RuntimeException("An error occured during the processing", failure);
}
@Override
@@ -16,7 +16,8 @@ Require-Bundle: com.sap.sailing.domain,
com.sap.sse.datamining.shared,
com.sap.sse,
com.sap.sse.common,
org.eclipse.osgi
org.eclipse.osgi,
com.sap.sailing.polars
Bundle-ClassPath: .
Bundle-Activator: com.sap.sailing.datamining.Activator
Bundle-ActivationPolicy: lazy
@@ -43,5 +43,5 @@ Bft9=Sturm
Bft10=schwerer Sturm
Bft11=orkanartiger Sturm
Bft12=Orkan
RelativeRankInRace=Relativer Rang im Rennen
RelativeScoreInRace=Relative Wertung im Rennen (0 am besten; 1 am schlechtesten)
WindSpeedInBeaufort=Windgeschwindigkeit in Bft.
@@ -43,5 +43,5 @@ Bft9=Strong gale
Bft10=Storm
Bft11=Violent storm
Bft12=Hurricane
RelativeRankInRace=Relative rank in race
RelativeScoreInRace=Relative score in race (0 best; 1 worst)
WindSpeedInBeaufort=Wind speed in Bft
@@ -3,10 +3,10 @@ package com.sap.sailing.datamining;
import java.util.ArrayList;
import java.util.Collection;
import com.sap.sailing.datamining.data.HasRaceResultOfCompetitorContext;
import com.sap.sailing.datamining.data.HasGPSFixContext;
import com.sap.sailing.datamining.data.HasLeaderboardContext;
import com.sap.sailing.datamining.data.HasMarkPassingContext;
import com.sap.sailing.datamining.data.HasRaceResultOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedLegContext;
import com.sap.sailing.datamining.data.HasTrackedLegOfCompetitorContext;
import com.sap.sailing.datamining.data.HasTrackedRaceContext;
@@ -18,7 +18,7 @@ import com.sap.sailing.datamining.impl.components.MarkPassingRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.TrackedLegOfCompetitorRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.TrackedLegRetrievalProcessor;
import com.sap.sailing.datamining.impl.components.TrackedRaceRetrievalProcessor;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContext;
import com.sap.sailing.server.RacingEventService;
import com.sap.sse.datamining.DataRetrieverChainDefinition;
import com.sap.sse.datamining.impl.SimpleDataRetrieverChainDefinition;
@@ -32,7 +32,7 @@ public class SailingDataRetrievalChainDefinitions {
final DataRetrieverChainDefinition<RacingEventService, HasLeaderboardContext> leaderboardRetrieverChainDefinition = new SimpleDataRetrieverChainDefinition<>(
RacingEventService.class, HasLeaderboardContext.class, "LeaderboardSailingDomainRetrieverChain");
leaderboardRetrieverChainDefinition.startWith(LeaderboardGroupRetrievalProcessor.class, LeaderboardGroup.class, "LeaderboardGroup");
leaderboardRetrieverChainDefinition.startWith(LeaderboardGroupRetrievalProcessor.class, LeaderboardGroupWithContext.class, "LeaderboardGroup");
leaderboardRetrieverChainDefinition.endWith(LeaderboardGroupRetrievalProcessor.class, LeaderboardRetrievalProcessor.class,
HasLeaderboardContext.class, "Leaderboard");
@@ -1,9 +1,12 @@
package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.polars.PolarDataService;
public interface HasLeaderboardContext {
public Leaderboard getLeaderboard();
Leaderboard getLeaderboard();
PolarDataService getPolarDataService();
}
@@ -0,0 +1,12 @@
package com.sap.sailing.datamining.data;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.polars.PolarDataService;
public interface HasLeaderboardGroupContext {
LeaderboardGroup getLeaderboardGroup();
PolarDataService getPolarDataService();
}
@@ -16,10 +16,12 @@ public interface HasRaceResultOfCompetitorContext {
/**
* 0 means the competitor won the race, 1 means the competitor ranked last
*/
@Statistic(messageKey="RelativeRankInRace", ordinal=3)
@Statistic(messageKey="RelativeScoreInRace", ordinal=3, resultDecimals=2)
public double getRelativeRank();
@Dimension(messageKey="WindSpeedInBeaufort")
int getAverageWindSpeedInRoundedBeaufort();
@Dimension(messageKey="Regatta")
String getRegattaName();
}
@@ -16,8 +16,8 @@ public class CompetitorOfRaceInLeaderboardRetrievalProcessor extends
AbstractSimpleRetrievalProcessor<HasLeaderboardContext, HasRaceResultOfCompetitorContext> {
public CompetitorOfRaceInLeaderboardRetrievalProcessor(ExecutorService executor,
Collection<Processor<HasRaceResultOfCompetitorContext, ?>> resultReceivers) {
super(HasLeaderboardContext.class, HasRaceResultOfCompetitorContext.class, executor, resultReceivers);
Collection<Processor<HasRaceResultOfCompetitorContext, ?>> resultReceivers, int retrievalLevel) {
super(HasLeaderboardContext.class, HasRaceResultOfCompetitorContext.class, executor, resultReceivers, retrievalLevel);
}
@Override
@@ -25,7 +25,8 @@ public class CompetitorOfRaceInLeaderboardRetrievalProcessor extends
Collection<HasRaceResultOfCompetitorContext> raceResultsOfCompetitor = new ArrayList<>();
for (RaceColumn raceColumn : element.getLeaderboard().getRaceColumns()) {
for (Competitor competitor : element.getLeaderboard().getCompetitors()) {
HasRaceResultOfCompetitorContext raceResultOfCompetitorContext = new RaceResultOfCompetitorWithContext(element, raceColumn, competitor);
HasRaceResultOfCompetitorContext raceResultOfCompetitorContext = new RaceResultOfCompetitorWithContext(element, raceColumn, competitor,
element.getPolarDataService());
raceResultsOfCompetitor.add(raceResultOfCompetitorContext);
}
}
@@ -16,8 +16,8 @@ import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor;
public class GPSFixRetrievalProcessor extends AbstractSimpleRetrievalProcessor<HasTrackedLegOfCompetitorContext, HasGPSFixContext> {
public GPSFixRetrievalProcessor(ExecutorService executor, Collection<Processor<HasGPSFixContext, ?>> resultReceivers) {
super(HasTrackedLegOfCompetitorContext.class, HasGPSFixContext.class, executor, resultReceivers);
public GPSFixRetrievalProcessor(ExecutorService executor, Collection<Processor<HasGPSFixContext, ?>> resultReceivers, int retrievalLevel) {
super(HasTrackedLegOfCompetitorContext.class, HasGPSFixContext.class, executor, resultReceivers, retrievalLevel);
}
@Override
@@ -2,23 +2,30 @@ package com.sap.sailing.datamining.impl.components;
import java.util.Collection;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContext;
import com.sap.sailing.polars.PolarDataService;
import com.sap.sailing.server.RacingEventService;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor;
public class LeaderboardGroupRetrievalProcessor extends
AbstractSimpleRetrievalProcessor<RacingEventService, LeaderboardGroup> {
AbstractSimpleRetrievalProcessor<RacingEventService, LeaderboardGroupWithContext> {
public LeaderboardGroupRetrievalProcessor(ExecutorService executor,
Collection<Processor<LeaderboardGroup, ?>> resultReceivers) {
super(RacingEventService.class, LeaderboardGroup.class, executor, resultReceivers);
Collection<Processor<LeaderboardGroupWithContext, ?>> resultReceivers, int retrievalLevel) {
super(RacingEventService.class, LeaderboardGroupWithContext.class, executor, resultReceivers, retrievalLevel);
}
@Override
protected Iterable<LeaderboardGroup> retrieveData(RacingEventService element) {
return element.getLeaderboardGroups().values();
protected Iterable<LeaderboardGroupWithContext> retrieveData(RacingEventService element) {
final PolarDataService polarDataService = element.getPolarDataService();
return element.getLeaderboardGroups()
.values()
.stream()
.map(lg -> new LeaderboardGroupWithContext(lg, polarDataService))
.collect(Collectors.toSet());
}
}
@@ -5,25 +5,25 @@ import java.util.Collection;
import java.util.concurrent.ExecutorService;
import com.sap.sailing.datamining.data.HasLeaderboardContext;
import com.sap.sailing.datamining.impl.data.LeaderboardGroupWithContext;
import com.sap.sailing.datamining.impl.data.LeaderboardWithContext;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sse.datamining.components.Processor;
import com.sap.sse.datamining.impl.components.AbstractSimpleRetrievalProcessor;
public class LeaderboardRetrievalProcessor extends
AbstractSimpleRetrievalProcessor<LeaderboardGroup, HasLeaderboardContext> {
AbstractSimpleRetrievalProcessor<LeaderboardGroupWithContext, HasLeaderboardContext> {
public LeaderboardRetrievalProcessor(ExecutorService executor,
Collection<Processor<HasLeaderboardContext, ?>> resultReceivers) {
super(LeaderboardGroup.class, HasLeaderboardContext.class, executor, resultReceivers);
Collection<Processor<HasLeaderboardContext, ?>> resultReceivers, int retrievalLevel) {
super(LeaderboardGroupWithContext.class, HasLeaderboardContext.class, executor, resultReceivers, retrievalLevel);
}
@Override
protected Iterable<HasLeaderboardContext> retrieveData(LeaderboardGroup element) {
protected Iterable<HasLeaderboardContext> retrieveData(LeaderboardGroupWithContext element) {
Collection<HasLeaderboardContext> leaderboardsWithContext = new ArrayList<>();
for (Leaderboard leaderboard : element.getLeaderboards()) {
leaderboardsWithContext.add(new LeaderboardWithContext(leaderboard));
for (Leaderboard leaderboard : element.getLeaderboardGroup().getLeaderboards()) {
leaderboardsWithContext.add(new LeaderboardWithContext(leaderboard, element.getPolarDataService()));
}
return leaderboardsWithContext;
}
@@ -19,8 +19,8 @@ public class MarkPassingRetrievalProcessor extends
AbstractSimpleRetrievalProcessor<HasTrackedRaceContext, HasMarkPassingContext> {
public MarkPassingRetrievalProcessor(ExecutorService executor,
Collection<Processor<HasMarkPassingContext, ?>> resultReceivers) {
super(HasTrackedRaceContext.class, HasMarkPassingContext.class, executor, resultReceivers);
Collection<Processor<HasMarkPassingContext, ?>> resultReceivers, int retrievalLevel) {
super(HasTrackedRaceContext.class, HasMarkPassingContext.class, executor, resultReceivers, retrievalLevel);
}
@Override
@@ -15,8 +15,8 @@ public class TrackedLegOfCompetitorRetrievalProcessor extends
AbstractSimpleRetrievalProcessor<HasTrackedLegContext, HasTrackedLegOfCompetitorContext> {
public TrackedLegOfCompetitorRetrievalProcessor(ExecutorService executor,
Collection<Processor<HasTrackedLegOfCompetitorContext, ?>> resultReceivers) {
super(HasTrackedLegContext.class, HasTrackedLegOfCompetitorContext.class, executor, resultReceivers);
Collection<Processor<HasTrackedLegOfCompetitorContext, ?>> resultReceivers, int retrievalLevel) {
super(HasTrackedLegContext.class, HasTrackedLegOfCompetitorContext.class, executor, resultReceivers, retrievalLevel);
}
@Override
@@ -15,8 +15,8 @@ public class TrackedLegRetrievalProcessor extends
AbstractSimpleRetrievalProcessor<HasTrackedRaceContext, HasTrackedLegContext> {
public TrackedLegRetrievalProcessor(ExecutorService executor,
Collection<Processor<HasTrackedLegContext, ?>> resultReceivers) {
super(HasTrackedRaceContext.class, HasTrackedLegContext.class, executor, resultReceivers);
Collection<Processor<HasTrackedLegContext, ?>> resultReceivers, int retrievalLevel) {
super(HasTrackedRaceContext.class, HasTrackedLegContext.class, executor, resultReceivers, retrievalLevel);
}
@Override
@@ -18,8 +18,8 @@ public class TrackedRaceRetrievalProcessor extends
AbstractSimpleRetrievalProcessor<HasLeaderboardContext, HasTrackedRaceContext> {
public TrackedRaceRetrievalProcessor(ExecutorService executor,
Collection<Processor<HasTrackedRaceContext, ?>> resultReceivers) {
super(HasLeaderboardContext.class, HasTrackedRaceContext.class, executor, resultReceivers);
Collection<Processor<HasTrackedRaceContext, ?>> resultReceivers, int retrievalLevel) {
super(HasLeaderboardContext.class, HasTrackedRaceContext.class, executor, resultReceivers, retrievalLevel);
}
@Override
@@ -0,0 +1,27 @@
package com.sap.sailing.datamining.impl.data;
import com.sap.sailing.datamining.data.HasLeaderboardGroupContext;
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
import com.sap.sailing.polars.PolarDataService;
public class LeaderboardGroupWithContext implements HasLeaderboardGroupContext {
private final LeaderboardGroup leaderboardGroup;
private final PolarDataService polarDataService;
public LeaderboardGroupWithContext(LeaderboardGroup leaderboardGroup, PolarDataService polarDataService) {
this.leaderboardGroup = leaderboardGroup;
this.polarDataService = polarDataService;
}
@Override
public LeaderboardGroup getLeaderboardGroup() {
return leaderboardGroup;
}
@Override
public PolarDataService getPolarDataService() {
return polarDataService;
}
}
@@ -2,13 +2,16 @@ package com.sap.sailing.datamining.impl.data;
import com.sap.sailing.datamining.data.HasLeaderboardContext;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.polars.PolarDataService;
public class LeaderboardWithContext implements HasLeaderboardContext {
private final Leaderboard leaderboard;
private final PolarDataService polarDataService;
public LeaderboardWithContext(Leaderboard leaderboard) {
public LeaderboardWithContext(Leaderboard leaderboard, PolarDataService polarDataService) {
this.leaderboard = leaderboard;
this.polarDataService = polarDataService;
}
@Override
@@ -16,4 +19,9 @@ public class LeaderboardWithContext implements HasLeaderboardContext {
return leaderboard;
}
@Override
public PolarDataService getPolarDataService() {
return polarDataService;
}
}
@@ -1,35 +1,50 @@
package com.sap.sailing.datamining.impl.data;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sap.sailing.datamining.data.HasLeaderboardContext;
import com.sap.sailing.datamining.data.HasRaceResultOfCompetitorContext;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.Leg;
import com.sap.sailing.domain.base.RaceColumn;
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
import com.sap.sailing.domain.common.LegType;
import com.sap.sailing.domain.common.NoWindException;
import com.sap.sailing.domain.common.Position;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.Tack;
import com.sap.sailing.domain.common.scalablevalue.impl.ScalableSpeed;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.domain.tracking.Wind;
import com.sap.sailing.domain.tracking.WindWithConfidence;
import com.sap.sailing.polars.PolarDataService;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.Util;
import com.sap.sse.common.Util.Pair;
import com.sap.sse.common.impl.MillisecondsTimePoint;
public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompetitorContext {
private final static Logger logger = Logger.getLogger(RaceResultOfCompetitorWithContext.class.getName());
private final HasLeaderboardContext leaderboardWithContext;
private final RaceColumn raceColumn;
private final Competitor competitor;
private final PolarDataService polarDataService;
public RaceResultOfCompetitorWithContext(HasLeaderboardContext leaderboardWithContext, RaceColumn raceColumn, Competitor competitor) {
public RaceResultOfCompetitorWithContext(HasLeaderboardContext leaderboardWithContext, RaceColumn raceColumn,
Competitor competitor, PolarDataService polarDataService) {
this.leaderboardWithContext = leaderboardWithContext;
this.raceColumn = raceColumn;
this.competitor = competitor;
this.polarDataService = polarDataService;
}
@Override
@@ -47,20 +62,23 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti
Leaderboard leaderboard = getLeaderboardContext().getLeaderboard();
final MillisecondsTimePoint now = MillisecondsTimePoint.now();
double competitorCount = Util.size(leaderboard.getCompetitors());
double rank = leaderboard.getNetPoints(competitor, raceColumn, now);
final double result = rank / competitorCount;
double points = leaderboard.getNetPoints(competitor, raceColumn, now);
double relativeLowPoints = leaderboard.getScoringScheme().isHigherBetter() ?
competitorCount - points : points;
final double result = relativeLowPoints / competitorCount;
return result;
}
@Override
public int getAverageWindSpeedInRoundedBeaufort() {
Speed exactResult = getAverageWindSpeed();
return (int) exactResult.getBeaufort();
return (int) Math.round(exactResult.getBeaufort());
}
/**
* If there is no tracked race for the competitor or the race has no wind data, <code>null</code> is returned. Otherwise,
* the average wind speed for the competitor is sampled in a one-minute interval throughout the race duration.
* If there is no tracked race for the competitor or the race has no wind data, <code>null</code> is returned.
* Otherwise, the average wind speed for the competitor is sampled in a one-minute interval throughout the race
* duration.
*/
private Speed getAverageWindSpeed() {
final Speed result;
@@ -68,8 +86,9 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti
if (trackedRace == null) {
result = null;
} else {
ScalableSpeed windSpeedSum = new ScalableSpeed(Speed.NULL);
long count = 0;
final ScalableSpeed[] windSpeedSum = new ScalableSpeed[1];
windSpeedSum[0] = new ScalableSpeed(Speed.NULL);
final long[] count = new long[1];
final List<Leg> legs = trackedRace.getRace().getCourse().getLegs();
if (legs.isEmpty()) {
result = null;
@@ -88,14 +107,43 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti
} else {
position = trackedRace.getCenterOfCourse(timePoint);
}
final Wind wind = trackedRace.getWind(position, timePoint);
final WindWithConfidence<Pair<Position, TimePoint>> wind = trackedRace.getWindWithConfidence(
position, timePoint);
if (wind != null) {
windSpeedSum = windSpeedSum.add(new ScalableSpeed(wind));
count++;
if (wind.useSpeed()) {
windSpeedSum[0] = windSpeedSum[0].add(new ScalableSpeed(wind.getObject()));
count[0]++;
} else {
if (!track.hasDirectionChange(timePoint, /* minimumDegreeDifference */10)) {
// TODO try to estimate wind speed using polar data service, wind direction and COG/SOG
SpeedWithBearing cog = track.getEstimatedSpeed(timePoint);
final TrackedLegOfCompetitor currentLeg = trackedRace.getCurrentLeg(competitor, timePoint);
if (currentLeg != null) {
LegType legType;
try {
legType = trackedRace.getTrackedLeg(currentLeg.getLeg()).getLegType(timePoint);
final Tack tack = trackedRace.getTack(competitor, timePoint);
if (tack != null) {
Set<SpeedWithBearingWithConfidence<Void>> estimatedWindSpeeds = polarDataService
.getAverageTrueWindSpeedAndAngleCandidates(trackedRace.getRace()
.getBoatClass(), cog, legType, tack);
if (!estimatedWindSpeeds.isEmpty()) {
estimatedWindSpeeds.stream().max((a,b)->(int) Math.signum(a.getConfidence()-b.getConfidence())).ifPresent(swbwc-> {
windSpeedSum[0] = windSpeedSum[0].add(new ScalableSpeed(swbwc.getObject()));
count[0]++;
});
}
}
if (count > 0) {
result = windSpeedSum.divide(count);
} catch (NoWindException e) {
logger.log(Level.FINEST, "Can't determine wind direction, so no tack nor leg type known", e);
}
}
}
}
}
}
if (count[0] > 0) {
result = windSpeedSum[0].divide(count[0]);
} else {
result = null;
}
@@ -103,4 +151,12 @@ public class RaceResultOfCompetitorWithContext implements HasRaceResultOfCompeti
}
return result;
}
@Override
public String getRegattaName() {
Leaderboard leaderboard = getLeaderboardContext().getLeaderboard();;
final String result = leaderboard.getName();
return result;
}
}
@@ -1,7 +1,9 @@
package com.sap.sailing.domain.common;
import com.sap.sailing.domain.common.impl.NauticalMileDistance;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.common.impl.MillisecondsDurationImpl;
public abstract class AbstractSpeedImpl implements Speed {
@@ -17,6 +19,11 @@ public abstract class AbstractSpeedImpl implements Speed {
return new NauticalMileDistance((t2.asMillis() - t1.asMillis()) / 1000. / 3600. * getKnots());
}
@Override
public Duration getDuration(Distance distance) {
return new MillisecondsDurationImpl((long) (1000 * distance.getMeters() / getMetersPerSecond()));
}
@Override
public double getMetersPerSecond() {
return getKnots() * Mile.METERS_PER_SEA_MILE / 3600;
@@ -15,12 +15,14 @@ import java.io.Serializable;
*
*/
public enum DetailType implements Serializable {
DISTANCE_TRAVELED(0, ASCENDING), AVERAGE_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING), RACE_RANK(0, ASCENDING), REGATTA_RANK(0, ASCENDING), OVERALL_RANK(0, ASCENDING),
DISTANCE_TRAVELED(0, ASCENDING), DISTANCE_TRAVELED_INCLUDING_GATE_START(0, ASCENDING),
AVERAGE_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING), RACE_RANK(0, ASCENDING), REGATTA_RANK(0, ASCENDING), OVERALL_RANK(0, ASCENDING),
RANK_GAIN(0, ASCENDING), NUMBER_OF_MANEUVERS(0, ASCENDING), CURRENT_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING),
ESTIMATED_TIME_TO_NEXT_WAYPOINT_IN_SECONDS(1, ASCENDING), VELOCITY_MADE_GOOD_IN_KNOTS(2, DESCENDING),
GAP_TO_LEADER_IN_SECONDS(0, ASCENDING), GAP_CHANGE_SINCE_LEG_START_IN_SECONDS(0, ASCENDING),
SIDE_TO_WHICH_MARK_AT_LEG_START_WAS_ROUNDED(0, ASCENDING), WINDWARD_DISTANCE_TO_GO_IN_METERS(0, ASCENDING),
AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS(0, ASCENDING), AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS(0, ASCENDING), RACE_DISTANCE_TRAVELED(0, ASCENDING),
RACE_DISTANCE_TRAVELED_INCLUDING_GATE_START(0, ASCENDING),
RACE_AVERAGE_SPEED_OVER_GROUND_IN_KNOTS(2, DESCENDING), RACE_GAP_TO_LEADER_IN_SECONDS(0, ASCENDING),
RACE_DISTANCE_TO_LEADER_IN_METERS(0, ASCENDING), RACE_AVERAGE_ABSOLUTE_CROSS_TRACK_ERROR_IN_METERS(0, ASCENDING),
RACE_AVERAGE_SIGNED_CROSS_TRACK_ERROR_IN_METERS(0, ASCENDING),
@@ -41,7 +43,7 @@ public enum DetailType implements Serializable {
RACE_RATIO_BETWEEN_TIME_SINCE_LAST_POSITION_FIX_AND_AVERAGE_SAMPLING_INTERVAL(1, ASCENDING), RACE_DISTANCE_TO_START_FIVE_SECONDS_BEFORE_RACE_START(1, ASCENDING),
RACE_SPEED_OVER_GROUND_FIVE_SECONDS_BEFORE_START(2, DESCENDING),
RACE_TIME_TRAVELED(0, ASCENDING), RACE_TIME_TRAVELED_UPWIND(0, ASCENDING), RACE_TIME_TRAVELED_DOWNWIND(0, ASCENDING), RACE_TIME_TRAVELED_REACHING(0, ASCENDING),
DISTANCE_TO_START_LINE(0, ASCENDING), BEAT_ANGLE(0, ASCENDING);
DISTANCE_TO_START_LINE(0, ASCENDING), BEAT_ANGLE(0, ASCENDING), COURSE_OVER_GROUND_TRUE_DEGREES(0, ASCENDING);
private int precision;
@@ -0,0 +1,26 @@
package com.sap.sailing.domain.common;
public class DoubleTriple {
private final double a;
private final double b;
private final double c;
public DoubleTriple(double a, double b, double c) {
super();
this.a = a;
this.b = b;
this.c = c;
}
public double getA() {
return a;
}
public double getB() {
return b;
}
public double getC() {
return c;
}
}
@@ -40,7 +40,9 @@ public enum MaxPointsReason {
/** Discretionary Penalty Imposed by the race committee */
DPI(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ false),
/** Retired */
RET(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ true);
RET(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ true),
/** Uniform Flag Disqualification */
UFD(/* discardable */ true, /* advanceCompetitorsTrackedWorse */ true);
private final boolean discardable;
@@ -32,6 +32,6 @@ public interface PolarSheetGenerationSettings extends Serializable {
boolean areDefault();
int getNumberOfLeadingCompetitorsToInclude();
double getPctOfLeadingCompetitorsToInclude();
}
@@ -2,6 +2,7 @@ package com.sap.sailing.domain.common;
import java.io.Serializable;
import com.sap.sse.common.Duration;
import com.sap.sse.common.TimePoint;
import com.sap.sse.datamining.shared.Unit;
import com.sap.sse.datamining.shared.annotations.Statistic;
@@ -46,6 +47,11 @@ public interface Speed extends Comparable<Speed>, Serializable {
return Distance.NULL;
}
@Override
public Duration getDuration(Distance distance) {
throw new ArithmeticException("Cannot determine duration for any distance with zero speed");
}
@Override
public String toString() {
return "0kn";
@@ -68,4 +74,10 @@ public interface Speed extends Comparable<Speed>, Serializable {
*/
Distance travel(TimePoint from, TimePoint to);
/**
* The duration it takes to travel the <code>distance</code> specified on a great circle (the "straight
* line" on a sphere) with this speed.
*/
Duration getDuration(Distance distance);
}
@@ -19,6 +19,7 @@ public class LegEntryDTO implements Serializable {
private static final long serialVersionUID = -1236734337344886025L;
public LegType legType;
public Double distanceTraveledInMeters;
public Double distanceTraveledIncludingGateStartInMeters;
public Double averageSpeedOverGroundInKnots;
public int rank;
public Double gapToLeaderInSeconds;
@@ -62,6 +63,7 @@ public class LegEntryDTO implements Serializable {
result = prime * result
+ ((currentSpeedOverGroundInKnots == null) ? 0 : currentSpeedOverGroundInKnots.hashCode());
result = prime * result + ((distanceTraveledInMeters == null) ? 0 : distanceTraveledInMeters.hashCode());
result = prime * result + ((distanceTraveledIncludingGateStartInMeters == null) ? 0 : distanceTraveledIncludingGateStartInMeters.hashCode());
result = prime
* result
+ ((estimatedTimeToNextWaypointInSeconds == null) ? 0 : estimatedTimeToNextWaypointInSeconds.hashCode());
@@ -120,6 +122,11 @@ public class LegEntryDTO implements Serializable {
return false;
} else if (!distanceTraveledInMeters.equals(other.distanceTraveledInMeters))
return false;
if (distanceTraveledIncludingGateStartInMeters == null) {
if (other.distanceTraveledIncludingGateStartInMeters != null)
return false;
} else if (!distanceTraveledIncludingGateStartInMeters.equals(other.distanceTraveledIncludingGateStartInMeters))
return false;
if (estimatedTimeToNextWaypointInSeconds == null) {
if (other.estimatedTimeToNextWaypointInSeconds != null)
return false;
@@ -12,7 +12,6 @@ import java.util.Set;
import com.sap.sailing.domain.common.LeaderboardNameConstants;
import com.sap.sailing.domain.common.RaceIdentifier;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.TimingConstants;
import com.sap.sse.common.Util;
public class RaceColumnDTO extends NamedDTO implements Serializable {
@@ -25,6 +24,8 @@ public class RaceColumnDTO extends NamedDTO implements Serializable {
private double effectiveFactor;
private Map<FleetDTO, RaceLogTrackingInfoDTO> raceLogTrackingInfos;
public enum RaceColumnLiveState { NOT_TRACKED, TRACKED, TRACKED_AND_LIVE };
public RaceColumnDTO() {
trackedRaceIdentifiersPerFleet = new HashMap<FleetDTO, RegattaAndRaceIdentifier>();
raceLogTrackingInfos = new HashMap<FleetDTO, RaceLogTrackingInfoDTO>();
@@ -32,6 +33,16 @@ public class RaceColumnDTO extends NamedDTO implements Serializable {
fleets = new ArrayList<FleetDTO>();
}
public RaceColumnLiveState getLiveState(long serverTimePointAsMillis) {
RaceColumnLiveState result = RaceColumnLiveState.NOT_TRACKED;
if (hasLiveRaces(serverTimePointAsMillis) && hasGPSData() && hasWindData()) {
result = RaceColumnLiveState.TRACKED_AND_LIVE;
} else if (hasTrackedRaces()) {
result = RaceColumnLiveState.TRACKED;
}
return result;
}
public String getRaceColumnName() {
return getName();
}
@@ -144,42 +155,11 @@ public class RaceColumnDTO extends NamedDTO implements Serializable {
* point of the "live" interval as defined above.
*/
public boolean isLive(FleetDTO fleet, long serverTimePointAsMillis) {
final Date startOfLivePeriod;
final Date endOfLivePeriod;
boolean result = false;
final RaceDTO raceDTO = racesPerFleet.get(fleet);
if (raceDTO == null || raceDTO.trackedRace == null || !raceDTO.trackedRace.hasGPSData || !raceDTO.trackedRace.hasWindData) {
startOfLivePeriod = null;
endOfLivePeriod = null;
} else {
if (raceDTO.startOfRace == null) {
startOfLivePeriod = raceDTO.trackedRace.startOfTracking;
} else {
startOfLivePeriod = new Date(raceDTO.startOfRace.getTime() - TimingConstants.PRE_START_PHASE_DURATION_IN_MILLIS);
if(raceDTO != null) {
result = raceDTO.isLive(serverTimePointAsMillis);
}
if (raceDTO.endOfRace == null) {
if (raceDTO.trackedRace.timePointOfNewestEvent != null) {
endOfLivePeriod = new Date(raceDTO.trackedRace.timePointOfNewestEvent.getTime()
+ TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
} else {
endOfLivePeriod = null;
}
} else {
endOfLivePeriod = new Date(raceDTO.endOfRace.getTime() + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
}
}
// if an empty timepoint is given then take the start of the race
if (serverTimePointAsMillis == 0) {
serverTimePointAsMillis = startOfLivePeriod.getTime()+1;
}
// whenLastTrackedRaceWasLive is null if there is no tracked race for fleet, or the tracked race hasn't started yet at the server time
// when this DTO was assembled, or there were no GPS or wind data
final boolean result =
startOfLivePeriod != null &&
endOfLivePeriod != null &&
startOfLivePeriod.getTime() <= serverTimePointAsMillis &&
serverTimePointAsMillis <= endOfLivePeriod.getTime();
return result;
}
@@ -4,6 +4,7 @@ import java.util.Date;
import com.sap.sailing.domain.common.RegattaAndRaceIdentifier;
import com.sap.sailing.domain.common.RegattaNameAndRaceName;
import com.sap.sailing.domain.common.TimingConstants;
/**
* Master data about a single race that is to be transferred to the client.<p>
@@ -14,6 +15,8 @@ import com.sap.sailing.domain.common.RegattaNameAndRaceName;
public class RaceDTO extends NamedDTO {
private static final long serialVersionUID = 2613189982608149975L;
public enum RaceLiveState { NOT_TRACKED, TRACKED, TRACKED_AND_LIVE, TRACKED_BUT_NOT_SCHEDULED };
/**
* Tells if this race is currently being tracked, meaning that a {@link RaceTracker} is
* listening for incoming GPS fixes, mark passings etc., to update a {@link TrackedRace} object
@@ -46,6 +49,20 @@ public class RaceDTO extends NamedDTO {
this.isTracked = isCurrentlyTracked;
}
public RaceLiveState getLiveState(long serverTimePointAsMillis) {
RaceLiveState result = RaceLiveState.NOT_TRACKED;
if(trackedRace != null && trackedRace.hasGPSData && trackedRace.hasWindData) {
result = RaceLiveState.TRACKED;
if(isLive(serverTimePointAsMillis)) {
result = RaceLiveState.TRACKED_AND_LIVE;
if(startOfRace == null) {
result = RaceLiveState.TRACKED_BUT_NOT_SCHEDULED;
}
}
}
return result;
}
public RegattaAndRaceIdentifier getRaceIdentifier() {
return new RegattaNameAndRaceName(regattaName, getName());
}
@@ -54,6 +71,52 @@ public class RaceDTO extends NamedDTO {
return regattaName;
}
/**
* @see {@link TrackedRace#isLive} for further explanation.
* @param serverTimePointAsMillis
* the time point (in server clock time) at which to determine whether the race is/was live
* @return <code>true</code> if <code>serverTimePointAsMillis</code> is between (inclusively) the start and end time
* point of the "live" interval as defined above.
*/
public boolean isLive(long serverTimePointAsMillis) {
final Date startOfLivePeriod;
final Date endOfLivePeriod;
if (trackedRace == null || !trackedRace.hasGPSData || !trackedRace.hasWindData) {
startOfLivePeriod = null;
endOfLivePeriod = null;
} else {
if (startOfRace == null) {
startOfLivePeriod = trackedRace.startOfTracking;
} else {
startOfLivePeriod = new Date(startOfRace.getTime() - TimingConstants.PRE_START_PHASE_DURATION_IN_MILLIS);
}
if (endOfRace == null) {
if (trackedRace.timePointOfNewestEvent != null) {
endOfLivePeriod = new Date(trackedRace.timePointOfNewestEvent.getTime()
+ TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
} else {
endOfLivePeriod = null;
}
} else {
endOfLivePeriod = new Date(endOfRace.getTime() + TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS);
}
}
// if an empty timepoint is given then take the start of the race
if (serverTimePointAsMillis == 0) {
serverTimePointAsMillis = startOfLivePeriod.getTime()+1;
}
// whenLastTrackedRaceWasLive is null if there is no tracked race for fleet, or the tracked race hasn't started yet at the server time
// when this DTO was assembled, or there were no GPS or wind data
final boolean result =
startOfLivePeriod != null &&
endOfLivePeriod != null &&
startOfLivePeriod.getTime() <= serverTimePointAsMillis &&
serverTimePointAsMillis <= endOfLivePeriod.getTime();
return result;
}
@Override
public int hashCode() {
final int prime = 31;
@@ -11,7 +11,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
double[] levels = { 4., 6., 8., 10., 12., 14., 16., 20., 25., 30. };
WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 2.5);
return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping,
false, 0);
false, 1);
}
public static PolarSheetGenerationSettings createBackendPolarSettings() {
@@ -26,7 +26,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
}
WindSpeedSteppingWithMaxDistance windStepping = new WindSpeedSteppingWithMaxDistance(levels, 0.5);
return new PolarSheetGenerationSettingsImpl(50, 0.1, 20, 20, 0.1, true, true, 2, 0.05, true, windStepping,
false, 1);
false, 0.1);
}
private static final long serialVersionUID = 2731616509404813790L;
@@ -42,7 +42,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
private boolean useOnlyEstimationForWindDirection;
private WindSpeedSteppingWithMaxDistance windStepping;
private boolean splitByWindGauges;
private int numberOfLeadingCompetitorsToInclude;
private double pctOfLeadingCompetitorsToInclude;
//GWT
PolarSheetGenerationSettingsImpl() {};
@@ -52,7 +52,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
boolean useOnlyWindGaugesForWindSpeed, boolean shouldRemoveOutliers,
double outlierDetectionNeighboorhoodRadius, double outlierMinimumNeighboorhoodPct,
boolean useOnlyEstimationForWindDirection, WindSpeedSteppingWithMaxDistance windStepping,
boolean splitByWindGauges, int numberOfLeadingCompetitorsToInclude) {
boolean splitByWindGauges, double pctOfLeadingCompetitorsToInclude) {
this.minimumDataCountPerGraph = minimumDataCountPerGraph;
this.minimumWindConfidence = minimumWindConfidence;
this.minimumDataCountPerAngle = minimumDataCountPerAngle;
@@ -65,7 +65,7 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
this.useOnlyEstimationForWindDirection = useOnlyEstimationForWindDirection;
this.windStepping = windStepping;
this.splitByWindGauges = splitByWindGauges;
this.numberOfLeadingCompetitorsToInclude = numberOfLeadingCompetitorsToInclude;
this.pctOfLeadingCompetitorsToInclude = pctOfLeadingCompetitorsToInclude;
}
@Override
@@ -209,8 +209,8 @@ public class PolarSheetGenerationSettingsImpl implements PolarSheetGenerationSet
}
@Override
public int getNumberOfLeadingCompetitorsToInclude() {
return numberOfLeadingCompetitorsToInclude;
public double getPctOfLeadingCompetitorsToInclude() {
return pctOfLeadingCompetitorsToInclude;
}
@@ -8,7 +8,7 @@ package com.sap.sailing.domain.common.racelog;
*/
public interface RaceLogServletConstants {
static final String PARAMS_LEADERBOARD_NAME = "leaderboard";
static final String PARAMS_RACE_COLUMN_NAME = "raceColumn";
static final String PARAMS_RACE_COLUMN_NAME = "race_column";
static final String PARAMS_RACE_FLEET_NAME = "fleet";
static final String PARAMS_CLIENT_UUID = "clientuuid";
static final String PARAM_COURSE_AREA_FILTER = "courseArea";
@@ -21,7 +21,8 @@ public class DeviceMappingConstants {
// schema and host are case insensitive.
// But to adhere to best practices, and due to the deficiencies of some web servers and clients,
// all URL components should be treated as case insensitive, preferring _underscores_ to CamelCase.
public static final String URL_BASE = "/tracking/checkin";
public static final String TRACKING_URL_BASE = "/tracking/checkin";
public static final String BUOY_TENDER_URL_BASE = "/buoy-tender/checkin";
public static final String URL_EVENT_ID = "event_id";
public static final String URL_LEADERBOARD_NAME = "leaderboard_name";
public static final String URL_COMPETITOR_ID_AS_STRING = "competitor_id";
@@ -43,11 +44,16 @@ public class DeviceMappingConstants {
public static String getDeviceMappingForRegattaLogUrl(String serverUrlWithoutTrailingSlash, String eventId,
String leaderboardName, String mappedItemType, String mappedItemId, UrlHelper helper) {
return helper.encodeUrl(serverUrlWithoutTrailingSlash + URL_BASE + "?" + URL_EVENT_ID + "=" + eventId + "&"
return helper.encodeUrl(serverUrlWithoutTrailingSlash + TRACKING_URL_BASE + "?" + URL_EVENT_ID + "=" + eventId + "&"
+ URL_LEADERBOARD_NAME + "=" + leaderboardName + "&" + mappedItemType + "="
+ mappedItemId);
}
public static String getBuoyTenderInvitationUrl(String serverUrlWithoutTrailingSlash,
String leaderboardName, UrlHelper helper) {
return helper.encodeUrl(serverUrlWithoutTrailingSlash + BUOY_TENDER_URL_BASE + "?" + URL_LEADERBOARD_NAME + "=" + leaderboardName);
}
@Deprecated
public static String getDeviceMappingForRaceLogUrl(String serverUrlWithoutTrailingSlash, String leaderboardName,
String raceColumnName, String fleetName, String mappedItemType, String mappedItemId, long fromMillis,
@@ -1,12 +1,12 @@
package com.sap.sailing.domain.common.scalablevalue.impl;
import com.sap.sailing.domain.common.Bearing;
import com.sap.sailing.domain.common.DoubleTriple;
import com.sap.sailing.domain.common.Speed;
import com.sap.sailing.domain.common.SpeedWithBearing;
import com.sap.sailing.domain.common.impl.KnotSpeedImpl;
import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl;
import com.sap.sailing.domain.common.impl.RadianBearingImpl;
import com.sap.sse.common.Util;
import com.sap.sse.common.scalablevalue.ScalableValue;
/**
@@ -14,42 +14,47 @@ import com.sap.sse.common.scalablevalue.ScalableValue;
* bearing is scaled separately, and the speed is scaled as a scalar value independently of the bearing. This is
* particularly useful for {@link Wind} scaling where it makes more sense to average the wind speed independently of the
* wind direction / bearing than adding up the "wind vectors" and averaging, which would reduce the resulting wind speed
* for constant wind speeds across all fixes with different directions.
* for constant wind speeds across all fixes with different directions.<p>
*
* The triple used for scaling uses the speed in knots as the first component, the sine as the second and the
* cosine as the third value.
*
* @author Axel Uhl (d043530)
*
*/
public class ScalableSpeedWithBearing implements ScalableValue<Util.Triple<Speed, Double, Double>, SpeedWithBearing> {
private final Speed speed;
public class ScalableSpeedWithBearing implements ScalableValue<DoubleTriple, SpeedWithBearing> {
private final double speedInKnots;
private final double sin;
private final double cos;
public ScalableSpeedWithBearing(SpeedWithBearing speedWithBearing) {
this(new KnotSpeedImpl(speedWithBearing.getKnots()), Math.sin(speedWithBearing.getBearing()
this(speedWithBearing.getKnots(), Math.sin(speedWithBearing.getBearing()
.getRadians()), Math.cos(speedWithBearing.getBearing().getRadians()));
}
public ScalableSpeedWithBearing(Speed speed, double sin, double cos) {
this.speed = speed;
this(speed.getKnots(), sin, cos);
}
private ScalableSpeedWithBearing(double speedInKnots, double sin, double cos) {
this.speedInKnots = speedInKnots;
this.sin = sin;
this.cos = cos;
}
@Override
public ScalableSpeedWithBearing multiply(double factor) {
Speed newSpeed = new KnotSpeedImpl(factor*speed.getKnots());
return new ScalableSpeedWithBearing(newSpeed, factor*sin, factor*cos);
return new ScalableSpeedWithBearing(factor*speedInKnots, factor*sin, factor*cos);
}
@Override
public ScalableSpeedWithBearing add(ScalableValue<Util.Triple<Speed, Double, Double>, SpeedWithBearing> t) {
Speed newSpeed = new KnotSpeedImpl(speed.getKnots() + t.getValue().getA().getKnots());
return new ScalableSpeedWithBearing(newSpeed, sin+t.getValue().getB(), cos+t.getValue().getC());
public ScalableSpeedWithBearing add(ScalableValue<DoubleTriple, SpeedWithBearing> t) {
return new ScalableSpeedWithBearing(speedInKnots + t.getValue().getA(), sin+t.getValue().getB(), cos+t.getValue().getC());
}
@Override
public SpeedWithBearing divide(double divisor) {
Speed newSpeed = new KnotSpeedImpl(speed.getKnots() / divisor);
Speed newSpeed = new KnotSpeedImpl(speedInKnots / divisor);
double angle;
if (cos == 0) {
angle = sin >= 0 ? Math.PI / 2 : -Math.PI / 2;
@@ -61,8 +66,8 @@ public class ScalableSpeedWithBearing implements ScalableValue<Util.Triple<Speed
}
@Override
public Util.Triple<Speed, Double, Double> getValue() {
return new Util.Triple<Speed, Double, Double>(speed, sin, cos);
public DoubleTriple getValue() {
return new DoubleTriple(speedInKnots, sin, cos);
}
}
@@ -120,7 +120,6 @@ public class MongoGPSFixStoreImpl implements MongoGPSFixStore {
public void loadMarkTrack(DynamicGPSFixTrack<Mark, GPSFix> track, AbstractLog<?, ?> log, Mark mark)
throws NoCorrespondingServiceRegisteredException, TransformationException{
List<DeviceMapping<Mark>> mappings = new DeviceMarkMappingFinder<>(log).analyze().get(mark);
if (mappings != null) {
for (DeviceMapping<Mark> mapping : mappings) {
loadTrack(track, mapping.getDevice(), mapping.getTimeRange().from(), mapping.getTimeRange().to(), true /*inclusive*/);
@@ -2,3 +2,4 @@ trackingInvitationFor=Einladung zum Tracken für
scanQRCodeOrVisitUrlToRegisterAs=Scanne den QRCode oder klicke den Link mit deinem Smartphone an um dich zu registrieren als
welcomeTo=Willkommen zu
alternativelyVisitThisLink=Alternativ kannst du diesen Link anklicken
buoyTender=Bojenleger
@@ -2,3 +2,4 @@ trackingInvitationFor=Tracking Invitation for
scanQRCodeOrVisitUrlToRegisterAs=Scan the following QRCode or click the link using your smartphone to register as
welcomeTo=Welcome to
alternativelyVisitThisLink=Alternatively, you can click this Link
buoyTender=buoy tender
@@ -21,9 +21,9 @@ import com.sap.sailing.domain.common.racelog.tracking.NotDenotedForRaceLogTracki
import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState;
import com.sap.sailing.domain.leaderboard.Leaderboard;
import com.sap.sailing.domain.leaderboard.RegattaLeaderboard;
import com.sap.sailing.domain.racelog.tracking.GPSFixStore;
import com.sap.sailing.domain.racelogtracking.impl.RaceLogRaceTracker;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.RaceHandle;
import com.sap.sailing.domain.tracking.TrackedRace;
import com.sap.sailing.server.RacingEventService;
import com.sap.sse.common.mail.MailException;
@@ -40,7 +40,7 @@ public interface RaceLogTrackingAdapter {
* <li>Is a {@link RaceLogStartTrackingEvent} present in the racelog? If not, add one</li>
* </ul>
*/
void startTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet)
RaceHandle startTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet)
throws NotDenotedForRaceLogTrackingException, Exception;
RaceLogTrackingState getRaceLogTrackingState(RacingEventService service, RaceColumn raceColumn, Fleet fleet);
@@ -76,10 +76,15 @@ public interface RaceLogTrackingAdapter {
throws NotDenotableForRaceLogTrackingException;
/**
* Add a fix to the {@link GPSFixStore}, and create a mapping with a virtual device for exactly that timepoint.
* @see #pingMark(RaceLog, Mark, GPSFix, RacingEventService) using a random {@link PingDeviceIdentifier}
*/
void pingMark(RaceLog raceLogToAddTo, Mark mark, GPSFix gpsFix, RacingEventService service);
/**
* @see #pingMark(RaceLog, Mark, GPSFix, RacingEventService)
*/
void pingMark(RegattaLog regattaLogToAddTo, Mark mark, GPSFix gpsFix, RacingEventService service);
/**
* Duplicate the course and competitor registrations in the newest {@link RaceLogCourseDesignChangedEvent} in
* {@code from} race log to the {@code to} race logs. The {@link Mark}s and {@link ControlPoint}s are duplicated and
@@ -102,7 +107,15 @@ public interface RaceLogTrackingAdapter {
/**
* Invite competitors for tracking via the Tracking App by sending out emails.
* @throws MailException
*/
void inviteCompetitorsForTrackingViaEmail(Event event, Leaderboard leaderboard,
String serverUrlWithoutTrailingSlash, Set<Competitor> competitors, Locale locale) throws MailException;
/**
* Invite buoy tenders for buoy pinging via the Buoy Tender App by sending out emails.
* @throws MailException
*/
void inviteBuoyTenderViaEmail(Event event, Leaderboard leaderboard, String serverUrlWithoutTrailingSlash,
String emails, Locale locale) throws MailException;
}
@@ -18,6 +18,7 @@ import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory;
import com.sap.sailing.domain.racelogtracking.SmartphoneUUIDIdentifier;
import com.sap.sailing.domain.trackfiles.TrackFileImportDeviceIdentifier;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.server.MasterDataImportClassLoaderService;
import com.sap.sailing.server.gateway.deserialization.JsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.GPSFixJsonDeserializer;
import com.sap.sailing.server.gateway.deserialization.impl.GPSFixMovingJsonDeserializer;
@@ -78,6 +79,10 @@ public class Activator implements BundleActivator {
registerGPSFixJsonService(context, new GPSFixMovingNmeaDTOJsonDeserializer(), new GPSFixMovingNmeaDTOJsonSerializer(), GPSFixMovingNmeaDTOJsonDeserializer.TYPE);
registrations.add(context.registerService(RaceLogTrackingAdapterFactory.class, RaceLogTrackingAdapterFactory.INSTANCE, null));
registrations.add(context.registerService(MasterDataImportClassLoaderService.class,
new MasterDataImportClassLoaderServiceImpl(), null));
logger.log(Level.INFO, "Started "+context.getBundle().getSymbolicName());
}
@@ -0,0 +1,12 @@
package com.sap.sailing.domain.racelogtracking.impl;
import com.sap.sailing.server.MasterDataImportClassLoaderService;
public class MasterDataImportClassLoaderServiceImpl implements MasterDataImportClassLoaderService {
@Override
public ClassLoader getClassLoader() {
return this.getClass().getClassLoader();
}
}
@@ -2,11 +2,9 @@ package com.sap.sailing.domain.racelogtracking.impl;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
@@ -38,12 +36,12 @@ import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTracking
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogStartTrackingEvent;
import com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl.RaceLogTrackingStateAnalyzer;
import com.sap.sailing.domain.abstractlog.regatta.RegattaLog;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDeviceMarkMappingEventImpl;
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogRegisterCompetitorEventImpl;
import com.sap.sailing.domain.abstractlog.shared.analyzing.RegisteredCompetitorsAnalyzer;
import com.sap.sailing.domain.abstractlog.shared.events.RegisterCompetitorEvent;
import com.sap.sailing.domain.base.BoatClass;
import com.sap.sailing.domain.base.Competitor;
import com.sap.sailing.domain.base.ControlPoint;
import com.sap.sailing.domain.base.CourseBase;
import com.sap.sailing.domain.base.DomainFactory;
import com.sap.sailing.domain.base.Event;
@@ -54,7 +52,6 @@ import com.sap.sailing.domain.base.Regatta;
import com.sap.sailing.domain.base.SharedDomainFactory;
import com.sap.sailing.domain.base.Waypoint;
import com.sap.sailing.domain.base.impl.CourseDataImpl;
import com.sap.sailing.domain.common.PassingInstruction;
import com.sap.sailing.domain.common.RegattaIdentifier;
import com.sap.sailing.domain.common.abstractlog.NotRevokableException;
import com.sap.sailing.domain.common.racelog.tracking.DeviceMappingConstants;
@@ -94,7 +91,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
}
@Override
public void startTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet)
public RaceHandle startTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet)
throws NotDenotedForRaceLogTrackingException, Exception {
RaceLog raceLog = raceColumn.getRaceLog(fleet);
RaceLogTrackingState raceLogTrackingState = new RaceLogTrackingStateAnalyzer(raceLog).analyze();
@@ -102,26 +99,29 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
throw new NotDenotedForRaceLogTrackingException();
}
RegattaIdentifier regatta = ((RegattaLeaderboard) leaderboard).getRegatta().getRegattaIdentifier();
if (raceLogTrackingState != RaceLogTrackingState.TRACKING) {
RaceLogEvent event = RaceLogEventFactory.INSTANCE.createStartTrackingEvent(MillisecondsTimePoint.now(),
service.getServerAuthor(), raceLog.getCurrentPassId());
raceLog.add(event);
}
final RaceHandle result;
if (!isRaceLogRaceTrackerAttached(service, raceLog)) {
addTracker(service, regatta, leaderboard, raceColumn, fleet, -1);
result = addTracker(service, regatta, leaderboard, raceColumn, fleet, -1);
} else {
result = null;
}
return result;
}
/**
* Adds a {@link RaceLogRaceTracker}. If a {@link RaceLogStartTrackingEvent} is already present in the {@code RaceLog}
* linked to the {@code raceColumn} and {@code fleet}, a {@code TrackedRace} is created immediately and tracking begins.
* Otherwise, the {@code RaceLogRaceTracker} waits until a {@code StartTrackingEvent} is added to perform these actions.
* The race first has to be denoted for racelog tracking.
* Adds a {@link RaceLogRaceTracker}. If a {@link RaceLogStartTrackingEvent} is already present in the
* {@code RaceLog} linked to the {@code raceColumn} and {@code fleet}, a {@code TrackedRace} is created immediately
* and tracking begins. Otherwise, the {@code RaceLogRaceTracker} waits until a {@code StartTrackingEvent} is added
* to perform these actions. The race first has to be denoted for racelog tracking.
*/
private RaceHandle addTracker(RacingEventService service, RegattaIdentifier regattaToAddTo, Leaderboard leaderboard,
RaceColumn raceColumn, Fleet fleet, long timeoutInMilliseconds) throws RaceLogRaceTrackerExistsException, Exception {
private RaceHandle addTracker(RacingEventService service, RegattaIdentifier regattaToAddTo,
Leaderboard leaderboard, RaceColumn raceColumn, Fleet fleet, long timeoutInMilliseconds)
throws RaceLogRaceTrackerExistsException, Exception {
RaceLog raceLog = raceColumn.getRaceLog(fleet);
assert !isRaceLogRaceTrackerAttached(service, raceLog) : new RaceLogRaceTrackerExistsException(
leaderboard.getName() + " - " + raceColumn.getName() + " - " + fleet.getName());
@@ -133,15 +133,16 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
}
@Override
public void denoteRaceForRaceLogTracking(RacingEventService service, Leaderboard leaderboard, RaceColumn raceColumn,
Fleet fleet, String raceName) throws NotDenotableForRaceLogTrackingException {
public void denoteRaceForRaceLogTracking(RacingEventService service, Leaderboard leaderboard,
RaceColumn raceColumn, Fleet fleet, String raceName) throws NotDenotableForRaceLogTrackingException {
BoatClass boatClass = null;
if (leaderboard instanceof RegattaLeaderboard) {
RegattaLeaderboard rLeaderboard = (RegattaLeaderboard) leaderboard;
boatClass = rLeaderboard.getRegatta().getBoatClass();
} else {
throw new NotDenotableForRaceLogTrackingException("Can only denote races in RegattaLeaderboards for RaceLog-tracking");
throw new NotDenotableForRaceLogTrackingException(
"Can only denote races in RegattaLeaderboards for RaceLog-tracking");
}
if (raceName == null) {
@@ -170,7 +171,8 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
for (Fleet fleet : column.getFleets()) {
try {
denoteRaceForRaceLogTracking(service, leaderboard, column, fleet, null);
} catch (NotDenotableForRaceLogTrackingException e) {}
} catch (NotDenotableForRaceLogTrackingException e) {
}
}
}
}
@@ -185,46 +187,9 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
return new RaceLogTrackingStateAnalyzer(raceColumn.getRaceLog(fleet)).analyze();
}
private Waypoint duplicateWaypoint(Waypoint waypoint, Map<ControlPoint, ControlPoint> controlPointDuplicationCache,
Map<Mark, Mark> markDuplicationCache, SharedDomainFactory baseDomainFactory) {
ControlPoint oldCP = waypoint.getControlPoint();
ControlPoint newCP = null;
PassingInstruction pi = waypoint.getPassingInstructions();
if (controlPointDuplicationCache.get(oldCP) != null) {
newCP = controlPointDuplicationCache.get(oldCP);
} else {
Mark[] newMarks = new Mark[Util.size(oldCP.getMarks())];
int i = 0;
for (Mark oldMark : oldCP.getMarks()) {
newMarks[i] = null;
if (markDuplicationCache.get(oldMark) != null) {
newMarks[i] = markDuplicationCache.get(oldMark);
} else {
newMarks[i] = baseDomainFactory.getOrCreateMark(UUID.randomUUID(), oldMark.getName(), oldMark.getType(),
oldMark.getColor(), oldMark.getShape(), oldMark.getPattern());
markDuplicationCache.put(oldMark, newMarks[i]);
}
i++;
}
switch (newMarks.length) {
case 1:
newCP = newMarks[0];
break;
case 2:
newCP = baseDomainFactory.createControlPointWithTwoMarks(newMarks[0], newMarks[1], oldCP.getName());
break;
default:
logger.log(Level.WARNING, "Don't know how to duplicate CP with more than 2 marks");
throw new RuntimeException("Don't know how to duplicate CP with more than 2 marks");
}
controlPointDuplicationCache.put(oldCP, newCP);
}
return baseDomainFactory.createWaypoint(newCP, pi);
}
private void revokeAlreadyDefinedMarks(RaceLog raceLog, AbstractLogEventAuthor author) {
List<RaceLogEvent> markEvents = new AllEventsOfTypeFinder<>(raceLog, true, RaceLogDefineMarkEvent.class).analyze();
List<RaceLogEvent> markEvents = new AllEventsOfTypeFinder<>(raceLog, /* only unrevoked */ true, RaceLogDefineMarkEvent.class)
.analyze();
for (RaceLogEvent event : markEvents) {
try {
raceLog.revokeEvent(author, event, "removing mark that was already defined");
@@ -235,11 +200,12 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
}
@Override
public void copyCourseAndCompetitors(RaceLog fromRaceLog, Set<RaceLog> toRaceLogs, SharedDomainFactory baseDomainFactory,
RacingEventService service) {
public void copyCourseAndCompetitors(RaceLog fromRaceLog, Set<RaceLog> toRaceLogs,
SharedDomainFactory baseDomainFactory, RacingEventService service) {
CourseBase course = new LastPublishedCourseDesignFinder(fromRaceLog).analyze();
final Set<Mark> marks = new HashSet<>();
course.getWaypoints().forEach(wp->Util.addAll(wp.getMarks(), marks));
Set<Competitor> competitors = new RegisteredCompetitorsAnalyzer<>(fromRaceLog).analyze();
for (RaceLog toRaceLog : toRaceLogs) {
if (course == null || !new RaceLogTrackingStateAnalyzer(toRaceLog).analyze().isForTracking()) {
continue;
@@ -247,58 +213,77 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
CourseBase to = new CourseDataImpl("Copy of \"" + course.getName());
TimePoint now = MillisecondsTimePoint.now();
int i = 0;
Map<ControlPoint, ControlPoint> controlPointDuplicationCache = new HashMap<ControlPoint, ControlPoint>();
revokeAlreadyDefinedMarks(toRaceLog, service.getServerAuthor());
Map<Mark, Mark> markDuplicationCache = new HashMap<Mark, Mark>();
for (Waypoint oldWaypoint : course.getWaypoints()) {
to.addWaypoint(i++, duplicateWaypoint(oldWaypoint, controlPointDuplicationCache, markDuplicationCache, baseDomainFactory));
to.addWaypoint(i++, oldWaypoint);
}
for (Mark mark : markDuplicationCache.values()) {
RaceLogEvent event = RaceLogEventFactory.INSTANCE.createDefineMarkEvent(now, service.getServerAuthor(),
toRaceLog.getCurrentPassId(), mark);
for (Mark mark : marks) {
RaceLogEvent event = RaceLogEventFactory.INSTANCE.createDefineMarkEvent(now,
service.getServerAuthor(), toRaceLog.getCurrentPassId(), mark);
toRaceLog.add(event);
}
int passId = toRaceLog.getCurrentPassId();
RaceLogEvent newCourseEvent = RaceLogEventFactory.INSTANCE.createCourseDesignChangedEvent(
now, service.getServerAuthor(), passId, to);
RaceLogEvent newCourseEvent = RaceLogEventFactory.INSTANCE.createCourseDesignChangedEvent(now,
service.getServerAuthor(), passId, to);
toRaceLog.add(newCourseEvent);
registerCompetitors(service, toRaceLog, competitors);
}
}
@FunctionalInterface
private static interface DeviceMarkMappingEventFactory<VisitorT, EventT extends AbstractLogEvent<VisitorT>> {
EventT createDeviceMarkMapping(DeviceIdentifier device, TimePoint timePoint);
}
private <VisitorT, EventT extends AbstractLogEvent<VisitorT>, LogT extends AbstractLog<EventT, VisitorT>> void pingMark(
LogT log, Mark mark, GPSFix gpsFix, RacingEventService service,
DeviceMarkMappingEventFactory<VisitorT, EventT> factory, DeviceIdentifier device) {
TimePoint time = gpsFix.getTimePoint();
EventT mapping = factory.createDeviceMarkMapping(device, time);
log.add(mapping);
try {
service.getGPSFixStore().storeFix(device, gpsFix);
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not ping mark " + mark);
}
}
@Override
public void pingMark(RaceLog raceLog, Mark mark, GPSFix gpsFix, RacingEventService service) {
DeviceIdentifier device = new PingDeviceIdentifierImpl();
TimePoint time = gpsFix.getTimePoint();
RaceLogEvent mapping = RaceLogEventFactory.INSTANCE.createDeviceMarkMappingEvent(time,
service.getServerAuthor(), device, mark, raceLog.getCurrentPassId(), time, time);
raceLog.add(mapping);
try {
service.getGPSFixStore().storeFix(device, gpsFix);
} catch (TransformationException | NoCorrespondingServiceRegisteredException e) {
logger.log(Level.WARNING, "Could not pint mark " + mark);
pingMark(raceLog, mark, gpsFix, service,
(DeviceIdentifier dev, TimePoint timePoint) -> RaceLogEventFactory.INSTANCE
.createDeviceMarkMappingEvent(timePoint, service.getServerAuthor(), dev, mark,
raceLog.getCurrentPassId(), timePoint, timePoint), new PingDeviceIdentifierImpl());
}
@Override
public void pingMark(RegattaLog regattaLog, Mark mark, GPSFix gpsFix, RacingEventService service) {
pingMark(
regattaLog,
mark,
gpsFix,
service,
(DeviceIdentifier dev, TimePoint timePoint) -> new RegattaLogDeviceMarkMappingEventImpl(timePoint, service
.getServerAuthor(), timePoint, UUID.randomUUID(), mark, dev, timePoint, timePoint), new PingDeviceIdentifierImpl());
}
@Override
public void removeDenotationForRaceLogTracking(RacingEventService service, RaceLog raceLog) {
RaceLogEvent denoteForTrackingEvent = new LastEventOfTypeFinder<>(raceLog, true, RaceLogDenoteForTrackingEvent.class).analyze();
RaceLogEvent startTrackingEvent = new LastEventOfTypeFinder<>(raceLog, true, RaceLogStartTrackingEvent.class).analyze();
RaceLogEvent denoteForTrackingEvent = new LastEventOfTypeFinder<>(raceLog, true,
RaceLogDenoteForTrackingEvent.class).analyze();
RaceLogEvent startTrackingEvent = new LastEventOfTypeFinder<>(raceLog, true, RaceLogStartTrackingEvent.class)
.analyze();
try {
raceLog.revokeEvent(service.getServerAuthor(), denoteForTrackingEvent, "remove denotation");
raceLog.revokeEvent(service.getServerAuthor(), startTrackingEvent, "reset start time upon removing denotation");
raceLog.revokeEvent(service.getServerAuthor(), startTrackingEvent,
"reset start time upon removing denotation");
} catch (NotRevokableException e) {
logger.log(Level.WARNING, "could not remove denotation by adding RevokeEvents", e);
}
}
private <LogT extends AbstractLog<EventT, VisitorT>, EventT extends AbstractLogEvent<VisitorT>, VisitorT>
void registerCompetitors(AbstractLogEventAuthor author, LogT log, Set<Competitor> competitors,
private <LogT extends AbstractLog<EventT, VisitorT>, EventT extends AbstractLogEvent<VisitorT>, VisitorT> void registerCompetitors(
AbstractLogEventAuthor author, LogT log, Set<Competitor> competitors,
Function<Competitor, EventT> registerEventFactory) {
Set<Competitor> alreadyRegistered = new HashSet<Competitor>(new RegisteredCompetitorsAnalyzer<>(log).analyze());
Set<Competitor> toBeRegistered = new HashSet<Competitor>();
@@ -334,7 +319,10 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
@Override
public void registerCompetitors(RacingEventService service, RaceLog raceLog, Set<Competitor> competitors) {
registerCompetitors(service.getServerAuthor(), raceLog, competitors,
registerCompetitors(
service.getServerAuthor(),
raceLog,
competitors,
c -> RaceLogEventFactory.INSTANCE.createRegisterCompetitorEvent(MillisecondsTimePoint.now(),
service.getServerAuthor(), raceLog.getCurrentPassId(), c));
}
@@ -347,8 +335,7 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
}
private MailService getMailService() {
ServiceReference<MailService> ref = Activator.getContext()
.getServiceReference(MailService.class);
ServiceReference<MailService> ref = Activator.getContext().getServiceReference(MailService.class);
if (ref == null) {
logger.warning("No file storage management service registered");
return null;
@@ -370,17 +357,30 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
String url = DeviceMappingConstants.getDeviceMappingForRegattaLogUrl(serverUrlWithoutTrailingSlash,
event.getId().toString(), leaderboardName, DeviceMappingConstants.URL_COMPETITOR_ID_AS_STRING,
competitor.getId().toString(), NonGwtUrlHelper.INSTANCE);
try {
sendInvitationEmail(locale, toAddress, leaderboardName, competitorName, url);
} catch (MailException e) {
occuredExceptions.append(e.getMessage() + "\r\n");
}
}
}
if (!(occuredExceptions.length() == 0)) {
throw new MailException(occuredExceptions.toString());
}
}
private void sendInvitationEmail(Locale locale, final String toAddress, String leaderboardName, String invitee,
String url) throws MailException {
String subject = String.format("%s %s",
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "trackingInvitationFor"), competitorName);
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "trackingInvitationFor"), invitee);
// taken from http://www.tutorialspoint.com/javamail_api/javamail_api_send_inlineimage_in_email.htm
BodyPart messageTextPart = new MimeBodyPart();
String htmlText = String.format("<h1>%s %s</h1>" + "<p>%s <b>%s</b></p>"
+ "<img src=\"cid:image\" title=\"%s\"><br/>"
+ "<a href=\"%s\">%s</a>",
+ "<img src=\"cid:image\" title=\"%s\"><br/>" + "<a href=\"%s\">%s</a>",
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "welcomeTo"), leaderboardName,
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "scanQRCodeOrVisitUrlToRegisterAs"), competitorName,
url, url, RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "alternativelyVisitThisLink"));
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "scanQRCodeOrVisitUrlToRegisterAs"), invitee, url, url,
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "alternativelyVisitThisLink"));
try {
messageTextPart.setContent(htmlText, "text/html");
@@ -397,12 +397,32 @@ public class RaceLogTrackingAdapterImpl implements RaceLogTrackingAdapter {
getMailService().sendMail(toAddress, subject, multipart);
} catch (MessagingException | MailException | WriterException | IOException e) {
logger.log(Level.SEVERE, "Error trying to send mail to " + competitor.getName()
+ " with e-mail address " + toAddress, e);
logger.log(Level.SEVERE, "Error trying to send mail to " + invitee + " with e-mail address " + toAddress, e);
throw new MailException(e.getMessage());
}
}
@Override
public void inviteBuoyTenderViaEmail(Event event, Leaderboard leaderboard, String serverUrlWithoutTrailingSlash,
String emails, Locale locale) throws MailException {
StringBuilder occuredExceptions = new StringBuilder();
String[] emailArray = emails.split(",");
String leaderboardName = leaderboard.getName();
// http://<host>/buoy-tender/checkin&leaderboard_name=<leaderboard-name>
String url = DeviceMappingConstants.getBuoyTenderInvitationUrl(serverUrlWithoutTrailingSlash, leaderboardName,
NonGwtUrlHelper.INSTANCE);
for (String toAddress : emailArray) {
try {
sendInvitationEmail(locale, toAddress, leaderboardName,
RaceLogTrackingI18n.STRING_MESSAGES.get(locale, "buoyTender"), url);
} catch (MailException e) {
occuredExceptions.append(e.getMessage() + "\r\n");
}
}
}
if (!(occuredExceptions.length() == 0)) {
throw new MailException(occuredExceptions.toString());
}
@@ -133,7 +133,8 @@ public class ReadonlyRaceStateImpl implements ReadonlyRaceState, RaceLogChangedL
this.cachedRacingProcedureType = determineInitialProcedureType();
this.cachedRaceStatus = RaceLogRaceStatus.UNKNOWN;
this.cachedPassId = raceLog.getCurrentPassId();
// TODO bug 2083: make sure the listener registration is "weak" in the sense that it is removed when this race state is no longer strongly referenced
// see resolved bug 2083: make sure the listener registration is "weak" in the sense that it is removed when
// this race state is no longer strongly referenced
this.raceLog.addListener(new WeakRaceLogChangedVisitor(this.raceLog, this));
// We known that recreateRacingProcedure calls update() when done, therefore this RaceState
// will be fully initialized after this line
@@ -6,6 +6,7 @@ import com.sap.sailing.domain.base.Mark;
/**
* Defines a mark, that can then be used to build a course.
*
* @author Fredrik Teschke
*
*/
@@ -1,7 +1,6 @@
package com.sap.sailing.domain.abstractlog.race.tracking.analyzing.impl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.sap.sailing.domain.abstractlog.race.RaceLog;
@@ -10,13 +9,13 @@ import com.sap.sailing.domain.abstractlog.race.analyzing.impl.RaceLogAnalyzer;
import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDefineMarkEvent;
import com.sap.sailing.domain.base.Mark;
public class DefinedMarkFinder extends RaceLogAnalyzer<Collection<Mark>> {
public class DefinedMarkFinder extends RaceLogAnalyzer<Iterable<Mark>> {
public DefinedMarkFinder(RaceLog raceLog) {
super(raceLog);
}
@Override
protected Collection<Mark> performAnalysis() {
protected Iterable<Mark> performAnalysis() {
List< Mark> result = new ArrayList<Mark>();
for (RaceLogEvent event : getLog().getUnrevokedEvents()) {
if (event instanceof RaceLogDefineMarkEvent) {
@@ -985,6 +985,37 @@ public class LeaderboardScoringAndRankingTest extends AbstractLeaderboardTest {
assertEquals(29 /* two wins, one second */, leaderboardGroup.getOverallLeaderboard().getTotalPoints(c[3], later), 0.000000001);
}
@Test
public void testOverallLeaderboardWithESSHighPointScoringWithTwoCompetitorsHavingWonTheSameNumberOfRegattas() throws NoWindException {
Competitor[] c = createCompetitors(4).toArray(new Competitor[0]);
Competitor[] f1 = new Competitor[] { c[2], c[0], c[1], c[3] };
Competitor[] f2 = new Competitor[] { c[0], c[2], c[1], c[3] };
TimePoint now = MillisecondsTimePoint.now();
TimePoint later = new MillisecondsTimePoint(now.asMillis()+1000);
FlexibleLeaderboard leaderboard1 = new FlexibleLeaderboardImpl("Regatta 1", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
new HighPointFirstGets10Or8AndLastBreaksTie(), null);
leaderboard1.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f1)), "R1", /* medalRace */
false);
assertTrue(leaderboard1.getTotalPoints(c[2], later) > leaderboard1.getTotalPoints(c[0], later)); // c2 better than c[0]
assertEquals(10.0, leaderboard1.getTotalPoints(c[2], later), 0.1);
assertEquals(9.0, leaderboard1.getTotalPoints(c[0], later), 0.1);
FlexibleLeaderboard leaderboard2 = new FlexibleLeaderboardImpl("Regatta 2", new ThresholdBasedResultDiscardingRuleImpl(/* discarding thresholds */ new int[0]),
new HighPointFirstGets10Or8AndLastBreaksTie(), null);
leaderboard2.addRace(new MockedTrackedRaceWithStartTimeAndRanks(now, Arrays.asList(f2)), "R1", /* medalRace */
false);
LeaderboardGroup leaderboardGroup = new LeaderboardGroupImpl("Leaderboard Group", "Leaderboard Group", /* displayName */ null, false, Arrays.asList(leaderboard1,
leaderboard2));
leaderboardGroup.setOverallLeaderboard(new LeaderboardGroupMetaLeaderboard(leaderboardGroup, new HighPointExtremeSailingSeriesOverall(),
new ThresholdBasedResultDiscardingRuleImpl(new int[0])));
List<Competitor> rankedCompetitors = leaderboardGroup.getOverallLeaderboard().getCompetitorsFromBestToWorst(later);
assertEquals(4, rankedCompetitors.size());
assertEquals(19, leaderboardGroup.getOverallLeaderboard().getTotalPoints(c[0], later), 0.000000001);
assertEquals(19, leaderboardGroup.getOverallLeaderboard().getTotalPoints(c[2], later), 0.000000001);
assertEquals(16, leaderboardGroup.getOverallLeaderboard().getTotalPoints(c[1], later), 0.000000001);
assertEquals(14, leaderboardGroup.getOverallLeaderboard().getTotalPoints(c[3], later), 0.000000001);
assertEquals(c[0], rankedCompetitors.get(0));
assertEquals(c[2], rankedCompetitors.get(1));
}
@Test
public void testHighPointScoringEightWithInterpolationWhenFleetNotComplete() throws NoWindException {
int competitorsCount = 21;
@@ -8,12 +8,18 @@ import static org.junit.Assert.assertTrue;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Ignore;
import org.junit.Test;
import com.sap.sse.util.SmartFutureCache;
import com.sap.sse.util.SmartFutureCache.CacheUpdater;
import com.sap.sse.util.SmartFutureCache.EmptyUpdateInterval;
import com.sap.sse.util.SmartFutureCache.UpdateInterval;
@@ -247,4 +253,124 @@ public class SmartFutureCacheTest {
System.out.println("Tasks re-used: "+sfc.getSmartFutureCacheTaskReuseCounter());
assertEquals(updatesTriggeredFor.size(), updateWasCalled.size());
}
@Test
public void testIfNoNewFuturesAreRunForTheSameKeyWhileCurrentTaskIsSleeping() throws InterruptedException, BrokenBarrierException {
final AtomicInteger callCounter = new AtomicInteger(0);
CyclicBarrier barrier = new CyclicBarrier(2);
CacheUpdater<Integer, Integer, EmptyUpdateInterval> cacheUpdater = new CacheUpdater<Integer, Integer, SmartFutureCache.EmptyUpdateInterval>() {
@Override
public Integer computeCacheUpdate(Integer key, EmptyUpdateInterval updateInterval) throws Exception {
synchronized (callCounter) {
if (key == 1) {
callCounter.incrementAndGet();
}
}
barrier.await();
barrier.await();
barrier.await();
//For this test case value will be same as key, when updated.
return key;
}
@Override
public Integer provideNewCacheValue(Integer key, Integer oldCacheValue, Integer computedCacheUpdate,
EmptyUpdateInterval updateInterval) {
return computedCacheUpdate;
}
};
SmartFutureCache<Integer, Integer, EmptyUpdateInterval> testCache = new SmartFutureCache<Integer, Integer, SmartFutureCache.EmptyUpdateInterval>(
cacheUpdater, "SmartFutureTestCacheLock");
testCache.triggerUpdate(1, null);
barrier.await();
assertEquals(1, callCounter.get());
testCache.triggerUpdate(1, null);
barrier.await();
// Counter should still be one here, since first future is still sleeping at this point
assertEquals(1, callCounter.get());
barrier.await();
// Now the first future should be done, and the second update should have been called, so the counter should be 2
barrier.await();
assertEquals(2, callCounter.get());
barrier.await();
barrier.await();
}
@Test
public void testTriggerAndGetWithWaitForLatestWithRunningFutureOnSuspendedCache() throws InterruptedException, BrokenBarrierException {
final AtomicInteger callCounter = new AtomicInteger(0);
CyclicBarrier barrier = new CyclicBarrier(2);
CacheUpdater<Integer, Integer, EmptyUpdateInterval> cacheUpdater = new CacheUpdater<Integer, Integer, SmartFutureCache.EmptyUpdateInterval>() {
@Override
public Integer computeCacheUpdate(Integer key, EmptyUpdateInterval updateInterval) throws Exception {
synchronized (callCounter) {
if (key == 1) {
callCounter.incrementAndGet();
}
}
barrier.await();
barrier.await();
barrier.await();
return callCounter.get();
}
@Override
public Integer provideNewCacheValue(Integer key, Integer oldCacheValue, Integer computedCacheUpdate,
EmptyUpdateInterval updateInterval) {
return computedCacheUpdate;
}
};
SmartFutureCache<Integer, Integer, EmptyUpdateInterval> testCache = new SmartFutureCache<Integer, Integer, SmartFutureCache.EmptyUpdateInterval>(
cacheUpdater, "SmartFutureTestCacheLock");
testCache.triggerUpdate(1, null);
barrier.await();
assertEquals(1, callCounter.get());
testCache.triggerUpdate(1, null);
testCache.suspend();
CyclicBarrier getResultBarrier = new CyclicBarrier(2);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//FIXME find a way to guarantee that get is called, while the first computeUpdate Future is still running
int result = testCache.get(1, true);
assertEquals(2, result);
try {
getResultBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
});
thread.start();
barrier.await();
barrier.await();
barrier.await();
assertEquals(2, callCounter.get());
//Trigger again. This update should not retrigger add another future, since cache is disabled and no
// get with waitForLatest is called afterwards
testCache.triggerUpdate(1, null);
barrier.await();
barrier.await();
//Wait for get Thread to finish with getting and asserting
getResultBarrier.await();
assertEquals(2, callCounter.get());
//Check if no compute Thread is waiting at barrier.
boolean timeOut = false;
try {
barrier.await(10, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
timeOut = true;
}
assertTrue(timeOut);
}
}
@@ -48,6 +48,7 @@ import com.sap.sailing.domain.tracking.DynamicTrackedRace;
import com.sap.sailing.domain.tracking.DynamicTrackedRegatta;
import com.sap.sailing.domain.tracking.GPSFix;
import com.sap.sailing.domain.tracking.GPSFixMoving;
import com.sap.sailing.domain.tracking.GPSFixTrack;
import com.sap.sailing.domain.tracking.LineDetails;
import com.sap.sailing.domain.tracking.Maneuver;
import com.sap.sailing.domain.tracking.MarkPassing;
@@ -873,13 +874,29 @@ public class MockedTrackedRace implements DynamicTrackedRace {
@Override
public void attachRegattaLog(RegattaLog regattaLog) {
// TODO Auto-generated method stub
}
@Override
public void waitForLoadingFromGPSFixStoreToFinishRunning(RegattaLog fromRegattaLog) throws InterruptedException {
// TODO Auto-generated method stub
}
@Override
public Boolean isGateStart() {
return null;
}
@Override
public Distance getDistanceTraveledIncludingGateStart(Competitor competitor, TimePoint timePoint) {
return null;
}
@Override
public Distance getAdditionalGateStartDistance(Competitor competitor, TimePoint timePoint) {
return null;
}
@Override
public GPSFixTrack<Mark, GPSFix> getTrack(Mark mark) {
return null;
}
}
Loaded 100 of 334 files, more files were not shown because too many files have changed in this diff. Show more