diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index b15bf6dfc7c..d16cf3fadcc 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -205,9 +205,12 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public void recordWind(Wind wind, WindSource windSource) { + public boolean recordWind(Wind wind, WindSource windSource) { if (windSource.getType() == WindSourceType.EXPEDITION) { windTrack.add(wind); + return true; + } else { + return false; } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java index f40104584c1..c9c58bb47e0 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRace.java @@ -11,7 +11,10 @@ public interface DynamicTrackedRace extends TrackedRace { void recordFix(Mark mark, GPSFix fix); - void recordWind(Wind wind, WindSource windSource); + /** + * @return True if the specified wind has been accepteed and added to this race's windtrack and database, else false. + */ + boolean recordWind(Wind wind, WindSource windSource); void removeWind(Wind wind, WindSource windSource); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java index 6211bd7f492..316be9e2b3a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceImpl.java @@ -634,7 +634,7 @@ DynamicTrackedRace, GPSTrackListener { } @Override - public void recordWind(Wind wind, WindSource windSource) { + public boolean recordWind(Wind wind, WindSource windSource) { // TODO check what a good filter is; remember that start/end of tracking may change over time; what if we have discarded valuable wind fixes? TimePoint startOfRace = getStartOfRace(); TimePoint startOfTracking = getStartOfTracking(); @@ -649,6 +649,9 @@ DynamicTrackedRace, GPSTrackListener { updated(/* time point */null); // wind events shouldn't advance race time triggerManeuverCacheRecalculationForAllCompetitors(); notifyListeners(wind, windSource); + return true; + } else { + return false; } } diff --git a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java index 3a5aa78fad7..c1d6acec4c5 100755 --- a/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java +++ b/java/com.sap.sailing.expeditionconnector.test/src/com/sap/sailing/expeditionconnector/test/UDPExpeditionReceiverTest.java @@ -152,8 +152,9 @@ public class UDPExpeditionReceiverTest { ExpeditionWindTracker windTracker = new ExpeditionWindTracker(new MockedTrackedRace() { private static final long serialVersionUID = 4444197492014940699L; @Override - public void recordWind(Wind wind, WindSource windSource) { + public boolean recordWind(Wind wind, WindSource windSource) { windFixes.add(wind); + return true; } }, null, receiver, /* ExpeditionWindTrackerFactory */ null); receiver.addListener(windTracker, /* validMessagesOnly */ false); diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java index 45c94ab1228..606237c370e 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java @@ -205,9 +205,12 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public void recordWind(Wind wind, WindSource windSource) { + public boolean recordWind(Wind wind, WindSource windSource) { if (windSource.getType() == WindSourceType.EXPEDITION) { windTrack.add(wind); + return true; + } else { + return false; } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceSelection.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceSelection.java new file mode 100644 index 00000000000..9ac6cd60f19 --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceSelection.java @@ -0,0 +1,30 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import com.google.gwt.core.client.JavaScriptObject; + +public class RaceSelection extends JavaScriptObject { + + protected RaceSelection() {}; + + public static final native RaceSelection create() /*-{return [];}-*/; + + public final native String toJson() /*-{return JSON.stringify(this);}-*/; + + public final native void addRace(RaceEntry race) /*-{ + + this.push(race); + + }-*/; + + public static class RaceEntry extends JavaScriptObject { + + protected RaceEntry() {}; + + public static final native RaceEntry create() /*-{return {};}-*/; + + public final native void setRaceName(String raceName) /*-{this.race = raceName;}-*/; + + public final native void setRegatteName(String regatteName) /*-{this.regatta = regatteName;}-*/; + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindImportResult.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindImportResult.java new file mode 100644 index 00000000000..34517031ece --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindImportResult.java @@ -0,0 +1,38 @@ +package com.sap.sailing.gwt.ui.adminconsole; + +import com.google.gwt.core.client.JavaScriptObject; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.core.client.JsDate; + +public class WindImportResult extends JavaScriptObject { + + protected WindImportResult() { + }; + + static public final native WindImportResult fromJson(String json) /*-{return JSON.parse(json);}-*/; + + public final native String getError() /*-{return this.error;}-*/; + + public final native JsDate getFirst() /*-{return new Date(this.first);}-*/; + + public final native JsDate getLast() /*-{return new Date(this.last);}-*/; + + public final native JsArray getRaceEntries() /*-{return this.raceEntries;}-*/; + + static class RaceEntry extends JavaScriptObject { + + protected RaceEntry() {}; + + public final native String getRegattaName() /*-{return this.regattaName;}-*/; + + public final native String getRaceName() /*-{return this.raceName;}-*/; + + public final native int getCount() /*-{return this.count;}-*/; + + public final native JsDate getFirst() /*-{return new Date(this.first);}-*/; + + public final native JsDate getLast() /*-{return new Date(this.last);}-*/; + + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java index 4752dc2b985..3024b34f672 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/WindPanel.java @@ -11,24 +11,36 @@ import java.util.Set; import com.google.gwt.cell.client.ActionCell; import com.google.gwt.cell.client.ActionCell.Delegate; import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.JsArray; +import com.google.gwt.event.dom.client.ChangeEvent; +import com.google.gwt.event.dom.client.ChangeHandler; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.i18n.client.NumberFormat; +import com.google.gwt.regexp.shared.MatchResult; +import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.cellview.client.ColumnSortEvent.Handler; import com.google.gwt.user.cellview.client.ColumnSortEvent.ListHandler; import com.google.gwt.user.cellview.client.IdentityColumn; import com.google.gwt.user.cellview.client.TextColumn; +import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.CaptionPanel; import com.google.gwt.user.client.ui.CheckBox; +import com.google.gwt.user.client.ui.FileUpload; import com.google.gwt.user.client.ui.FormPanel; import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.HasVerticalAlignment; +import com.google.gwt.user.client.ui.Hidden; +import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.TabPanel; +import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.view.client.ListDataProvider; import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; @@ -37,6 +49,7 @@ import com.sap.sailing.domain.common.WindSourceType; import com.sap.sailing.domain.common.dto.RaceDTO; import com.sap.sailing.domain.common.impl.WindSourceImpl; import com.sap.sailing.gwt.ui.actions.AsyncActionsExecutor; +import com.sap.sailing.gwt.ui.adminconsole.WindImportResult.RaceEntry; import com.sap.sailing.gwt.ui.client.DataEntryDialog.DialogCallback; import com.sap.sailing.gwt.ui.client.ErrorReporter; import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; @@ -61,6 +74,13 @@ import com.sap.sailing.gwt.ui.shared.WindTrackInfoDTO; * */ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower, RaceSelectionChangeListener { + + private static final String EXPEDITON_IMPORT_PARAMETER_RACES = "races"; + + private static final String EXPEDITON_IMPORT_PARAMETER_BOAT_ID = "boatId"; + + private static final String URL_SAILINGSERVER_EXPEDITION_IMPORT = "/../../sailingserver/expedition-import"; + private final SailingServiceAsync sailingService; private final ErrorReporter errorReporter; private final StringMessages stringMessages; @@ -79,21 +99,21 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower private final ListDataProvider rawWindFixesDataProvider; private final CellTable rawWindFixesTable; private final VerticalPanel windFixPanel; - - public WindPanel(final SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, + + public WindPanel(final SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, ErrorReporter errorReporter, RegattaRefresher regattaRefresher, final StringMessages stringMessages) { this.sailingService = sailingService; this.errorReporter = errorReporter; this.stringMessages = stringMessages; this.raceSelectionProvider = new RaceSelectionModel(); windSourcesToExcludeSelectorPanel = new WindSourcesToExcludeSelectorPanel(sailingService, stringMessages, errorReporter); - + VerticalPanel mainPanel = new VerticalPanel(); mainPanel.setSize("100%", "100%"); this.setWidget(mainPanel); - trackedRacesListComposite = new TrackedRacesListComposite(sailingService, errorReporter, regattaRefresher, - raceSelectionProvider, stringMessages, false); + trackedRacesListComposite = new TrackedRacesListComposite(sailingService, errorReporter, regattaRefresher, + raceSelectionProvider, stringMessages, /*multiselection*/true); mainPanel.add(trackedRacesListComposite); raceSelectionProvider.addRaceSelectionChangeListener(this); @@ -107,12 +127,12 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower tabPanel.setSize("95%", "95%"); windFixesDisplayPanel = new VerticalPanel(); - Button addWindFixButton = new Button(stringMessages.actionAddWindData()+ "..."); + Button addWindFixButton = new Button(stringMessages.actionAddWindData() + "..."); addWindFixButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - RegattaAndRaceIdentifier selectedRace = getSelectedRace(); - if(selectedRace != null) { + RegattaAndRaceIdentifier selectedRace = getSelectedRace(); + if (selectedRace != null) { final RaceDTO race = trackedRacesListComposite.getRaceByIdentifier(selectedRace); sailingService.getCoursePositions(selectedRace, race.trackedRace.startOfTracking, new AsyncCallback() { @Override @@ -135,7 +155,7 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower tabPanel.add(windSourcesPanel, stringMessages.windSourcesUsed()); tabPanel.add(windFixesDisplayPanel, "Wind fixes"); tabPanel.selectTab(0); - + raceIsKnownToStartUpwindBox = new CheckBox(stringMessages.raceIsKnownToStartUpwind()); windSourcesPanel.add(raceIsKnownToStartUpwindBox); @@ -146,10 +166,10 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower setRaceIsKnownToStartUpwind(); } }); - + windFixPanel = new VerticalPanel(); windSourcesPanel.add(windFixPanel); - + windSourceLabel = new Label(); windFixesDisplayPanel.add(windSourceLabel); @@ -158,19 +178,19 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower @Override public void execute(final WindDTO wind) { List selectedRaces = raceSelectionProvider.getSelectedRaces(); - final RegattaAndRaceIdentifier raceIdentifier = selectedRaces.get(selectedRaces.size()-1); + final RegattaAndRaceIdentifier raceIdentifier = selectedRaces.get(selectedRaces.size() - 1); sailingService.removeWind(raceIdentifier, wind, new AsyncCallback() { @Override public void onSuccess(Void result) { // remove row from underlying list: rawWindFixesDataProvider.getList().remove(wind); } + @Override public void onFailure(Throwable caught) { - WindPanel.this.errorReporter.reportError( - WindPanel.this.stringMessages.errorSettingWindForRace()+ " "+raceIdentifier - + ": "+ caught.getMessage()); - } + WindPanel.this.errorReporter.reportError(WindPanel.this.stringMessages.errorSettingWindForRace() + + " " + raceIdentifier + ": " + caught.getMessage()); + } }); } })); @@ -183,21 +203,21 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower speedInKnotsColumn = new TextColumn() { @Override public String getValue(WindDTO object) { - return ""+object.trueWindSpeedInKnots; + return "" + object.trueWindSpeedInKnots; } }; windDirectionInDegColumn = new TextColumn() { @Override public String getValue(WindDTO object) { - return ""+object.trueWindFromDeg; + return "" + object.trueWindFromDeg; } }; positionColumn = new TextColumn() { @Override public String getValue(WindDTO object) { String result = ""; - if(object.position != null) { - result = "Lat: " + object.position.latDeg + ", Lon: " + object.position.lngDeg; + if (object.position != null) { + result = "Lat: " + object.position.latDeg + ", Lon: " + object.position.lngDeg; } return result; } @@ -215,25 +235,154 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower rawWindFixesTable.addColumn(removeColumn, stringMessages.actions()); rawWindFixesDataProvider = new ListDataProvider(); rawWindFixesDataProvider.addDataDisplay(rawWindFixesTable); - Handler columnSortHandler = getWindTableColumnSortHandler(rawWindFixesDataProvider.getList(), timeColumn, - speedInKnotsColumn, windDirectionInDegColumn); + Handler columnSortHandler = getWindTableColumnSortHandler(rawWindFixesDataProvider.getList(), timeColumn, speedInKnotsColumn, windDirectionInDegColumn); rawWindFixesTable.addColumnSortHandler(columnSortHandler); rawWindFixesTable.getColumnSortList().push(timeColumn); windFixesDisplayPanel.add(rawWindFixesTable); + + createWindImportPanel(mainPanel, stringMessages); + + } + + private void createWindImportPanel(Panel mainPanel, final StringMessages stringMessages) { + + /* + * To style the "browse" button of the file upload widget + * see http://www.shauninman.com/archive/2007/09/10/styling_file_inputs_with_css_and_the_dom + */ + + CaptionPanel windImportRootPanel = new CaptionPanel(stringMessages.windImport_Title()); + mainPanel.add(windImportRootPanel); + VerticalPanel windImportContentPanel = new VerticalPanel(); + windImportRootPanel.add(windImportContentPanel); + final FormPanel form = new FormPanel(); + windImportContentPanel.add(form); + VerticalPanel formContentPanel = new VerticalPanel(); + form.add(formContentPanel); + HorizontalPanel inputPanel = new HorizontalPanel(); + formContentPanel.add(inputPanel); + final Panel importResultPanel = new VerticalPanel(); + windImportContentPanel.add(importResultPanel); + + form.setMethod(FormPanel.METHOD_POST); + form.setEncoding(FormPanel.ENCODING_MULTIPART); + form.setAction(GWT.getHostPageBaseURL() + URL_SAILINGSERVER_EXPEDITION_IMPORT); + + final TextBox boatIdTextBox = new TextBox(); + boatIdTextBox.setName(EXPEDITON_IMPORT_PARAMETER_BOAT_ID); + final Button submitButton = new Button(stringMessages.windImport_Upload(), new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + importResultPanel.clear(); + form.submit(); + } + }); + submitButton.setEnabled(false); + + final FileUpload fileUpload = new FileUpload(); + fileUpload.setName("upload"); + fileUpload.addChangeHandler(new ChangeHandler() { + + @Override + public void onChange(ChangeEvent event) { + importResultPanel.clear(); + String fileName = fileUpload.getFilename(); + boolean isValidFileName = (fileName != null) && (fileUpload.getFilename().trim().length() > 0); + submitButton.setEnabled(isValidFileName); + + String boatId = ""; + if (isValidFileName) { + RegExp EXPEDITION_EXPORT_FILE_PATTERN = RegExp.compile("^.*_([0-9]+)\\.csv"); //matches typical expedition log file names like "2013Jun26_0.csv" where 0 as group[1] indicates the boat id. + MatchResult match = EXPEDITION_EXPORT_FILE_PATTERN.exec(fileName); + if (match.getGroupCount() > 0) { + boatId = match.getGroup(1); + } + } + boatIdTextBox.setText(boatId); + + } + }); + + inputPanel.add(fileUpload); + + Label boatIdLabel = new Label(stringMessages.windImport_BoatId()); + inputPanel.add(boatIdLabel); + inputPanel.add(boatIdTextBox); + boatIdLabel.setWordWrap(false); + inputPanel.setSpacing(5); + inputPanel.setCellVerticalAlignment(fileUpload, HasVerticalAlignment.ALIGN_MIDDLE); + inputPanel.setCellVerticalAlignment(boatIdLabel, HasVerticalAlignment.ALIGN_MIDDLE); + inputPanel.setCellVerticalAlignment(boatIdTextBox, HasVerticalAlignment.ALIGN_MIDDLE); + + final Hidden hiddenRacesField = new Hidden(EXPEDITON_IMPORT_PARAMETER_RACES); + inputPanel.add(hiddenRacesField); + + formContentPanel.add(submitButton); + + form.addSubmitHandler(new FormPanel.SubmitHandler() { + public void onSubmit(SubmitEvent event) { + if ((fileUpload.getFilename() != null) && (fileUpload.getFilename().trim().length() > 0)) { + List selectedRaces = raceSelectionProvider.getSelectedRaces(); + String warningMessage; + if (selectedRaces.size() > 0) { + warningMessage = stringMessages.windImport_SelectedRacesWarning(selectedRaces.size()); + RaceSelection raceSelection = RaceSelection.create(); + for (int i = 0; i < selectedRaces.size(); i++) { + RegattaAndRaceIdentifier raceIdentifier = selectedRaces.get(i); + RaceSelection.RaceEntry raceEntry = RaceSelection.RaceEntry.create(); + raceEntry.setRaceName(raceIdentifier.getRaceName()); + raceEntry.setRegatteName(raceIdentifier.getRegattaName()); + raceSelection.addRace(raceEntry); + } + hiddenRacesField.setValue(raceSelection.toJson()); + } else { + warningMessage = stringMessages.windImport_AllRacesWarning(); + hiddenRacesField.setValue(null); + } + if (!Window.confirm(warningMessage)) { + event.cancel(); + } + } else { + event.cancel(); + } + } + }); + form.addSubmitCompleteHandler(new FormPanel.SubmitCompleteHandler() { + public void onSubmitComplete(SubmitCompleteEvent event) { + importResultPanel.clear(); + String windImportResultJson = event.getResults(); + + WindImportResult windImportResult = WindImportResult.fromJson(windImportResultJson); + JsArray raceEntries = windImportResult.getRaceEntries(); + Label resultHeader = new Label(stringMessages.windImport_ResultHeader(String.valueOf(windImportResult.getFirst()), + String.valueOf(windImportResult.getLast()), raceEntries.length())); + importResultPanel.add(resultHeader); + if (windImportResult.getError() != null) { + Label errorText = new Label(stringMessages.windImport_ResultError(windImportResult.getError())); + importResultPanel.add(errorText); + } + for (int i = 0; i < raceEntries.length(); i++) { + RaceEntry raceEntry = raceEntries.get(i); + Label entryText = new Label(stringMessages.windImport_ResultEntry(raceEntry.getRaceName(), raceEntry.getRegattaName(), + raceEntry.getCount(), String.valueOf(raceEntry.getFirst()), String.valueOf(raceEntry.getLast()))); + importResultPanel.add(entryText); + } + } + }); + } private void showWindSettingDialog(RaceDTO race, CoursePositionsDTO course) { - AddWindFixDialog windSettingDialog = new AddWindFixDialog(race, course, stringMessages, - new DialogCallback() { - @Override - public void cancel() { - } + AddWindFixDialog windSettingDialog = new AddWindFixDialog(race, course, stringMessages, new DialogCallback() { + @Override + public void cancel() { + } - @Override - public void ok(final WindDTO result) { - addWindFix(result); - } - }); + @Override + public void ok(final WindDTO result) { + addWindFix(result); + } + }); windSettingDialog.show(); } @@ -251,7 +400,7 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower } }); } - + @Override public void fillRegattas(List result) { trackedRacesListComposite.fillRegattas(result); @@ -259,46 +408,44 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower @Override public void showWind(final RegattaAndRaceIdentifier raceIdentifier) { - sailingService.getWindSourcesInfo(raceIdentifier, - new AsyncCallback() { - @Override - public void onSuccess(final WindInfoForRaceDTO result) { - if (result != null) { - updateWindSourcesToExclude(result, raceIdentifier); - raceIsKnownToStartUpwindBox.setValue(result.raceIsKnownToStartUpwind); - - // load the raw wind fixes - sailingService.getRawWindFixes(raceIdentifier, null, - new AsyncCallback() { - @Override - public void onSuccess(WindInfoForRaceDTO result) { - if (result != null) { - udapteRawWindFixes(result); - } else { - // no wind fixes known for untracked race - clearWindSources(); - clearWindFixes(); - } - } + sailingService.getWindSourcesInfo(raceIdentifier, new AsyncCallback() { + @Override + public void onSuccess(final WindInfoForRaceDTO result) { + if (result != null) { + updateWindSourcesToExclude(result, raceIdentifier); + raceIsKnownToStartUpwindBox.setValue(result.raceIsKnownToStartUpwind); - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error reading wind fixes: " + caught.getMessage()); - } - }); - - } else { - // no wind sources known for untracked race - clearWindSources(); - clearWindFixes(); + // load the raw wind fixes + sailingService.getRawWindFixes(raceIdentifier, null, new AsyncCallback() { + @Override + public void onSuccess(WindInfoForRaceDTO result) { + if (result != null) { + udapteRawWindFixes(result); + } else { + // no wind fixes known for untracked race + clearWindSources(); + clearWindFixes(); + } } - } - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError("Error reading wind source information: " + caught.getMessage()); - } - }); + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error reading wind fixes: " + caught.getMessage()); + } + }); + + } else { + // no wind sources known for untracked race + clearWindSources(); + clearWindFixes(); + } + } + + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError("Error reading wind source information: " + caught.getMessage()); + } + }); } private void updateWindSourcesToExclude(WindInfoForRaceDTO result, RegattaAndRaceIdentifier raceIdentifier) { @@ -318,9 +465,8 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower } } } - - private Handler getWindTableColumnSortHandler(List list, TextColumn timeColumn, - TextColumn speedInKnotsColumn, TextColumn windDirectionInDegColumn) { + + private Handler getWindTableColumnSortHandler(List list, TextColumn timeColumn, TextColumn speedInKnotsColumn, TextColumn windDirectionInDegColumn) { ListHandler result = new ListHandler(list); result.setComparator(timeColumn, new Comparator() { @Override @@ -331,15 +477,13 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower result.setComparator(speedInKnotsColumn, new Comparator() { @Override public int compare(WindDTO o1, WindDTO o2) { - return o1.trueWindSpeedInKnots < o2.trueWindSpeedInKnots ? -1 : - o1.trueWindSpeedInKnots == o2.trueWindSpeedInKnots ? 0 : 1; + return o1.trueWindSpeedInKnots < o2.trueWindSpeedInKnots ? -1 : o1.trueWindSpeedInKnots == o2.trueWindSpeedInKnots ? 0 : 1; } }); result.setComparator(windDirectionInDegColumn, new Comparator() { @Override public int compare(WindDTO o1, WindDTO o2) { - return o1.trueWindFromDeg < o2.trueWindFromDeg ? -1 : - o1.trueWindFromDeg == o2.trueWindFromDeg ? 0 : 1; + return o1.trueWindFromDeg < o2.trueWindFromDeg ? -1 : o1.trueWindFromDeg == o2.trueWindFromDeg ? 0 : 1; } }); return result; @@ -347,24 +491,24 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower private void setRaceIsKnownToStartUpwind() { final RegattaAndRaceIdentifier selectedRace = getSelectedRace(); - sailingService.setRaceIsKnownToStartUpwind(selectedRace, - raceIsKnownToStartUpwindBox.getValue(), new AsyncCallback() { - @Override - public void onFailure(Throwable caught) { - errorReporter.reportError(WindPanel.this.stringMessages.errorWhileTryingToSetWindSourceForRace()+ - " "+selectedRace+": "+caught.getMessage()); - } - @Override - public void onSuccess(Void result) { - } - }); + sailingService.setRaceIsKnownToStartUpwind(selectedRace, raceIsKnownToStartUpwindBox.getValue(), new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(WindPanel.this.stringMessages.errorWhileTryingToSetWindSourceForRace() + " " + selectedRace + ": " + caught.getMessage()); + } + + @Override + public void onSuccess(Void result) { + } + }); } private RegattaAndRaceIdentifier getSelectedRace() { List selectedRaces = raceSelectionProvider.getSelectedRaces(); - if(selectedRaces.isEmpty()) + if (selectedRaces.isEmpty() || selectedRaces.size() > 1) { return null; - + } + return selectedRaces.get(0); } @@ -375,7 +519,7 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower private void clearWindFixes() { } - + private void showWindFixesList(RegattaAndRaceIdentifier selectedRace, RaceDTO raceDTO) { List windSourceTypeNames = new ArrayList(); windSourceTypeNames.add(WindSourceType.COMBINED.name()); @@ -388,27 +532,19 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower @Override public void onSuccess(WindInfoForRaceDTO result) { windFixPanel.clear(); - - for (WindSourceType input : new WindSourceType[]{WindSourceType.COMBINED}) { + + for (WindSourceType input : new WindSourceType[] { WindSourceType.COMBINED }) { windFixPanel.add(new HTML(" ")); windFixPanel.add(new Label(stringMessages.windFixListingDescription() + " " + input.name())); WindTrackInfoDTO windTrackInfo = result.windTrackInfoByWindSource.get(new WindSourceImpl(input)); - if (windTrackInfo != null && windTrackInfo.windFixes.size()>=7) { + if (windTrackInfo != null && windTrackInfo.windFixes.size() >= 7) { NumberFormat formatter = NumberFormat.getFormat(".##"); for (WindDTO windFix : windTrackInfo.windFixes.subList(0, 3)) { - windFixPanel.add(new Label(""+formatter.format(windFix.trueWindFromDeg)+" (deg) "+ - formatter.format(windFix.trueWindSpeedInKnots)+" (kt) "+ - formatter.format(windFix.position.latDeg) + " (lat) "+ - formatter.format(windFix.position.lngDeg) + " (lng) " + - new Date(windFix.measureTimepoint))); + windFixPanel.add(new Label("" + formatter.format(windFix.trueWindFromDeg) + " (deg) " + formatter.format(windFix.trueWindSpeedInKnots) + " (kt) " + formatter.format(windFix.position.latDeg) + " (lat) " + formatter.format(windFix.position.lngDeg) + " (lng) " + new Date(windFix.measureTimepoint))); } // These fixes must not necessarily be the real last ones. This especially holds for long races. - for (WindDTO windFix : windTrackInfo.windFixes.subList(windTrackInfo.windFixes.size()-4, windTrackInfo.windFixes.size()-1)) { - windFixPanel.add(new Label(""+formatter.format(windFix.trueWindFromDeg)+" (deg) "+ - formatter.format(windFix.trueWindSpeedInKnots)+" (kt) "+ - formatter.format(windFix.position.latDeg) + " (lat) "+ - formatter.format(windFix.position.lngDeg) + " (lng) " + - new Date(windFix.measureTimepoint))); + for (WindDTO windFix : windTrackInfo.windFixes.subList(windTrackInfo.windFixes.size() - 4, windTrackInfo.windFixes.size() - 1)) { + windFixPanel.add(new Label("" + formatter.format(windFix.trueWindFromDeg) + " (deg) " + formatter.format(windFix.trueWindSpeedInKnots) + " (kt) " + formatter.format(windFix.position.latDeg) + " (lat) " + formatter.format(windFix.position.lngDeg) + " (lng) " + new Date(windFix.measureTimepoint))); } } else { windFixPanel.add(new Label(stringMessages.noWindFixesAvailable())); @@ -417,7 +553,7 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower } }); } - + private void updateWindDisplay() { RegattaAndRaceIdentifier selectedRace = getSelectedRace(); RaceDTO raceDTO = selectedRace != null ? trackedRacesListComposite.getRaceByIdentifier(selectedRace) : null; @@ -435,7 +571,7 @@ public class WindPanel extends FormPanel implements RegattaDisplayer, WindShower clearWindSources(); clearWindFixes(); } - + } @Override diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java index 0a81277140c..2beea02e3d4 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.java @@ -750,4 +750,20 @@ public interface StringMessages extends Messages { String speedOverGroundFiveSecondsBeforeStart(); String distanceToLineFiveSecondsBeforeStartTooltip(); String speedOverGroundFiveSecondsBeforeStartTooltip(); + String windImport_Upload(); + String windImport_Title(); + String windImport_BoatId(); + String windImport_SelectedRacesWarning(int selectedRacesCount); + String windImport_AllRacesWarning(); + String windImport_ResultHeader(String firstWindDate, String lastWindDate, int affectedRacesCount); + String windImport_ResultError(String errorMessage); + String windImport_ResultEntry(String raceName, String regattaName, int windFixesCount, String firstWindDate, String lastWindDate); + String showWindSpeedSeriesTooltip(); + String showWindDirectionSeriesTooltip(); + String combinedWindSourceTypeTooltip(); + String courseBasedWindSourceTypeTooltip(); + String trackBasedEstimationWindSourceTypeTooltip(); + String expeditionWindSourceTypeTooltip(); + String webWindSourceTypeTooltip(); + String raceCommitteeWindSourceTypeTooltip(); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties index 6e45da7a4a1..35fbac57ad5 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages.properties @@ -740,7 +740,23 @@ importOverrideSwitchLabel=Override existing data if names and ids match importSelectedLeaderboardGroups=Import selected leaderboard groups importSelectAtLeastOne=You have to select at least one leaderboard group to import deselectAll=Deselect all -polarDiagramButton=Polar Diagram +polarDiagramButton=Polar Diagram timeSinceLastPositionFix=Last GPS timeSinceLastPositionFixTooltip=Time since last received GPS position trackingQuality=Tracking quality +windImport_Upload=Upload +windImport_Title=Import Wind from Expedition +windImport_BoatId=Boat Id: +windImport_SelectedRacesWarning=Do you really want to import wind into the selected {0} races?\nOnly races with applicable time window will be affected anyway. +windImport_AllRacesWarning=Do you really want to import wind into all races?\nOnly races with applicable time window will be affected anyway. +windImport_ResultHeader=Imported wind from: {0} to: {1}. {2} races affected. +windImport_ResultEntry=Race: {0} ({1}) - {2} fixes from: {3} to: {4} +windImport_ResultError=Error: {0} +showWindSpeedSeriesTooltip=Toggles the visibility of the wind speed for all data sources +showWindDirectionSeriesTooltip=Toggles the visibility of the wind direction for all data sources +combinedWindSourceTypeTooltip=Combines all other wind sources to one data set +courseBasedWindSourceTypeTooltip=A value based on the course layout (under the assumption, that the race starts with an upwind leg) +trackBasedEstimationWindSourceTypeTooltip=Calculated data from the competitor tracks +expeditionWindSourceTypeTooltip=Data captured from mobile measuring systems +webWindSourceTypeTooltip=Manually set data values +raceCommitteeWindSourceTypeTooltip=Data manually set from the race officers \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties index 89e96dfd4f1..e5ca48705fb 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_de.properties @@ -740,3 +740,19 @@ polarDiagramButton=Polardiagramm timeSinceLastPositionFix=Letztes GPS timeSinceLastPositionFixTooltip=Zeit seit der letzten empfangenen GPS-Position trackingQuality=Tracking-Qualität +windImport_Upload=Abschicken +windImport_Title=Expedition Wind Import +windImport_BoatId=Boot Id: +windImport_SelectedRacesWarning=Möchten Sie den Wind für die {0} ausgewählten Rennen importieren?\nEs sind nur solche Rennen betroffen, deren Zeitraum mit der Windmessung überlappt. +windImport_AllRacesWarning=Möchten Sie den Wind für sämtliche Rennen importieren?\nEs sind nur solche Rennen betroffen, deren Zeitraum mit der Windmessung überlappt. +windImport_ResultHeader=Wind wurde importiert von: {0} bis: {1}. {2} Rennen betroffen. +windImport_ResultEntry=Rennen: {0} ({1}) - {2} Messwerte von: {3} bis: {4} +windImport_ResultError=Fehler: {0} +showWindSpeedSeriesTooltip=Setzt die Sichtbarkeit der Windgeschwindigkeit aller Datenquellen +showWindDirectionSeriesTooltip=Setzt die Sichtbarkeit der Windrichtung aller Datenquellen +combinedWindSourceTypeTooltip=Kombiniert alle Datenquellen zu einem Datensatz +courseBasedWindSourceTypeTooltip=Ein Datenwert basierend auf dem Kurslayout (unter der Annahme, dass das Rennen gegen den Wind startet) +trackBasedEstimationWindSourceTypeTooltip=Berechnet die Daten aus dem gefahrenen Kurs der Teilnehmer +expeditionWindSourceTypeTooltip=Von mobilen Messanlagen erfasste Daten +webWindSourceTypeTooltip=Manuell gesetzte Datenwerte +raceCommitteeWindSourceTypeTooltip=Von den Wettfahrtleitern manuell gesetzte Daten \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/WindSourceTypeFormatter.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/WindSourceTypeFormatter.java index ae47d16e59b..b04b73d98aa 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/WindSourceTypeFormatter.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/WindSourceTypeFormatter.java @@ -22,6 +22,24 @@ public class WindSourceTypeFormatter { return null; } + public static String tooltipFor(WindSourceType windSourceType, StringMessages stringMessages) { + switch (windSourceType) { + case COMBINED: + return stringMessages.combinedWindSourceTypeTooltip(); + case COURSE_BASED: + return stringMessages.courseBasedWindSourceTypeTooltip(); + case TRACK_BASED_ESTIMATION: + return stringMessages.trackBasedEstimationWindSourceTypeTooltip(); + case EXPEDITION: + return stringMessages.expeditionWindSourceTypeTooltip(); + case WEB: + return stringMessages.webWindSourceTypeTooltip(); + case RACECOMMITTEE: + return stringMessages.raceCommitteeWindSourceTypeTooltip(); + } + return null; + } + public static String format(WindSource windSource, StringMessages stringMessages) { return format(windSource.getType(), stringMessages)+(windSource.getId() == null ? "" : (" "+windSource.getId().toString())); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChartSettingsDialogComponent.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChartSettingsDialogComponent.java index 2007b074f62..51b144494b8 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChartSettingsDialogComponent.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/WindChartSettingsDialogComponent.java @@ -65,6 +65,7 @@ public class WindChartSettingsDialogComponent implements SettingsDialogComponent grid.setWidget(0, 1, windSpeedSourcesPanel); showWindSpeedSeriesCheckbox = dialog.createCheckbox(stringMessages.showWindSpeedSeries()); + showWindSpeedSeriesCheckbox.setTitle(stringMessages.showWindSpeedSeriesTooltip()); showWindSpeedSeriesCheckbox.setValue(initialSettings.isShowWindSpeedSeries()); showWindSpeedSeriesCheckbox.addValueChangeHandler(new ValueChangeHandler() { @Override @@ -78,6 +79,7 @@ public class WindChartSettingsDialogComponent implements SettingsDialogComponent windSpeedSourcesPanel.add(showWindSpeedSeriesCheckbox); showWindDirectionsSeriesCheckbox = dialog.createCheckbox(stringMessages.showWindDirectionSeries()); + showWindDirectionsSeriesCheckbox.setTitle(stringMessages.showWindDirectionSeriesTooltip()); showWindDirectionsSeriesCheckbox.setValue(initialSettings.isShowWindDirectionsSeries()); showWindDirectionsSeriesCheckbox.addValueChangeHandler(new ValueChangeHandler() { @Override @@ -92,6 +94,7 @@ public class WindChartSettingsDialogComponent implements SettingsDialogComponent for (WindSourceType windSourceType : WindSourceType.values()) { CheckBox checkbox = dialog.createCheckbox(WindSourceTypeFormatter.format(windSourceType, stringMessages)); + checkbox.setTitle(WindSourceTypeFormatter.tooltipFor(windSourceType, stringMessages)); checkbox.setEnabled(initialSettings.isShowWindDirectionsSeries()); windDirectionsCheckboxes.put(windSourceType, checkbox); checkbox.setValue(initialSettings.getWindDirectionSourcesToDisplay().contains(windSourceType)); @@ -101,6 +104,7 @@ public class WindChartSettingsDialogComponent implements SettingsDialogComponent for (WindSourceType windSourceType : WindSourceType.values()) { CheckBox checkbox = dialog.createCheckbox(WindSourceTypeFormatter.format(windSourceType, stringMessages)); + checkbox.setTitle(WindSourceTypeFormatter.tooltipFor(windSourceType, stringMessages)); checkbox.setEnabled(initialSettings.isShowWindSpeedSeries() && windSourceType.useSpeed()); windSpeedCheckboxes.put(windSourceType, checkbox); checkbox.setValue(initialSettings.getWindSpeedSourcesToDisplay().contains(windSourceType)); diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/expeditionimport/CompleteRow.csv b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/expeditionimport/CompleteRow.csv new file mode 100644 index 00000000000..3b0a982b7a8 --- /dev/null +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/expeditionimport/CompleteRow.csv @@ -0,0 +1,4 @@ +Utc,Bsp,Awa,Aws,Twa,Tws,Twd,Rudder2,Leeway,Set,Drift,Hdg,AirTmp,SeaTmp,Baro,Depth,Heel,Trim,Rudder,Tab,Forestay,Downhaul,MastAng,FrstyLen,MastButt,LoadStbd,LoadPort,Rake,Volts,ROT,GpsQual,GpsPDOP,GpsNum,GpsAge,GpsAlt,GpsGeoSep,GpsMode,Lat,Lon,Cog,Sog,DiffStn,Error,StbRunner,PrtRunner,Vang,Traveller,MainSheet,KeelAng,KeelHt,CanardH,OilPres,RPM1,RPM2,Board,Board2,DistToLn,RchTmToLn,RchDtToLn,GPS Time,Downhaul2,MkLat,MkLon,PortLat,PortLon,StbdLat,StbdLon,GPS HPE,RH,LeadPt,LeadSb,BkStay,User 0,User 1,User 2,User 3,User 4,User 5,User 6,User 7,User 8,User 9,User 10,User 11,User 12,User 13,User 14,User 15,User 16,User 17,User 18,User 19,User 20,User 21,User 22,User 23,User 24,User 25,User 26,User 27,User 28,User 29,User 30,User 31,TmToGun,TmToLn,TmToBurn,BelowLn,GunBlwLn,WvSigHt,WvSigPd,WvMaxHt,WvMaxPd,Slam,Motion,Mwa,Mws,Boom Pos +41451.30756,,104.1,11.2,,,,,,,,,,,,,,,,,,,,,,,,,,11.7,,,,,,,,,,315.2,0.1,,,,,,,,,,,,,,,,,,,,,,,,,,,9.54 +41451.30757,,,,,11.2,257.2,,,,,,,,,,,,,,,,,,,,,,,,2,,9,,-0.7,,,54.430272,10.172062,,,,,,,,,,,,,,,,,,,,,41451.307558,,,,,,,,9.54 +41451.30758,,104.1,11.2,,,,,,,,,,,,,,,,,,,,,,,,,,11.7,,,,,,,,,,315.2,0.1,,,,,,,,,,,,,,,,,,,,,,,,,,,9.54 diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/expeditionimport/PositionFirst.csv b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/expeditionimport/PositionFirst.csv new file mode 100644 index 00000000000..1bd95039d2f --- /dev/null +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/expeditionimport/PositionFirst.csv @@ -0,0 +1,4 @@ +Utc,Bsp,Awa,Aws,Twa,Tws,Twd,Rudder2,Leeway,Set,Drift,Hdg,AirTmp,SeaTmp,Baro,Depth,Heel,Trim,Rudder,Tab,Forestay,Downhaul,MastAng,FrstyLen,MastButt,LoadStbd,LoadPort,Rake,Volts,ROT,GpsQual,GpsPDOP,GpsNum,GpsAge,GpsAlt,GpsGeoSep,GpsMode,Lat,Lon,Cog,Sog,DiffStn,Error,StbRunner,PrtRunner,Vang,Traveller,MainSheet,KeelAng,KeelHt,CanardH,OilPres,RPM1,RPM2,Board,Board2,DistToLn,RchTmToLn,RchDtToLn,GPS Time,Downhaul2,MkLat,MkLon,PortLat,PortLon,StbdLat,StbdLon,GPS HPE,RH,LeadPt,LeadSb,BkStay,User 0,User 1,User 2,User 3,User 4,User 5,User 6,User 7,User 8,User 9,User 10,User 11,User 12,User 13,User 14,User 15,User 16,User 17,User 18,User 19,User 20,User 21,User 22,User 23,User 24,User 25,User 26,User 27,User 28,User 29,User 30,User 31,TmToGun,TmToLn,TmToBurn,BelowLn,GunBlwLn,WvSigHt,WvSigPd,WvMaxHt,WvMaxPd,Slam,Motion,Mwa,Mws,Boom Pos +41451.30755,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,9,,-0.7,,,54.430272,10.172062,,,,,,,,,,,,,,,,,,,,,41451.307558,,,,,,,,9.54 +41451.30756,,104.1,11.2,,,,,,,,,,,,,,,,,,,,,,,,,,11.7,,,,,,,,,,315.2,0.1,,,,,,,,,,,,,,,,,,,,,,,,,,,9.54 +41451.30757,,,,,11.2,257.2,,,,,,12.6,,1017.4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9.54 diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/expeditionimport/TestWindLogParser.java b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/expeditionimport/TestWindLogParser.java new file mode 100644 index 00000000000..a0631593221 --- /dev/null +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/expeditionimport/TestWindLogParser.java @@ -0,0 +1,56 @@ +package com.sap.sailing.server.gateway.expeditionimport; + +import java.util.List; + +import org.junit.Test; + +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.server.gateway.expeditionimport.WindLogParser; + +import static org.junit.Assert.*; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.number.IsCloseTo.*; + +public class TestWindLogParser { + + private static final double DOUBLE_TOLERANCE = 1e-7; + + @Test + public void testCompleteRow() throws Exception { + List windImport = WindLogParser.importWind(getClass().getResourceAsStream("CompleteRow.csv")); + assertThat(windImport.size(), is(1)); + Wind wind = windImport.get(0); + assertWind(wind, 11.2, 257.2, 54.430272, 10.172062); + } + + @Test + public void testWindFirst() throws Exception { + List windImport = WindLogParser.importWind(getClass().getResourceAsStream("WindFirst.csv")); + assertThat(windImport.size(), is(1)); + Wind wind = windImport.get(0); + assertWind(wind, 11.2, 257.2, 54.430272, 10.172062); + } + + @Test + public void testPositionFirst() throws Exception { + List windImport = WindLogParser.importWind(getClass().getResourceAsStream("PositionFirst.csv")); + assertThat(windImport.size(), is(1)); + Wind wind = windImport.get(0); + assertWind(wind, 11.2, 257.2, 54.430272, 10.172062); + } + + private void assertWind(Wind wind, double knots, double degrees, double lat, double lon) { + assertThat(wind.getKnots(), closeTo(knots, DOUBLE_TOLERANCE)); + assertThat(wind.getFrom().getDegrees(), closeTo(degrees, DOUBLE_TOLERANCE)); + assertThat(wind.getPosition().getLatDeg(), closeTo(lat, DOUBLE_TOLERANCE)); + assertThat(wind.getPosition().getLngDeg(), closeTo(lon, DOUBLE_TOLERANCE)); + } + +// @Test +// public void test2013Jun26_0() throws Exception { +// List windImport = WindLogParser.importWind(getClass().getResourceAsStream("2013Jun26_0.csv")); +// assertThat(windImport.size(), is(85586)); +// } + +} diff --git a/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/expeditionimport/WindFirst.csv b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/expeditionimport/WindFirst.csv new file mode 100644 index 00000000000..40bd4ea120d --- /dev/null +++ b/java/com.sap.sailing.server.gateway.test/src/com/sap/sailing/server/gateway/expeditionimport/WindFirst.csv @@ -0,0 +1,4 @@ +Utc,Bsp,Awa,Aws,Twa,Tws,Twd,Rudder2,Leeway,Set,Drift,Hdg,AirTmp,SeaTmp,Baro,Depth,Heel,Trim,Rudder,Tab,Forestay,Downhaul,MastAng,FrstyLen,MastButt,LoadStbd,LoadPort,Rake,Volts,ROT,GpsQual,GpsPDOP,GpsNum,GpsAge,GpsAlt,GpsGeoSep,GpsMode,Lat,Lon,Cog,Sog,DiffStn,Error,StbRunner,PrtRunner,Vang,Traveller,MainSheet,KeelAng,KeelHt,CanardH,OilPres,RPM1,RPM2,Board,Board2,DistToLn,RchTmToLn,RchDtToLn,GPS Time,Downhaul2,MkLat,MkLon,PortLat,PortLon,StbdLat,StbdLon,GPS HPE,RH,LeadPt,LeadSb,BkStay,User 0,User 1,User 2,User 3,User 4,User 5,User 6,User 7,User 8,User 9,User 10,User 11,User 12,User 13,User 14,User 15,User 16,User 17,User 18,User 19,User 20,User 21,User 22,User 23,User 24,User 25,User 26,User 27,User 28,User 29,User 30,User 31,TmToGun,TmToLn,TmToBurn,BelowLn,GunBlwLn,WvSigHt,WvSigPd,WvMaxHt,WvMaxPd,Slam,Motion,Mwa,Mws,Boom Pos +41451.30755,,,,,11.2,257.2,,,,,,12.6,,1017.4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9.54 +41451.30756,,104.1,11.2,,,,,,,,,,,,,,,,,,,,,,,,,,11.7,,,,,,,,,,315.2,0.1,,,,,,,,,,,,,,,,,,,,,,,,,,,9.54 +41451.30757,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,9,,-0.7,,,54.430272,10.172062,,,,,,,,,,,,,,,,,,,,,41451.307558,,,,,,,,9.54 diff --git a/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF b/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF index fa56a3620d8..a9d5728dc78 100644 --- a/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.server.gateway/META-INF/MANIFEST.MF @@ -36,7 +36,8 @@ Require-Bundle: com.sap.sailing.domain, org.eclipse.jetty.websocket.common;bundle-version="9.0.4", javax.validation;bundle-version="1.0.0", com.sun.jersey;bundle-version="1.17.0", - org.objectweb.asm;bundle-version="3.3.1" + org.objectweb.asm;bundle-version="3.3.1", + org.apache.commons.fileupload;bundle-version="1.2.2" Bundle-ClassPath: . Web-ContextPath: /sailingserver Export-Package: com.sap.sailing.server.gateway diff --git a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml index b10e84c5c1d..d95e2fe59c3 100644 --- a/java/com.sap.sailing.server.gateway/WEB-INF/web.xml +++ b/java/com.sap.sailing.server.gateway/WEB-INF/web.xml @@ -1,227 +1,236 @@ - - - - - AdminApp - com.sap.sailing.server.gateway.impl.AdminApp - - - AdminApp - /admin - - - - RaceGroups - com.sap.sailing.server.gateway.impl.rc.RaceGroupJsonExportServlet - - - RaceGroups - /rc/racegroups - - - - RCAddToRaceLog - com.sap.sailing.server.gateway.impl.rc.AddEntryToRaceLogJsonPostServlet - - - RCAddToRaceLog - /rc/racelog - - - - RCMarks - com.sap.sailing.server.gateway.impl.rc.MarksJsonExportServlet - - - RCMarks - /rc/marks - - - - RCMarkPositions - com.sap.sailing.server.gateway.impl.rc.MarkPositionsJsonExportServlet - - - RCMarkPositions - /rc/markpositions - - - - RCCurrentCourse - com.sap.sailing.server.gateway.impl.rc.CourseJsonExportServlet - - - RCCurrentCourse - /rc/currentcourse - - - - RCCompetitors - com.sap.sailing.server.gateway.impl.rc.CompetitorsJsonExportServlet - - - RCCompetitors - /rc/competitors - - - - - - RegattaAndRaceDataJsonGetServlet - com.sap.sailing.server.gateway.impl.RegattaAndRaceDataJsonGetServlet - - - RegattaAndRaceDataJsonGetServlet - /moderator - - - - ExpeditionThroughHttpPostServlet - com.sap.sailing.server.gateway.impl.ExpeditionThroughHttpPostServlet - - - ExpeditionThroughHttpPostServlet - /expedition - - - - EventsJsonGetServlet - com.sap.sailing.server.gateway.impl.EventsJsonGetServlet - - - EventsJsonGetServlet - /events - - - - EventRaceStatesServlet - com.sap.sailing.server.gateway.impl.EventRaceStatesJsonGetServlet - - - EventRaceStatesServlet - /eventRaceStates - - - - RegattasJsonGetServlet - com.sap.sailing.server.gateway.impl.RegattasJsonGetServlet - - - RegattasJsonGetServlet - /regattas - - - RegattaJsonGetServlet - com.sap.sailing.server.gateway.impl.RegattaJsonGetServlet - - - RegattaJsonGetServlet - /regatta - - - - LeaderboardsJsonGetServlet - com.sap.sailing.server.gateway.impl.LeaderboardsJsonGetServlet - - - LeaderboardsJsonGetServlet - /leaderboards - - - - LeaderboardJsonGetServlet - com.sap.sailing.server.gateway.impl.LeaderboardJsonGetServlet - - - LeaderboardJsonGetServlet - /leaderboard - - - - LeaderboardGroupJsonGetServlet - com.sap.sailing.server.gateway.impl.LeaderboardGroupJsonGetServlet - - - LeaderboardGroupJsonGetServlet - /leaderboardgroup - - - - LeaderboardGroupsJsonGetServlet - com.sap.sailing.server.gateway.impl.LeaderboardGroupsJsonGetServlet - - - LeaderboardGroupsJsonGetServlet - /leaderboardgroups - - - - WindJsonGetServlet - com.sap.sailing.server.gateway.impl.WindJsonGetServlet - - - WindJsonGetServlet - /wind - - - - ExpeditionWindMeasureStatusGetServlet - com.sap.sailing.server.gateway.impl.ExpeditionWindMeasureStatusGetServlet - - - ExpeditionWindMeasureStatusGetServlet - /expeditionWindStatus - - - - TrackFilesExportPostServlet - com.sap.sailing.server.gateway.impl.TrackFilesExportPostServlet - - - TrackFilesExportPostServlet - /trackfiles - - - - websockettest - com.sap.sailing.server.gateway.impl.WebSocketTestServlet - - - - websockettest - /websockettest - - - - Jersey REST API - com.sap.sailing.server.gateway.jaxrs.RestServletContainer - - javax.ws.rs.Application - com.sap.sailing.server.gateway.jaxrs.api.RestApiApplication - - 1 - - - Jersey REST API - /api/* - - - - Jersey REST SPI - com.sap.sailing.server.gateway.jaxrs.RestServletContainer - - javax.ws.rs.Application - com.sap.sailing.server.gateway.jaxrs.spi.RestSpiApplication - - 1 - - - Jersey REST SPI - /spi/* - - - + + + + + AdminApp + com.sap.sailing.server.gateway.impl.AdminApp + + + AdminApp + /admin + + + + RaceGroups + com.sap.sailing.server.gateway.impl.rc.RaceGroupJsonExportServlet + + + RaceGroups + /rc/racegroups + + + + RCAddToRaceLog + com.sap.sailing.server.gateway.impl.rc.AddEntryToRaceLogJsonPostServlet + + + RCAddToRaceLog + /rc/racelog + + + + RCMarks + com.sap.sailing.server.gateway.impl.rc.MarksJsonExportServlet + + + RCMarks + /rc/marks + + + + RCMarkPositions + com.sap.sailing.server.gateway.impl.rc.MarkPositionsJsonExportServlet + + + RCMarkPositions + /rc/markpositions + + + + RCCurrentCourse + com.sap.sailing.server.gateway.impl.rc.CourseJsonExportServlet + + + RCCurrentCourse + /rc/currentcourse + + + + RCCompetitors + com.sap.sailing.server.gateway.impl.rc.CompetitorsJsonExportServlet + + + RCCompetitors + /rc/competitors + + + + + + RegattaAndRaceDataJsonGetServlet + com.sap.sailing.server.gateway.impl.RegattaAndRaceDataJsonGetServlet + + + RegattaAndRaceDataJsonGetServlet + /moderator + + + + ExpeditionThroughHttpPostServlet + com.sap.sailing.server.gateway.impl.ExpeditionThroughHttpPostServlet + + + ExpeditionThroughHttpPostServlet + /expedition + + + + EventsJsonGetServlet + com.sap.sailing.server.gateway.impl.EventsJsonGetServlet + + + EventsJsonGetServlet + /events + + + + EventRaceStatesServlet + com.sap.sailing.server.gateway.impl.EventRaceStatesJsonGetServlet + + + EventRaceStatesServlet + /eventRaceStates + + + + RegattasJsonGetServlet + com.sap.sailing.server.gateway.impl.RegattasJsonGetServlet + + + RegattasJsonGetServlet + /regattas + + + RegattaJsonGetServlet + com.sap.sailing.server.gateway.impl.RegattaJsonGetServlet + + + RegattaJsonGetServlet + /regatta + + + + LeaderboardsJsonGetServlet + com.sap.sailing.server.gateway.impl.LeaderboardsJsonGetServlet + + + LeaderboardsJsonGetServlet + /leaderboards + + + + LeaderboardJsonGetServlet + com.sap.sailing.server.gateway.impl.LeaderboardJsonGetServlet + + + LeaderboardJsonGetServlet + /leaderboard + + + + LeaderboardGroupJsonGetServlet + com.sap.sailing.server.gateway.impl.LeaderboardGroupJsonGetServlet + + + LeaderboardGroupJsonGetServlet + /leaderboardgroup + + + + LeaderboardGroupsJsonGetServlet + com.sap.sailing.server.gateway.impl.LeaderboardGroupsJsonGetServlet + + + LeaderboardGroupsJsonGetServlet + /leaderboardgroups + + + + WindJsonGetServlet + com.sap.sailing.server.gateway.impl.WindJsonGetServlet + + + WindJsonGetServlet + /wind + + + + WindImportServlet + com.sap.sailing.server.gateway.expeditionimport.WindImportServlet + + + WindImportServlet + /expedition-import + + + + ExpeditionWindMeasureStatusGetServlet + com.sap.sailing.server.gateway.impl.ExpeditionWindMeasureStatusGetServlet + + + ExpeditionWindMeasureStatusGetServlet + /expeditionWindStatus + + + + TrackFilesExportPostServlet + com.sap.sailing.server.gateway.impl.TrackFilesExportPostServlet + + + TrackFilesExportPostServlet + /trackfiles + + + + websockettest + com.sap.sailing.server.gateway.impl.WebSocketTestServlet + + + + websockettest + /websockettest + + + + Jersey REST API + com.sap.sailing.server.gateway.jaxrs.RestServletContainer + + javax.ws.rs.Application + com.sap.sailing.server.gateway.jaxrs.api.RestApiApplication + + 1 + + + Jersey REST API + /api/* + + + + Jersey REST SPI + com.sap.sailing.server.gateway.jaxrs.RestServletContainer + + javax.ws.rs.Application + com.sap.sailing.server.gateway.jaxrs.spi.RestSpiApplication + + 1 + + + Jersey REST SPI + /spi/* + + + diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/expeditionimport/WindImportServlet.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/expeditionimport/WindImportServlet.java new file mode 100644 index 00000000000..45e5a7b8c34 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/expeditionimport/WindImportServlet.java @@ -0,0 +1,238 @@ +package com.sap.sailing.server.gateway.expeditionimport; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileItemFactory; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.RegattaNameAndRaceName; +import com.sap.sailing.domain.common.WindSource; +import com.sap.sailing.domain.common.WindSourceType; +import com.sap.sailing.domain.common.impl.WindSourceImpl; +import com.sap.sailing.domain.common.impl.WindSourceWithAdditionalID; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.server.gateway.SailingServerHttpServlet; +import com.sap.sailing.server.gateway.expeditionimport.WindImportServlet.WindImportResult.RaceEntry; + +public class WindImportServlet extends SailingServerHttpServlet { + + private static final long serialVersionUID = 1L; + + static class UploadRequest { + public String boatId; + public final List files = new ArrayList(); + public List races = new ArrayList();; + } + + static class WindImportResult { + + private Date first; + private Date last; + public String error; + + public final List raceEntries = new ArrayList(); + + public Date getFirst() { + return first; + } + + public Date getLast() { + return last; + } + + public void update(Wind newWind) { + Date newDate = newWind.getTimePoint().asDate(); + if (this.first == null || newDate.before(this.first)) { + this.first = newDate; + } + if (this.last == null || newDate.after(this.last)) { + this.last = newDate; + } + } + + RaceEntry addRaceEntry(String regattaName, String raceName) { + RaceEntry raceEntry = new RaceEntry(regattaName, raceName); + raceEntries.add(raceEntry); + return raceEntry; + } + + static class RaceEntry { + + public final String regattaName; + public final String raceName; + private int count; + private Date first; + private Date last; + + private RaceEntry(String regattaName, String raceName) { + this.regattaName = regattaName; + this.raceName = raceName; + } + + public void update(Wind newWind) { + count++; + Date newDate = newWind.getTimePoint().asDate(); + if (this.first == null || newDate.before(this.first)) { + this.first = newDate; + } + if (this.last == null || newDate.after(this.last)) { + this.last = newDate; + } + } + + public int getCount() { + return count; + } + + public Date getFirst() { + return first; + } + + public Date getLast() { + return last; + } + + private JSONObject json() { + JSONObject result = new JSONObject(); + result.put("regattaName", regattaName); + result.put("raceName", raceName); + result.put("count", getCount()); + if (getFirst() != null) { + result.put("first", getFirst().getTime()); + } + if (getLast() != null) { + result.put("last", getLast().getTime()); + } + return result; + } + } + + public JSONObject json() { + JSONObject result = new JSONObject(); + + if (getFirst() != null) { + result.put("first", getFirst().getTime()); + } + if (getLast() != null) { + result.put("last", getLast().getTime()); + } + result.put("error", error); + + JSONArray raceEntriesJson = new JSONArray(); + for (RaceEntry raceEntry : raceEntries) { + if (raceEntry.count > 0) { + raceEntriesJson.add(raceEntry.json()); + } + } + result.put("raceEntries", raceEntriesJson); + + + return result; + + } + + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if (!ServletFileUpload.isMultipartContent(request)) { + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + + WindImportResult windImportResult = new WindImportResult(); + + try { + + UploadRequest uploadRequest = readRequest(request); + WindSource windSource; + if (uploadRequest.boatId == null) { + windSource = new WindSourceImpl(WindSourceType.EXPEDITION); + } else { + windSource = new WindSourceWithAdditionalID(WindSourceType.EXPEDITION, uploadRequest.boatId); + } + + List trackedRaces = new ArrayList(); + if (uploadRequest.races.size() > 0) { + for (RegattaAndRaceIdentifier raceEntry : uploadRequest.races) { + DynamicTrackedRace trackedRace = getService().getTrackedRace(raceEntry); + if (trackedRace != null) { + trackedRaces.add(trackedRace); + } + } + } else { + for (Regatta regatta : getService().getAllRegattas()) { + for (RaceDefinition raceDefinition : regatta.getAllRaces()) { + trackedRaces.add(getService().getTrackedRegatta(regatta).getTrackedRace(raceDefinition)); + } + } + } + + for (FileItem file : uploadRequest.files) { + List windFixes = WindLogParser.importWind(file.getInputStream()); + if (windFixes.size() > 0) { + windImportResult.update(windFixes.get(0)); + windImportResult.update(windFixes.get(windFixes.size() - 1)); + for (DynamicTrackedRace trackedRace : trackedRaces) { + RegattaAndRaceIdentifier raceIdentifier = trackedRace.getRaceIdentifier(); + RaceEntry raceEntry = windImportResult.addRaceEntry(raceIdentifier.getRegattaName(), raceIdentifier.getRaceName()); + for (Wind wind : windFixes) { + if (trackedRace.recordWind(wind, windSource)) { + raceEntry.update(wind); + } + } + } + } + } + + // Use text/html to prevent browsers from wrapping the response body, + // see "Handling File Upload Responses in GWT" at http://www.artofsolving.com/node/50 + } catch (Exception e) { + windImportResult.error = e.toString(); + } + response.setContentType("text/html;charset=UTF-8"); + response.getWriter().append(windImportResult.json().toJSONString()); + } + + private UploadRequest readRequest(HttpServletRequest req) throws FileUploadException, ParseException { + UploadRequest result = new UploadRequest(); + // http://commons.apache.org/fileupload/using.html + FileItemFactory factory = new DiskFileItemFactory(); + ServletFileUpload upload = new ServletFileUpload(factory); + @SuppressWarnings("unchecked") + List items = upload.parseRequest(req); + for (FileItem item : items) { + if (item.isFormField() && (item.getString() != null) && (item.getString().trim().length() > 0)) { + if ("boatId".equals(item.getFieldName())) { + result.boatId = item.getString().trim(); + } else if ("races".equals(item.getFieldName())) { + JSONArray races = (JSONArray) new JSONParser().parse(item.getString().trim()); + for (Object raceEntry : races) { + result.races.add(new RegattaNameAndRaceName((String) ((JSONObject) raceEntry).get("regatta"), (String) ((JSONObject ) raceEntry).get("race"))); + } + } + } else if (item.getSize() > 0) { + result.files.add(item); + } + } + return result; + } +} diff --git a/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/expeditionimport/WindLogParser.java b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/expeditionimport/WindLogParser.java new file mode 100644 index 00000000000..6900f3128b1 --- /dev/null +++ b/java/com.sap.sailing.server.gateway/src/com/sap/sailing/server/gateway/expeditionimport/WindLogParser.java @@ -0,0 +1,161 @@ +package com.sap.sailing.server.gateway.expeditionimport; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.List; +import java.util.TimeZone; +import java.util.regex.Pattern; + +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.SpeedWithBearing; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.common.impl.KnotSpeedWithBearingImpl; +import com.sap.sailing.domain.common.impl.MillisecondsTimePoint; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.impl.WindImpl; + +public class WindLogParser { + + /** + * Collects partial wind data until everything is there to create a Wind instance. + * @author D047974 + * + */ + private static class WindBuffer { + + /** + * The 1900 Date System http://support.microsoft.com/kb/180162/en-us + */ + private static final Calendar EXCEL_EPOCH_START = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + static { + EXCEL_EPOCH_START.set(1899, Calendar.DECEMBER, 30, 0, 0, 0); + } + private static final int MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000; + + private TimePoint timePoint; + private Position position; + private SpeedWithBearing trueWindSpeedWithBearing; + + public void updateTime(String time_ExcelEpoch) { + double excelTimeStamp = Double.parseDouble(time_ExcelEpoch); + long millisecondsSinceExcelEpochStart = (long) (MILLISECONDS_PER_DAY * excelTimeStamp); + this.timePoint = new MillisecondsTimePoint(EXCEL_EPOCH_START.getTimeInMillis() + millisecondsSinceExcelEpochStart); + + } + + public void updateWindData(String trueWindSpeedData, String trueWindDirectionData) { + if (!trueWindSpeedData.trim().isEmpty() && !trueWindDirectionData.trim().isEmpty()) { + + double trueWindSpeed = Double.parseDouble(trueWindSpeedData); + double trueWindDirection = Double.parseDouble(trueWindDirectionData); + Bearing trueWindBearing = new DegreeBearingImpl(trueWindDirection + 180); + this.trueWindSpeedWithBearing = new KnotSpeedWithBearingImpl(trueWindSpeed, trueWindBearing); + } + } + + public void updatePosition(String latData, String lonData) { + if (!latData.trim().isEmpty() && !lonData.trim().isEmpty()) { + double lat = Double.parseDouble(latData); + double lon = Double.parseDouble(lonData); + + this.position = new DegreePosition(lat, lon); + } + } + + public Wind createWindIfReady() { + if ((timePoint != null) && (position != null) && (trueWindSpeedWithBearing != null)) { + WindImpl result = new WindImpl(position, timePoint, trueWindSpeedWithBearing); + reset(); + return result; + } else { + return null; + } + } + + private void reset() { + timePoint = null; + position = null; + trueWindSpeedWithBearing = null; + } + + } + + private static final int CSV_INDEX_TIME_STAMP = 0; + private static final int CSV_INDEX_TRUE_WIND_SPEED = 5; + private static final int CSV_INDEX_TRUE_WIND_DIRECTION = 6; + private static final int CSV_INDEX_LAT = 37; + private static final int CSV_INDEX_LONG = 38; + + private static final int CSV_MAX_INDEX = Collections.max(Arrays.asList(CSV_INDEX_TIME_STAMP, CSV_INDEX_TRUE_WIND_SPEED, CSV_INDEX_TRUE_WIND_DIRECTION, CSV_INDEX_LAT, CSV_INDEX_LONG)); + + private static final Pattern LINE_PATTERN = Pattern.compile(","); + + /** + * See ExpeditionMessage, UDPExpeditionReceiver + * @param windStream + * @return + * @throws IOException + */ + public static List importWind(InputStream windStream) throws IOException { + BufferedReader csvReader = new BufferedReader(new InputStreamReader(windStream)); + String headerLine = csvReader.readLine(); + assertHeaderFormat(headerLine); + + List result = new ArrayList(); + String dataLine; + WindBuffer windBuffer = new WindBuffer(); + while ((dataLine = csvReader.readLine()) != null) { + String[] data = parseDataLine(dataLine); + + if (data != null) { + windBuffer.updateTime(data[CSV_INDEX_TIME_STAMP]); + windBuffer.updateWindData(data[CSV_INDEX_TRUE_WIND_SPEED], data[CSV_INDEX_TRUE_WIND_DIRECTION]); + windBuffer.updatePosition(data[CSV_INDEX_LAT], data[CSV_INDEX_LONG]); + + Wind wind = windBuffer.createWindIfReady(); + if (wind != null) { + result.add(wind); + } + } + } + + return result; + } + + private static void assertHeaderFormat(String headerLine) { + if (headerLine == null) { + throw new NullPointerException("Empty header line."); + } + //supposed to be Utc,Bsp,Awa,Aws,Twa,Tws,Twd,Rudder2,Leeway,Set,Drift,Hdg,AirTmp,SeaTmp,Baro,Depth,Heel,Trim,Rudder,Tab,Forestay,Downhaul,MastAng,FrstyLen,MastButt,LoadStbd,LoadPort,Rake,Volts,ROT,GpsQual,GpsPDOP,GpsNum,GpsAge,GpsAltitude,GpsGeoSep,GpsMode,Lat,Lon,Cog,Sog,DiffStn,Error,StbRunner,PrtRunner,Vang,Traveller,MainSheet,KeelAng,KeelHt,CanardH,OilPres,RPM1,RPM2,Dagger Bd,Boom Pos,DistToLn,RchTmToLn,RchDtToLn,GPS Time,Downhaul2,MkLat,MkLon,PortLat,PortLon,StbdLat,StbdLon,GPS HPE,RH,LeadPt,LeadSb,BkStay,User 0,User 1,User 2,User 3,User 4,User 5,User 6,User 7,User 8,User 9,User 10,User 11,User 12,User 13,User 14,User 15,User 16,User 17,User 18,User 19,User 20,User 21,User 22,User 23,User 24,User 25,User 26,User 27,User 28,User 29,User 30,User 31,TmToGun,TmToLn,TmToBurn,BelowLn,GunBlwLn,WvSigHt,WvSigPd,WvMaxHt,WvMaxPd,Slam,Motion + String[] columns = LINE_PATTERN.split(headerLine); + if ("Utc".equals(columns[CSV_INDEX_TIME_STAMP]) && "Tws".equals(columns[CSV_INDEX_TRUE_WIND_SPEED]) && "Twd".equals(columns[CSV_INDEX_TRUE_WIND_DIRECTION]) && "Lat".equals(columns[CSV_INDEX_LAT]) && "Lon".equals(columns[CSV_INDEX_LONG])) { + return; + } else { + throw new RuntimeException("Unexpected csv header for Expedition wind import: " + headerLine); + } + } + + private static String[] parseDataLine(String dataLine) { + String[] data = LINE_PATTERN.split(dataLine); + if (data.length >= CSV_MAX_INDEX) { + // if (!data[CSV_INDEX_TIME_STAMP].trim().isEmpty() + // && !data[CSV_INDEX_TRUE_WIND_SPEED].trim().isEmpty() + // && !data[CSV_INDEX_TRUE_WIND_DIRECTION].trim().isEmpty() + // && !data[CSV_INDEX_LAT].trim().isEmpty() + // && !data[CSV_INDEX_LONG].trim().isEmpty()) { + return data; + // } + } + //else + return null; + } +} diff --git a/java/com.sap.sailing.server.windimport.test/src/com/sap/sailing/server/windimport/TestWindImport.java b/java/com.sap.sailing.server.windimport.test/src/com/sap/sailing/server/windimport/TestWindImport.java new file mode 100644 index 00000000000..60664af785b --- /dev/null +++ b/java/com.sap.sailing.server.windimport.test/src/com/sap/sailing/server/windimport/TestWindImport.java @@ -0,0 +1,30 @@ +package com.sap.sailing.server.windimport; + +import java.util.List; + +import org.junit.Test; + +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.server.windimport.WindImport.WindSequence; + +import static org.junit.Assert.*; +import static org.hamcrest.CoreMatchers.*; + +public class TestWindImport { + + @Test + public void testImport() throws Exception { + List windImport = WindImport.importWind(getClass().getResourceAsStream("Wind_2012Nov23_2.csv")); + assertThat(windImport.size(), is(11042)); + Wind wind_0 = windImport.get(0); + } + + @Test + public void test2013Jun26_0() throws Exception { + List windImport = WindImport.importWind(getClass().getResourceAsStream("2013Jun26_0.csv")); + int fiveMinutes_seconds = 5 * 60; + List windSequences = WindImport.splitWindSequences(windImport, fiveMinutes_seconds); + assertThat(windSequences.size(), is(12)); + } + +} diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java index 34fe49b59f2..d284e1d4ec1 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/impl/RacingEventServiceImpl.java @@ -1418,7 +1418,7 @@ public class RacingEventServiceImpl implements RacingEventService, RegattaListen return getOrCreateTrackedRegatta(regatta).getTrackedRace(race); } - private TrackedRace getExistingTrackedRace(Regatta regatta, RaceDefinition race) { + private DynamicTrackedRace getExistingTrackedRace(Regatta regatta, RaceDefinition race) { return getOrCreateTrackedRegatta(regatta).getExistingTrackedRace(race); }