mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-11-13 14:12:11 +00:00
implemented creation of session per start and setting start line
Change-Id: I614efd18f49bbd65c9db6dee6c55314a2310f8ba
This commit is contained in:
1 parent
1444c36e0b
commit
3a5b2fbe54
12 files changed
+312
-52
No files matched your search
+1
-1
@@ -5,7 +5,7 @@ public final class ExpeditionAllInOneConstants {
|
||||
public static final String REQUEST_PARAMETER_BOAT_CLASS = "boatClass";
|
||||
public static final String REQUEST_PARAMETER_REGATTA_NAME = "regattaName";
|
||||
public static final String REQUEST_PARAMETER_LOCALE = "locale";
|
||||
public static final String REQUEST_PARAMETER_IMPORT_START_LINE_PINGS = "importStartLinePings";
|
||||
public static final String REQUEST_PARAMETER_IMPORT_START_DATA = "importStartData";
|
||||
public static final String START_TIMES = "startTimes";
|
||||
public enum ImportMode{
|
||||
NEW_COMPETITOR, NEW_EVENT, NEW_RACE;
|
||||
|
||||
-1
@@ -36,7 +36,6 @@ public class ExpeditionGPSFixImporter implements GPSFixImporter {
|
||||
final AtomicBoolean importedFixes = new AtomicBoolean(false);
|
||||
CompressedStreamsUtil.handlePotentiallyCompressedFiles(sourceName, inputStream,
|
||||
new ExpeditionImportFileHandler() {
|
||||
|
||||
@Override
|
||||
protected void handleExpeditionFile(String fileName, InputStream stream) throws IOException {
|
||||
final BufferedReader br = new BufferedReader(new InputStreamReader(stream));
|
||||
|
||||
+1
@@ -66,6 +66,7 @@ public class ExpeditionAllInOneAfterImportHandler {
|
||||
this.raceEntries = raceEntries;
|
||||
this.regattaName = regattaName;
|
||||
this.startTimes = startTimes;
|
||||
// TODO from the start times, suggest the user to split the session into one session per start, with start tracking at n minutes before start
|
||||
sailingService.getEventById(eventId, false, new DataLoadingCallback<EventDTO>() {
|
||||
@Override
|
||||
public void onSuccess(EventDTO result) {
|
||||
|
||||
+4
-4
@@ -100,10 +100,10 @@ public class ExpeditionAllInOneImportPanel extends Composite implements Regattas
|
||||
regattaNamePanel.setVisible(true);
|
||||
boatClassPanel.setVisible(false);
|
||||
});
|
||||
final CheckBox importStartLinePings = new CheckBox(stringMessages.importStartLinePings());
|
||||
importStartLinePings.setValue(true);
|
||||
importStartLinePings.setName(ExpeditionAllInOneConstants.REQUEST_PARAMETER_IMPORT_START_LINE_PINGS);
|
||||
importModePanel.add(importStartLinePings);
|
||||
final CheckBox importStartData = new CheckBox(stringMessages.importStartData());
|
||||
importStartData.setValue(true);
|
||||
importStartData.setName(ExpeditionAllInOneConstants.REQUEST_PARAMETER_IMPORT_START_DATA);
|
||||
importModePanel.add(importStartData);
|
||||
regattaNamePanel.setSpacing(5);
|
||||
final Label regattaNameLabel = new Label(stringMessages.regattaName() + ":");
|
||||
regattaNamePanel.add(regattaNameLabel);
|
||||
|
||||
+1
-1
@@ -2164,5 +2164,5 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages,
|
||||
String filterToBoatsRegisteredOnRegatta();
|
||||
String unableToObtainCompetitorsAndBoatsForRaceInFleetInLeaderboard(String raceColumnName, String fleetName, String leaderboardName, String message);
|
||||
String unableToObtainBoatsRegisteredWith(String leaderboardName, String message);
|
||||
String importStartLinePings();
|
||||
String importStartData();
|
||||
}
|
||||
+1
-1
@@ -2153,4 +2153,4 @@ managePlaybackMedia=Manage Playback Media
|
||||
filterToBoatsRegisteredOnRegatta=Filter to boats registered on regatta
|
||||
unableToObtainCompetitorsAndBoatsForRaceInFleetInLeaderboard=Unable to obtain competitors and boats for race {0} in fleet {1} for leaderboard {2}: {3}
|
||||
unableToObtainBoatsRegisteredWith=Unable to obtain boats registered with {0}: {1}
|
||||
importStartLinePings=Import start line pings
|
||||
importStartData=Import start line pings and create race per start
|
||||
+1
-1
@@ -2149,4 +2149,4 @@ managePlaybackMedia=Abspielbare Medien verwalten
|
||||
filterToBoatsRegisteredOnRegatta=Nach Booten filtern, die bei der Regatta regstriert sind
|
||||
unableToObtainCompetitorsAndBoatsForRaceInFleetInLeaderboard=Teilnemer der Wettfahrt {0} in Gruppe {1} der Ergebnisliste {2} konnte nicht ermittelt werden: {3}
|
||||
unableToObtainBoatsRegisteredWith=Die bei der Ergebnisliste {0} angemeldeten Boote konnten nicht ermittelt werden: {1}
|
||||
importStartLinePings=Positionen für Startlinie importieren
|
||||
importStartData=Positionen für Startlinie importieren und Rennen pro Start anlegen
|
||||
+5
-1
@@ -12,6 +12,8 @@ import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory;
|
||||
import com.sap.sailing.domain.trackimport.FormatNotSupportedException;
|
||||
import com.sap.sailing.server.gateway.trackfiles.impl.ExpeditionCourseInferrer;
|
||||
import com.sap.sailing.server.gateway.trackfiles.impl.ExpeditionStartData;
|
||||
@@ -25,7 +27,9 @@ public class ExpeditionStartDataTest {
|
||||
@Test
|
||||
public void readFile() throws IOException, FormatNotSupportedException, ParseException {
|
||||
final String filename = "2018Apr28_05.csv.gz";
|
||||
final ExpeditionStartData result = new ExpeditionCourseInferrer().getStartData(getClass().getResourceAsStream(filename), filename);
|
||||
final ExpeditionStartData result = new ExpeditionCourseInferrer(
|
||||
RaceLogTrackingAdapterFactory.INSTANCE.getAdapter(DomainFactory.INSTANCE)).getStartData(
|
||||
getClass().getResourceAsStream(filename), filename);
|
||||
assertNotNull(result);
|
||||
final Set<TimePoint> startTimes = new HashSet<>();
|
||||
Util.addAll(result.getStartTimes(), startTimes);
|
||||
|
||||
@@ -56,7 +56,8 @@ Require-Bundle: com.sap.sailing.domain,
|
||||
com.sap.sailing.server,
|
||||
com.sap.sailing.grib,
|
||||
com.sap.sailing.nmeaconnector,
|
||||
com.sap.sailing.geocoding
|
||||
com.sap.sailing.geocoding,
|
||||
com.googlecode.java-diff-utils
|
||||
Bundle-ClassPath: .
|
||||
Web-ContextPath: /sailingserver
|
||||
Export-Package: com.sap.sailing.server.gateway,
|
||||
|
||||
+10
-5
@@ -19,6 +19,7 @@ import org.osgi.util.tracker.ServiceTracker;
|
||||
|
||||
import com.sap.sailing.domain.common.dto.ExpeditionAllInOneConstants;
|
||||
import com.sap.sailing.domain.common.dto.ExpeditionAllInOneConstants.ImportMode;
|
||||
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter;
|
||||
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapterFactory;
|
||||
import com.sap.sailing.server.gateway.impl.AbstractFileUploadServlet;
|
||||
import com.sap.sailing.server.gateway.trackfiles.impl.ExpeditionAllInOneImporter.ImporterResult;
|
||||
@@ -65,7 +66,7 @@ public class ExpeditionAllInOneImportServlet extends AbstractFileUploadServlet {
|
||||
String regattaName = null;
|
||||
String importModeName = null;
|
||||
String localeName = null;
|
||||
boolean importStartLinePings = false;
|
||||
boolean importStartData = false;
|
||||
for (FileItem fi : fileItems) {
|
||||
if (!fi.isFormField()) {
|
||||
fileName = fi.getName();
|
||||
@@ -83,8 +84,8 @@ public class ExpeditionAllInOneImportServlet extends AbstractFileUploadServlet {
|
||||
if (ExpeditionAllInOneConstants.REQUEST_PARAMETER_LOCALE.equals(fi.getFieldName())) {
|
||||
localeName = fi.getString();
|
||||
}
|
||||
if (ExpeditionAllInOneConstants.REQUEST_PARAMETER_IMPORT_START_LINE_PINGS.equals(fi.getFieldName())) {
|
||||
importStartLinePings = Boolean.valueOf(fi.getString().equalsIgnoreCase("on"));
|
||||
if (ExpeditionAllInOneConstants.REQUEST_PARAMETER_IMPORT_START_DATA.equals(fi.getFieldName())) {
|
||||
importStartData = Boolean.valueOf(fi.getString().equalsIgnoreCase("on"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,8 +122,8 @@ public class ExpeditionAllInOneImportServlet extends AbstractFileUploadServlet {
|
||||
}
|
||||
}
|
||||
importerResult = new ExpeditionAllInOneImporter(serverStringMessages, uiLocale, getService(),
|
||||
raceLogTrackingAdapterTracker.getService().getAdapter(getService().getBaseDomainFactory()),
|
||||
getServiceFinderFactory(), getContext()).importFiles(fileName, fileItem, boatClassName, importMode, regattaName, importStartLinePings);
|
||||
getRaceLogTrackingAdapter(), getServiceFinderFactory(), getContext()).importFiles(fileName,
|
||||
fileItem, boatClassName, importMode, regattaName, importStartData);
|
||||
} catch (AllinOneImportException e) {
|
||||
importerResult = new ImporterResult(e, e.additionalErrors);
|
||||
logger.log(Level.SEVERE, e.getMessage());
|
||||
@@ -134,6 +135,10 @@ public class ExpeditionAllInOneImportServlet extends AbstractFileUploadServlet {
|
||||
}
|
||||
}
|
||||
|
||||
private RaceLogTrackingAdapter getRaceLogTrackingAdapter() {
|
||||
return raceLogTrackingAdapterTracker.getService().getAdapter(getService().getBaseDomainFactory());
|
||||
}
|
||||
|
||||
private JSONObject toJSON(ImporterResult importerResult) {
|
||||
final JSONObject json = new JSONObject();
|
||||
json.put("eventId", importerResult.eventId == null ? null : importerResult.eventId.toString());
|
||||
|
||||
+182
-36
@@ -18,6 +18,8 @@ import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
@@ -29,6 +31,7 @@ import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor;
|
||||
import com.sap.sailing.domain.abstractlog.race.RaceLog;
|
||||
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogEndOfTrackingEventImpl;
|
||||
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartOfTrackingEventImpl;
|
||||
import com.sap.sailing.domain.abstractlog.race.impl.RaceLogStartTimeEventImpl;
|
||||
import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogDenoteForTrackingEventImpl;
|
||||
import com.sap.sailing.domain.abstractlog.race.tracking.impl.RaceLogStartTrackingEventImpl;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
@@ -83,6 +86,7 @@ import com.sap.sailing.server.operationaltransformation.CreateLeaderboardGroup;
|
||||
import com.sap.sailing.server.operationaltransformation.CreateRegattaLeaderboard;
|
||||
import com.sap.sailing.server.operationaltransformation.UpdateEvent;
|
||||
import com.sap.sailing.server.util.WaitForTrackedRaceUtil;
|
||||
import com.sap.sse.common.Duration;
|
||||
import com.sap.sse.common.NoCorrespondingServiceRegisteredException;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.TypeBasedServiceFinderFactory;
|
||||
@@ -125,6 +129,18 @@ public class ExpeditionAllInOneImporter {
|
||||
private static final Logger logger = Logger.getLogger(ExpeditionAllInOneImporter.class.getName());
|
||||
|
||||
private static final double VENUE_RANGE_CHECK = 10;
|
||||
|
||||
/**
|
||||
* For sessions created automatically from start times found in the log, tries to set the tracking start
|
||||
* time this much before the race start, unless it would be before the first fix received which then would
|
||||
* provide the start of tracking time instead.
|
||||
*/
|
||||
private static final Duration TRACKING_DURATION_BEFORE_START = Duration.ONE_MINUTE.times(5);
|
||||
|
||||
/**
|
||||
* This prefix is used to create race columns based on start times automatically.
|
||||
*/
|
||||
private static final String START_PER_SESSION_RACE_COLUMN_NAME_PREFIX = "R";
|
||||
|
||||
private final RacingEventService service;
|
||||
private final RaceLogTrackingAdapter adapter;
|
||||
@@ -177,7 +193,8 @@ public class ExpeditionAllInOneImporter {
|
||||
}
|
||||
}
|
||||
|
||||
public ExpeditionAllInOneImporter(ResourceBundleStringMessages serverStringMessages, Locale uiLocale, final RacingEventService service, RaceLogTrackingAdapter adapter,
|
||||
public ExpeditionAllInOneImporter(ResourceBundleStringMessages serverStringMessages, Locale uiLocale,
|
||||
final RacingEventService service, RaceLogTrackingAdapter adapter,
|
||||
final TypeBasedServiceFinderFactory serviceFinderFactory, final BundleContext context) {
|
||||
this.serverStringMessages = serverStringMessages;
|
||||
this.uiLocale = uiLocale;
|
||||
@@ -188,7 +205,7 @@ public class ExpeditionAllInOneImporter {
|
||||
}
|
||||
|
||||
public ImporterResult importFiles(final String filenameWithSuffix, final FileItem fileItem,
|
||||
final String boatClassName, ImportMode importMode, String existingRegattaName, boolean importStartLinePings)
|
||||
final String boatClassName, ImportMode importMode, String existingRegattaName, boolean importStartData)
|
||||
throws AllinOneImportException, IOException, FormatNotSupportedException {
|
||||
final List<ErrorImportDTO> errors = new ArrayList<>();
|
||||
final String importTimeString = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(LocalDateTime.now(ZoneOffset.UTC));
|
||||
@@ -247,23 +264,25 @@ public class ExpeditionAllInOneImporter {
|
||||
final String regattaNameAndleaderboardName;
|
||||
final List<Triple<String, String, String>> raceNameRaceColumnNameFleetnameList = new ArrayList<>();
|
||||
final List<DynamicTrackedRace> trackedRaces = new ArrayList<>();
|
||||
final ExpeditionStartData startData = new ExpeditionCourseInferrer().getStartData(fileItem.getInputStream(), filenameWithSuffix);
|
||||
// TODO from the start times, suggest the user to split the session into one session per start, with start tracking at n minutes before start
|
||||
if (importStartLinePings) {
|
||||
// TODO optionally populate the session(s) with a start line if none is defined yet and if desired (flag!) add the pings
|
||||
}
|
||||
final ExpeditionCourseInferrer expeditionCourseInferrer = new ExpeditionCourseInferrer(adapter);
|
||||
final ExpeditionStartData startData = expeditionCourseInferrer.getStartData(fileItem.getInputStream(), filenameWithSuffix);
|
||||
if (importMode == ImportMode.NEW_EVENT) {
|
||||
leaderboardGroupName = filenameWithDateTimeSuffix;
|
||||
regattaNameAndleaderboardName = filenameWithDateTimeSuffix;
|
||||
String raceColumnName = filename;
|
||||
eventId = UUID.randomUUID();
|
||||
String fleetName = LeaderboardNameConstants.DEFAULT_FLEET_NAME;
|
||||
final DynamicTrackedRace trackedRace = createEventStructureWithASingleRaceAndTrackIt(filenameWithSuffix, boatClassName, errors, importTimeString, filename,
|
||||
filenameWithDateTimeSuffix, trackedRaceName, discardThresholds, firstFixAt, lastFixAt,
|
||||
eventStartDate, eventEndDate, eventId, leaderboardGroupName, regattaNameAndleaderboardName,
|
||||
fleetName, raceColumnName);
|
||||
trackedRaces.add(trackedRace);
|
||||
raceNameRaceColumnNameFleetnameList.add(new Triple<>(trackedRaceName, raceColumnName, fleetName));
|
||||
final Iterable<Triple<DynamicTrackedRace, String, String>> trackedRacesAndRaceColumnNamesAndFleetNames =
|
||||
createEventStructureWithASingleRaceAndTrackIt(
|
||||
filenameWithSuffix, boatClassName, errors, importTimeString, filename,
|
||||
filenameWithDateTimeSuffix, trackedRaceName, discardThresholds, firstFixAt, lastFixAt,
|
||||
eventStartDate, eventEndDate, eventId, leaderboardGroupName, regattaNameAndleaderboardName,
|
||||
fleetName, raceColumnName, importStartData?startData:null);
|
||||
for (Triple<DynamicTrackedRace, String, String> i : trackedRacesAndRaceColumnNamesAndFleetNames) {
|
||||
trackedRaces.add(i.getA());
|
||||
raceNameRaceColumnNameFleetnameList.add(new Triple<>(i.getA().getRace().getName(),
|
||||
/* race column name */ i.getB(), /* fleet name */ i.getC()));
|
||||
}
|
||||
updateVenueName(filename, jsonHolderForGpsFixImport.getImportResult(), eventId);
|
||||
} else {
|
||||
regattaNameAndleaderboardName = existingRegattaName;
|
||||
@@ -318,16 +337,28 @@ public class ExpeditionAllInOneImporter {
|
||||
if (Util.size(fleets) != 1) {
|
||||
return new ImporterResult(serverStringMessages.get(uiLocale, "allInOneErrorMultiSeries"));
|
||||
}
|
||||
final Fleet fleet = fleets.iterator().next();
|
||||
final String fleetName = fleet.getName();
|
||||
// When uploading files with identical name, the second RaceColumn will be named with the upload time in its name
|
||||
// First, create the session for the full log
|
||||
final String raceColumnName = regatta.getRaceColumnByName(filename) == null ? filename : filenameWithDateTimeSuffix;
|
||||
final RaceColumn raceColumn = service.apply(new AddColumnToSeries(regatta.getRegattaIdentifier(), series.getName(), raceColumnName));
|
||||
|
||||
final DynamicTrackedRace trackedRace = createTrackedRaceAndSetupRaceTimes(errors, trackedRaceName, firstFixAt, lastFixAt, regatta, regattaLeaderboard,
|
||||
raceColumn, fleet);
|
||||
trackedRaces.add(trackedRace);
|
||||
raceNameRaceColumnNameFleetnameList.add(new Triple<>(trackedRaceName, raceColumnName, fleetName));
|
||||
final Triple<DynamicTrackedRace, String, String> trackedRaceAndRaceColumnNameAndFleetName = addRace(
|
||||
errors, regatta, raceColumnName, trackedRaceName, regattaLeaderboard, firstFixAt, lastFixAt, /* start time */ null);
|
||||
trackedRaces.add(trackedRaceAndRaceColumnNameAndFleetName.getA());
|
||||
raceNameRaceColumnNameFleetnameList.add(new Triple<>(trackedRaceAndRaceColumnNameAndFleetName.getA().getRace().getName(),
|
||||
/* race column name */ trackedRaceAndRaceColumnNameAndFleetName.getB(),
|
||||
/* fleet name */ trackedRaceAndRaceColumnNameAndFleetName.getC()));
|
||||
if (importStartData) {
|
||||
// then create another session per start time found:
|
||||
for (final Triple<TimePoint, TimePoint, TimePoint> startTimesAndStartAndEndOfTrackingTimes : getStartTimesAndStartAndEndOfTrackingTimes(startData.getStartTimes(), firstFixAt, lastFixAt)) {
|
||||
final Triple<DynamicTrackedRace, String, String> session = createSessionForStartTime(
|
||||
startTimesAndStartAndEndOfTrackingTimes.getA(),
|
||||
startTimesAndStartAndEndOfTrackingTimes.getB(),
|
||||
startTimesAndStartAndEndOfTrackingTimes.getC(), errors, regatta, regattaLeaderboard);
|
||||
trackedRaces.add(session.getA());
|
||||
raceNameRaceColumnNameFleetnameList.add(new Triple<>(session.getA().getRace().getName(),
|
||||
/* race column name */ session.getB(),
|
||||
/* fleet name */ session.getC()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return new ImporterResult(serverStringMessages.get(uiLocale, "allInOneErrorInvalidImportMode") + importMode);
|
||||
}
|
||||
@@ -335,6 +366,9 @@ public class ExpeditionAllInOneImporter {
|
||||
return new ImporterResult(serverStringMessages.get(uiLocale, "allInOneErrorInvalidRegattaName"));
|
||||
}
|
||||
}
|
||||
if (importStartData) {
|
||||
expeditionCourseInferrer.setStartLine(startData, trackedRaces, service);
|
||||
}
|
||||
// Import Wind Data
|
||||
try {
|
||||
final WindImportResult windImportResult = new AbstractWindImporter.WindImportResult();
|
||||
@@ -351,6 +385,62 @@ public class ExpeditionAllInOneImporter {
|
||||
}
|
||||
}
|
||||
|
||||
private Triple<DynamicTrackedRace, String, String> createSessionForStartTime(TimePoint startTime,
|
||||
TimePoint firstFixAt, TimePoint lastFixAt, List<ErrorImportDTO> errors, Regatta regatta,
|
||||
RegattaLeaderboard regattaLeaderboard) throws AllinOneImportException {
|
||||
final int sessionCounter = getNextAvailableStartBasedSessionCount(regatta);
|
||||
final String raceColumnName = START_PER_SESSION_RACE_COLUMN_NAME_PREFIX + sessionCounter;
|
||||
final Triple<DynamicTrackedRace, String, String> trackedRaceAndRaceColumnNameAndFleetName = addRace(
|
||||
errors, regatta, raceColumnName, raceColumnName, regattaLeaderboard, firstFixAt, lastFixAt, startTime);
|
||||
return trackedRaceAndRaceColumnNameAndFleetName;
|
||||
}
|
||||
|
||||
private int getNextAvailableStartBasedSessionCount(Regatta regatta) {
|
||||
int maxNumberFound = 0;
|
||||
final Pattern pattern = Pattern.compile(START_PER_SESSION_RACE_COLUMN_NAME_PREFIX+"\\([0-9]+\\)");
|
||||
for (final RaceColumn raceColumn : regatta.getRaceColumns()) {
|
||||
Matcher matcher = pattern.matcher(raceColumn.getName());
|
||||
if (matcher.matches()) {
|
||||
if (matcher.groupCount() > 0) {
|
||||
final String numberAsString = matcher.group(1);
|
||||
if (!numberAsString.isEmpty()) {
|
||||
final int number = Integer.valueOf(numberAsString);
|
||||
if (number > maxNumberFound) {
|
||||
maxNumberFound = number;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxNumberFound+1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param startTime
|
||||
* optional; if not {@code null}, a start time race log event will be added to fix this start time
|
||||
* @return the race created, and the race column name and the fleet name
|
||||
*/
|
||||
private Triple<DynamicTrackedRace, String, String> addRace(final List<ErrorImportDTO> errors, final Regatta regatta,
|
||||
final String raceColumnName, final String trackedRaceName, final RegattaLeaderboard regattaLeaderboard,
|
||||
final TimePoint startOfTracking, final TimePoint endOfTracking, TimePoint startTime) throws AllinOneImportException {
|
||||
final Iterable<? extends Series> seriesInRegatta = regatta.getSeries();
|
||||
assert !Util.isEmpty(seriesInRegatta);
|
||||
final Series series = Util.get(seriesInRegatta, Util.size(seriesInRegatta) - 1);
|
||||
final Iterable<? extends Fleet> fleets = series.getFleets();
|
||||
assert !Util.isEmpty(fleets);
|
||||
final Fleet fleet = fleets.iterator().next();
|
||||
final String fleetName = fleet.getName();
|
||||
// When uploading files with identical name, the second RaceColumn will be named with the upload time in its name
|
||||
final RaceColumn raceColumn = service.apply(new AddColumnToSeries(regatta.getRegattaIdentifier(), series.getName(), raceColumnName));
|
||||
final DynamicTrackedRace trackedRace = createTrackedRaceAndSetupRaceTimes(errors, trackedRaceName,
|
||||
startOfTracking, endOfTracking, regatta, regattaLeaderboard, raceColumn, fleet);
|
||||
if (startTime != null) {
|
||||
final RaceLog raceLog = raceColumn.getRaceLog(raceColumn.getFleets().iterator().next());
|
||||
raceLog.add(new RaceLogStartTimeEventImpl(startTime, service.getServerAuthor(), /* priority */ 0, startTime));
|
||||
}
|
||||
return new Triple<>(trackedRace, raceColumnName, fleetName);
|
||||
}
|
||||
|
||||
private void ensureEventLongEnough(TimePoint firstFixAt, TimePoint lastFixAt, UUID eventId) {
|
||||
Event event = service.getEvent(eventId);
|
||||
TimePoint startDate = event.getStartDate();
|
||||
@@ -382,12 +472,17 @@ public class ExpeditionAllInOneImporter {
|
||||
return null;
|
||||
}
|
||||
|
||||
private DynamicTrackedRace createEventStructureWithASingleRaceAndTrackIt(final String filenameWithSuffix, final String boatClassName,
|
||||
/**
|
||||
* @param startData if not {@code null}, an additional race column will be created for each start time; see also
|
||||
* {@link #createTrackedRaceAndSetupRaceTimes(List, String, TimePoint, TimePoint, Regatta, RegattaLeaderboard, RaceColumn, Fleet)}.
|
||||
*/
|
||||
private Iterable<Triple<DynamicTrackedRace, String, String>> createEventStructureWithASingleRaceAndTrackIt(
|
||||
final String filenameWithSuffix, final String boatClassName,
|
||||
final List<ErrorImportDTO> errors, final String importTimeString, final String filename,
|
||||
final String filenameWithDateTimeSuffix, final String trackedRaceName, final int[] discardThresholds,
|
||||
TimePoint firstFixAt, TimePoint lastFixAt, final TimePoint eventStartDate, final TimePoint eventEndDate,
|
||||
final UUID eventId, final String leaderboardGroupName, final String regattaNameAndleaderboardName,
|
||||
final String fleetName, final String raceColumnName) throws AllinOneImportException {
|
||||
final String fleetName, final String raceColumnName, final ExpeditionStartData startData) throws AllinOneImportException {
|
||||
final DynamicTrackedRace trackedRace;
|
||||
final String eventName = filenameWithDateTimeSuffix;
|
||||
final String description = MessageFormat.format("Event imported from expedition file ''{0}'' on {1}",
|
||||
@@ -405,9 +500,70 @@ public class ExpeditionAllInOneImporter {
|
||||
final RaceColumn raceColumn = regattaLeaderboard.getRaceColumns().iterator().next();
|
||||
final Fleet fleet = raceColumn.getFleets().iterator().next();
|
||||
trackedRace = createTrackedRaceAndSetupRaceTimes(errors, trackedRaceName, firstFixAt, lastFixAt, regatta, regattaLeaderboard, raceColumn, fleet);
|
||||
return trackedRace;
|
||||
final List<Triple<DynamicTrackedRace, String, String>> result = new ArrayList<>();
|
||||
result.add(new Triple<>(trackedRace, raceColumnName, fleetName));
|
||||
if (startData != null) {
|
||||
// then create another session per start time found:
|
||||
for (final Triple<TimePoint, TimePoint, TimePoint> startTimesAndStartAndEndOfTrackingTimes : getStartTimesAndStartAndEndOfTrackingTimes(startData.getStartTimes(), firstFixAt, lastFixAt)) {
|
||||
final Triple<DynamicTrackedRace, String, String> session = createSessionForStartTime(
|
||||
startTimesAndStartAndEndOfTrackingTimes.getA(),
|
||||
startTimesAndStartAndEndOfTrackingTimes.getB(),
|
||||
startTimesAndStartAndEndOfTrackingTimes.getC(), errors, regatta, regattaLeaderboard);
|
||||
result.add(session);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* From the start times inferred from the log and the first and last fix determines a sequence of triples telling
|
||||
* for each session to create the race start time as well as the tracking start/end times. Tracking intervals will
|
||||
* be cropped so they fit into {@code [firstFixAt,lastFixAt]}. Ideally, tracking is set such that it starts
|
||||
* {@link #TRACKING_DURATION_BEFORE_START} before the race start or at {@code firstFixAt} if it needs to be cropped.
|
||||
* The last session extends until {@code lastFixAt}. All other sessions are set to end {@link #TRACKING_DURATION_BEFORE_START}
|
||||
* before the next start.
|
||||
*/
|
||||
private Iterable<Triple<TimePoint, TimePoint, TimePoint>> getStartTimesAndStartAndEndOfTrackingTimes(Iterable<TimePoint> startTimes, TimePoint firstFixAt, TimePoint lastFixAt) {
|
||||
final List<Triple<TimePoint, TimePoint, TimePoint>> result = new ArrayList<>();
|
||||
TimePoint previousStartOfTracking = null;
|
||||
TimePoint previousStartTime = null;
|
||||
for (final TimePoint startTime : startTimes) {
|
||||
if (previousStartOfTracking != null) {
|
||||
assert previousStartTime != null;
|
||||
final TimePoint preferredEndOfTracking = startTime.minus(TRACKING_DURATION_BEFORE_START);
|
||||
result.add(createInterval(previousStartOfTracking, preferredEndOfTracking, previousStartTime, firstFixAt, lastFixAt));
|
||||
}
|
||||
final TimePoint preferredStartOfTracking = startTime.minus(TRACKING_DURATION_BEFORE_START);
|
||||
if (preferredStartOfTracking.before(firstFixAt)) {
|
||||
previousStartOfTracking = firstFixAt;
|
||||
} else {
|
||||
previousStartOfTracking = preferredStartOfTracking;
|
||||
}
|
||||
previousStartTime = startTime;
|
||||
}
|
||||
if (previousStartOfTracking != null) {
|
||||
// close last interval
|
||||
result.add(createInterval(previousStartOfTracking, /* preferred end of tracking */ lastFixAt, previousStartTime, firstFixAt, lastFixAt));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Triple<TimePoint, TimePoint, TimePoint> createInterval(TimePoint startOfTracking, TimePoint preferredEndOfTracking,
|
||||
TimePoint startTime, TimePoint firstFixAt, TimePoint lastFixAt) {
|
||||
assert startOfTracking != null;
|
||||
assert startTime != null;
|
||||
final TimePoint endOfTracking;
|
||||
if (preferredEndOfTracking.after(lastFixAt)) {
|
||||
endOfTracking = lastFixAt;
|
||||
} else {
|
||||
if (preferredEndOfTracking.before(firstFixAt)) {
|
||||
endOfTracking = firstFixAt;
|
||||
} else {
|
||||
endOfTracking = preferredEndOfTracking;
|
||||
}
|
||||
}
|
||||
return new Triple<>(startTime, startOfTracking, endOfTracking);
|
||||
}
|
||||
private void updateVenueName(String filename, List<TrackImportDTO> list, UUID eventId) {
|
||||
for (TrackImportDTO f : list) {
|
||||
TrackFileImportDeviceIdentifier deviceIdentifier = TrackFileImportDeviceIdentifierImpl.getOrCreate(f.getDevice());
|
||||
@@ -456,14 +612,10 @@ public class ExpeditionAllInOneImporter {
|
||||
private Regatta createRegattaWithOneRaceColumn(final String boatClassName, final String regattaNameAndleaderboardName, final String fleetName,
|
||||
final String raceColumnName, final RegattaIdentifier regattaIdentifier, final UUID courseAreaId,
|
||||
final Double buoyZoneRadiusInHullLengths, final String seriesName) throws AllinOneImportException {
|
||||
|
||||
// TODO wild guess...
|
||||
final ScoringSchemeType scoringSchemeType = ScoringSchemeType.LOW_POINT;
|
||||
final RankingMetrics rankingMetric = RankingMetrics.ONE_DESIGN;
|
||||
|
||||
final Regatta regatta;
|
||||
final ScoringScheme scoringScheme = service.getBaseDomainFactory().createScoringScheme(scoringSchemeType);
|
||||
|
||||
final LinkedHashMap<String, SeriesCreationParametersDTO> seriesCreationParameters = new LinkedHashMap<>();
|
||||
final List<FleetDTO> fleets = new ArrayList<>();
|
||||
fleets.add(new FleetDTO(fleetName, 0, null));
|
||||
@@ -471,9 +623,7 @@ public class ExpeditionAllInOneImporter {
|
||||
new SeriesCreationParametersDTO(fleets, /*isMedal*/ false,
|
||||
/* isFleetsCanRunInParallel */ false, /*isStartsWithZeroScore*/ false, /*firstColumnIsNonDiscardableCarryForward*/false, /*discardingThresholds*/ null,
|
||||
/*hasSplitFleetContiguousScoring*/ false, /*maximumNumberOfDiscards*/ null));
|
||||
final RegattaCreationParametersDTO regattaCreationParameters = new RegattaCreationParametersDTO(
|
||||
seriesCreationParameters);
|
||||
|
||||
final RegattaCreationParametersDTO regattaCreationParameters = new RegattaCreationParametersDTO(seriesCreationParameters);
|
||||
regatta = service.apply(new AddSpecificRegatta(regattaNameAndleaderboardName, boatClassName,
|
||||
/* can boats of competitors change */ false,
|
||||
/* start date */ null, /* end date */ null, UUID.randomUUID(),
|
||||
@@ -498,15 +648,13 @@ public class ExpeditionAllInOneImporter {
|
||||
final String trackedRaceName, TimePoint firstFixAt, TimePoint lastFixAt, final Regatta regatta,
|
||||
final RegattaLeaderboard regattaLeaderboard, final RaceColumn raceColumn, final Fleet fleet)
|
||||
throws AllinOneImportException {
|
||||
// TODO this could be where we evaluate the ExpeditionStartData to optionally create one additional race per start
|
||||
final RaceLog raceLog = raceColumn.getRaceLog(fleet);
|
||||
|
||||
final AbstractLogEventAuthor author = service.getServerAuthor();
|
||||
|
||||
final TimePoint startOfTracking = firstFixAt;
|
||||
final TimePoint endOfTracking = lastFixAt;
|
||||
raceLog.add(new RaceLogStartOfTrackingEventImpl(startOfTracking, author, raceLog.getCurrentPassId()));
|
||||
raceLog.add(new RaceLogEndOfTrackingEventImpl(endOfTracking, author, raceLog.getCurrentPassId()));
|
||||
|
||||
try {
|
||||
TimePoint startTrackingTimePoint = MillisecondsTimePoint.now();
|
||||
// this ensures that the events consistently have different timepoints to ensure a consistent result of the
|
||||
@@ -515,9 +663,7 @@ public class ExpeditionAllInOneImporter {
|
||||
final TimePoint denotationTimePoint = startTrackingTimePoint.minus(1);
|
||||
raceLog.add(new RaceLogDenoteForTrackingEventImpl(denotationTimePoint, service.getServerAuthor(),
|
||||
raceLog.getCurrentPassId(), trackedRaceName, regatta.getBoatClass(), UUID.randomUUID()));
|
||||
|
||||
raceLog.add(new RaceLogStartTrackingEventImpl(startTrackingTimePoint, author, raceLog.getCurrentPassId()));
|
||||
|
||||
return trackRace(regattaLeaderboard, raceColumn, fleet);
|
||||
} catch (Exception e) {
|
||||
throw new AllinOneImportException(e, errors);
|
||||
|
||||
+104
@@ -5,20 +5,38 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.sap.sailing.domain.abstractlog.regatta.events.impl.RegattaLogDefineMarkEventImpl;
|
||||
import com.sap.sailing.domain.base.ControlPoint;
|
||||
import com.sap.sailing.domain.base.Fleet;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.RaceColumn;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.SharedDomainFactory;
|
||||
import com.sap.sailing.domain.common.PassingInstruction;
|
||||
import com.sap.sailing.domain.common.impl.DegreePosition;
|
||||
import com.sap.sailing.domain.common.sensordata.ExpeditionExtendedSensorDataMetadata;
|
||||
import com.sap.sailing.domain.common.tracking.GPSFix;
|
||||
import com.sap.sailing.domain.common.tracking.impl.GPSFixImpl;
|
||||
import com.sap.sailing.domain.racelogtracking.RaceLogTrackingAdapter;
|
||||
import com.sap.sailing.domain.trackimport.FormatNotSupportedException;
|
||||
import com.sap.sailing.domain.tracking.DynamicTrackedRace;
|
||||
import com.sap.sailing.domain.tracking.TrackedRace;
|
||||
import com.sap.sailing.server.RacingEventService;
|
||||
import com.sap.sailing.server.trackfiles.impl.CompressedStreamsUtil;
|
||||
import com.sap.sailing.server.trackfiles.impl.ExpeditionExtendedDataImporterImpl;
|
||||
import com.sap.sailing.server.trackfiles.impl.ExpeditionImportFileHandler;
|
||||
import com.sap.sse.common.TimePoint;
|
||||
import com.sap.sse.common.Util.Pair;
|
||||
import com.sap.sse.common.impl.MillisecondsTimePoint;
|
||||
import difflib.PatchFailedException;
|
||||
|
||||
/**
|
||||
* From an Expedition log file extracts a {@link ExpeditionStartData} object that has all mark "ping" positions for the
|
||||
@@ -34,7 +52,16 @@ public class ExpeditionCourseInferrer {
|
||||
private static final String START_LINE_PORT_END_LON = "Port lon";
|
||||
private static final String START_LINE_STARBOARD_END_LAT = "Stbd lat";
|
||||
private static final String START_LINE_STARBOARD_END_LON = "Stbd lon";
|
||||
private static final String START_LINE_PORT_END_MARK_NAME = "Start P";
|
||||
private static final String START_LINE_STARBOARD_END_MARK_NAME = "Start S";
|
||||
|
||||
private final RaceLogTrackingAdapter raceLogTrackingAdapter;
|
||||
|
||||
public ExpeditionCourseInferrer(RaceLogTrackingAdapter raceLogTrackingAdapter) {
|
||||
super();
|
||||
this.raceLogTrackingAdapter = raceLogTrackingAdapter;
|
||||
}
|
||||
|
||||
public ExpeditionStartData getStartData(InputStream inputStream, String filenameWithSuffix)
|
||||
throws IOException, FormatNotSupportedException {
|
||||
final List<TimePoint> startTimeCandidates = new ArrayList<>();
|
||||
@@ -99,4 +126,81 @@ public class ExpeditionCourseInferrer {
|
||||
: lineContentTokens[columnIndex].trim().isEmpty() ? null
|
||||
: Double.parseDouble(lineContentTokens[columnIndex]);
|
||||
}
|
||||
|
||||
/**
|
||||
* For the tracked races provided constructs a start line based on the {@code startData} and its
|
||||
* {@link ExpeditionStartData#getStartLinePortFixes() port} and
|
||||
* {@link ExpeditionStartData#getStartLineStarboardFixes() starboard} fixes. Course marks named
|
||||
* {@link #START_LINE_PORT_END_MARK_NAME} and {@link #START_LINE_STARBOARD_END_MARK_NAME} are looked
|
||||
* up in the tracked races. If not found, such marks are defined through the regatta log.
|
||||
*/
|
||||
public void setStartLine(ExpeditionStartData startData, Iterable<DynamicTrackedRace> trackedRaces, RacingEventService racingEventService) {
|
||||
for (final DynamicTrackedRace trackedRace : trackedRaces) {
|
||||
setStartLine(startData, trackedRace, racingEventService);
|
||||
}
|
||||
}
|
||||
|
||||
private void setStartLine(ExpeditionStartData startData, DynamicTrackedRace trackedRace, RacingEventService racingEventService) {
|
||||
final Mark portMark = getOrCreateMarkPings(trackedRace, START_LINE_PORT_END_MARK_NAME, startData.getStartLinePortFixes(), racingEventService);
|
||||
final Mark starboardMark = getOrCreateMarkPings(trackedRace, START_LINE_STARBOARD_END_MARK_NAME, startData.getStartLineStarboardFixes(), racingEventService);
|
||||
final ControlPoint startLine = racingEventService.getBaseDomainFactory().getOrCreateControlPointWithTwoMarks(
|
||||
UUID.randomUUID(), "Start", portMark, starboardMark);
|
||||
try {
|
||||
trackedRace.getRace().getCourse().update(Collections.singleton(new Pair<>(startLine, PassingInstruction.Line)),
|
||||
racingEventService.getBaseDomainFactory());
|
||||
} catch (PatchFailedException e) {
|
||||
logger.log(Level.WARNING, "Internal error while setting a course with a start line for race "+trackedRace.getRace().getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private Mark getOrCreateMarkPings(DynamicTrackedRace trackedRace, String markName, Iterable<GPSFix> markFixes, RacingEventService racingEventService) {
|
||||
final Mark mark = getOrCreateMark(trackedRace, markName, racingEventService);
|
||||
for (final GPSFix fix : markFixes) {
|
||||
recordFix(mark, fix, trackedRace, racingEventService);
|
||||
}
|
||||
return mark;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "ping" device mapping for the {@code mark} in the regatta log that is retrieved from the tracked race's
|
||||
* regatta.
|
||||
*/
|
||||
private void recordFix(Mark mark, GPSFix fix, DynamicTrackedRace trackedRace, RacingEventService racingEventService) {
|
||||
raceLogTrackingAdapter.pingMark(trackedRace.getAttachedRegattaLogs().iterator().next(), mark, fix, racingEventService);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param racingEventService
|
||||
* used to determine the server author based on the user currently logged on, furthermore to get access
|
||||
* to the {@link SharedDomainFactory} used to create a new mark in case a mark by the desired name is not
|
||||
* found in the race or regatta.
|
||||
* @return the mark found or created
|
||||
*/
|
||||
private Mark getOrCreateMark(DynamicTrackedRace trackedRace, String markName, RacingEventService racingEventService) {
|
||||
Mark mark = null;
|
||||
final Regatta regatta = trackedRace.getTrackedRegatta().getRegatta();
|
||||
outer: for (final RaceColumn raceColumn : regatta.getRaceColumns()) {
|
||||
for (final Fleet fleet : raceColumn.getFleets()) {
|
||||
final TrackedRace trackedRaceForFleet = raceColumn.getTrackedRace(fleet);
|
||||
if (trackedRaceForFleet == trackedRace) {
|
||||
// found it; grab marks:
|
||||
for (final Mark availableMark : raceColumn.getAvailableMarks(fleet)) {
|
||||
if (availableMark.getName().equals(markName)) {
|
||||
mark = availableMark;
|
||||
break outer; // found the mark with the correct name; done
|
||||
}
|
||||
}
|
||||
if (mark == null) {
|
||||
mark = racingEventService.getBaseDomainFactory().getOrCreateMark(UUID.randomUUID(), markName);
|
||||
final TimePoint now = MillisecondsTimePoint.now();
|
||||
RegattaLogDefineMarkEventImpl defineMarkEvent = new RegattaLogDefineMarkEventImpl(now,
|
||||
racingEventService.getServerAuthor(), now, UUID.randomUUID(), mark);
|
||||
regatta.getRegattaLog().add(defineMarkEvent);
|
||||
}
|
||||
break outer; // tracked race found, mark found or created at this point; no need to scan more columns
|
||||
}
|
||||
}
|
||||
}
|
||||
return mark;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user