implemented creation of session per start and setting start line

Change-Id: I614efd18f49bbd65c9db6dee6c55314a2310f8ba
This commit is contained in:
Axel Uhl committed 2018-04-30 13:46:51 +02:00
1 parent 1444c36e0b
commit 3a5b2fbe54
12 files changed
+312 -52

No files matched your search

@@ -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;
@@ -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));
@@ -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) {
@@ -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);
@@ -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();
}
@@ -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
@@ -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
@@ -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,
@@ -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());
@@ -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);
@@ -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;
}
}