diff --git a/java/com.sap.sailing.autoload/src/com/sap/sailing/autoload/TracTrac.java b/java/com.sap.sailing.autoload/src/com/sap/sailing/autoload/TracTrac.java index e4a5ec3275c..3ecf17a7c01 100644 --- a/java/com.sap.sailing.autoload/src/com/sap/sailing/autoload/TracTrac.java +++ b/java/com.sap.sailing.autoload/src/com/sap/sailing/autoload/TracTrac.java @@ -95,7 +95,7 @@ public class TracTrac { new MillisecondsTimePoint(record.getTrackingEndTime().asMillis()), raceLogStore, regattaLogStore, RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, /* simulateWithStartTimeNow */ - false, + false, /*useTracTracMarkPassings*/ false, /* tracTracUsername */"", /* tracTracPassword */"", record.getRaceStatus(), record.getRaceVisibility()); } else { diff --git a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch b/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch index aefd1e3ffb6..e6230b9950b 100755 --- a/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch +++ b/java/com.sap.sailing.dashboards.gwt/SailingDashboardsGWT sdm.launch @@ -1,47 +1,47 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PassingInstruction.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PassingInstruction.java index 76433b896a4..6a1a4cf576e 100644 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PassingInstruction.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/PassingInstruction.java @@ -32,8 +32,9 @@ public enum PassingInstruction { } public static PassingInstruction valueOfIgnoringCase(String value) { + final String lowerCaseValue = value.toLowerCase(); for (PassingInstruction p : PassingInstruction.values()) { - if (value.toLowerCase().equals(p.toString().toLowerCase())) { + if (lowerCaseValue.equals(p.toString().toLowerCase())) { return p; } } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java index ef2bff5a261..bd3f36827d8 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/DomainObjectFactoryImpl.java @@ -33,6 +33,7 @@ import com.mongodb.util.JSON; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; import com.sap.sailing.domain.abstractlog.impl.LogEventAuthorImpl; import com.sap.sailing.domain.abstractlog.race.CompetitorResults; +import com.sap.sailing.domain.abstractlog.race.FixedMarkPassingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; @@ -50,6 +51,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; +import com.sap.sailing.domain.abstractlog.race.SuppressedMarkPassingsEvent; import com.sap.sailing.domain.abstractlog.race.impl.CompetitorResultsImpl; import com.sap.sailing.domain.abstractlog.race.impl.RaceLogImpl; import com.sap.sailing.domain.abstractlog.race.scoring.AdditionalScoringInformationType; @@ -1373,6 +1375,10 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { return loadRaceLogCloseOpenEndedDeviceMappingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } else if (eventClass.equals(RaceLogAdditionalScoringInformationEvent.class.getSimpleName())) { return loadRaceLogAdditionalScoringInformationEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(FixedMarkPassingEvent.class.getSimpleName())){ + return loadRaceLogFixedMarkPassingEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); + } else if (eventClass.equals(SuppressedMarkPassingsEvent.class.getSimpleName())){ + return loadRaceLogSuppressedMarkPassingsEvent(createdAt, author, logicalTimePoint, id, passId, competitors, dbObject); } throw new IllegalStateException(String.format("Unknown RaceLogEvent type %s", eventClass)); @@ -1541,6 +1547,19 @@ public class DomainObjectFactoryImpl implements DomainObjectFactory { return raceLogEventFactory.createCourseAreaChangedEvent(createdAt, author, logicalTimePoint, id, competitors, passId, courseAreaId); } + private RaceLogEvent loadRaceLogFixedMarkPassingEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + TimePoint ofFixedPassing = loadTimePoint(dbObject, FieldNames.TIMEPOINT_OF_FIXED_MARKPASSING); + Integer zeroBasedIndexOfWaypoint = (Integer) dbObject.get(FieldNames.INDEX_OF_PASSED_WAYPOINT.name()); + return raceLogEventFactory.createFixedMarkPassingEvent(logicalTimePoint, author, id, competitors, passId, ofFixedPassing, zeroBasedIndexOfWaypoint); + } + + private RaceLogEvent loadRaceLogSuppressedMarkPassingsEvent(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable id, Integer passId, List competitors, DBObject dbObject) { + Integer zeroBasedIndexOfFirstSuppressedWaypoint = (Integer) dbObject.get(FieldNames.INDEX_OF_FIRST_SUPPRESSED_WAYPOINT.name()); + return raceLogEventFactory.createSuppressedMarkPassingsEvent(logicalTimePoint, author, id, competitors, passId, zeroBasedIndexOfFirstSuppressedWaypoint); + } + private CompetitorResults loadPositionedCompetitors(BasicDBList dbPositionedCompetitorList) { CompetitorResults positionedCompetitors = new CompetitorResultsImpl(); for (Object object : dbPositionedCompetitorList) { diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java index 601d9384913..251e93346b1 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/FieldNames.java @@ -89,7 +89,7 @@ public enum FieldNames { RACE_LOG_POSITIONED_COMPETITORS, RACE_LOG_PATHFINDER_ID, RACE_LOG_GATE_LINE_OPENING_TIME, RACE_LOG_GOLF_DOWN_TIME, RACE_LOG_START_PROCEDURE_TYPE, RACE_LOG_PROTEST_START_TIME, RACE_LOG_COURSE_DESIGN_NAME, RACE_LOG_REVOKED_EVENT_ID, RACE_LOG_REVOKED_EVENT_TYPE, RACE_LOG_REVOKED_EVENT_SHORT_INFO, RACE_LOG_REVOKED_REASON, RACE_LOG_FROM, RACE_LOG_TO, RACE_LOG_MARK, RACE_LOG_DEVICE_MAPPING_EVENT_ID, - RACE_LOG_CLOSING_TIMEPOINT, MARK, RACE_LOG_COMPETITOR_ID, + RACE_LOG_CLOSING_TIMEPOINT, TIMEPOINT_OF_FIXED_MARKPASSING, INDEX_OF_PASSED_WAYPOINT,INDEX_OF_FIRST_SUPPRESSED_WAYPOINT, MARK, RACE_LOG_COMPETITOR_ID, /** @deprecated Replaced by WAYPOINT_PASSINGINSTRUCTIONS, still used to migrate old races*/ @Deprecated WAYPOINT_PASSINGSIDE, /** @deprecated Replaced by CONTROLPOINTWITHTWOMARKS_ID, still used to migrate old races*/ @Deprecated GATE_ID, /** @deprecated Replaced by CONTROLPOINTWITHTWOMARKS_NAME, still used to migrate old races*/ @Deprecated GATE_NAME, diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java index 0ca97c7c81c..403d28bfd43 100755 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoObjectFactoryImpl.java @@ -19,6 +19,7 @@ import com.mongodb.DBObject; import com.mongodb.WriteConcern; import com.mongodb.util.JSON; import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.abstractlog.race.FixedMarkPassingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; @@ -35,6 +36,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent; +import com.sap.sailing.domain.abstractlog.race.SuppressedMarkPassingsEvent; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogCloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDefineMarkEvent; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDenoteForTrackingEvent; @@ -113,25 +115,25 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { private static Logger logger = Logger.getLogger(MongoObjectFactoryImpl.class.getName()); private final DB database; private final CompetitorJsonSerializer competitorSerializer = CompetitorJsonSerializer.create(); - private final TypeBasedServiceFinder deviceIdentifierServiceFinder; + private final TypeBasedServiceFinder deviceIdentifierServiceFinder; - /** - * Uses null for the device type service finder and hence will be unable to store device identifiers. - * Use this constructor only for testing purposes or in cases where there will happen absolutely no access to - * {@link DeviceIdentifier} objects. - */ + /** + * Uses null for the device type service finder and hence will be unable to store device identifiers. + * Use this constructor only for testing purposes or in cases where there will happen absolutely no access to + * {@link DeviceIdentifier} objects. + */ public MongoObjectFactoryImpl(DB database) { - this(database, /* deviceTypeServiceFinder */ null); - } - - public MongoObjectFactoryImpl(DB database, TypeBasedServiceFinderFactory serviceFinderFactory) { + this(database, /* deviceTypeServiceFinder */ null); + } + + public MongoObjectFactoryImpl(DB database, TypeBasedServiceFinderFactory serviceFinderFactory) { this.database = database; - if (serviceFinderFactory != null) { - this.deviceIdentifierServiceFinder = serviceFinderFactory.createServiceFinder(DeviceIdentifierMongoHandler.class); - this.deviceIdentifierServiceFinder.setFallbackService(new PlaceHolderDeviceIdentifierMongoHandler()); - } else { - this.deviceIdentifierServiceFinder = null; - } + if (serviceFinderFactory != null) { + this.deviceIdentifierServiceFinder = serviceFinderFactory.createServiceFinder(DeviceIdentifierMongoHandler.class); + this.deviceIdentifierServiceFinder.setFallbackService(new PlaceHolderDeviceIdentifierMongoHandler()); + } else { + this.deviceIdentifierServiceFinder = null; + } } @Override @@ -147,14 +149,14 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { return result; } - public static void storeTimePoint(TimePoint timePoint, DBObject result, String fieldName) { + public static void storeTimePoint(TimePoint timePoint, DBObject result, String fieldName) { if (timePoint != null) { - result.put(fieldName, timePoint.asMillis()); + result.put(fieldName, timePoint.asMillis()); } } - public static void storeTimePoint(TimePoint timePoint, DBObject result, FieldNames field) { - storeTimePoint(timePoint, result, field.name()); + public static void storeTimePoint(TimePoint timePoint, DBObject result, FieldNames field) { + storeTimePoint(timePoint, result, field.name()); } public static void storeTimeRange(TimeRange timeRange, DBObject result, FieldNames field) { @@ -164,29 +166,29 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { storeTimePoint(timeRange.to(), timeRangeObj, FieldNames.TO_MILLIS); result.put(field.name(), timeRangeObj); } - } - - public void storeTimed(Timed timed, DBObject result) { + } + + public void storeTimed(Timed timed, DBObject result) { if (timed.getTimePoint() != null) { storeTimePoint(timed.getTimePoint(), result, FieldNames.TIME_AS_MILLIS); } } - public void storeSpeedWithBearing(SpeedWithBearing speedWithBearing, DBObject result) { + public void storeSpeedWithBearing(SpeedWithBearing speedWithBearing, DBObject result) { storeSpeed(speedWithBearing, result); storeBearing(speedWithBearing.getBearing(), result); } - public void storeBearing(Bearing bearing, DBObject result) { + public void storeBearing(Bearing bearing, DBObject result) { result.put(FieldNames.DEGREE_BEARING.name(), bearing.getDegrees()); } - public void storeSpeed(Speed speed, DBObject result) { + public void storeSpeed(Speed speed, DBObject result) { result.put(FieldNames.KNOT_SPEED.name(), speed.getKnots()); } - public void storePositioned(Positioned positioned, DBObject result) { + public void storePositioned(Positioned positioned, DBObject result) { if (positioned.getPosition() != null) { result.put(FieldNames.LAT_DEG.name(), positioned.getPosition().getLatDeg()); result.put(FieldNames.LNG_DEG.name(), positioned.getPosition().getLngDeg()); @@ -205,13 +207,13 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { return result; } - public DBCollection getGPSFixCollection() { - DBCollection gpsFixCollection = database.getCollection(CollectionNames.GPS_FIXES.name()); - DBObject index = new BasicDBObject(); - index.put(FieldNames.DEVICE_ID.name(), null); + public DBCollection getGPSFixCollection() { + DBCollection gpsFixCollection = database.getCollection(CollectionNames.GPS_FIXES.name()); + DBObject index = new BasicDBObject(); + index.put(FieldNames.DEVICE_ID.name(), null); index.put(FieldNames.TIME_AS_MILLIS.name(), null); - gpsFixCollection.createIndex(index); - return gpsFixCollection; + gpsFixCollection.createIndex(index); + return gpsFixCollection; } public DBCollection getGPSFixMetadataCollection() { @@ -220,8 +222,8 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { index.put(FieldNames.DEVICE_ID.name(), null); collection.createIndex(index); return collection; - } - + } + /** * @param regattaName * the regatta name is stored only for human readability purposes because a time stamp may be a bit unhandy for @@ -777,81 +779,81 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartProcedureChangedEvent event) { DBObject result = new BasicDBObject(); - storeRaceLogIdentifier(raceLogIdentifier, result); + storeRaceLogIdentifier(raceLogIdentifier, result); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartProcedureChangedEvent(event)); return result; } public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogProtestStartTimeEvent event) { DBObject result = new BasicDBObject(); - storeRaceLogIdentifier(raceLogIdentifier, result); + storeRaceLogIdentifier(raceLogIdentifier, result); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogProtestStartTimeEvent(event)); return result; } public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogWindFixEvent event) { DBObject result = new BasicDBObject(); - storeRaceLogIdentifier(raceLogIdentifier, result); + storeRaceLogIdentifier(raceLogIdentifier, result); result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogWindFix(event)); return result; } - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDeviceCompetitorMappingEvent event) { - BasicDBObject result = new BasicDBObject(); - storeRaceLogIdentifier(raceLogIdentifier, result); - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDeviceCompetitorMappingEvent(event)); - return result; - } - - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDeviceMarkMappingEvent event) { - BasicDBObject result = new BasicDBObject(); - storeRaceLogIdentifier(raceLogIdentifier, result); - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDeviceMarkMappingEvent(event)); - return result; - } - - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDenoteForTrackingEvent event) { - BasicDBObject result = new BasicDBObject(); - storeRaceLogIdentifier(raceLogIdentifier, result); - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDenoteForTrackingEvent(event)); - return result; - } - - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartTrackingEvent event) { - BasicDBObject result = new BasicDBObject(); - storeRaceLogIdentifier(raceLogIdentifier, result); - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartTrackingEvent(event)); - return result; - } - - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRevokeEvent event) { - BasicDBObject result = new BasicDBObject(); - storeRaceLogIdentifier(raceLogIdentifier, result); - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRevokeEvent(event)); - return result; - } - - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRegisterCompetitorEvent event) { - BasicDBObject result = new BasicDBObject(); - storeRaceLogIdentifier(raceLogIdentifier, result); - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRegisterCompetitorEvent(event)); - return result; - } - - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDefineMarkEvent event) { - BasicDBObject result = new BasicDBObject(); - storeRaceLogIdentifier(raceLogIdentifier, result); - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDefineMarkEvent(event)); - return result; - } - - public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCloseOpenEndedDeviceMappingEvent event) { - BasicDBObject result = new BasicDBObject(); - storeRaceLogIdentifier(raceLogIdentifier, result); - result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCloseOpenEndedDeviceMappingEvent(event)); - return result; - } - + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDeviceCompetitorMappingEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDeviceCompetitorMappingEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDeviceMarkMappingEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDeviceMarkMappingEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDenoteForTrackingEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDenoteForTrackingEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogStartTrackingEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogStartTrackingEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRevokeEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRevokeEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogRegisterCompetitorEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogRegisterCompetitorEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogDefineMarkEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogDefineMarkEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogCloseOpenEndedDeviceMappingEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogCloseOpenEndedDeviceMappingEvent(event)); + return result; + } + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, RaceLogAdditionalScoringInformationEvent event) { BasicDBObject result = new BasicDBObject(); storeRaceLogIdentifier(raceLogIdentifier, result); @@ -867,6 +869,20 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { return result; } + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, FixedMarkPassingEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogFixedMarkPassingEvent(event)); + return result; + } + + public DBObject storeRaceLogEntry(RaceLogIdentifier raceLogIdentifier, SuppressedMarkPassingsEvent event) { + BasicDBObject result = new BasicDBObject(); + storeRaceLogIdentifier(raceLogIdentifier, result); + result.put(FieldNames.RACE_LOG_EVENT.name(), storeRaceLogSuppressedMarkPassingsEvent(event)); + return result; + } + private Object storeRaceLogWindFix(RaceLogWindFixEvent event) { DBObject result = new BasicDBObject(); storeRaceLogEventProperties(event, result); @@ -899,88 +915,105 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { return result; } - private void storeDeviceMappingEvent(DeviceMappingEvent event, DBObject result, FieldNames fromField, FieldNames toField) { + private void storeDeviceMappingEvent(DeviceMappingEvent event, DBObject result, FieldNames fromField, FieldNames toField) { try { result.put(FieldNames.DEVICE_ID.name(), storeDeviceId(deviceIdentifierServiceFinder, event.getDevice())); } catch (TransformationException | NoCorrespondingServiceRegisteredException e) { logger.log(Level.WARNING, "Could not store device identifier for mappng event", e); - } - if (event.getFrom() != null) { - storeTimePoint(event.getFrom(), result, fromField); - } - if (event.getTo() != null) { - storeTimePoint(event.getTo(), result, toField); - } - } - - private Object storeRaceLogDeviceCompetitorMappingEvent(RaceLogDeviceCompetitorMappingEvent event) { + } + if (event.getFrom() != null) { + storeTimePoint(event.getFrom(), result, fromField); + } + if (event.getTo() != null) { + storeTimePoint(event.getTo(), result, toField); + } + } + + private Object storeRaceLogDeviceCompetitorMappingEvent(RaceLogDeviceCompetitorMappingEvent event) { DBObject result = new BasicDBObject(); - storeRaceLogEventProperties(event, result); - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDeviceCompetitorMappingEvent.class.getSimpleName()); - storeDeviceMappingEvent(event, result, FieldNames.RACE_LOG_FROM, FieldNames.RACE_LOG_TO); - result.put(FieldNames.COMPETITOR_ID.name(), event.getMappedTo().getId()); - return result; - } - - private Object storeRaceLogDeviceMarkMappingEvent(RaceLogDeviceMarkMappingEvent event) { - DBObject result = new BasicDBObject(); - storeRaceLogEventProperties(event, result); - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDeviceMarkMappingEvent.class.getSimpleName()); - storeDeviceMappingEvent(event, result, FieldNames.RACE_LOG_FROM, FieldNames.RACE_LOG_TO); - result.put(FieldNames.MARK.name(), storeMark(event.getMappedTo())); - return result; - } - - private Object storeRaceLogDenoteForTrackingEvent(RaceLogDenoteForTrackingEvent event) { - DBObject result = new BasicDBObject(); - storeRaceLogEventProperties(event, result); - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDenoteForTrackingEvent.class.getSimpleName()); - result.put(FieldNames.RACE_NAME.name(), event.getRaceName()); - result.put(FieldNames.BOAT_CLASS_NAME.name(), event.getBoatClass().getName()); - result.put(FieldNames.RACE_ID.name(), event.getRaceId()); - return result; - } - - private Object storeRaceLogStartTrackingEvent(RaceLogStartTrackingEvent event) { - DBObject result = new BasicDBObject(); - storeRaceLogEventProperties(event, result); - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartTrackingEvent.class.getSimpleName()); - return result; - } - - private Object storeRaceLogRevokeEvent(RaceLogRevokeEvent event) { - DBObject result = new BasicDBObject(); - storeRaceLogEventProperties(event, result); - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRevokeEvent.class.getSimpleName()); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDeviceCompetitorMappingEvent.class.getSimpleName()); + storeDeviceMappingEvent(event, result, FieldNames.RACE_LOG_FROM, FieldNames.RACE_LOG_TO); + result.put(FieldNames.COMPETITOR_ID.name(), event.getMappedTo().getId()); + return result; + } + + private Object storeRaceLogDeviceMarkMappingEvent(RaceLogDeviceMarkMappingEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDeviceMarkMappingEvent.class.getSimpleName()); + storeDeviceMappingEvent(event, result, FieldNames.RACE_LOG_FROM, FieldNames.RACE_LOG_TO); + result.put(FieldNames.MARK.name(), storeMark(event.getMappedTo())); + return result; + } + + private Object storeRaceLogDenoteForTrackingEvent(RaceLogDenoteForTrackingEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDenoteForTrackingEvent.class.getSimpleName()); + result.put(FieldNames.RACE_NAME.name(), event.getRaceName()); + result.put(FieldNames.BOAT_CLASS_NAME.name(), event.getBoatClass().getName()); + result.put(FieldNames.RACE_ID.name(), event.getRaceId()); + return result; + } + + private Object storeRaceLogStartTrackingEvent(RaceLogStartTrackingEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartTrackingEvent.class.getSimpleName()); + return result; + } + + private Object storeRaceLogRevokeEvent(RaceLogRevokeEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRevokeEvent.class.getSimpleName()); result.put(FieldNames.RACE_LOG_REVOKED_EVENT_ID.name(), event.getRevokedEventId()); result.put(FieldNames.RACE_LOG_REVOKED_EVENT_TYPE.name(), event.getRevokedEventType()); result.put(FieldNames.RACE_LOG_REVOKED_EVENT_SHORT_INFO.name(), event.getRevokedEventShortInfo()); - result.put(FieldNames.RACE_LOG_REVOKED_REASON.name(), event.getReason()); - return result; - } - - private Object storeRaceLogRegisterCompetitorEvent(RaceLogRegisterCompetitorEvent event) { - DBObject result = new BasicDBObject(); - storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_REVOKED_REASON.name(), event.getReason()); + return result; + } + + private Object storeRaceLogRegisterCompetitorEvent(RaceLogRegisterCompetitorEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogRegisterCompetitorEvent.class.getSimpleName()); - result.put(FieldNames.RACE_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId()); - return result; - } - - private Object storeRaceLogDefineMarkEvent(RaceLogDefineMarkEvent event) { - DBObject result = new BasicDBObject(); - storeRaceLogEventProperties(event, result); - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDefineMarkEvent.class.getSimpleName()); - result.put(FieldNames.RACE_LOG_MARK.name(), storeMark(event.getMark())); - return result; - } - - private Object storeRaceLogCloseOpenEndedDeviceMappingEvent(RaceLogCloseOpenEndedDeviceMappingEvent event) { - DBObject result = new BasicDBObject(); - storeRaceLogEventProperties(event, result); - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName()); - result.put(FieldNames.RACE_LOG_DEVICE_MAPPING_EVENT_ID.name(), event.getDeviceMappingEventId()); - storeTimePoint(event.getClosingTimePoint(), result, FieldNames.RACE_LOG_CLOSING_TIMEPOINT); + result.put(FieldNames.RACE_LOG_COMPETITOR_ID.name(), event.getCompetitor().getId()); + return result; + } + + private Object storeRaceLogDefineMarkEvent(RaceLogDefineMarkEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogDefineMarkEvent.class.getSimpleName()); + result.put(FieldNames.RACE_LOG_MARK.name(), storeMark(event.getMark())); + return result; + } + + private Object storeRaceLogCloseOpenEndedDeviceMappingEvent(RaceLogCloseOpenEndedDeviceMappingEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogCloseOpenEndedDeviceMappingEvent.class.getSimpleName()); + result.put(FieldNames.RACE_LOG_DEVICE_MAPPING_EVENT_ID.name(), event.getDeviceMappingEventId()); + storeTimePoint(event.getClosingTimePoint(), result, FieldNames.RACE_LOG_CLOSING_TIMEPOINT); + return result; + } + + private Object storeRaceLogFixedMarkPassingEvent(FixedMarkPassingEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), FixedMarkPassingEvent.class.getSimpleName()); + result.put(FieldNames.INDEX_OF_PASSED_WAYPOINT.name(), event.getZeroBasedIndexOfPassedWaypoint()); + result.put(FieldNames.TIMEPOINT_OF_FIXED_MARKPASSING.name(), event.getTimePointOfFixedPassing().asMillis()); + return result; + } + + private Object storeRaceLogSuppressedMarkPassingsEvent(SuppressedMarkPassingsEvent event) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(event, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), SuppressedMarkPassingsEvent.class.getSimpleName()); + result.put(FieldNames.INDEX_OF_FIRST_SUPPRESSED_WAYPOINT.name(), event.getZeroBasedIndexOfFirstSuppressedWaypoint()); return result; } @@ -994,15 +1027,15 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { return result; } - private DBObject storeRaceLogStartTimeEvent(RaceLogStartTimeEvent startTimeEvent) { - DBObject result = new BasicDBObject(); - storeRaceLogEventProperties(startTimeEvent, result); - result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartTimeEvent.class.getSimpleName()); - storeTimePoint(startTimeEvent.getStartTime(), result, FieldNames.RACE_LOG_EVENT_START_TIME); - result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), startTimeEvent.getNextStatus().name()); - return result; - } - + private DBObject storeRaceLogStartTimeEvent(RaceLogStartTimeEvent startTimeEvent) { + DBObject result = new BasicDBObject(); + storeRaceLogEventProperties(startTimeEvent, result); + result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogStartTimeEvent.class.getSimpleName()); + storeTimePoint(startTimeEvent.getStartTime(), result, FieldNames.RACE_LOG_EVENT_START_TIME); + result.put(FieldNames.RACE_LOG_EVENT_NEXT_STATUS.name(), startTimeEvent.getNextStatus().name()); + return result; + } + private void storeRaceLogEventProperties(RaceLogEvent event, DBObject result) { // for compatibility reasons we reuse the field name of Timed storeTimePoint(event.getLogicalTimePoint(), result, FieldNames.TIME_AS_MILLIS); @@ -1147,7 +1180,7 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { result.put(FieldNames.MARK_TYPE.name(), mark.getType() == null ? null : mark.getType().name()); return result; } - + private String getPassingInstructions(PassingInstruction passingInstructions) { final String passing; if (passingInstructions != null) { @@ -1226,20 +1259,20 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory { configurationsCollections.remove(query); } - public static DBObject storeDeviceId( - TypeBasedServiceFinder deviceIdentifierServiceFinder, DeviceIdentifier device) - throws TransformationException, NoCorrespondingServiceRegisteredException { - String type = device.getIdentifierType(); - DeviceIdentifierMongoHandler handler = deviceIdentifierServiceFinder.findService(type); - com.sap.sse.common.Util.Pair pair = handler.serialize(device); - type = pair.getA(); - Object deviceTypeSpecificId = pair.getB(); - return new BasicDBObjectBuilder() - .add(FieldNames.DEVICE_TYPE.name(), type) - .add(FieldNames.DEVICE_TYPE_SPECIFIC_ID.name(), deviceTypeSpecificId) - .add(FieldNames.DEVICE_STRING_REPRESENTATION.name(), device.getStringRepresentation()).get(); - } - + public static DBObject storeDeviceId( + TypeBasedServiceFinder deviceIdentifierServiceFinder, DeviceIdentifier device) + throws TransformationException, NoCorrespondingServiceRegisteredException { + String type = device.getIdentifierType(); + DeviceIdentifierMongoHandler handler = deviceIdentifierServiceFinder.findService(type); + com.sap.sse.common.Util.Pair pair = handler.serialize(device); + type = pair.getA(); + Object deviceTypeSpecificId = pair.getB(); + return new BasicDBObjectBuilder() + .add(FieldNames.DEVICE_TYPE.name(), type) + .add(FieldNames.DEVICE_TYPE_SPECIFIC_ID.name(), deviceTypeSpecificId) + .add(FieldNames.DEVICE_STRING_REPRESENTATION.name(), device.getStringRepresentation()).get(); + } + void storeRaceLogEventEvent(DBObject eventEntry) { getRaceLogCollection().insert(eventEntry); } diff --git a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java index dfbb2440417..90e53800a11 100644 --- a/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java +++ b/java/com.sap.sailing.domain.persistence/src/com/sap/sailing/domain/persistence/impl/MongoRaceLogStoreVisitor.java @@ -3,6 +3,7 @@ package com.sap.sailing.domain.persistence.impl; import java.util.logging.Logger; import com.mongodb.DBObject; +import com.sap.sailing.domain.abstractlog.race.FixedMarkPassingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor; @@ -18,6 +19,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; +import com.sap.sailing.domain.abstractlog.race.SuppressedMarkPassingsEvent; import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogCloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDefineMarkEvent; @@ -173,4 +175,16 @@ public class MongoRaceLogStoreVisitor implements RaceLogEventVisitor { DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); mongoObjectFactory.storeRaceLogEventEvent(object); } + + @Override + public void visit(FixedMarkPassingEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } + + @Override + public void visit(SuppressedMarkPassingsEvent event) { + DBObject object = mongoObjectFactory.storeRaceLogEntry(raceLogIdentifier, event); + mongoObjectFactory.storeRaceLogEventEvent(object); + } } diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java index e4c276dd8ce..467928c2a89 100644 --- a/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter.test/src/com/sap/sailing/domain/racelogtracking/test/impl/TrackedRaceLoadsFixesTest.java @@ -78,7 +78,7 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest { EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, /* startDate */ null, /* endDate */null, null, null, "a", null)); DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race, - Collections. emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0); + Collections. emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0, /*useMarkPassingCalculator*/ false); trackedRace.attachRaceLog(raceLog); trackedRace.waitForLoadingFromGPSFixStoreToFinishRunning(raceLog); @@ -123,7 +123,7 @@ public class TrackedRaceLoadsFixesTest extends AbstractGPSFixStoreTest { EmptyRegattaLogStore.INSTANCE, RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, /* startDate */ null, /* endDate */null, null, null, "a", null)); DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race, - Collections. emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0); + Collections. emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0, /*useMarkPassingCalculator*/ false); trackedRace.addListener(new AbstractRaceChangeListener() { @Override diff --git a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java index 8918ce4a88c..ec6ebb7b894 100755 --- a/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java +++ b/java/com.sap.sailing.domain.racelogtrackingadapter/src/com/sap/sailing/domain/racelogtracking/impl/RaceLogRaceTracker.java @@ -59,7 +59,6 @@ import com.sap.sailing.domain.common.abstractlog.NotRevokableException; import com.sap.sailing.domain.common.racelog.tracking.RaceLogTrackingState; import com.sap.sailing.domain.common.racelog.tracking.RaceNotCreatedException; import com.sap.sailing.domain.common.racelog.tracking.TransformationException; -import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator; import com.sap.sailing.domain.racelog.tracking.GPSFixReceivedListener; import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.racelogtracking.DeviceIdentifier; @@ -446,7 +445,7 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener { trackedRace = regatta.createTrackedRace(raceDef, sidelines, windStore, gpsFixStore, params.getDelayToLiveInMillis(), WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, - boatClass.getApproximateManeuverDurationInMilliseconds(), null); + boatClass.getApproximateManeuverDurationInMilliseconds(), null, /*useMarkPassingCalculator*/ true); trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.TRACKING, 0)); @@ -464,9 +463,6 @@ public class RaceLogRaceTracker implements RaceTracker, GPSFixReceivedListener { addListeners(competitorMappings); addListeners(markMappings); - // add mark passing detection - new MarkPassingCalculator(trackedRace, true); - logger.info(String.format("Started tracking race-log race (%s)", raceLog)); // this wakes up all waiting race handles synchronized (this) { diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/FixedMarkPassingEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/FixedMarkPassingEvent.java new file mode 100644 index 00000000000..097234c765e --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/FixedMarkPassingEvent.java @@ -0,0 +1,12 @@ +package com.sap.sailing.domain.abstractlog.race; + +import com.sap.sailing.domain.abstractlog.Revokable; +import com.sap.sse.common.TimePoint; + +public interface FixedMarkPassingEvent extends RaceLogEvent, Revokable { + + public TimePoint getTimePointOfFixedPassing(); + + Integer getZeroBasedIndexOfPassedWaypoint(); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventFactory.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventFactory.java index 194a5812477..41f88f337ac 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventFactory.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventFactory.java @@ -120,7 +120,13 @@ public interface RaceLogEventFactory { RaceLogCloseOpenEndedDeviceMappingEvent createCloseOpenEndedDeviceMappingEvent(TimePoint logicalTimePoint, AbstractLogEventAuthor author, int passId, Serializable deviceMappingEventId, TimePoint closingTimePoint); + + RaceLogEvent createFixedMarkPassingEvent(TimePoint logicalTimePoint, AbstractLogEventAuthor author, Serializable id, + List competitors, Integer passId, TimePoint ofFixedPassing, Integer zeroBasedIndexOfWaypoint); RaceLogAdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint timePoint, Serializable id, AbstractLogEventAuthor author, int currentPassId, AdditionalScoringInformationType informationType); + + RaceLogEvent createSuppressedMarkPassingsEvent(TimePoint logicalTimePoint, AbstractLogEventAuthor author, Serializable id, + List competitors, Integer passId, Integer zeroBasedIndexOfFirstSuppressedWaypoint); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventVisitor.java index faac5c3074c..1049e62c387 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/RaceLogEventVisitor.java @@ -54,4 +54,9 @@ public interface RaceLogEventVisitor { public void visit(RaceLogCloseOpenEndedDeviceMappingEvent event); public void visit(RaceLogAdditionalScoringInformationEvent additionalScoringInformation); + + public void visit(FixedMarkPassingEvent event); + + + public void visit(SuppressedMarkPassingsEvent event); } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/SuppressedMarkPassingsEvent.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/SuppressedMarkPassingsEvent.java new file mode 100644 index 00000000000..e3d55cf6ec9 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/SuppressedMarkPassingsEvent.java @@ -0,0 +1,9 @@ +package com.sap.sailing.domain.abstractlog.race; + +import com.sap.sailing.domain.abstractlog.Revokable; + +public interface SuppressedMarkPassingsEvent extends RaceLogEvent, Revokable { + + public Integer getZeroBasedIndexOfFirstSuppressedWaypoint(); + +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/MarkPassingDataFinder.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/MarkPassingDataFinder.java new file mode 100644 index 00000000000..e92650b50af --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/analyzing/impl/MarkPassingDataFinder.java @@ -0,0 +1,35 @@ +package com.sap.sailing.domain.abstractlog.race.analyzing.impl; + +import java.util.HashSet; +import java.util.Set; + +import com.sap.sailing.domain.abstractlog.race.FixedMarkPassingEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.abstractlog.race.SuppressedMarkPassingsEvent; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Triple; + +public class MarkPassingDataFinder extends RaceLogAnalyzer>> { + + public MarkPassingDataFinder(RaceLog raceLog) { + super(raceLog); + } + + @Override + protected Set> performAnalysis() { + Set> result = new HashSet>(); + for (RaceLogEvent event : getLog().getUnrevokedEvents()) { + if (event instanceof FixedMarkPassingEvent) { + FixedMarkPassingEvent castedEvent = (FixedMarkPassingEvent) event; + result.add(new Triple(castedEvent.getInvolvedBoats().get(0), castedEvent + .getZeroBasedIndexOfPassedWaypoint(), castedEvent.getTimePointOfFixedPassing())); + } else if (event instanceof SuppressedMarkPassingsEvent){ + SuppressedMarkPassingsEvent castedEvent = (SuppressedMarkPassingsEvent) event; + result.add(new Triple(castedEvent.getInvolvedBoats().get(0), castedEvent.getZeroBasedIndexOfFirstSuppressedWaypoint(), null)); + } + } + return result; + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/AbstractRaceLogChangedVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/AbstractRaceLogChangedVisitor.java index 6be4ba80b79..3832110571d 100755 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/AbstractRaceLogChangedVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/AbstractRaceLogChangedVisitor.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.abstractlog.race.impl; +import com.sap.sailing.domain.abstractlog.race.FixedMarkPassingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; @@ -16,6 +17,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; +import com.sap.sailing.domain.abstractlog.race.SuppressedMarkPassingsEvent; import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogCloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDefineMarkEvent; @@ -131,4 +133,14 @@ public abstract class AbstractRaceLogChangedVisitor implements RaceLogEventVisit public void visit(RaceLogAdditionalScoringInformationEvent additionalScoringInformation) { notifyListenerAboutEventAdded(additionalScoringInformation); } + + @Override + public void visit(FixedMarkPassingEvent fixedMarkPassingEvent) { + notifyListenerAboutEventAdded(fixedMarkPassingEvent); + } + + @Override + public void visit(SuppressedMarkPassingsEvent event) { + notifyListenerAboutEventAdded(event); + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/BaseRaceLogEventVisitor.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/BaseRaceLogEventVisitor.java index 159948d96d7..a310c95cb12 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/BaseRaceLogEventVisitor.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/BaseRaceLogEventVisitor.java @@ -1,5 +1,6 @@ package com.sap.sailing.domain.abstractlog.race.impl; +import com.sap.sailing.domain.abstractlog.race.FixedMarkPassingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor; @@ -15,6 +16,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; +import com.sap.sailing.domain.abstractlog.race.SuppressedMarkPassingsEvent; import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogCloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDefineMarkEvent; @@ -138,4 +140,14 @@ public abstract class BaseRaceLogEventVisitor implements RaceLogEventVisitor { public void visit(RaceLogAdditionalScoringInformationEvent event) { } + + @Override + public void visit(FixedMarkPassingEvent event){ + + } + + @Override + public void visit(SuppressedMarkPassingsEvent event) { + + } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/FixedMarkPassingEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/FixedMarkPassingEventImpl.java new file mode 100644 index 00000000000..a981c8955b9 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/FixedMarkPassingEventImpl.java @@ -0,0 +1,47 @@ +package com.sap.sailing.domain.abstractlog.race.impl; + +import java.io.Serializable; +import java.util.List; + +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.abstractlog.race.FixedMarkPassingEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sse.common.TimePoint; + + +public class FixedMarkPassingEventImpl extends RaceLogEventImpl implements FixedMarkPassingEvent { + + private final Integer zeroBasedIndexOfWaypointOfPassing; + + private final TimePoint timePointOfPassing; + + public FixedMarkPassingEventImpl(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, + Serializable pId, List pInvolvedBoats, int pPassId, TimePoint timePoint, Integer zeroBasedIndexOfWaypointOfPassing) { + super(createdAt, author, logicalTimePoint, pId, pInvolvedBoats, pPassId); + this.timePointOfPassing = timePoint; + this.zeroBasedIndexOfWaypointOfPassing = zeroBasedIndexOfWaypointOfPassing; + } + + private static final long serialVersionUID = -1796278009919318553L; + + @Override + public void accept(RaceLogEventVisitor visitor) { + visitor.visit(this); + } + + @Override + public Integer getZeroBasedIndexOfPassedWaypoint() { + return zeroBasedIndexOfWaypointOfPassing; + } + + @Override + public TimePoint getTimePointOfFixedPassing() { + return timePointOfPassing; + } + + @Override + public String getShortInfo() { + return getInvolvedBoats().get(0).getName()+" at mark "+getZeroBasedIndexOfPassedWaypoint()+" at "+getTimePointOfFixedPassing(); + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogEventFactoryImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogEventFactoryImpl.java index 8b78d0e4101..ebe3b005d80 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogEventFactoryImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/RaceLogEventFactoryImpl.java @@ -276,7 +276,21 @@ public class RaceLogEventFactoryImpl implements RaceLogEventFactory { @Override public RaceLogAdditionalScoringInformationEvent createAdditionalScoringInformationEvent(TimePoint timePoint, Serializable id, AbstractLogEventAuthor author, int currentPassId, AdditionalScoringInformationType informationType) { - return new RaceLogAdditionalScoringInformationEventImpl(MillisecondsTimePoint.now(), author, timePoint, id, Collections.emptyList(), currentPassId, - informationType); + return new RaceLogAdditionalScoringInformationEventImpl(MillisecondsTimePoint.now(), author, timePoint, id, + Collections. emptyList(), currentPassId, informationType); + } + + @Override + public RaceLogEvent createFixedMarkPassingEvent(TimePoint logicalTimePoint, AbstractLogEventAuthor author, + Serializable id, List competitors, Integer passId, TimePoint ofFixedPassing, Integer zeroBasedIndexOfWaypoint) { + return new FixedMarkPassingEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, passId, ofFixedPassing, + zeroBasedIndexOfWaypoint); + } + + @Override + public RaceLogEvent createSuppressedMarkPassingsEvent(TimePoint logicalTimePoint, AbstractLogEventAuthor author, Serializable id, + List competitors, Integer passId, Integer zeroBasedIndexOfFirstSuppressedWaypoint) { + return new SuppressedMarkPassingsEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, passId, + zeroBasedIndexOfFirstSuppressedWaypoint); } } diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/SuppressedMarkPassingsEventImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/SuppressedMarkPassingsEventImpl.java new file mode 100644 index 00000000000..84d99673220 --- /dev/null +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/abstractlog/race/impl/SuppressedMarkPassingsEventImpl.java @@ -0,0 +1,39 @@ +package com.sap.sailing.domain.abstractlog.race.impl; + +import java.io.Serializable; +import java.util.List; + +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.abstractlog.race.RaceLogEventVisitor; +import com.sap.sailing.domain.abstractlog.race.SuppressedMarkPassingsEvent; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sse.common.TimePoint; + +public class SuppressedMarkPassingsEventImpl extends RaceLogEventImpl implements SuppressedMarkPassingsEvent { + + private static final long serialVersionUID = 3665678555023150888L; + + private final Integer indexOfFirstSuppressedWaypoint; + + public SuppressedMarkPassingsEventImpl(TimePoint createdAt, AbstractLogEventAuthor author, TimePoint logicalTimePoint, Serializable pId, + List pInvolvedBoats, int pPassId, Integer indexOfFirstSuppressedWaypoint) { + super(createdAt, author, logicalTimePoint, pId, pInvolvedBoats, pPassId); + this.indexOfFirstSuppressedWaypoint = indexOfFirstSuppressedWaypoint; + + } + + @Override + public void accept(RaceLogEventVisitor visitor) { + visitor.visit(this); + } + + @Override + public Integer getZeroBasedIndexOfFirstSuppressedWaypoint() { + return indexOfFirstSuppressedWaypoint; + } + + @Override + public String getShortInfo() { + return getInvolvedBoats().get(0).getName()+" at mark "+getZeroBasedIndexOfFirstSuppressedWaypoint(); + } +} diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CourseDataImpl.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CourseDataImpl.java index 55b482d480e..6d956701058 100644 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CourseDataImpl.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/base/impl/CourseDataImpl.java @@ -49,7 +49,7 @@ public class CourseDataImpl extends NamedImpl implements CourseBase { public int getIndexOfWaypoint(Waypoint waypoint) { return this.waypoints.indexOf(waypoint); } - + @Override public Waypoint getFirstWaypoint() { return this.waypoints.get(0); diff --git a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java old mode 100644 new mode 100755 index b0b7c62bd6b..e36d5b33927 --- a/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java +++ b/java/com.sap.sailing.domain.shared.android/src/com/sap/sailing/domain/tracking/Track.java @@ -1,147 +1,171 @@ -package com.sap.sailing.domain.tracking; - -import java.io.Serializable; -import java.util.Iterator; -import java.util.concurrent.locks.ReadWriteLock; - -import com.sap.sailing.domain.base.Timed; -import com.sap.sse.common.Duration; -import com.sap.sse.common.TimePoint; - -/** - * A track records {@link Timed} items for an object of type ItemType. It allows clients to ask for a value - * close to a given {@link TimePoint}. The track manages a time-based set of raw fixes. An implementation may have an - * understanding of how to eliminate outliers. For example, if a track implementation knows it's tracking boats, it may - * consider fixes that the boat cannot possibly have reached due to its speed and direction change limitations as - * outliers. The set of fixes with outliers filtered out can be obtained using {@link #getFixes} whereas - * {@link #getRawFixes()} returns the unfiltered, raw fixes. If an implementation has no idea what an outlier is, - * both methods will return the same fix sequence.

- * - * With tracks, concurrency is an important issue. Threads may want to modify a track while other threads may want to - * read from it. Several methods such as {@link #getLastFixAtOrBefore(TimePoint)} return a single fix and can manage - * concurrency internally. However, those methods returning a collection of fixes, such as {@link #getFixes()} or an - * iterator over a collection of fixes, such as {@link #getFixesIterator(TimePoint, boolean)}, need special treatment. - * Until we internalize such iterations (see bug 824, http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=824), - * callers need to manage a read lock which is part of a {@link ReadWriteLock} managed by this track. Callers do so - * by calling {@link #lockForRead} and {@link #unlockAfterRead}. - * - * @author Axel Uhl (d043530) - */ -public interface Track extends Serializable { - /** - * Locks this track for reading by the calling thread. If the thread already holds the lock for this track, - * the hold count will be incremented. Make sure to call {@link #unlockAfterRead()} in a finally - * block to release the lock under all possible circumstances. Failure to do so will inevitably lead to - * deadlocks! - */ - void lockForRead(); - - /** - * Decrements the hold count for this track's read lock for the calling thread. If it goes to zero, the lock will be - * released and other readers or a writer can obtain the lock. Make sure to call this method in a - * finally block for each {@link #lockForRead()} invocation. - */ - void unlockAfterRead(); - - /** - * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception - * will be thrown in case the caller has failed to do so. - * - * @return the smoothened fixes - */ - Iterable getFixes(); - - /** - * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception - * will be thrown in case the caller has failed to do so. - * - * @return The smoothened fixes between from and to. - */ - Iterable getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive); - - /** - * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception - * will be thrown in case the caller has failed to do so. - */ - Iterable getRawFixes(); - - FixType getLastFixAtOrBefore(TimePoint timePoint); - - FixType getLastFixBefore(TimePoint timePoint); - - FixType getLastRawFixAtOrBefore(TimePoint timePoint); - - FixType getFirstFixAtOrAfter(TimePoint timePoint); - - FixType getFirstRawFixAtOrAfter(TimePoint timePoint); - - FixType getLastRawFixBefore(TimePoint timePoint); - - FixType getFirstRawFixAfter(TimePoint timePoint); - - FixType getFirstFixAfter(TimePoint timePoint); - - /** - * The first fix in this track or null if the track is empty. The fix returned may - * be an outlier that is not returned by calls operating on the smoothened version of the track. - */ - FixType getFirstRawFix(); - - /** - * The last fix in this track or null if the track is empty. The fix returned may - * be an outlier that is not returned by calls operating on the smoothened version of the track. - */ - FixType getLastRawFix(); - - /** - * Returns an iterator starting at the first fix after startingAt (or "at or after" in case - * inclusive is true). The fixes returned by the iterator are the smoothened fixes (see - * also {@link #getFixes()}, without any smoothening or dampening applied. - * - * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception - * will be thrown in case the caller has failed to do so. - */ - Iterator getFixesIterator(TimePoint startingAt, boolean inclusive); - - /** - * Returns an iterator starting at the first raw fix after startingAt (or "at or after" in case - * inclusive is true). The fixes returned by the iterator are the raw fixes (see also - * {@link #getRawFixes()}, without any smoothening or dampening applied. - * - * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception - * will be thrown in case the caller has failed to do so. - */ - Iterator getRawFixesIterator(TimePoint startingAt, boolean inclusive); - - /** - * Returns a descending iterator starting at the first fix before startingAt (or "at or before" in case - * inclusive is true). The fixes returned by the iterator are the smoothened fixes (see - * also {@link #getFixes()}, without any smoothening or dampening applied. - * - * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception - * will be thrown in case the caller has failed to do so. - */ - Iterator getFixesDescendingIterator(TimePoint startingAt, boolean inclusive); - - /** - * Returns a descending iterator starting at the first raw fix before startingAt (or "at or before" in case - * inclusive is true). The fixes returned by the iterator are the raw fixes (see also - * {@link #getRawFixes()}, without any smoothening or dampening applied. - * - * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception - * will be thrown in case the caller has failed to do so. - */ - Iterator getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive); - - /** - * @return the average duration between two fixes (outliers removed) in this track or null if there is not - * more than one fix in the track - */ - Duration getAverageIntervalBetweenFixes(); - - /** - * @return the average duration between two fixes (outliers not removed) in this track or null if there is not - * more than one raw fix in the track - */ - Duration getAverageIntervalBetweenRawFixes(); -} +package com.sap.sailing.domain.tracking; + +import java.io.Serializable; +import java.util.Iterator; +import java.util.concurrent.locks.ReadWriteLock; + +import com.sap.sailing.domain.base.Timed; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; + +/** + * A track records {@link Timed} items for an object of type ItemType. It allows clients to ask for a value + * close to a given {@link TimePoint}. The track manages a time-based set of raw fixes. An implementation may have an + * understanding of how to eliminate outliers. For example, if a track implementation knows it's tracking boats, it may + * consider fixes that the boat cannot possibly have reached due to its speed and direction change limitations as + * outliers. The set of fixes with outliers filtered out can be obtained using {@link #getFixes} whereas + * {@link #getRawFixes()} returns the unfiltered, raw fixes. If an implementation has no idea what an outlier is, + * both methods will return the same fix sequence.

+ * + * With tracks, concurrency is an important issue. Threads may want to modify a track while other threads may want to + * read from it. Several methods such as {@link #getLastFixAtOrBefore(TimePoint)} return a single fix and can manage + * concurrency internally. However, those methods returning a collection of fixes, such as {@link #getFixes()} or an + * iterator over a collection of fixes, such as {@link #getFixesIterator(TimePoint, boolean)}, need special treatment. + * Until we internalize such iterations (see bug 824, http://bugzilla.sapsailing.com/bugzilla/show_bug.cgi?id=824), + * callers need to manage a read lock which is part of a {@link ReadWriteLock} managed by this track. Callers do so + * by calling {@link #lockForRead} and {@link #unlockAfterRead}. + * + * @author Axel Uhl (d043530) + */ +public interface Track extends Serializable { + /** + * Locks this track for reading by the calling thread. If the thread already holds the lock for this track, + * the hold count will be incremented. Make sure to call {@link #unlockAfterRead()} in a finally + * block to release the lock under all possible circumstances. Failure to do so will inevitably lead to + * deadlocks! + */ + void lockForRead(); + + /** + * Decrements the hold count for this track's read lock for the calling thread. If it goes to zero, the lock will be + * released and other readers or a writer can obtain the lock. Make sure to call this method in a + * finally block for each {@link #lockForRead()} invocation. + */ + void unlockAfterRead(); + + /** + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + * + * @return the smoothened fixes + */ + Iterable getFixes(); + + /** + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + * + * @return The smoothened fixes between from and to. + */ + Iterable getFixes(TimePoint from, boolean fromInclusive, TimePoint to, boolean toInclusive); + + /** + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + */ + Iterable getRawFixes(); + + /** + * Returns null if no such fix exists. + */ + FixType getLastFixAtOrBefore(TimePoint timePoint); + + /** + * Returns null if no such fix exists. + */ + FixType getLastFixBefore(TimePoint timePoint); + + /** + * Returns null if no such fix exists. + */ + FixType getLastRawFixAtOrBefore(TimePoint timePoint); + + /** + * Returns null if no such fix exists. + */ + FixType getFirstFixAtOrAfter(TimePoint timePoint); + + /** + * Returns null if no such fix exists. + */ + FixType getFirstRawFixAtOrAfter(TimePoint timePoint); + + /** + * Returns null if no such fix exists. + */ + FixType getLastRawFixBefore(TimePoint timePoint); + + /** + * Returns null if no such fix exists. + */ + FixType getFirstRawFixAfter(TimePoint timePoint); + + /** + * Returns null if no such fix exists. + */ + FixType getFirstFixAfter(TimePoint timePoint); + + /** + * The first fix in this track or null if the track is empty. The fix returned may + * be an outlier that is not returned by calls operating on the smoothened version of the track. + */ + FixType getFirstRawFix(); + + /** + * The last fix in this track or null if the track is empty. The fix returned may + * be an outlier that is not returned by calls operating on the smoothened version of the track. + */ + FixType getLastRawFix(); + + /** + * Returns an iterator starting at the first fix after startingAt (or "at or after" in case + * inclusive is true). The fixes returned by the iterator are the smoothened fixes (see + * also {@link #getFixes()}, without any smoothening or dampening applied. + * + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + */ + Iterator getFixesIterator(TimePoint startingAt, boolean inclusive); + + /** + * Returns an iterator starting at the first raw fix after startingAt (or "at or after" in case + * inclusive is true). The fixes returned by the iterator are the raw fixes (see also + * {@link #getRawFixes()}, without any smoothening or dampening applied. + * + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + */ + Iterator getRawFixesIterator(TimePoint startingAt, boolean inclusive); + + /** + * Returns a descending iterator starting at the first fix before startingAt (or "at or before" in case + * inclusive is true). The fixes returned by the iterator are the smoothened fixes (see + * also {@link #getFixes()}, without any smoothening or dampening applied. + * + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + */ + Iterator getFixesDescendingIterator(TimePoint startingAt, boolean inclusive); + + /** + * Returns a descending iterator starting at the first raw fix before startingAt (or "at or before" in case + * inclusive is true). The fixes returned by the iterator are the raw fixes (see also + * {@link #getRawFixes()}, without any smoothening or dampening applied. + * + * Callers must have called {@link #lockForRead()} before calling this method. This will be checked, and an exception + * will be thrown in case the caller has failed to do so. + */ + Iterator getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive); + + /** + * @return the average duration between two fixes (outliers removed) in this track or null if there is not + * more than one fix in the track + */ + Duration getAverageIntervalBetweenFixes(); + + /** + * @return the average duration between two fixes (outliers not removed) in this track or null if there is not + * more than one raw fix in the track + */ + Duration getAverageIntervalBetweenRawFixes(); +} diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java index 162b2aa1d33..3ecfdd0b314 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/DomainFactory.java @@ -62,7 +62,7 @@ public interface DomainFactory { RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID, String raceName, String raceDescription, BoatClass boatClass, StartList startList, long delayToLiveInMillis, SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, - RaceLogStore raceLogStore, RegattaLogStore regattaLogStore); + RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, boolean useInternalMarkPassingAlgorithm); ControlPoint getOrCreateControlPoint(Iterable devices); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java index 4742b2337af..4d75dbecd80 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingAdapter.java @@ -25,10 +25,11 @@ public interface SwissTimingAdapter { * created, with a single default series and a single default fleet. If a valid {@link RegattaIdentifier} * is specified, a regatta lookup is performed with that identifier; if the regatta is found, it is used * to add the races to. Otherwise, a default regatta as described above will be created and used. + * @param useInternalMarkPassingAlgorithm use our own instead of the SwissTiming-provided mark passing / split times */ RaceHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, - StartList startList, RaceLogStore logStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds) + StartList startList, RaceLogStore logStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds, boolean useInternalMarkPassingAlgorithm) throws InterruptedException, UnknownHostException, IOException, ParseException, Exception; StartList readStartListForRace(String raceId, RegattaResults regattaResults); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java index db22f45b03d..ec4dfc7bb83 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/SwissTimingFactory.java @@ -50,15 +50,18 @@ public interface SwissTimingFactory { SwissTimingConfiguration createSwissTimingConfiguration(String name, String jsonURL, String hostname, int port); - SwissTimingRaceTracker createRaceTracker(String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, - StartList startList, long delayToLiveInMillis, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, - WindStore windStore, GPSFixStore gpsFixStore, - DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry) throws InterruptedException, UnknownHostException, + SwissTimingRaceTracker createRaceTracker(String raceID, String raceName, String raceDescription, + BoatClass boatClass, String hostname, int port, StartList startList, long delayToLiveInMillis, + RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, + boolean useInternalMarkPassingAlgorithm, DomainFactory domainFactory, + TrackedRegattaRegistry trackedRegattaRegistry) throws InterruptedException, UnknownHostException, IOException, ParseException; - RaceTracker createRaceTracker(Regatta regatta, String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, - StartList startList, long delayToLiveInMillis, WindStore windStore, GPSFixStore gpsFixStore, DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry) throws UnknownHostException, InterruptedException, - IOException, ParseException; + RaceTracker createRaceTracker(Regatta regatta, String raceID, String raceName, String raceDescription, + BoatClass boatClass, String hostname, int port, StartList startList, long delayToLiveInMillis, + WindStore windStore, GPSFixStore gpsFixStore, boolean useInternalMarkPassingAlgorithm, + DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry) throws UnknownHostException, + InterruptedException, IOException, ParseException; Race createRace(String raceId, String raceName, String description, BoatClass boatClass); diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java index 673737f2f5b..03950c8f79f 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/DomainFactoryImpl.java @@ -357,10 +357,10 @@ public class DomainFactoryImpl implements DomainFactory { public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(String hostname, int port, String raceID, String raceName, String raceDescription, BoatClass boatClass, StartList startList, long delayToLiveInMillis, SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, - RaceLogStore raceLogStore, RegattaLogStore regattaLogStore) { + RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, boolean useInternalMarkPassingAlgorithm) { return new SwissTimingTrackingConnectivityParameters(hostname, port, raceID, raceName, raceDescription, boatClass, startList, delayToLiveInMillis, swissTimingFactory, domainFactory, raceLogStore, - regattaLogStore); + regattaLogStore, useInternalMarkPassingAlgorithm); } } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java index 3ffb30e48ba..5068ea0a08e 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingAdapterImpl.java @@ -69,12 +69,13 @@ public class SwissTimingAdapterImpl implements SwissTimingAdapter { @Override public RaceHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, - StartList startList, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds) + StartList startList, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds, + boolean useInternalMarkPassingAlgorithm) throws Exception { return trackerManager.addRace(regattaToAddTo, swissTimingDomainFactory.createTrackingConnectivityParameters( hostname, port, raceID, raceName, raceDescription, boatClass, startList, DEFAULT_SWISSTIMING_LIVE_DELAY_IN_MILLISECONDS, swissTimingFactory, swissTimingDomainFactory, - raceLogStore, regattaLogStore), timeoutInMilliseconds); + raceLogStore, regattaLogStore, useInternalMarkPassingAlgorithm), timeoutInMilliseconds); } @Override diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java index 5b194038bdb..3e54a269946 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingFactoryImpl.java @@ -91,21 +91,21 @@ public class SwissTimingFactoryImpl implements SwissTimingFactory { public SwissTimingRaceTracker createRaceTracker(String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, StartList startList, long delayToLiveInMillis, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, - WindStore windStore, GPSFixStore gpsFixStore, DomainFactory domainFactory, - TrackedRegattaRegistry trackedRegattaRegistry) throws InterruptedException, + WindStore windStore, GPSFixStore gpsFixStore, boolean useInternalMarkPassingAlgorithm, + DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry) throws InterruptedException, UnknownHostException, IOException, ParseException { return new SwissTimingRaceTrackerImpl(raceID, raceName, raceDescription, boatClass, hostname, port, startList, raceLogStore, regattaLogStore, windStore, gpsFixStore, domainFactory, this, - trackedRegattaRegistry, delayToLiveInMillis); + trackedRegattaRegistry, delayToLiveInMillis, useInternalMarkPassingAlgorithm); } @Override public RaceTracker createRaceTracker(Regatta regatta, String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, - int port, StartList startList, long delayToLiveInMillis, WindStore windStore, GPSFixStore gpsFixStore, DomainFactory domainFactory, - TrackedRegattaRegistry trackedRegattaRegistry) throws UnknownHostException, + int port, StartList startList, long delayToLiveInMillis, WindStore windStore, GPSFixStore gpsFixStore, boolean useInternalMarkPassingAlgorithm, + DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry) throws UnknownHostException, InterruptedException, IOException, ParseException { return new SwissTimingRaceTrackerImpl(regatta, raceID, raceName, raceDescription, boatClass, hostname, port, startList, windStore, gpsFixStore, domainFactory, - this, trackedRegattaRegistry, delayToLiveInMillis); + this, trackedRegattaRegistry, delayToLiveInMillis, useInternalMarkPassingAlgorithm); } @Override diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java index c99fb4ff1a3..7cd61b900e5 100644 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingRaceTrackerImpl.java @@ -82,6 +82,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen private final WindStore windStore; private final GPSFixStore gpsFixStore; private final boolean startListFromManage2Sail; + private final boolean useInternalMarkPassingAlgorithm; /** * Starts out as null and is set when the race definition has been created. When this happens, this object is @@ -108,17 +109,17 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen protected SwissTimingRaceTrackerImpl(String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, StartList startList, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, DomainFactory domainFactory, SwissTimingFactory factory, - TrackedRegattaRegistry trackedRegattaRegistry, long delayToLiveInMillis) throws InterruptedException, + TrackedRegattaRegistry trackedRegattaRegistry, long delayToLiveInMillis, boolean useInternalMarkPassingAlgorithm) throws InterruptedException, UnknownHostException, IOException, ParseException { this(domainFactory.getOrCreateDefaultRegatta(raceLogStore, regattaLogStore, raceID, boatClass, trackedRegattaRegistry), raceID, raceName, raceDescription, boatClass, hostname, port, startList, windStore, gpsFixStore, domainFactory, factory, trackedRegattaRegistry, - delayToLiveInMillis); + delayToLiveInMillis, useInternalMarkPassingAlgorithm); } protected SwissTimingRaceTrackerImpl(Regatta regatta, String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, StartList startList, WindStore windStore, GPSFixStore gpsFixStore, DomainFactory domainFactory, SwissTimingFactory factory, - TrackedRegattaRegistry trackedRegattaRegistry, long delayToLiveInMillis) throws InterruptedException, + TrackedRegattaRegistry trackedRegattaRegistry, long delayToLiveInMillis, boolean useInternalMarkPassingAlgorithm) throws InterruptedException, UnknownHostException, IOException, ParseException { super(); this.tmdMessageQueue = new TMDMessageQueue(this); @@ -138,6 +139,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen trackedRegatta = trackedRegattaRegistry.getOrCreateTrackedRegatta(regatta); this.delayToLiveInMillis = delayToLiveInMillis; this.competitorsByBoatId = new HashMap(); + this.useInternalMarkPassingAlgorithm = useInternalMarkPassingAlgorithm; } @Override @@ -328,69 +330,72 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen public void receivedTimingData(String raceID, String boatID, List> markIndicesRanksAndTimesSinceStartInMilliseconds) { assert this.raceID.equals(raceID); - if (isTrackedRaceStillReachable()) { - Competitor competitor = getCompetitorByBoatIDAndRaceIDOrBoatClass(boatID, raceID, boatClass); - if (competitor == null) { - logger.info("Received timing data for boat ID " + boatID + " in race " + raceID - + " but couldn't find a competitor with that boat ID in this race. Ignoring."); - } else { - // the list of mark indices and time stamps is partial and usually only shows the last mark passing; - // we need to use this to *update* the competitor's mark passings list, not *replace* it - TreeMap markPassingsByMarkIndex = new TreeMap(); - // now fill with the already existing mark passings for the competitor identified by boatID... - NavigableSet markPassings = trackedRace.getMarkPassings(competitor); - trackedRace.lockForRead(markPassings); - try { - for (MarkPassing markPassing : markPassings) { - markPassingsByMarkIndex.put( - trackedRace.getRace().getCourse().getIndexOfWaypoint(markPassing.getWaypoint()), - markPassing); - } - } finally { - trackedRace.unlockAfterRead(markPassings); - } - // ...and then overwrite those for which we received "new evidence" - for (com.sap.sse.common.Util.Triple markIndexRankAndTimeSinceStartInMilliseconds : markIndicesRanksAndTimesSinceStartInMilliseconds) { - Waypoint waypoint = Util.get(trackedRace.getRace().getCourse().getWaypoints(), - markIndexRankAndTimeSinceStartInMilliseconds.getA()); - // If the rank and time information is empty, we interpret this by clearing the mark rounding if any - // (see - // also bug 1911): - if (markIndexRankAndTimeSinceStartInMilliseconds.getC() == null) { - markPassingsByMarkIndex.remove(markIndexRankAndTimeSinceStartInMilliseconds.getA()); - } else { - // update mark passing only if we have a start time; guessed start times don't make sense and - // for the start line would lead subsequent calls to getStartOfRace() return that guessed start - // time - // which then cannot be identified as "guessed" anymore... - if (trackedRace.getStartOfRace() != null) { - final TimePoint startTime = trackedRace.getStartOfRace(); - MillisecondsTimePoint timePoint = new MillisecondsTimePoint(startTime.asMillis() - + markIndexRankAndTimeSinceStartInMilliseconds.getC()); - MarkPassing markPassing = domainFactory.createMarkPassing(timePoint, waypoint, - getCompetitorByBoatIDAndRaceIDOrBoatClass(boatID, raceID, boatClass)); - markPassingsByMarkIndex.put(markIndexRankAndTimeSinceStartInMilliseconds.getA(), + if (!useInternalMarkPassingAlgorithm) { + if (isTrackedRaceStillReachable()) { + Competitor competitor = getCompetitorByBoatIDAndRaceIDOrBoatClass(boatID, raceID, boatClass); + if (competitor == null) { + logger.info("Received timing data for boat ID " + boatID + " in race " + raceID + + " but couldn't find a competitor with that boat ID in this race. Ignoring."); + } else { + // the list of mark indices and time stamps is partial and usually only shows the last mark passing; + // we need to use this to *update* the competitor's mark passings list, not *replace* it + TreeMap markPassingsByMarkIndex = new TreeMap(); + // now fill with the already existing mark passings for the competitor identified by boatID... + NavigableSet markPassings = trackedRace.getMarkPassings(competitor); + trackedRace.lockForRead(markPassings); + try { + for (MarkPassing markPassing : markPassings) { + markPassingsByMarkIndex.put( + trackedRace.getRace().getCourse().getIndexOfWaypoint(markPassing.getWaypoint()), markPassing); + } + } finally { + trackedRace.unlockAfterRead(markPassings); + } + // ...and then overwrite those for which we received "new evidence" + for (com.sap.sse.common.Util.Triple markIndexRankAndTimeSinceStartInMilliseconds : markIndicesRanksAndTimesSinceStartInMilliseconds) { + Waypoint waypoint = Util.get(trackedRace.getRace().getCourse().getWaypoints(), + markIndexRankAndTimeSinceStartInMilliseconds.getA()); + // If the rank and time information is empty, we interpret this by clearing the mark rounding if + // any (see also bug 1911): + if (markIndexRankAndTimeSinceStartInMilliseconds.getC() == null) { + markPassingsByMarkIndex.remove(markIndexRankAndTimeSinceStartInMilliseconds.getA()); } else { - // - logger.warning("Received mark passing with time relative to start of race " - + trackedRace.getRace().getName() + " before having received a race start time." - + " Queueing message for re-application when a start time has been received."); - tmdMessageQueue.enqueue(raceID, boatID, markIndicesRanksAndTimesSinceStartInMilliseconds); + // update mark passing only if we have a start time; guessed start times don't make sense and + // for the start line would lead subsequent calls to getStartOfRace() return that guessed + // start time which then cannot be identified as "guessed" anymore... + if (trackedRace.getStartOfRace() != null) { + final TimePoint startTime = trackedRace.getStartOfRace(); + MillisecondsTimePoint timePoint = new MillisecondsTimePoint(startTime.asMillis() + + markIndexRankAndTimeSinceStartInMilliseconds.getC()); + MarkPassing markPassing = domainFactory.createMarkPassing(timePoint, waypoint, + getCompetitorByBoatIDAndRaceIDOrBoatClass(boatID, raceID, boatClass)); + markPassingsByMarkIndex.put(markIndexRankAndTimeSinceStartInMilliseconds.getA(), + markPassing); + } else { + logger.warning("Received mark passing with time relative to start of race " + + trackedRace.getRace().getName() + + " before having received a race start time." + + " Queueing message for re-application when a start time has been received."); + tmdMessageQueue.enqueue(raceID, boatID, + markIndicesRanksAndTimesSinceStartInMilliseconds); + } } } + trackedRace.updateMarkPassings(competitor, markPassingsByMarkIndex.values()); + } + } else { + if (!loggedIgnore) { + logger.info("Ignoring timing data " + + markIndicesRanksAndTimesSinceStartInMilliseconds + + " for SwissTiming race " + + raceID + + " because tracked race is no longer reachable. Was the race removed but is still tracked? " + + "(Future occurrences of this message will be suppressed)"); + loggedIgnore = true; } - trackedRace.updateMarkPassings(competitor, markPassingsByMarkIndex.values()); - } - } else { - if (!loggedIgnore) { - logger.info("Ignoring timing data " + markIndicesRanksAndTimesSinceStartInMilliseconds + " for SwissTiming race " + raceID - + " because tracked race is no longer reachable. Was the race removed but is still tracked? "+ - "(Future occurrences of this message will be suppressed)"); - loggedIgnore = true; } } - } @Override @@ -470,7 +475,7 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen // we already know our single RaceDefinition assert SwissTimingRaceTrackerImpl.this.race == race; } - }); + }, useInternalMarkPassingAlgorithm); logger.info("Created SwissTiming RaceDefinition and TrackedRace for "+race.getName()); } diff --git a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java index 96f68297d16..7045b444af6 100755 --- a/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java +++ b/java/com.sap.sailing.domain.swisstimingadapter/src/com/sap/sailing/domain/swisstimingadapter/impl/SwissTimingTrackingConnectivityParameters.java @@ -27,10 +27,12 @@ public class SwissTimingTrackingConnectivityParameters implements RaceTrackingCo private final RegattaLogStore regattaLogStore; private final long delayToLiveInMillis; private final StartList startList; + private final boolean useInternalMarkPassingAlgorithm; public SwissTimingTrackingConnectivityParameters(String hostname, int port, String raceID, String raceName, String raceDescription, BoatClass boatClass, StartList startList, long delayToLiveInMillis, - SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore) { + SwissTimingFactory swissTimingFactory, DomainFactory domainFactory, RaceLogStore raceLogStore, + RegattaLogStore regattaLogStore, boolean useInternalMarkPassingAlgorithm) { super(); this.hostname = hostname; this.port = port; @@ -44,21 +46,23 @@ public class SwissTimingTrackingConnectivityParameters implements RaceTrackingCo this.domainFactory = domainFactory; this.raceLogStore = raceLogStore; this.regattaLogStore = regattaLogStore; + this.useInternalMarkPassingAlgorithm = useInternalMarkPassingAlgorithm; } @Override public RaceTracker createRaceTracker(TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore, GPSFixStore gpsFixStore) throws Exception { return swissTimingFactory.createRaceTracker(raceID, raceName, raceDescription, boatClass, hostname, port, - startList, delayToLiveInMillis, raceLogStore, regattaLogStore, windStore, gpsFixStore, domainFactory, - trackedRegattaRegistry); + startList, delayToLiveInMillis, raceLogStore, regattaLogStore, windStore, gpsFixStore, useInternalMarkPassingAlgorithm, + domainFactory, trackedRegattaRegistry); } @Override - public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, WindStore windStore, GPSFixStore gpsFixStore) - throws Exception { - return swissTimingFactory.createRaceTracker(regatta, raceID, raceName, raceDescription, boatClass, hostname, port, startList, delayToLiveInMillis, windStore, gpsFixStore, domainFactory, - trackedRegattaRegistry); + public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, + WindStore windStore, GPSFixStore gpsFixStore) throws Exception { + return swissTimingFactory.createRaceTracker(regatta, raceID, raceName, raceDescription, boatClass, hostname, + port, startList, delayToLiveInMillis, windStore, gpsFixStore, useInternalMarkPassingAlgorithm, + domainFactory, trackedRegattaRegistry); } @Override diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java index f46eaf3e03c..e67c8f56bfd 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter.test/src/com/sap/sailing/domain/swisstimingreplayadapter/test/SwissTimingReplayAdapterServiceTest.java @@ -178,7 +178,7 @@ public class SwissTimingReplayAdapterServiceTest { @Test public void testRaceData_SAW010955_20120802_WithDomainAdapter() throws Exception { SwissTimingReplayToDomainAdapter replayListener = new SwissTimingReplayToDomainAdapter(null, DomainFactory.INSTANCE, - new DummyTrackedRegattaRegistry()); + new DummyTrackedRegattaRegistry(), /* useInternalMarkPassingAlgorithm */ false); new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAW005906.20120805.replay"), replayListener); Iterable trackedRaces = replayListener.getTrackedRaces(); assertFalse(Util.isEmpty(trackedRaces)); @@ -192,7 +192,7 @@ public class SwissTimingReplayAdapterServiceTest { @Test public void testStartPerformanceDetection() throws Exception { SwissTimingReplayToDomainAdapter replayListener = new SwissTimingReplayToDomainAdapter(null, DomainFactory.INSTANCE, - new DummyTrackedRegattaRegistry()); + new DummyTrackedRegattaRegistry(), /* useInternalMarkPassingAlgorithm */ false); new SwissTimingReplayParserImpl().readData(getClass().getResourceAsStream("/SAW005906.20120805.replay"), replayListener); Iterable trackedRaces = replayListener.getTrackedRaces(); TrackedRace trackedRace = trackedRaces.iterator().next(); diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayService.java b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayService.java index 194ebeb8a38..7000cd9e137 100755 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayService.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/SwissTimingReplayService.java @@ -39,7 +39,8 @@ public interface SwissTimingReplayService { * @param regatta * the regatta to which the race shall be added; if null, a default regatta will be * created/used + * @param useInternalMarkPassingAlgorithm use our own instead of the SwissTiming-provided mark rounding / split times */ - void loadRaceData(String link, Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry); + void loadRaceData(String link, Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, boolean useInternalMarkPassingAlgorithm); } diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java index e375689fbfc..35215a778d8 100644 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayServiceImpl.java @@ -143,8 +143,8 @@ public class SwissTimingReplayServiceImpl implements SwissTimingReplayService { } @Override - public void loadRaceData(String link, Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry) { - SwissTimingReplayListener listener = new SwissTimingReplayToDomainAdapter(regatta, domainFactory, trackedRegattaRegistry); + public void loadRaceData(String link, Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry, boolean useInternalMarkPassingAlgorithm) { + SwissTimingReplayListener listener = new SwissTimingReplayToDomainAdapter(regatta, domainFactory, trackedRegattaRegistry, useInternalMarkPassingAlgorithm); loadRaceData(link, listener); } } diff --git a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java index 2d409415b0e..134b445e6db 100755 --- a/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java +++ b/java/com.sap.sailing.domain.swisstimingreplayadapter/src/com/sap/sailing/domain/swisstimingreplayadapter/impl/SwissTimingReplayToDomainAdapter.java @@ -133,13 +133,17 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter { private final Map lastNextMark; private RaceStatus lastRaceStatus; + + private final boolean useInternalMarkPassingAlgorithm; /** * @param regatta * the regatta to associate the race(s) received by the listener with, or null to force the * use / creation of a default regatta per race + * @param useInternalMarkPassingAlgorithm use our own instead of the SwissTiming-provided mark rounding / split times */ - public SwissTimingReplayToDomainAdapter(Regatta regatta, DomainFactory domainFactory, TrackedRegattaRegistry trackedRegattaRegistry) { + public SwissTimingReplayToDomainAdapter(Regatta regatta, DomainFactory domainFactory, + TrackedRegattaRegistry trackedRegattaRegistry, boolean useInternalMarkPassingAlgorithm) { this.regatta = regatta; this.trackedRegattaRegistry = trackedRegattaRegistry; racePerRaceID = new HashMap<>(); @@ -153,6 +157,7 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter { windAtControlPoint = new HashMap<>(); lastNextMark = new HashMap<>(); this.domainFactory = domainFactory; + this.useInternalMarkPassingAlgorithm = useInternalMarkPassingAlgorithm; } public Iterable getTrackedRaces() { @@ -260,7 +265,7 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter { short trueWindDirectionInDegrees) { final List markNames = new ArrayList<>(); markNames.add(id1.trim()); - if (id2 != null) { + if (id2 != null && !id2.trim().isEmpty()) { markNames.add(id2.trim()); } final ControlPoint controlPoint = domainFactory.getOrCreateControlPoint(markNames); @@ -311,7 +316,7 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter { createTrackedRace(race, Collections. emptyList(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE,TrackedRace.DEFAULT_LIVE_DELAY_IN_MILLISECONDS, WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, /* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(), - /* raceDefinitionSetToUpdate */ null); + /* raceDefinitionSetToUpdate */ null, useInternalMarkPassingAlgorithm); trackedRace.setStatus(new TrackedRaceStatusImpl(TrackedRaceStatusEnum.LOADING, 0)); TimePoint bestStartTimeKnownSoFar = bestStartTimePerRaceID.get(currentRaceID); if (bestStartTimeKnownSoFar != null) { @@ -349,7 +354,7 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter { trackedRace.recordFix(competitor, fix); Course course = trackedRace.getRace().getCourse(); // record a mark passing, but not if the mark passing has happened longer than 30s before the race start - if (bestStartTimePerRaceID.get(currentRaceID) != null && + if (!useInternalMarkPassingAlgorithm && bestStartTimePerRaceID.get(currentRaceID) != null && !bestStartTimePerRaceID.get(currentRaceID).after( raceTimePoint.plus(THRESHOLD_FOR_EARLIEST_MARK_PASSING_BEFORE_START_IN_MILLIS)) && (!lastNextMark.containsKey(competitor) || lastNextMark.get(competitor) != nextMark) && nextMark > 0) { diff --git a/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_5.mtb b/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_5.mtb new file mode 100755 index 00000000000..adae6c1cef3 Binary files /dev/null and b/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_5.mtb differ diff --git a/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_5.txt b/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_5.txt new file mode 100755 index 00000000000..c58a32592d0 --- /dev/null +++ b/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_5.txt @@ -0,0 +1,685 @@ +stored-uri:datafiles/event_20130424_IDMStarboo-Race_5.mtb +StartReplay:1 + +// Input from params_static: + +// Input from event_parameters: + +CompMarkerUUID:1 +PopUpRow1Type:Name +PopUpRow1Name: +PopUpRow2Type:Ini +PopUpRow2Name:Sail +PopUpRow3Type:RowNumber +PopUpRow3Name:Rank +PopUpRow4Type:BoatSpeed +PopUpRow4Name:Knots +PopUpRow5Type:Heading +PopUpRow5Name:Head +PopUpRow6Type:BoatAfter +PopUpRow6Name:After +CodanIsSponsor:0 +DefaultTailLength:60 +APzoomChangeTime:2500 +disableAutoProduce:0 +autoProduce:1 +APstartLineShowOfLeg:0.25 +APsideOfStartLine:0.30 +APstartSwitch:0.8 +APwholeLegBehindMarks:0.15 +APmarkSwitch:0.13 +APmarkZoomBehindMarks:0.10 +APmarkZoomInFrontOfMarks:0.20 +PopupMarkerUUID:2 +ManualUrl:http://www.tractrac.com/files/userguide_sailing.pdf +Marker1UUID:1 +Marker1Type:RGBMarkerFactory +Marker1Directional:1 +Marker1Image:markers/boat_FF0000_big.png +Marker1Length:5 +Marker1MinPixelSize:30 +Startlocation:8.122902,51.486942 +MaxDiffTime:1200 +MaxMetersPerSecond:20 +PrecisionMeters:20 +ShortAvgTime:180 +LongAvgTime:900 +TimeZone:2 +CompMarkerTimeout:60 +FramesPerSecond:8 +replayspeedup:5 +MatchRaceClock:0 +MatchRaceClockNo:3 +MatchRaceClockSpacing:300 +Minimap:1 +MinimapUseLayers:2 +Minimapgeorect:8.033638,51.51558,8.18676,51.460852 +MinimapMarkerUUID:2 +Marker4UUID:2 +Marker4Type:RGBmarkerFactory +Marker4Directional:1 +Marker4Image:markers/boat_FF0000_small.png +MetersPerPixel:18 +MetersPerPixelMax:30 +MetersPerPixelMin:0.05 +isMatchRace:0 +weatherEnabled:0 +showRaceClock:1 +BackGroundImage:graphics/background.jpg +DisableRotation:0 +ControlPointMarkerRadius:21 +disableselection:0 +disableleaderline:0 +DisableChangeRace:0 +ResultColumn1Name:# +ResultColumn1Size:20 +ResultColumn1Type:RowNumber +ResultColumn2Size:20 +ResultColumn2Type:ColorMarker +ResultColumn3Name:sail +ResultColumn3Size:45 +ResultColumn3Type:Ini +ResultColumn4Name:name +ResultColumn4Size:110 +ResultColumn4Type:Name +ResultColumn5Name:after +ResultColumn5Size:45 +ResultColumn5Type:BoatAfter +ResultColumn6Name:kts +ResultColumn6Size:40 +ResultColumn6Type:BoatSpeed +DisableLeaderboard:0 +DisableAnalysis:0 +TableFontSize:11 +//live-uri:tcp://germanmaster.traclive.dk:4400 +//stored-uri:tcp://germanmaster.traclive.dk:4401 +compInitialsFontSize:11 +AutoRotate:0 +MassStart:0 +SegmentFile:segments.php +StartType:Mass +ReCalcDirAndSpeed:0 +RaceHandicapSystem:NONE +TeamCompetitionType:NONE +LiveDelaySecs: 20 + +//-------- AUTO GENERATED PARAMETERS START ---------------- + +EventID:e1995602-acc8-11e2-89c0-10bf48d758ce +EventName:IDM Starboot 2013 +EventDB:event_20130424_IDMStarboo +EventStartTime:2013-04-24 10:00:00 +EventEndTime:2013-05-05 23:00:00 + +RaceID:5e8fc7c6-b409-11e2-8561-10bf48d758ce +RaceName:Race 5 +RaceStartTime:2013-05-04 12:50:00 +RaceEndTime: +RaceTrackingStartTime:2013-05-04 12:44:00 +RaceTrackingEndTime:2013-05-04 13:46:15 +RaceDefaultRouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce +RaceHandicapSystem:NONE + +Class1UUID:dbe67500-b308-11e2-8561-10bf48d758ce +Class1Name:Star + +Team1UUID:3a798f60-b3f1-11e2-8561-10bf48d758ce +Team1Name:Team +Team1Comp1UUID:87a8f43c-b3f2-11e2-8561-10bf48d758ce +Team1Comp2UUID:8999a2dc-b3f2-11e2-8561-10bf48d758ce +Team1Comp3UUID:84737a80-b3f2-11e2-8561-10bf48d758ce +Team1Comp4UUID:84f68cf4-b3f2-11e2-8561-10bf48d758ce +Team1Comp5UUID:8ba307da-b3f2-11e2-8561-10bf48d758ce +Team1Comp6UUID:85dac766-b3f2-11e2-8561-10bf48d758ce +Team1Comp7UUID:82ddec3c-b3f2-11e2-8561-10bf48d758ce +Team1Comp8UUID:88920d16-b3f2-11e2-8561-10bf48d758ce +Team1Comp9UUID:88e60efc-b3f2-11e2-8561-10bf48d758ce +Team1Comp10UUID:86286462-b3f2-11e2-8561-10bf48d758ce +Team1Comp11UUID:86c9c3fc-b3f2-11e2-8561-10bf48d758ce +Team1Comp12UUID:8939a094-b3f2-11e2-8561-10bf48d758ce +Team1Comp13UUID:83a1f6fe-b3f2-11e2-8561-10bf48d758ce +Team1Comp14UUID:8cadf6d0-b3f2-11e2-8561-10bf48d758ce +Team1Comp15UUID:858f812a-b3f2-11e2-8561-10bf48d758ce +Team1Comp16UUID:8c003dba-b3f2-11e2-8561-10bf48d758ce +Team1Comp17UUID:867dd88e-b3f2-11e2-8561-10bf48d758ce +Team1Comp18UUID:8cfb82c4-b3f2-11e2-8561-10bf48d758ce +Team1Comp19UUID:83cc72c6-b3f2-11e2-8561-10bf48d758ce +Team1Comp20UUID:883da6ea-b3f2-11e2-8561-10bf48d758ce +Team1Comp21UUID:8339fee6-b3f2-11e2-8561-10bf48d758ce +Team1Comp22UUID:843187ec-b3f2-11e2-8561-10bf48d758ce +Team1Comp23UUID:8c57247c-b3f2-11e2-8561-10bf48d758ce +Team1Comp24UUID:8a3c52b6-b3f2-11e2-8561-10bf48d758ce +Team1Comp25UUID:875c9aec-b3f2-11e2-8561-10bf48d758ce +Team1Comp26UUID:8b05a148-b3f2-11e2-8561-10bf48d758ce +Team1Comp27UUID:83023b82-b3f2-11e2-8561-10bf48d758ce +Team1Comp28UUID:87f4d898-b3f2-11e2-8561-10bf48d758ce +Team1Comp29UUID:89e9dd1a-b3f2-11e2-8561-10bf48d758ce +Team1Comp30UUID:8aaca2a0-b3f2-11e2-8561-10bf48d758ce +Team1Comp31UUID:83550484-b3f2-11e2-8561-10bf48d758ce +Team1Comp32UUID:837c17cc-b3f2-11e2-8561-10bf48d758ce +Team1Comp33UUID:83237d2e-b3f2-11e2-8561-10bf48d758ce +Team1Comp34UUID:83fa5e70-b3f2-11e2-8561-10bf48d758ce +Team1Comp35UUID:871283e4-b3f2-11e2-8561-10bf48d758ce +Team1Comp36UUID:8b56f32c-b3f2-11e2-8561-10bf48d758ce +Team1Comp37UUID:84b5e8d4-b3f2-11e2-8561-10bf48d758ce +Team1Comp38UUID:853ca13a-b3f2-11e2-8561-10bf48d758ce +Team1Comp39UUID:8d50d12a-b3f2-11e2-8561-10bf48d758ce + +Comp1UUID:82ddec3c-b3f2-11e2-8561-10bf48d758ce +Comp1Name:Fallais, Dominik,Fallais, Simo +Comp1NameShort:GER7 +Comp1Color:000080 +Comp1Nationality:GER +Comp1Description:0 +Comp1Handicap:1.0000000000 +Comp1HandicapDistance:0.00000 +Comp1StartTime:2013-05-04 12:50:00 +Comp1ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp1RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp2UUID:83023b82-b3f2-11e2-8561-10bf48d758ce +Comp2Name:Nolte, Robin,Dieckmann, Hennin +Comp2NameShort:GER433 +Comp2Color:000000 +Comp2Nationality:GER +Comp2Description:0 +Comp2Handicap:1.0000000000 +Comp2HandicapDistance:0.00000 +Comp2StartTime:2013-05-04 12:50:00 +Comp2ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp2RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp3UUID:83237d2e-b3f2-11e2-8561-10bf48d758ce +Comp3Name:Seekamp, Jan,Müller Gösta +Comp3NameShort:GER1122 +Comp3Color:00FF00 +Comp3Nationality:GER +Comp3Description:0 +Comp3Handicap:1.0000000000 +Comp3HandicapDistance:0.00000 +Comp3StartTime:2013-05-04 12:50:00 +Comp3ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp3RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp4UUID:8339fee6-b3f2-11e2-8561-10bf48d758ce +Comp4Name:Luxa, Claus-Peter,Röttger, Rol +Comp4NameShort:GER7013 +Comp4Color:800000 +Comp4Nationality:GER +Comp4Description:0 +Comp4Handicap:1.0000000000 +Comp4HandicapDistance:0.00000 +Comp4StartTime:2013-05-04 12:50:00 +Comp4ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp4RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp5UUID:83550484-b3f2-11e2-8561-10bf48d758ce +Comp5Name:Schulte, Gert,Schulte, Gerrit +Comp5NameShort:GER7417 +Comp5Color:FFFF00 +Comp5Nationality:GER +Comp5Description:0 +Comp5Handicap:1.0000000000 +Comp5HandicapDistance:0.00000 +Comp5StartTime:2013-05-04 12:50:00 +Comp5ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp5RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp6UUID:837c17cc-b3f2-11e2-8561-10bf48d758ce +Comp6Name:Seefelder, Jürgen,Seefelder, N +Comp6NameShort:GER7616 +Comp6Color:00FFFF +Comp6Nationality:GER +Comp6Description:0 +Comp6Handicap:1.0000000000 +Comp6HandicapDistance:0.00000 +Comp6StartTime:2013-05-04 12:50:00 +Comp6ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp6RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp7UUID:83a1f6fe-b3f2-11e2-8561-10bf48d758ce +Comp7Name:Hoffmann, Peter,Theis, Nina +Comp7NameShort:GER7693 +Comp7Color:0000FF +Comp7Nationality:GER +Comp7Description:0 +Comp7Handicap:1.0000000000 +Comp7HandicapDistance:0.00000 +Comp7StartTime:2013-05-04 12:50:00 +Comp7ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp7RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp8UUID:83cc72c6-b3f2-11e2-8561-10bf48d758ce +Comp8Name:Kohler, Thomas,Roszinski, Dr. +Comp8NameShort:GER7727 +Comp8Color:800080 +Comp8Nationality:GER +Comp8Description:0 +Comp8Handicap:1.0000000000 +Comp8HandicapDistance:0.00000 +Comp8StartTime:2013-05-04 12:50:00 +Comp8ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp8RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp9UUID:83fa5e70-b3f2-11e2-8561-10bf48d758ce +Comp9Name:Spehr, Helge,Spehr, Jesper +Comp9NameShort:GER7747 +Comp9Color:008080 +Comp9Nationality:GER +Comp9Description:0 +Comp9Handicap:1.0000000000 +Comp9HandicapDistance:0.00000 +Comp9StartTime:2013-05-04 12:50:00 +Comp9ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp9RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp10UUID:843187ec-b3f2-11e2-8561-10bf48d758ce +Comp10Name:Massler, Julian,Seeberger, Uli +Comp10NameShort:GER7860 +Comp10Color:808000 +Comp10Nationality:GER +Comp10Description:0 +Comp10Handicap:1.0000000000 +Comp10HandicapDistance:0.00000 +Comp10StartTime:2013-05-04 12:50:00 +Comp10ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp10RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp11UUID:84737a80-b3f2-11e2-8561-10bf48d758ce +Comp11Name:Behn, Philip,Kaerger, Andreas +Comp11NameShort:GER7866 +Comp11Color:FF0000 +Comp11Nationality:GER +Comp11Description:0 +Comp11Handicap:1.0000000000 +Comp11HandicapDistance:0.00000 +Comp11StartTime:2013-05-04 12:50:00 +Comp11ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp11RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp12UUID:84b5e8d4-b3f2-11e2-8561-10bf48d758ce +Comp12Name:Tusch, Frank,Winkelmann, Sven +Comp12NameShort:GER7897 +Comp12Color:FFFFFF +Comp12Nationality:GER +Comp12Description:0 +Comp12Handicap:1.0000000000 +Comp12HandicapDistance:0.00000 +Comp12StartTime:2013-05-04 12:50:00 +Comp12ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp12RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp13UUID:84f68cf4-b3f2-11e2-8561-10bf48d758ce +Comp13Name:Bode, Klaus,Bode, Alexander +Comp13NameShort:GER7898 +Comp13Color:FF00FF +Comp13Nationality:GER +Comp13Description:0 +Comp13Handicap:1.0000000000 +Comp13HandicapDistance:0.00000 +Comp13StartTime:2013-05-04 12:50:00 +Comp13ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp13RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp14UUID:853ca13a-b3f2-11e2-8561-10bf48d758ce +Comp14Name:Vater, Ulrich,Nolte, Andreas +Comp14NameShort:GER7971 +Comp14Color:008000 +Comp14Nationality:GER +Comp14Description:0 +Comp14Handicap:1.0000000000 +Comp14HandicapDistance:0.00000 +Comp14StartTime:2013-05-04 12:50:00 +Comp14ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp14RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp15UUID:858f812a-b3f2-11e2-8561-10bf48d758ce +Comp15Name:Janson, Jürgen,Schulte, Axel +Comp15NameShort:GER7981 +Comp15Color:000080 +Comp15Nationality:GER +Comp15Description:0 +Comp15Handicap:1.0000000000 +Comp15HandicapDistance:0.00000 +Comp15StartTime:2013-05-04 12:50:00 +Comp15ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp15RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp16UUID:85dac766-b3f2-11e2-8561-10bf48d758ce +Comp16Name:Diercks, Jochen,Braasch, Herbe +Comp16NameShort:GER8055 +Comp16Color:000000 +Comp16Nationality:GER +Comp16Description:0 +Comp16Handicap:1.0000000000 +Comp16HandicapDistance:0.00000 +Comp16StartTime:2013-05-04 12:50:00 +Comp16ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp16RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp17UUID:86286462-b3f2-11e2-8561-10bf48d758ce +Comp17Name:Hagen, Alexander,Hasche, Marko +Comp17NameShort:GER8093 +Comp17Color:00FF00 +Comp17Nationality:GER +Comp17Description:0 +Comp17Handicap:1.0000000000 +Comp17HandicapDistance:0.00000 +Comp17StartTime:2013-05-04 12:50:00 +Comp17ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp17RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp18UUID:867dd88e-b3f2-11e2-8561-10bf48d758ce +Comp18Name:Kappes, Klaus,Rutz, Steffen +Comp18NameShort:GER8122 +Comp18Color:800000 +Comp18Nationality:GER +Comp18Description:0 +Comp18Handicap:1.0000000000 +Comp18HandicapDistance:0.00000 +Comp18StartTime:2013-05-04 12:50:00 +Comp18ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp18RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp19UUID:86c9c3fc-b3f2-11e2-8561-10bf48d758ce +Comp19Name:Hannemann, Uwe,Dahms, Anette +Comp19NameShort:GER8124 +Comp19Color:FFFF00 +Comp19Nationality:GER +Comp19Description:0 +Comp19Handicap:1.0000000000 +Comp19HandicapDistance:0.00000 +Comp19StartTime:2013-05-04 12:50:00 +Comp19ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp19RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp20UUID:871283e4-b3f2-11e2-8561-10bf48d758ce +Comp20Name:Stoll, Bernd,Stoll, Barbara +Comp20NameShort:GER8147 +Comp20Color:00FFFF +Comp20Nationality:GER +Comp20Description:0 +Comp20Handicap:1.0000000000 +Comp20HandicapDistance:0.00000 +Comp20StartTime:2013-05-04 12:50:00 +Comp20ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp20RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp21UUID:875c9aec-b3f2-11e2-8561-10bf48d758ce +Comp21Name:Meyer, Klaus,Müller, Jörg +Comp21NameShort:GER8179 +Comp21Color:0000FF +Comp21Nationality:GER +Comp21Description:0 +Comp21Handicap:1.0000000000 +Comp21HandicapDistance:0.00000 +Comp21StartTime:2013-05-04 12:50:00 +Comp21ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp21RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp22UUID:87a8f43c-b3f2-11e2-8561-10bf48d758ce +Comp22Name:Antrecht, Alexander,Schwill, F +Comp22NameShort:GER8190 +Comp22Color:800080 +Comp22Nationality:GER +Comp22Description:0 +Comp22Handicap:1.0000000000 +Comp22HandicapDistance:0.00000 +Comp22StartTime:2013-05-04 12:50:00 +Comp22ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp22RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp23UUID:87f4d898-b3f2-11e2-8561-10bf48d758ce +Comp23Name:Pieper, Josef,Vockel, Ralf +Comp23NameShort:GER8233 +Comp23Color:008080 +Comp23Nationality:GER +Comp23Description:0 +Comp23Handicap:1.0000000000 +Comp23HandicapDistance:0.00000 +Comp23StartTime:2013-05-04 12:50:00 +Comp23ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp23RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp24UUID:883da6ea-b3f2-11e2-8561-10bf48d758ce +Comp24Name:König, Kilian,Schäfer, Kai +Comp24NameShort:GER8301 +Comp24Color:808000 +Comp24Nationality:GER +Comp24Description:0 +Comp24Handicap:1.0000000000 +Comp24HandicapDistance:0.00000 +Comp24StartTime:2013-05-04 12:50:00 +Comp24ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp24RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp25UUID:88920d16-b3f2-11e2-8561-10bf48d758ce +Comp25Name:Glunde, Arnd,Beltz, Marcel +Comp25NameShort:GER8321 +Comp25Color:FF0000 +Comp25Nationality:GER +Comp25Description:0 +Comp25Handicap:1.0000000000 +Comp25HandicapDistance:0.00000 +Comp25StartTime:2013-05-04 12:50:00 +Comp25ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp25RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp26UUID:88e60efc-b3f2-11e2-8561-10bf48d758ce +Comp26Name:Griese, Achim,Marcour, Michael +Comp26NameShort:GER8340 +Comp26Color:FFFFFF +Comp26Nationality:GER +Comp26Description:0 +Comp26Handicap:1.0000000000 +Comp26HandicapDistance:0.00000 +Comp26StartTime:2013-05-04 12:50:00 +Comp26ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp26RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp27UUID:8939a094-b3f2-11e2-8561-10bf48d758ce +Comp27Name:Hellmich, Joachim,Lacher, Stef +Comp27NameShort:GER8412 +Comp27Color:FF00FF +Comp27Nationality:GER +Comp27Description:0 +Comp27Handicap:1.0000000000 +Comp27HandicapDistance:0.00000 +Comp27StartTime:2013-05-04 12:50:00 +Comp27ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp27RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp28UUID:8999a2dc-b3f2-11e2-8561-10bf48d758ce +Comp28Name:Babendererde, Johannes,Voigt, +Comp28NameShort:GER8433 +Comp28Color:008000 +Comp28Nationality:GER +Comp28Description:0 +Comp28Handicap:1.0000000000 +Comp28HandicapDistance:0.00000 +Comp28StartTime:2013-05-04 12:50:00 +Comp28ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp28RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp29UUID:89e9dd1a-b3f2-11e2-8561-10bf48d758ce +Comp29Name:Polgar, Johannes,Koy, Markus +Comp29NameShort:GER8442 +Comp29Color:000080 +Comp29Nationality:GER +Comp29Description:0 +Comp29Handicap:1.0000000000 +Comp29HandicapDistance:0.00000 +Comp29StartTime:2013-05-04 12:50:00 +Comp29ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp29RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp30UUID:8a3c52b6-b3f2-11e2-8561-10bf48d758ce +Comp30Name:Merkelbach, Hubert,Kleen, Frit +Comp30NameShort:GER8450 +Comp30Color:000000 +Comp30Nationality:GER +Comp30Description:0 +Comp30Handicap:1.0000000000 +Comp30HandicapDistance:0.00000 +Comp30StartTime:2013-05-04 12:50:00 +Comp30ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp30RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp31UUID:8aaca2a0-b3f2-11e2-8561-10bf48d758ce +Comp31Name:Schmidt, Reinhard,Hentschel, N +Comp31NameShort:GER8478 +Comp31Color:00FF00 +Comp31Nationality:GER +Comp31Description:0 +Comp31Handicap:1.0000000000 +Comp31HandicapDistance:0.00000 +Comp31StartTime:2013-05-04 12:50:00 +Comp31ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp31RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp32UUID:8b05a148-b3f2-11e2-8561-10bf48d758ce +Comp32Name:Müllejans, Christian,Morf, Kar +Comp32NameShort:ITA8400 +Comp32Color:800000 +Comp32Nationality:ITA +Comp32Description:0 +Comp32Handicap:1.0000000000 +Comp32HandicapDistance:0.00000 +Comp32StartTime:2013-05-04 12:50:00 +Comp32ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp32RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp33UUID:8b56f32c-b3f2-11e2-8561-10bf48d758ce +Comp33Name:Tennigkeit, Alexander,Schöller +Comp33NameShort:SUI7948 +Comp33Color:FFFF00 +Comp33Nationality:SUI +Comp33Description:0 +Comp33Handicap:1.0000000000 +Comp33HandicapDistance:0.00000 +Comp33StartTime:2013-05-04 12:50:00 +Comp33ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp33RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp34UUID:8ba307da-b3f2-11e2-8561-10bf48d758ce +Comp34Name:Bondar, Pavlo,Malzev, Konstant +Comp34NameShort:UKR8119 +Comp34Color:00FFFF +Comp34Nationality:UKR +Comp34Description:0 +Comp34Handicap:1.0000000000 +Comp34HandicapDistance:0.00000 +Comp34StartTime:2013-05-04 12:50:00 +Comp34ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp34RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp35UUID:8c003dba-b3f2-11e2-8561-10bf48d758ce +Comp35Name:Jensen, Johnny +Comp35NameShort:DEN8124 +Comp35Color:0000FF +Comp35Nationality:DEN +Comp35Description:0 +Comp35Handicap:1.0000000000 +Comp35HandicapDistance:0.00000 +Comp35StartTime:2013-05-04 12:50:00 +Comp35ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp35RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp36UUID:8c57247c-b3f2-11e2-8561-10bf48d758ce +Comp36Name:Mellis, Jörg,Flugel, Falko +Comp36NameShort:GER816 +Comp36Color:800080 +Comp36Nationality:GER +Comp36Description:0 +Comp36Handicap:1.0000000000 +Comp36HandicapDistance:0.00000 +Comp36StartTime:2013-05-04 12:50:00 +Comp36ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp36RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp37UUID:8cadf6d0-b3f2-11e2-8561-10bf48d758ce +Comp37Name:Hülsmann, Thorsten +Comp37NameShort:GER7897 +Comp37Color:008080 +Comp37Nationality:GER +Comp37Description:0 +Comp37Handicap:1.0000000000 +Comp37HandicapDistance:0.00000 +Comp37StartTime:2013-05-04 12:50:00 +Comp37ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp37RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp38UUID:8cfb82c4-b3f2-11e2-8561-10bf48d758ce +Comp38Name:Kleine, Frieder +Comp38NameShort:GER8301 +Comp38Color:808000 +Comp38Nationality:GER +Comp38Description:0 +Comp38Handicap:1.0000000000 +Comp38HandicapDistance:0.00000 +Comp38StartTime:2013-05-04 12:50:00 +Comp38ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp38RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp39UUID:8d50d12a-b3f2-11e2-8561-10bf48d758ce +Comp39Name:von Linde, Florian,Steffens, T +Comp39NameShort:GER8419 +Comp39Color:FF0000 +Comp39Nationality:GER +Comp39Description:0 +Comp39Handicap:1.0000000000 +Comp39HandicapDistance:0.00000 +Comp39StartTime:2013-05-04 12:50:00 +Comp39ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp39RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +ControlPoint1UUID:166ab660-b3f3-11e2-8561-10bf48d758ce +ControlPoint1Name:Start/Ziel +ControlPoint1HasTwoPoints:1 +ControlPoint1ShortName:St +ControlPoint1Mark1Lon:8.076407 +ControlPoint1Mark1Lat:51.482072 +ControlPoint1Mark2Lon:8.073195 +ControlPoint1Mark2Lat:51.479408 + +ControlPoint2UUID:2b5bde8c-b3f3-11e2-8561-10bf48d758ce +ControlPoint2Name:Luvtonne +ControlPoint2HasTwoPoints:0 +ControlPoint2ShortName:Lu +ControlPoint2Mark1Lon:8.064945 +ControlPoint2Mark1Lat:51.485257 + +ControlPoint3UUID:3f999718-b3f3-11e2-8561-10bf48d758ce +ControlPoint3Name:Offset Luvtonne +ControlPoint3HasTwoPoints:0 +ControlPoint3ShortName:Of +ControlPoint3Mark1Lon:8.064112 +ControlPoint3Mark1Lat:51.484790 + +ControlPoint4UUID:b160887a-b3f3-11e2-8561-10bf48d758ce +ControlPoint4Name:Leegate +ControlPoint4HasTwoPoints:1 +ControlPoint4ShortName:Le +ControlPoint4Mark1Lon:8.076623 +ControlPoint4Mark1Lat:51.480175 +ControlPoint4Mark2Lon:8.076272 +ControlPoint4Mark2Lat:51.479722 + + +SegmentFile:segments.php +Route1UUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce +Route1Description:Windward-Leeward - 3 Laps +Route1Length:0 +Route1ControlPoint1UUID:166ab660-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint2UUID:2b5bde8c-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint3UUID:3f999718-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint4UUID:b160887a-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint5UUID:2b5bde8c-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint6UUID:3f999718-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint7UUID:b160887a-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint8UUID:2b5bde8c-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint9UUID:3f999718-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint10UUID:b160887a-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint11UUID:166ab660-b3f3-11e2-8561-10bf48d758ce + +//-------- AUTO GENERATED PARAMETERS END ------------------ diff --git a/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_6.mtb b/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_6.mtb new file mode 100755 index 00000000000..d271984959a Binary files /dev/null and b/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_6.mtb differ diff --git a/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_6.txt b/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_6.txt new file mode 100755 index 00000000000..e76cb6d0d04 --- /dev/null +++ b/java/com.sap.sailing.domain.test/resources/event_20130424_IDMStarboo-Race_6.txt @@ -0,0 +1,685 @@ +stored-uri:datafiles/event_20130424_IDMStarboo-Race_6.mtb +StartReplay:1 + +// Input from params_static: + +// Input from event_parameters: + +CompMarkerUUID:1 +PopUpRow1Type:Name +PopUpRow1Name: +PopUpRow2Type:Ini +PopUpRow2Name:Sail +PopUpRow3Type:RowNumber +PopUpRow3Name:Rank +PopUpRow4Type:BoatSpeed +PopUpRow4Name:Knots +PopUpRow5Type:Heading +PopUpRow5Name:Head +PopUpRow6Type:BoatAfter +PopUpRow6Name:After +CodanIsSponsor:0 +DefaultTailLength:60 +APzoomChangeTime:2500 +disableAutoProduce:0 +autoProduce:1 +APstartLineShowOfLeg:0.25 +APsideOfStartLine:0.30 +APstartSwitch:0.8 +APwholeLegBehindMarks:0.15 +APmarkSwitch:0.13 +APmarkZoomBehindMarks:0.10 +APmarkZoomInFrontOfMarks:0.20 +PopupMarkerUUID:2 +ManualUrl:http://www.tractrac.com/files/userguide_sailing.pdf +Marker1UUID:1 +Marker1Type:RGBMarkerFactory +Marker1Directional:1 +Marker1Image:markers/boat_FF0000_big.png +Marker1Length:5 +Marker1MinPixelSize:30 +Startlocation:8.122902,51.486942 +MaxDiffTime:1200 +MaxMetersPerSecond:20 +PrecisionMeters:20 +ShortAvgTime:180 +LongAvgTime:900 +TimeZone:2 +CompMarkerTimeout:60 +FramesPerSecond:8 +replayspeedup:5 +MatchRaceClock:0 +MatchRaceClockNo:3 +MatchRaceClockSpacing:300 +Minimap:1 +MinimapUseLayers:2 +Minimapgeorect:8.033638,51.51558,8.18676,51.460852 +MinimapMarkerUUID:2 +Marker4UUID:2 +Marker4Type:RGBmarkerFactory +Marker4Directional:1 +Marker4Image:markers/boat_FF0000_small.png +MetersPerPixel:18 +MetersPerPixelMax:30 +MetersPerPixelMin:0.05 +isMatchRace:0 +weatherEnabled:0 +showRaceClock:1 +BackGroundImage:graphics/background.jpg +DisableRotation:0 +ControlPointMarkerRadius:21 +disableselection:0 +disableleaderline:0 +DisableChangeRace:0 +ResultColumn1Name:# +ResultColumn1Size:20 +ResultColumn1Type:RowNumber +ResultColumn2Size:20 +ResultColumn2Type:ColorMarker +ResultColumn3Name:sail +ResultColumn3Size:45 +ResultColumn3Type:Ini +ResultColumn4Name:name +ResultColumn4Size:110 +ResultColumn4Type:Name +ResultColumn5Name:after +ResultColumn5Size:45 +ResultColumn5Type:BoatAfter +ResultColumn6Name:kts +ResultColumn6Size:40 +ResultColumn6Type:BoatSpeed +DisableLeaderboard:0 +DisableAnalysis:0 +TableFontSize:11 +//live-uri:tcp://germanmaster.traclive.dk:4400 +//stored-uri:tcp://germanmaster.traclive.dk:4401 +compInitialsFontSize:11 +AutoRotate:0 +MassStart:0 +SegmentFile:segments.php +StartType:Mass +ReCalcDirAndSpeed:0 +RaceHandicapSystem:NONE +TeamCompetitionType:NONE +LiveDelaySecs: 20 + +//-------- AUTO GENERATED PARAMETERS START ---------------- + +EventID:e1995602-acc8-11e2-89c0-10bf48d758ce +EventName:IDM Starboot 2013 +EventDB:event_20130424_IDMStarboo +EventStartTime:2013-04-24 10:00:00 +EventEndTime:2013-05-05 23:00:00 + +RaceID:5ee63a52-b409-11e2-8561-10bf48d758ce +RaceName:Race 6 +RaceStartTime:2013-05-04 14:00:00 +RaceEndTime: +RaceTrackingStartTime:2013-05-04 13:48:11 +RaceTrackingEndTime:2013-05-04 14:58:38 +RaceDefaultRouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce +RaceHandicapSystem:NONE + +Class1UUID:dbe67500-b308-11e2-8561-10bf48d758ce +Class1Name:Star + +Team1UUID:3a798f60-b3f1-11e2-8561-10bf48d758ce +Team1Name:Team +Team1Comp1UUID:87a8f43c-b3f2-11e2-8561-10bf48d758ce +Team1Comp2UUID:8999a2dc-b3f2-11e2-8561-10bf48d758ce +Team1Comp3UUID:84737a80-b3f2-11e2-8561-10bf48d758ce +Team1Comp4UUID:84f68cf4-b3f2-11e2-8561-10bf48d758ce +Team1Comp5UUID:8ba307da-b3f2-11e2-8561-10bf48d758ce +Team1Comp6UUID:85dac766-b3f2-11e2-8561-10bf48d758ce +Team1Comp7UUID:82ddec3c-b3f2-11e2-8561-10bf48d758ce +Team1Comp8UUID:88920d16-b3f2-11e2-8561-10bf48d758ce +Team1Comp9UUID:88e60efc-b3f2-11e2-8561-10bf48d758ce +Team1Comp10UUID:86286462-b3f2-11e2-8561-10bf48d758ce +Team1Comp11UUID:86c9c3fc-b3f2-11e2-8561-10bf48d758ce +Team1Comp12UUID:8939a094-b3f2-11e2-8561-10bf48d758ce +Team1Comp13UUID:83a1f6fe-b3f2-11e2-8561-10bf48d758ce +Team1Comp14UUID:8cadf6d0-b3f2-11e2-8561-10bf48d758ce +Team1Comp15UUID:858f812a-b3f2-11e2-8561-10bf48d758ce +Team1Comp16UUID:8c003dba-b3f2-11e2-8561-10bf48d758ce +Team1Comp17UUID:867dd88e-b3f2-11e2-8561-10bf48d758ce +Team1Comp18UUID:8cfb82c4-b3f2-11e2-8561-10bf48d758ce +Team1Comp19UUID:83cc72c6-b3f2-11e2-8561-10bf48d758ce +Team1Comp20UUID:883da6ea-b3f2-11e2-8561-10bf48d758ce +Team1Comp21UUID:8339fee6-b3f2-11e2-8561-10bf48d758ce +Team1Comp22UUID:843187ec-b3f2-11e2-8561-10bf48d758ce +Team1Comp23UUID:8c57247c-b3f2-11e2-8561-10bf48d758ce +Team1Comp24UUID:8a3c52b6-b3f2-11e2-8561-10bf48d758ce +Team1Comp25UUID:875c9aec-b3f2-11e2-8561-10bf48d758ce +Team1Comp26UUID:8b05a148-b3f2-11e2-8561-10bf48d758ce +Team1Comp27UUID:83023b82-b3f2-11e2-8561-10bf48d758ce +Team1Comp28UUID:87f4d898-b3f2-11e2-8561-10bf48d758ce +Team1Comp29UUID:89e9dd1a-b3f2-11e2-8561-10bf48d758ce +Team1Comp30UUID:8aaca2a0-b3f2-11e2-8561-10bf48d758ce +Team1Comp31UUID:83550484-b3f2-11e2-8561-10bf48d758ce +Team1Comp32UUID:837c17cc-b3f2-11e2-8561-10bf48d758ce +Team1Comp33UUID:83237d2e-b3f2-11e2-8561-10bf48d758ce +Team1Comp34UUID:83fa5e70-b3f2-11e2-8561-10bf48d758ce +Team1Comp35UUID:871283e4-b3f2-11e2-8561-10bf48d758ce +Team1Comp36UUID:8b56f32c-b3f2-11e2-8561-10bf48d758ce +Team1Comp37UUID:84b5e8d4-b3f2-11e2-8561-10bf48d758ce +Team1Comp38UUID:853ca13a-b3f2-11e2-8561-10bf48d758ce +Team1Comp39UUID:8d50d12a-b3f2-11e2-8561-10bf48d758ce + +Comp1UUID:82ddec3c-b3f2-11e2-8561-10bf48d758ce +Comp1Name:Fallais, Dominik,Fallais, Simo +Comp1NameShort:GER7 +Comp1Color:000080 +Comp1Nationality:GER +Comp1Description:0 +Comp1Handicap:1.0000000000 +Comp1HandicapDistance:0.00000 +Comp1StartTime:2013-05-04 14:00:00 +Comp1ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp1RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp2UUID:83023b82-b3f2-11e2-8561-10bf48d758ce +Comp2Name:Nolte, Robin,Dieckmann, Hennin +Comp2NameShort:GER433 +Comp2Color:000000 +Comp2Nationality:GER +Comp2Description:0 +Comp2Handicap:1.0000000000 +Comp2HandicapDistance:0.00000 +Comp2StartTime:2013-05-04 14:00:00 +Comp2ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp2RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp3UUID:83237d2e-b3f2-11e2-8561-10bf48d758ce +Comp3Name:Seekamp, Jan,Müller Gösta +Comp3NameShort:GER1122 +Comp3Color:00FF00 +Comp3Nationality:GER +Comp3Description:0 +Comp3Handicap:1.0000000000 +Comp3HandicapDistance:0.00000 +Comp3StartTime:2013-05-04 14:00:00 +Comp3ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp3RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp4UUID:8339fee6-b3f2-11e2-8561-10bf48d758ce +Comp4Name:Luxa, Claus-Peter,Röttger, Rol +Comp4NameShort:GER7013 +Comp4Color:800000 +Comp4Nationality:GER +Comp4Description:0 +Comp4Handicap:1.0000000000 +Comp4HandicapDistance:0.00000 +Comp4StartTime:2013-05-04 14:00:00 +Comp4ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp4RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp5UUID:83550484-b3f2-11e2-8561-10bf48d758ce +Comp5Name:Schulte, Gert,Schulte, Gerrit +Comp5NameShort:GER7417 +Comp5Color:FFFF00 +Comp5Nationality:GER +Comp5Description:0 +Comp5Handicap:1.0000000000 +Comp5HandicapDistance:0.00000 +Comp5StartTime:2013-05-04 14:00:00 +Comp5ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp5RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp6UUID:837c17cc-b3f2-11e2-8561-10bf48d758ce +Comp6Name:Seefelder, Jürgen,Seefelder, N +Comp6NameShort:GER7616 +Comp6Color:00FFFF +Comp6Nationality:GER +Comp6Description:0 +Comp6Handicap:1.0000000000 +Comp6HandicapDistance:0.00000 +Comp6StartTime:2013-05-04 14:00:00 +Comp6ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp6RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp7UUID:83a1f6fe-b3f2-11e2-8561-10bf48d758ce +Comp7Name:Hoffmann, Peter,Theis, Nina +Comp7NameShort:GER7693 +Comp7Color:0000FF +Comp7Nationality:GER +Comp7Description:0 +Comp7Handicap:1.0000000000 +Comp7HandicapDistance:0.00000 +Comp7StartTime:2013-05-04 14:00:00 +Comp7ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp7RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp8UUID:83cc72c6-b3f2-11e2-8561-10bf48d758ce +Comp8Name:Kohler, Thomas,Roszinski, Dr. +Comp8NameShort:GER7727 +Comp8Color:800080 +Comp8Nationality:GER +Comp8Description:0 +Comp8Handicap:1.0000000000 +Comp8HandicapDistance:0.00000 +Comp8StartTime:2013-05-04 14:00:00 +Comp8ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp8RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp9UUID:83fa5e70-b3f2-11e2-8561-10bf48d758ce +Comp9Name:Spehr, Helge,Spehr, Jesper +Comp9NameShort:GER7747 +Comp9Color:008080 +Comp9Nationality:GER +Comp9Description:0 +Comp9Handicap:1.0000000000 +Comp9HandicapDistance:0.00000 +Comp9StartTime:2013-05-04 14:00:00 +Comp9ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp9RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp10UUID:843187ec-b3f2-11e2-8561-10bf48d758ce +Comp10Name:Massler, Julian,Seeberger, Uli +Comp10NameShort:GER7860 +Comp10Color:808000 +Comp10Nationality:GER +Comp10Description:0 +Comp10Handicap:1.0000000000 +Comp10HandicapDistance:0.00000 +Comp10StartTime:2013-05-04 14:00:00 +Comp10ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp10RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp11UUID:84737a80-b3f2-11e2-8561-10bf48d758ce +Comp11Name:Behn, Philip,Kaerger, Andreas +Comp11NameShort:GER7866 +Comp11Color:FF0000 +Comp11Nationality:GER +Comp11Description:0 +Comp11Handicap:1.0000000000 +Comp11HandicapDistance:0.00000 +Comp11StartTime:2013-05-04 14:00:00 +Comp11ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp11RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp12UUID:84b5e8d4-b3f2-11e2-8561-10bf48d758ce +Comp12Name:Tusch, Frank,Winkelmann, Sven +Comp12NameShort:GER7897 +Comp12Color:FFFFFF +Comp12Nationality:GER +Comp12Description:0 +Comp12Handicap:1.0000000000 +Comp12HandicapDistance:0.00000 +Comp12StartTime:2013-05-04 14:00:00 +Comp12ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp12RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp13UUID:84f68cf4-b3f2-11e2-8561-10bf48d758ce +Comp13Name:Bode, Klaus,Bode, Alexander +Comp13NameShort:GER7898 +Comp13Color:FF00FF +Comp13Nationality:GER +Comp13Description:0 +Comp13Handicap:1.0000000000 +Comp13HandicapDistance:0.00000 +Comp13StartTime:2013-05-04 14:00:00 +Comp13ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp13RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp14UUID:853ca13a-b3f2-11e2-8561-10bf48d758ce +Comp14Name:Vater, Ulrich,Nolte, Andreas +Comp14NameShort:GER7971 +Comp14Color:008000 +Comp14Nationality:GER +Comp14Description:0 +Comp14Handicap:1.0000000000 +Comp14HandicapDistance:0.00000 +Comp14StartTime:2013-05-04 14:00:00 +Comp14ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp14RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp15UUID:858f812a-b3f2-11e2-8561-10bf48d758ce +Comp15Name:Janson, Jürgen,Schulte, Axel +Comp15NameShort:GER7981 +Comp15Color:000080 +Comp15Nationality:GER +Comp15Description:0 +Comp15Handicap:1.0000000000 +Comp15HandicapDistance:0.00000 +Comp15StartTime:2013-05-04 14:00:00 +Comp15ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp15RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp16UUID:85dac766-b3f2-11e2-8561-10bf48d758ce +Comp16Name:Diercks, Jochen,Braasch, Herbe +Comp16NameShort:GER8055 +Comp16Color:000000 +Comp16Nationality:GER +Comp16Description:0 +Comp16Handicap:1.0000000000 +Comp16HandicapDistance:0.00000 +Comp16StartTime:2013-05-04 14:00:00 +Comp16ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp16RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp17UUID:86286462-b3f2-11e2-8561-10bf48d758ce +Comp17Name:Hagen, Alexander,Hasche, Marko +Comp17NameShort:GER8093 +Comp17Color:00FF00 +Comp17Nationality:GER +Comp17Description:0 +Comp17Handicap:1.0000000000 +Comp17HandicapDistance:0.00000 +Comp17StartTime:2013-05-04 14:00:00 +Comp17ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp17RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp18UUID:867dd88e-b3f2-11e2-8561-10bf48d758ce +Comp18Name:Kappes, Klaus,Rutz, Steffen +Comp18NameShort:GER8122 +Comp18Color:800000 +Comp18Nationality:GER +Comp18Description:0 +Comp18Handicap:1.0000000000 +Comp18HandicapDistance:0.00000 +Comp18StartTime:2013-05-04 14:00:00 +Comp18ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp18RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp19UUID:86c9c3fc-b3f2-11e2-8561-10bf48d758ce +Comp19Name:Hannemann, Uwe,Dahms, Anette +Comp19NameShort:GER8124 +Comp19Color:FFFF00 +Comp19Nationality:GER +Comp19Description:0 +Comp19Handicap:1.0000000000 +Comp19HandicapDistance:0.00000 +Comp19StartTime:2013-05-04 14:00:00 +Comp19ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp19RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp20UUID:871283e4-b3f2-11e2-8561-10bf48d758ce +Comp20Name:Stoll, Bernd,Stoll, Barbara +Comp20NameShort:GER8147 +Comp20Color:00FFFF +Comp20Nationality:GER +Comp20Description:0 +Comp20Handicap:1.0000000000 +Comp20HandicapDistance:0.00000 +Comp20StartTime:2013-05-04 14:00:00 +Comp20ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp20RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp21UUID:875c9aec-b3f2-11e2-8561-10bf48d758ce +Comp21Name:Meyer, Klaus,Müller, Jörg +Comp21NameShort:GER8179 +Comp21Color:0000FF +Comp21Nationality:GER +Comp21Description:0 +Comp21Handicap:1.0000000000 +Comp21HandicapDistance:0.00000 +Comp21StartTime:2013-05-04 14:00:00 +Comp21ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp21RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp22UUID:87a8f43c-b3f2-11e2-8561-10bf48d758ce +Comp22Name:Antrecht, Alexander,Schwill, F +Comp22NameShort:GER8190 +Comp22Color:800080 +Comp22Nationality:GER +Comp22Description:0 +Comp22Handicap:1.0000000000 +Comp22HandicapDistance:0.00000 +Comp22StartTime:2013-05-04 14:00:00 +Comp22ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp22RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp23UUID:87f4d898-b3f2-11e2-8561-10bf48d758ce +Comp23Name:Pieper, Josef,Vockel, Ralf +Comp23NameShort:GER8233 +Comp23Color:008080 +Comp23Nationality:GER +Comp23Description:0 +Comp23Handicap:1.0000000000 +Comp23HandicapDistance:0.00000 +Comp23StartTime:2013-05-04 14:00:00 +Comp23ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp23RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp24UUID:883da6ea-b3f2-11e2-8561-10bf48d758ce +Comp24Name:König, Kilian,Schäfer, Kai +Comp24NameShort:GER8301 +Comp24Color:808000 +Comp24Nationality:GER +Comp24Description:0 +Comp24Handicap:1.0000000000 +Comp24HandicapDistance:0.00000 +Comp24StartTime:2013-05-04 14:00:00 +Comp24ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp24RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp25UUID:88920d16-b3f2-11e2-8561-10bf48d758ce +Comp25Name:Glunde, Arnd,Beltz, Marcel +Comp25NameShort:GER8321 +Comp25Color:FF0000 +Comp25Nationality:GER +Comp25Description:0 +Comp25Handicap:1.0000000000 +Comp25HandicapDistance:0.00000 +Comp25StartTime:2013-05-04 14:00:00 +Comp25ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp25RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp26UUID:88e60efc-b3f2-11e2-8561-10bf48d758ce +Comp26Name:Griese, Achim,Marcour, Michael +Comp26NameShort:GER8340 +Comp26Color:FFFFFF +Comp26Nationality:GER +Comp26Description:0 +Comp26Handicap:1.0000000000 +Comp26HandicapDistance:0.00000 +Comp26StartTime:2013-05-04 14:00:00 +Comp26ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp26RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp27UUID:8939a094-b3f2-11e2-8561-10bf48d758ce +Comp27Name:Hellmich, Joachim,Lacher, Stef +Comp27NameShort:GER8412 +Comp27Color:FF00FF +Comp27Nationality:GER +Comp27Description:0 +Comp27Handicap:1.0000000000 +Comp27HandicapDistance:0.00000 +Comp27StartTime:2013-05-04 14:00:00 +Comp27ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp27RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp28UUID:8999a2dc-b3f2-11e2-8561-10bf48d758ce +Comp28Name:Babendererde, Johannes,Voigt, +Comp28NameShort:GER8433 +Comp28Color:008000 +Comp28Nationality:GER +Comp28Description:0 +Comp28Handicap:1.0000000000 +Comp28HandicapDistance:0.00000 +Comp28StartTime:2013-05-04 14:00:00 +Comp28ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp28RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp29UUID:89e9dd1a-b3f2-11e2-8561-10bf48d758ce +Comp29Name:Polgar, Johannes,Koy, Markus +Comp29NameShort:GER8442 +Comp29Color:000080 +Comp29Nationality:GER +Comp29Description:0 +Comp29Handicap:1.0000000000 +Comp29HandicapDistance:0.00000 +Comp29StartTime:2013-05-04 14:00:00 +Comp29ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp29RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp30UUID:8a3c52b6-b3f2-11e2-8561-10bf48d758ce +Comp30Name:Merkelbach, Hubert,Kleen, Frit +Comp30NameShort:GER8450 +Comp30Color:000000 +Comp30Nationality:GER +Comp30Description:0 +Comp30Handicap:1.0000000000 +Comp30HandicapDistance:0.00000 +Comp30StartTime:2013-05-04 14:00:00 +Comp30ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp30RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp31UUID:8aaca2a0-b3f2-11e2-8561-10bf48d758ce +Comp31Name:Schmidt, Reinhard,Hentschel, N +Comp31NameShort:GER8478 +Comp31Color:00FF00 +Comp31Nationality:GER +Comp31Description:0 +Comp31Handicap:1.0000000000 +Comp31HandicapDistance:0.00000 +Comp31StartTime:2013-05-04 14:00:00 +Comp31ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp31RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp32UUID:8b05a148-b3f2-11e2-8561-10bf48d758ce +Comp32Name:Müllejans, Christian,Morf, Kar +Comp32NameShort:ITA8400 +Comp32Color:800000 +Comp32Nationality:ITA +Comp32Description:0 +Comp32Handicap:1.0000000000 +Comp32HandicapDistance:0.00000 +Comp32StartTime:2013-05-04 14:00:00 +Comp32ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp32RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp33UUID:8b56f32c-b3f2-11e2-8561-10bf48d758ce +Comp33Name:Tennigkeit, Alexander,Schöller +Comp33NameShort:SUI7948 +Comp33Color:FFFF00 +Comp33Nationality:SUI +Comp33Description:0 +Comp33Handicap:1.0000000000 +Comp33HandicapDistance:0.00000 +Comp33StartTime:2013-05-04 14:00:00 +Comp33ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp33RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp34UUID:8ba307da-b3f2-11e2-8561-10bf48d758ce +Comp34Name:Bondar, Pavlo,Malzev, Konstant +Comp34NameShort:UKR8119 +Comp34Color:00FFFF +Comp34Nationality:UKR +Comp34Description:0 +Comp34Handicap:1.0000000000 +Comp34HandicapDistance:0.00000 +Comp34StartTime:2013-05-04 14:00:00 +Comp34ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp34RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp35UUID:8c003dba-b3f2-11e2-8561-10bf48d758ce +Comp35Name:Jensen, Johnny +Comp35NameShort:DEN8124 +Comp35Color:0000FF +Comp35Nationality:DEN +Comp35Description:0 +Comp35Handicap:1.0000000000 +Comp35HandicapDistance:0.00000 +Comp35StartTime:2013-05-04 14:00:00 +Comp35ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp35RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp36UUID:8c57247c-b3f2-11e2-8561-10bf48d758ce +Comp36Name:Mellis, Jörg,Flugel, Falko +Comp36NameShort:GER816 +Comp36Color:800080 +Comp36Nationality:GER +Comp36Description:0 +Comp36Handicap:1.0000000000 +Comp36HandicapDistance:0.00000 +Comp36StartTime:2013-05-04 14:00:00 +Comp36ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp36RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp37UUID:8cadf6d0-b3f2-11e2-8561-10bf48d758ce +Comp37Name:Hülsmann, Thorsten +Comp37NameShort:GER7897 +Comp37Color:008080 +Comp37Nationality:GER +Comp37Description:0 +Comp37Handicap:1.0000000000 +Comp37HandicapDistance:0.00000 +Comp37StartTime:2013-05-04 14:00:00 +Comp37ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp37RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp38UUID:8cfb82c4-b3f2-11e2-8561-10bf48d758ce +Comp38Name:Kleine, Frieder +Comp38NameShort:GER8301 +Comp38Color:808000 +Comp38Nationality:GER +Comp38Description:0 +Comp38Handicap:1.0000000000 +Comp38HandicapDistance:0.00000 +Comp38StartTime:2013-05-04 14:00:00 +Comp38ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp38RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +Comp39UUID:8d50d12a-b3f2-11e2-8561-10bf48d758ce +Comp39Name:von Linde, Florian,Steffens, T +Comp39NameShort:GER8419 +Comp39Color:FF0000 +Comp39Nationality:GER +Comp39Description:0 +Comp39Handicap:1.0000000000 +Comp39HandicapDistance:0.00000 +Comp39StartTime:2013-05-04 14:00:00 +Comp39ClassUUID:dbe67500-b308-11e2-8561-10bf48d758ce +Comp39RouteUUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce + +ControlPoint1UUID:166ab660-b3f3-11e2-8561-10bf48d758ce +ControlPoint1Name:Start/Ziel +ControlPoint1HasTwoPoints:1 +ControlPoint1ShortName:St +ControlPoint1Mark1Lon:8.076375 +ControlPoint1Mark1Lat:51.481972 +ControlPoint1Mark2Lon:8.074255 +ControlPoint1Mark2Lat:51.480293 + +ControlPoint2UUID:2b5bde8c-b3f3-11e2-8561-10bf48d758ce +ControlPoint2Name:Luvtonne +ControlPoint2HasTwoPoints:0 +ControlPoint2ShortName:Lu +ControlPoint2Mark1Lon:8.064938 +ControlPoint2Mark1Lat:51.485242 + +ControlPoint3UUID:3f999718-b3f3-11e2-8561-10bf48d758ce +ControlPoint3Name:Offset Luvtonne +ControlPoint3HasTwoPoints:0 +ControlPoint3ShortName:Of +ControlPoint3Mark1Lon:8.064113 +ControlPoint3Mark1Lat:51.484798 + +ControlPoint4UUID:b160887a-b3f3-11e2-8561-10bf48d758ce +ControlPoint4Name:Leegate +ControlPoint4HasTwoPoints:1 +ControlPoint4ShortName:Le +ControlPoint4Mark1Lon:8.076637 +ControlPoint4Mark1Lat:51.480125 +ControlPoint4Mark2Lon:8.077277 +ControlPoint4Mark2Lat:51.480682 + + +SegmentFile:segments.php +Route1UUID:e2f725ce-b3f3-11e2-8561-10bf48d758ce +Route1Description:Windward-Leeward - 3 Laps +Route1Length:0 +Route1ControlPoint1UUID:166ab660-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint2UUID:2b5bde8c-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint3UUID:3f999718-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint4UUID:b160887a-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint5UUID:2b5bde8c-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint6UUID:3f999718-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint7UUID:b160887a-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint8UUID:2b5bde8c-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint9UUID:3f999718-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint10UUID:b160887a-b3f3-11e2-8561-10bf48d758ce +Route1ControlPoint11UUID:166ab660-b3f3-11e2-8561-10bf48d758ce + +//-------- AUTO GENERATED PARAMETERS END ------------------ diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseTest.java index f40799cb99d..b609ccd57a4 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseTest.java @@ -303,7 +303,7 @@ public class CourseTest { new RaceDefinitionImpl("Test Race", course, new BoatClassImpl("49er", /* upwind start */true), hasso), Collections. emptyList(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE,/* delayToLiveInMillis */3000, /* millisecondsOverWhichToAverageWind */30000, - /* millisecondsOverWhichToAverageSpeed */8000); + /* millisecondsOverWhichToAverageSpeed */8000, /*useMarkPassingCalculator*/ false); assertLegStructure(course, trackedRace); course.removeWaypoint(0); assertLegStructure(course, trackedRace); @@ -334,7 +334,7 @@ public class CourseTest { hasso), Collections. emptyList(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 3000, /* millisecondsOverWhichToAverageWind */ 30000, - /* millisecondsOverWhichToAverageSpeed */ 8000); + /* millisecondsOverWhichToAverageSpeed */ 8000, /*useMarkPassingCalculator*/ false); assertLegStructure(course, trackedRace); final WaypointImpl wp1_5 = new WaypointImpl(new MarkImpl("Test Mark 1.5")); assertWaypointIndexes(course); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateDuringNonAtomicSerializationTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateDuringNonAtomicSerializationTest.java index 74de470e028..4434046c244 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateDuringNonAtomicSerializationTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateDuringNonAtomicSerializationTest.java @@ -80,7 +80,7 @@ public class CourseUpdateDuringNonAtomicSerializationTest implements Serializabl trackedRace = new DynamicTrackedRaceImpl(trackedRegatta, race, Collections. emptySet(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */10000, /* millisecondsOverWhichToAverageWind */30000, /* millisecondsOverWhichToAverageSpeed */ - 7000) { + 7000, /*useMarkPassingCalculator*/ false) { private static final long serialVersionUID = 9114777576548711763L; @Override diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java index e2932e41b28..b7f07a53906 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/CourseUpdateTest.java @@ -88,7 +88,7 @@ public class CourseUpdateTest extends AbstractTracTracLiveTest { } }, /* delayToLiveInMillis */0l, /* millisecondsOverWhichToAverageWind */30000, /* simulator */null, /* courseDesignUpdateURI */ - null, /* tracTracUsername */null, /* tracTracPassword */null, getEventSubscriber(), getRaceSubscriber()) { + null, /* tracTracUsername */null, /* tracTracPassword */null, getEventSubscriber(), getRaceSubscriber(), /*ignoreTracTracMarkPassings*/false) { @Override protected void handleEvent(Triple event) { super.handleEvent(event); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java index b05c4afee5e..9e2febd92db 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/LeaderboardCourseChangeTest.java @@ -137,7 +137,7 @@ public class LeaderboardCourseChangeTest { TrackedRegatta mockedTrackedRegatta = createMockedTrackedRegatta(regatta); RaceDefinition mockedRace = createMockedRace(course, boatClass); TrackedRace spyedTrackedRace = spy(new DynamicTrackedRaceImpl(mockedTrackedRegatta, mockedRace, - new HashSet(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, 5000, 20000, 20000)); + new HashSet(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, 5000, 20000, 20000, /*useMarkPassingCalculator*/ false)); return spyedTrackedRace; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/MultipleClassesInRegattaTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/MultipleClassesInRegattaTest.java index 5c9b12bbc5a..f25fe97ef2c 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/MultipleClassesInRegattaTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/MultipleClassesInRegattaTest.java @@ -57,7 +57,7 @@ public class MultipleClassesInRegattaTest { httpAndHost + "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=cce678c8-97e6-11e0-9aed-406186cbf87c"), new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI), - /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ + /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword, TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY, @@ -68,7 +68,7 @@ public class MultipleClassesInRegattaTest { httpAndHost + "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=11290bd6-97e7-11e0-9aed-406186cbf87c"), new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI), - /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ + /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword, TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY, @@ -79,7 +79,7 @@ public class MultipleClassesInRegattaTest { httpAndHost + "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=39635b24-97e7-11e0-9aed-406186cbf87c"), new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI), - /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ + /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword, TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY, @@ -90,7 +90,7 @@ public class MultipleClassesInRegattaTest { httpAndHost + "/events/event_20110505_SailingTea/clientparams.php?event=event_20110505_SailingTea&race=04498426-7dfd-11e0-8236-406186cbf87c"), new URI(liveURI), new URI(storedURI), new URI(courseDesignUpdateURI), - /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ + /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, tracTracUsername, tracTracPassword, TracTracConnectionConstants.ONLINE_STATUS, TracTracConnectionConstants.ONLINE_VISIBILITY, diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OnlineTracTracBasedTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OnlineTracTracBasedTest.java index 5918eda0c56..37a59dba5e8 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OnlineTracTracBasedTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/OnlineTracTracBasedTest.java @@ -114,7 +114,7 @@ public abstract class OnlineTracTracBasedTest extends AbstractTracTracLiveTest { for (Receiver r : domainFactory.getUpdateReceivers(trackedRegatta, getTracTracRace(), EmptyWindStore.INSTANCE, /* delayToLiveInMillis */0l, /* simulator */null, createRaceDefinitionSet(), /* trackedRegattaRegistry */null, /* courseDesignUpdateURI */null, /* tracTracUsername */null, null, /* tracTracPassword */ - getEventSubscriber(), getRaceSubscriber(), receiverTypes)) { + getEventSubscriber(), getRaceSubscriber(), /*ignoreTracTracMarkPassings*/ false, receiverTypes)) { receivers.add(r); } getRaceSubscriber().subscribeConnectionStatus(new IConnectionStatusListener() { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReachingLegTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReachingLegTest.java index d735ef24847..ab900d9e77c 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReachingLegTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReachingLegTest.java @@ -1,378 +1,378 @@ -package com.sap.sailing.domain.test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.GregorianCalendar; -import java.util.Iterator; -import java.util.List; - -import org.junit.Before; -import org.junit.Test; - -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.ControlPoint; -import com.sap.sailing.domain.base.Course; -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.base.RaceDefinition; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Sideline; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.base.impl.BoatClassImpl; -import com.sap.sailing.domain.base.impl.CompetitorImpl; -import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl; -import com.sap.sailing.domain.base.impl.CourseImpl; -import com.sap.sailing.domain.base.impl.MarkImpl; -import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.WaypointImpl; -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.common.NoWindException; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.common.WindSourceType; -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.WindSourceImpl; -import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; -import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; -import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; -import com.sap.sailing.domain.tracking.MarkPassing; -import com.sap.sailing.domain.tracking.TrackedLeg; -import com.sap.sailing.domain.tracking.TrackedRegatta; -import com.sap.sailing.domain.tracking.WindPositionMode; -import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; -import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; -import com.sap.sailing.domain.tracking.impl.EmptyWindStore; -import com.sap.sailing.domain.tracking.impl.GPSFixImpl; -import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; -import com.sap.sailing.domain.tracking.impl.WindImpl; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class ReachingLegTest extends TrackBasedTest { - private List competitors; - private CompetitorImpl plattner; - private CompetitorImpl hunger; - private MillisecondsTimePoint start; - private Competitor schomaeker; - - /** - * Creates the race and two competitors ({@link #plattner} and {@link #hunger}) and sets the start line passing for both of them - * to {@link #start}. - */ - @Before - public void setUp() { - competitors = new ArrayList(); - hunger = createCompetitor("Wolfgang Hunger"); - competitors.add(hunger); - plattner = createCompetitor("Dr. Hasso Plattner"); - competitors.add(plattner); - schomaeker = createCompetitor("Meike Schom�ker"); - competitors.add(schomaeker); - start = new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()); - setTrackedRace(createTrackedRace("Kieler Woche", "505 Race 2", "505", competitors, start)); - List hungersMarkPassings = createMarkPassings(hunger, start); - getTrackedRace().updateMarkPassings(hunger, hungersMarkPassings); - List plattnersMarkPassings = createMarkPassings(plattner, start); - getTrackedRace().updateMarkPassings(plattner, plattnersMarkPassings); - } - - protected DynamicTrackedRaceImpl createTrackedRace(String regattaName, String raceName, String boatClassName, - Iterable competitors, TimePoint timePointForFixes) { - BoatClassImpl boatClass = new BoatClassImpl(boatClassName, /* typicallyStartsUpwind */ true); - Regatta regatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, - RegattaImpl.getDefaultName(regattaName, boatClass.getName()), boatClass, /*startDate*/ null, /*endDate*/ null, /* trackedRegattaRegistry */ null, - DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", null); - TrackedRegatta trackedRegatta = new DynamicTrackedRegattaImpl(regatta); - List waypoints = new ArrayList(); - // create a two-lap upwind/downwind course: - MarkImpl left = new MarkImpl("Left lee gate buoy"); - MarkImpl right = new MarkImpl("Right lee gate buoy"); - ControlPoint leeGate = new ControlPointWithTwoMarksImpl(left, right, "Lee Gate"); - Mark windwardMark = new MarkImpl("Windward mark"); - Mark offsetMark = new MarkImpl("Offset mark"); - waypoints.add(new WaypointImpl(leeGate)); - waypoints.add(new WaypointImpl(windwardMark)); - waypoints.add(new WaypointImpl(offsetMark)); - waypoints.add(new WaypointImpl(leeGate)); - Course course = new CourseImpl(raceName, waypoints); - RaceDefinition race = new RaceDefinitionImpl(raceName, course, boatClass, competitors); - DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(trackedRegatta, race, Collections. emptyList(), EmptyWindStore.INSTANCE, - EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 0, - /* millisecondsOverWhichToAverageWind */ 30000, /* millisecondsOverWhichToAverageSpeed */ 30000, - /* delay for wind estimation cache invalidation */ 0); - // in this simplified artificial course, the top mark is exactly north of the right leeward gate, the offset - // mark is slightly west of the top mark; wind from the north makes the leg from top to offset a reaching leg - Position leftPosition = new DegreePosition(0, -0.00001); - Position rightPosition = new DegreePosition(0, 0.00001); - Position topPosition = new DegreePosition(1, 0); - Position offsetPosition = new DegreePosition(1, -0.0001); - TimePoint afterTheRace = new MillisecondsTimePoint(timePointForFixes.asMillis() + 36000000); // 10h after the fix time - trackedRace.getOrCreateTrack(left).addGPSFix(new GPSFixImpl(leftPosition, new MillisecondsTimePoint(0))); - trackedRace.getOrCreateTrack(right).addGPSFix(new GPSFixImpl(rightPosition, new MillisecondsTimePoint(0))); - trackedRace.getOrCreateTrack(windwardMark).addGPSFix(new GPSFixImpl(topPosition, new MillisecondsTimePoint(0))); - trackedRace.getOrCreateTrack(offsetMark).addGPSFix(new GPSFixImpl(offsetPosition, new MillisecondsTimePoint(0))); - trackedRace.getOrCreateTrack(left).addGPSFix(new GPSFixImpl(leftPosition, afterTheRace)); - trackedRace.getOrCreateTrack(right).addGPSFix(new GPSFixImpl(rightPosition, afterTheRace)); - trackedRace.getOrCreateTrack(windwardMark).addGPSFix(new GPSFixImpl(topPosition, afterTheRace)); - trackedRace.getOrCreateTrack(offsetMark).addGPSFix(new GPSFixImpl(offsetPosition, afterTheRace)); - trackedRace.recordWind(new WindImpl(topPosition, timePointForFixes, new KnotSpeedWithBearingImpl( - /* speedInKnots */14.7, new DegreeBearingImpl(180))), new WindSourceImpl(WindSourceType.WEB)); - return trackedRace; - } - - @Test - public void testSecondLegIsReaching() throws NoWindException { - Iterator legIter = getTrackedRace().getTrackedLegs().iterator(); - assertEquals(LegType.UPWIND, legIter.next().getLegType(start)); - assertEquals(LegType.REACHING, legIter.next().getLegType(start)); - assertEquals(LegType.DOWNWIND, legIter.next().getLegType(start)); - } - - @Test - public void testHungerInReachingPlattnerInUpwind() { - // give Hunger a mark passing for the windward mark, putting him into the reaching leg - final MillisecondsTimePoint whenHungerFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); - getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenHungerFinishedUpwind)); - assertEquals( - getTrackedRace().getRace().getCourse().getLegs().get(1), - getTrackedRace().getTrackedLeg(hunger, - new MillisecondsTimePoint(whenHungerFinishedUpwind.asMillis() + 10000)).getLeg()); - assertEquals( - getTrackedRace().getRace().getCourse().getLegs().get(0), - getTrackedRace().getTrackedLeg(plattner, - new MillisecondsTimePoint(whenHungerFinishedUpwind.asMillis() + 10000)).getLeg()); - } - - @Test - public void testHungerAndPlattnerInReaching() { - // give Hunger and Plattner a mark passing for the windward mark, putting both of them into the reaching leg - final MillisecondsTimePoint whenBothFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); - getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenBothFinishedUpwind)); - getTrackedRace().updateMarkPassings(plattner, createMarkPassings(plattner, start, whenBothFinishedUpwind)); - assertEquals( - getTrackedRace().getRace().getCourse().getLegs().get(1), - getTrackedRace().getTrackedLeg(hunger, - new MillisecondsTimePoint(whenBothFinishedUpwind.asMillis() + 10000)).getLeg()); - assertEquals( - getTrackedRace().getRace().getCourse().getLegs().get(1), - getTrackedRace().getTrackedLeg(plattner, - new MillisecondsTimePoint(whenBothFinishedUpwind.asMillis() + 10000)).getLeg()); - } - - @Test - public void testDistanceWithHungerAndPlattnerInReaching() throws NoWindException { - // give Hunger and Plattner a mark passing for the windward mark, putting both of them into the reaching leg - final MillisecondsTimePoint whenBothFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); - final MillisecondsTimePoint timePointInReaching = new MillisecondsTimePoint(whenBothFinishedUpwind.asMillis()+10000); - Position windwardMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getTo(), timePointInReaching); - Position offsetMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(1).getTo(), timePointInReaching); - Distance distanceOfReachingLeg = windwardMarkPos.getDistance(offsetMarkPos); - assertTrue(distanceOfReachingLeg.getMeters() > 0); - getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenBothFinishedUpwind)); - getTrackedRace().updateMarkPassings(plattner, createMarkPassings(plattner, start, whenBothFinishedUpwind)); - getTrackedRace().recordFix( - hunger, - new GPSFixMovingImpl(offsetMarkPos, timePointInReaching, new KnotSpeedWithBearingImpl(12, - new DegreeBearingImpl(270)))); - getTrackedRace().recordFix( - plattner, - new GPSFixMovingImpl(windwardMarkPos, timePointInReaching, new KnotSpeedWithBearingImpl(12, - new DegreeBearingImpl(270)))); - assertEquals(0., - getTrackedRace().getTrack(hunger).getEstimatedPosition(timePointInReaching, /* extrapolate */false) - .getDistance(offsetMarkPos).getMeters(), 0.00001); - assertEquals(0., - getTrackedRace().getTrack(plattner).getEstimatedPosition(timePointInReaching, /* extrapolate */false) - .getDistance(windwardMarkPos).getMeters(), 0.00001); - Distance hungersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(hunger, timePointInReaching, WindPositionMode.LEG_MIDDLE); - Distance plattnersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(plattner, timePointInReaching, WindPositionMode.LEG_MIDDLE); - assertEquals(0., hungersDistanceToLeader.getMeters(), 0.00001); - assertEquals(distanceOfReachingLeg.getMeters(), plattnersDistanceToLeader.getMeters(), 0.001); - } - - @Test - public void testDistanceWithHungerInReachingAndPlattnerInUpwind() throws NoWindException { - // give Hunger and Plattner a mark passing for the windward mark, putting both of them into the reaching leg - final MillisecondsTimePoint whenBothFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); - final MillisecondsTimePoint timePointInReaching = new MillisecondsTimePoint(whenBothFinishedUpwind.asMillis()+10000); - Position windwardMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getTo(), timePointInReaching); - Position offsetMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(1).getTo(), timePointInReaching); - Distance distanceOfReachingLeg = windwardMarkPos.getDistance(offsetMarkPos); - assertTrue(distanceOfReachingLeg.getMeters() > 0); - getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenBothFinishedUpwind)); - getTrackedRace().recordFix( - hunger, - new GPSFixMovingImpl(offsetMarkPos, timePointInReaching, new KnotSpeedWithBearingImpl(12, - new DegreeBearingImpl(270)))); - getTrackedRace().recordFix( - plattner, - new GPSFixMovingImpl(windwardMarkPos, timePointInReaching, new KnotSpeedWithBearingImpl(12, - new DegreeBearingImpl(270)))); - assertEquals(0., - getTrackedRace().getTrack(hunger).getEstimatedPosition(timePointInReaching, /* extrapolate */false) - .getDistance(offsetMarkPos).getMeters(), 0.00001); - assertEquals(0., - getTrackedRace().getTrack(plattner).getEstimatedPosition(timePointInReaching, /* extrapolate */false) - .getDistance(windwardMarkPos).getMeters(), 0.00001); - Distance hungersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(hunger, timePointInReaching, WindPositionMode.LEG_MIDDLE); - Distance plattnersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(plattner, timePointInReaching, WindPositionMode.LEG_MIDDLE); - assertEquals(0., hungersDistanceToLeader.getMeters(), 0.00001); - assertEquals(distanceOfReachingLeg.getMeters(), plattnersDistanceToLeader.getMeters(), 0.001); - } - - @Test - public void testDistanceWithHungerInDownwindAndPlattnerInReaching() throws NoWindException { - // give Hunger and Plattner a mark passing for the windward mark, putting both of them into the reaching leg - final MillisecondsTimePoint whenBothFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); - final MillisecondsTimePoint timePointInReaching = new MillisecondsTimePoint(whenBothFinishedUpwind.asMillis()+10000); - Position windwardMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getTo(), timePointInReaching); - Position offsetMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(1).getTo(), timePointInReaching); - Distance distanceOfReachingLeg = windwardMarkPos.getDistance(offsetMarkPos); - assertTrue(distanceOfReachingLeg.getMeters() > 0); - getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenBothFinishedUpwind)); - getTrackedRace().recordFix( - hunger, - new GPSFixMovingImpl(offsetMarkPos, timePointInReaching, new KnotSpeedWithBearingImpl(12, - new DegreeBearingImpl(270)))); - getTrackedRace().recordFix( - plattner, - new GPSFixMovingImpl(windwardMarkPos, timePointInReaching, new KnotSpeedWithBearingImpl(12, - new DegreeBearingImpl(270)))); - assertEquals(0., - getTrackedRace().getTrack(hunger).getEstimatedPosition(timePointInReaching, /* extrapolate */false) - .getDistance(offsetMarkPos).getMeters(), 0.00001); - assertEquals(0., - getTrackedRace().getTrack(plattner).getEstimatedPosition(timePointInReaching, /* extrapolate */false) - .getDistance(windwardMarkPos).getMeters(), 0.00001); - Distance hungersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(hunger, timePointInReaching, WindPositionMode.LEG_MIDDLE); - Distance plattnersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(plattner, timePointInReaching, WindPositionMode.LEG_MIDDLE); - assertEquals(0., hungersDistanceToLeader.getMeters(), 0.00001); - assertEquals(distanceOfReachingLeg.getMeters(), plattnersDistanceToLeader.getMeters(), 0.001); - } - - @Test - public void testDistanceWithHungerInDownwindAndPlattnerInUpwindWithReachingInBetween() throws NoWindException { - // give Hunger a mark passing for the windward mark, consume Plattner is stuck at the starting line, putting - // the empty reaching leg between the two; see if the reaching leg's non-windward distance is counted, and if - // Plattner's distance is accumulated correctly - final MillisecondsTimePoint whenHungerFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); - final MillisecondsTimePoint whenHungerFinishedReaching = new MillisecondsTimePoint(whenHungerFinishedUpwind.asMillis()+10000); - final MillisecondsTimePoint timePointToConsider = new MillisecondsTimePoint(whenHungerFinishedReaching.asMillis()+10000); - Position leewardPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getFrom(), timePointToConsider); - Position windwardMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getTo(), timePointToConsider); - Position offsetMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(1).getTo(), timePointToConsider); - Distance distanceOfReachingLeg = windwardMarkPos.getDistance(offsetMarkPos); - Distance windwardDistanceOfUpwindLeg = leewardPos.getDistance(windwardMarkPos); - assertTrue(distanceOfReachingLeg.getMeters() > 0); - getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenHungerFinishedUpwind, whenHungerFinishedReaching)); - getTrackedRace().recordFix( - hunger, - new GPSFixMovingImpl(offsetMarkPos, timePointToConsider, new KnotSpeedWithBearingImpl(12, - new DegreeBearingImpl(270)))); - getTrackedRace().recordFix( - plattner, - new GPSFixMovingImpl(leewardPos, timePointToConsider, new KnotSpeedWithBearingImpl(12, - new DegreeBearingImpl(0)))); - assertEquals(0., - getTrackedRace().getTrack(hunger).getEstimatedPosition(timePointToConsider, /* extrapolate */false) - .getDistance(offsetMarkPos).getMeters(), 0.00001); - assertEquals(0., - getTrackedRace().getTrack(plattner).getEstimatedPosition(timePointToConsider, /* extrapolate */false) - .getDistance(leewardPos).getMeters(), 0.00001); - Distance hungersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(hunger, timePointToConsider, WindPositionMode.LEG_MIDDLE); - Distance plattnersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(plattner, timePointToConsider, WindPositionMode.LEG_MIDDLE); - assertEquals(0., hungersDistanceToLeader.getMeters(), 0.00001); - assertEquals(distanceOfReachingLeg.getMeters()+windwardDistanceOfUpwindLeg.getMeters(), plattnersDistanceToLeader.getMeters(), 0.001); - } - - @Test - public void testWindwardDistanceToOverallLeaderWithHungerFirstAtWindwardMarkButSecondAfterReachingLeg() throws NoWindException { - // Hunger finished first upwind before Schomaeker; Schomaeker finished reaching before Hunger, leading now; - // Schoemaeker made it half through the downwind while Hunger got stuck at the offset mark - final MillisecondsTimePoint whenHungerFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); - final MillisecondsTimePoint whenSchomaekerFinishedUpwind = new MillisecondsTimePoint(whenHungerFinishedUpwind.asMillis()+1000); - final MillisecondsTimePoint whenSchomaekerFinishedReaching = new MillisecondsTimePoint(whenSchomaekerFinishedUpwind.asMillis()+10000); - final MillisecondsTimePoint whenHungerFinishedReaching = new MillisecondsTimePoint(whenSchomaekerFinishedReaching.asMillis()+1000); - final MillisecondsTimePoint timePointToConsider = new MillisecondsTimePoint(whenHungerFinishedReaching.asMillis()+10000); - - Position leewardPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getFrom(), timePointToConsider); - Position windwardMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getTo(), timePointToConsider); - Position offsetMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(1).getTo(), timePointToConsider); - Distance distanceOfReachingLeg = windwardMarkPos.getDistance(offsetMarkPos); - Distance windwardDistanceOfUpwindLeg = leewardPos.getDistance(windwardMarkPos); - assertTrue(distanceOfReachingLeg.getMeters() > 0); - getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenHungerFinishedUpwind, whenHungerFinishedReaching)); - getTrackedRace().updateMarkPassings(schomaeker, createMarkPassings(schomaeker, start, whenSchomaekerFinishedUpwind, whenSchomaekerFinishedReaching)); - getTrackedRace().recordFix( - hunger, - new GPSFixMovingImpl(offsetMarkPos, timePointToConsider, new KnotSpeedWithBearingImpl(12, - new DegreeBearingImpl(270)))); - getTrackedRace().recordFix( - schomaeker, - new GPSFixMovingImpl(offsetMarkPos.translateGreatCircle( - offsetMarkPos.getBearingGreatCircle(leewardPos), offsetMarkPos.getDistance(leewardPos) - .scale(0.5)), timePointToConsider, new KnotSpeedWithBearingImpl(12, - new DegreeBearingImpl(180)))); - getTrackedRace().recordFix( - plattner, - new GPSFixMovingImpl(leewardPos, timePointToConsider, new KnotSpeedWithBearingImpl(12, - new DegreeBearingImpl(0)))); - assertEquals(0., - getTrackedRace().getTrack(hunger).getEstimatedPosition(timePointToConsider, /* extrapolate */false) - .getDistance(offsetMarkPos).getMeters(), 0.00001); - assertEquals(0., - getTrackedRace().getTrack(plattner).getEstimatedPosition(timePointToConsider, /* extrapolate */false) - .getDistance(leewardPos).getMeters(), 0.00001); - Distance schomaekersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(schomaeker, timePointToConsider, WindPositionMode.LEG_MIDDLE); - Distance plattnersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(plattner, timePointToConsider, WindPositionMode.LEG_MIDDLE); - assertEquals(0., schomaekersDistanceToLeader.getMeters(), 0.00001); - // distance to leading Schomaeker expected to be the entire upwind distance plus the offset distance plus half the downwind - assertEquals(distanceOfReachingLeg.getMeters()+windwardDistanceOfUpwindLeg.getMeters()*1.5, plattnersDistanceToLeader.getMeters(), 0.001); - } - - @Test - public void testWindwardDistanceOnReachingLegProjectsOntoLegDirection() throws NoWindException { - final MillisecondsTimePoint whenBothFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); - final MillisecondsTimePoint timePointToConsider = new MillisecondsTimePoint(whenBothFinishedUpwind.asMillis()+10000); - - Position windwardMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getTo(), timePointToConsider); - Position offsetMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(1).getTo(), timePointToConsider); - Distance distanceOfReachingLeg = windwardMarkPos.getDistance(offsetMarkPos); - assertTrue(distanceOfReachingLeg.getMeters() > 0); - getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenBothFinishedUpwind)); - getTrackedRace().updateMarkPassings(plattner, createMarkPassings(plattner, start, whenBothFinishedUpwind)); - Bearing reachingLegBearing = windwardMarkPos.getBearingGreatCircle(offsetMarkPos); - // Hunger turns left after the windward mark - getTrackedRace().recordFix( - hunger, - new GPSFixMovingImpl(windwardMarkPos.translateGreatCircle(new DegreeBearingImpl(reachingLegBearing.getDegrees()-45), - distanceOfReachingLeg.scale(1./Math.sqrt(2.))), timePointToConsider, new KnotSpeedWithBearingImpl(12, - new DegreeBearingImpl(270)))); - // Plattner turns right after the windward mark; both travel the same distance projected along the leg - getTrackedRace().recordFix( - plattner, - new GPSFixMovingImpl(windwardMarkPos.translateGreatCircle(new DegreeBearingImpl(reachingLegBearing.getDegrees()+45), - distanceOfReachingLeg.scale(1./Math.sqrt(2.))), timePointToConsider, new KnotSpeedWithBearingImpl(12, - new DegreeBearingImpl(270)))); - // with 90deg separating them, traveling 1/sqrt(2) the distance of the leg should put them the distance of the leg apart geometrically - assertEquals( - distanceOfReachingLeg.getMeters(), - getTrackedRace() - .getTrack(hunger) - .getEstimatedPosition(timePointToConsider, /* extrapolate */false) - .getDistance( - getTrackedRace().getTrack(plattner).getEstimatedPosition(timePointToConsider, /* extrapolate */ - false)).getMeters(), 0.00001); - // however, projected onto the leg their distance should be 0 - Distance plattnersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(plattner, timePointToConsider, WindPositionMode.LEG_MIDDLE); - assertEquals(0., plattnersDistanceToLeader.getMeters(), 0.00001); - } -} +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.ControlPoint; +import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Sideline; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.CompetitorImpl; +import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl; +import com.sap.sailing.domain.base.impl.CourseImpl; +import com.sap.sailing.domain.base.impl.MarkImpl; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.LegType; +import com.sap.sailing.domain.common.NoWindException; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.common.WindSourceType; +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.WindSourceImpl; +import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; +import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedRegatta; +import com.sap.sailing.domain.tracking.WindPositionMode; +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; +import com.sap.sailing.domain.tracking.impl.EmptyWindStore; +import com.sap.sailing.domain.tracking.impl.GPSFixImpl; +import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; +import com.sap.sailing.domain.tracking.impl.WindImpl; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class ReachingLegTest extends TrackBasedTest { + private List competitors; + private CompetitorImpl plattner; + private CompetitorImpl hunger; + private MillisecondsTimePoint start; + private Competitor schomaeker; + + /** + * Creates the race and two competitors ({@link #plattner} and {@link #hunger}) and sets the start line passing for both of them + * to {@link #start}. + */ + @Before + public void setUp() { + competitors = new ArrayList(); + hunger = createCompetitor("Wolfgang Hunger"); + competitors.add(hunger); + plattner = createCompetitor("Dr. Hasso Plattner"); + competitors.add(plattner); + schomaeker = createCompetitor("Meike Schom�ker"); + competitors.add(schomaeker); + start = new MillisecondsTimePoint(new GregorianCalendar(2011, 05, 23).getTime()); + setTrackedRace(createTrackedRace("Kieler Woche", "505 Race 2", "505", competitors, start)); + List hungersMarkPassings = createMarkPassings(hunger, start); + getTrackedRace().updateMarkPassings(hunger, hungersMarkPassings); + List plattnersMarkPassings = createMarkPassings(plattner, start); + getTrackedRace().updateMarkPassings(plattner, plattnersMarkPassings); + } + + protected DynamicTrackedRaceImpl createTrackedRace(String regattaName, String raceName, String boatClassName, + Iterable competitors, TimePoint timePointForFixes) { + BoatClassImpl boatClass = new BoatClassImpl(boatClassName, /* typicallyStartsUpwind */ true); + Regatta regatta = new RegattaImpl(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, + RegattaImpl.getDefaultName(regattaName, boatClass.getName()), boatClass, /*startDate*/ null, /*endDate*/ null, /* trackedRegattaRegistry */ null, + DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", null); + TrackedRegatta trackedRegatta = new DynamicTrackedRegattaImpl(regatta); + List waypoints = new ArrayList(); + // create a two-lap upwind/downwind course: + MarkImpl left = new MarkImpl("Left lee gate buoy"); + MarkImpl right = new MarkImpl("Right lee gate buoy"); + ControlPoint leeGate = new ControlPointWithTwoMarksImpl(left, right, "Lee Gate"); + Mark windwardMark = new MarkImpl("Windward mark"); + Mark offsetMark = new MarkImpl("Offset mark"); + waypoints.add(new WaypointImpl(leeGate)); + waypoints.add(new WaypointImpl(windwardMark)); + waypoints.add(new WaypointImpl(offsetMark)); + waypoints.add(new WaypointImpl(leeGate)); + Course course = new CourseImpl(raceName, waypoints); + RaceDefinition race = new RaceDefinitionImpl(raceName, course, boatClass, competitors); + DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(trackedRegatta, race, Collections. emptyList(), EmptyWindStore.INSTANCE, + EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 0, + /* millisecondsOverWhichToAverageWind */ 30000, /* millisecondsOverWhichToAverageSpeed */ 30000, + /* delay for wind estimation cache invalidation */ 0, /*useMarkPassingCalculator*/ false); + // in this simplified artificial course, the top mark is exactly north of the right leeward gate, the offset + // mark is slightly west of the top mark; wind from the north makes the leg from top to offset a reaching leg + Position leftPosition = new DegreePosition(0, -0.00001); + Position rightPosition = new DegreePosition(0, 0.00001); + Position topPosition = new DegreePosition(1, 0); + Position offsetPosition = new DegreePosition(1, -0.0001); + TimePoint afterTheRace = new MillisecondsTimePoint(timePointForFixes.asMillis() + 36000000); // 10h after the fix time + trackedRace.getOrCreateTrack(left).addGPSFix(new GPSFixImpl(leftPosition, new MillisecondsTimePoint(0))); + trackedRace.getOrCreateTrack(right).addGPSFix(new GPSFixImpl(rightPosition, new MillisecondsTimePoint(0))); + trackedRace.getOrCreateTrack(windwardMark).addGPSFix(new GPSFixImpl(topPosition, new MillisecondsTimePoint(0))); + trackedRace.getOrCreateTrack(offsetMark).addGPSFix(new GPSFixImpl(offsetPosition, new MillisecondsTimePoint(0))); + trackedRace.getOrCreateTrack(left).addGPSFix(new GPSFixImpl(leftPosition, afterTheRace)); + trackedRace.getOrCreateTrack(right).addGPSFix(new GPSFixImpl(rightPosition, afterTheRace)); + trackedRace.getOrCreateTrack(windwardMark).addGPSFix(new GPSFixImpl(topPosition, afterTheRace)); + trackedRace.getOrCreateTrack(offsetMark).addGPSFix(new GPSFixImpl(offsetPosition, afterTheRace)); + trackedRace.recordWind(new WindImpl(topPosition, timePointForFixes, new KnotSpeedWithBearingImpl( + /* speedInKnots */14.7, new DegreeBearingImpl(180))), new WindSourceImpl(WindSourceType.WEB)); + return trackedRace; + } + + @Test + public void testSecondLegIsReaching() throws NoWindException { + Iterator legIter = getTrackedRace().getTrackedLegs().iterator(); + assertEquals(LegType.UPWIND, legIter.next().getLegType(start)); + assertEquals(LegType.REACHING, legIter.next().getLegType(start)); + assertEquals(LegType.DOWNWIND, legIter.next().getLegType(start)); + } + + @Test + public void testHungerInReachingPlattnerInUpwind() { + // give Hunger a mark passing for the windward mark, putting him into the reaching leg + final MillisecondsTimePoint whenHungerFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); + getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenHungerFinishedUpwind)); + assertEquals( + getTrackedRace().getRace().getCourse().getLegs().get(1), + getTrackedRace().getTrackedLeg(hunger, + new MillisecondsTimePoint(whenHungerFinishedUpwind.asMillis() + 10000)).getLeg()); + assertEquals( + getTrackedRace().getRace().getCourse().getLegs().get(0), + getTrackedRace().getTrackedLeg(plattner, + new MillisecondsTimePoint(whenHungerFinishedUpwind.asMillis() + 10000)).getLeg()); + } + + @Test + public void testHungerAndPlattnerInReaching() { + // give Hunger and Plattner a mark passing for the windward mark, putting both of them into the reaching leg + final MillisecondsTimePoint whenBothFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); + getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenBothFinishedUpwind)); + getTrackedRace().updateMarkPassings(plattner, createMarkPassings(plattner, start, whenBothFinishedUpwind)); + assertEquals( + getTrackedRace().getRace().getCourse().getLegs().get(1), + getTrackedRace().getTrackedLeg(hunger, + new MillisecondsTimePoint(whenBothFinishedUpwind.asMillis() + 10000)).getLeg()); + assertEquals( + getTrackedRace().getRace().getCourse().getLegs().get(1), + getTrackedRace().getTrackedLeg(plattner, + new MillisecondsTimePoint(whenBothFinishedUpwind.asMillis() + 10000)).getLeg()); + } + + @Test + public void testDistanceWithHungerAndPlattnerInReaching() throws NoWindException { + // give Hunger and Plattner a mark passing for the windward mark, putting both of them into the reaching leg + final MillisecondsTimePoint whenBothFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); + final MillisecondsTimePoint timePointInReaching = new MillisecondsTimePoint(whenBothFinishedUpwind.asMillis()+10000); + Position windwardMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getTo(), timePointInReaching); + Position offsetMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(1).getTo(), timePointInReaching); + Distance distanceOfReachingLeg = windwardMarkPos.getDistance(offsetMarkPos); + assertTrue(distanceOfReachingLeg.getMeters() > 0); + getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenBothFinishedUpwind)); + getTrackedRace().updateMarkPassings(plattner, createMarkPassings(plattner, start, whenBothFinishedUpwind)); + getTrackedRace().recordFix( + hunger, + new GPSFixMovingImpl(offsetMarkPos, timePointInReaching, new KnotSpeedWithBearingImpl(12, + new DegreeBearingImpl(270)))); + getTrackedRace().recordFix( + plattner, + new GPSFixMovingImpl(windwardMarkPos, timePointInReaching, new KnotSpeedWithBearingImpl(12, + new DegreeBearingImpl(270)))); + assertEquals(0., + getTrackedRace().getTrack(hunger).getEstimatedPosition(timePointInReaching, /* extrapolate */false) + .getDistance(offsetMarkPos).getMeters(), 0.00001); + assertEquals(0., + getTrackedRace().getTrack(plattner).getEstimatedPosition(timePointInReaching, /* extrapolate */false) + .getDistance(windwardMarkPos).getMeters(), 0.00001); + Distance hungersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(hunger, timePointInReaching, WindPositionMode.LEG_MIDDLE); + Distance plattnersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(plattner, timePointInReaching, WindPositionMode.LEG_MIDDLE); + assertEquals(0., hungersDistanceToLeader.getMeters(), 0.00001); + assertEquals(distanceOfReachingLeg.getMeters(), plattnersDistanceToLeader.getMeters(), 0.001); + } + + @Test + public void testDistanceWithHungerInReachingAndPlattnerInUpwind() throws NoWindException { + // give Hunger and Plattner a mark passing for the windward mark, putting both of them into the reaching leg + final MillisecondsTimePoint whenBothFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); + final MillisecondsTimePoint timePointInReaching = new MillisecondsTimePoint(whenBothFinishedUpwind.asMillis()+10000); + Position windwardMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getTo(), timePointInReaching); + Position offsetMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(1).getTo(), timePointInReaching); + Distance distanceOfReachingLeg = windwardMarkPos.getDistance(offsetMarkPos); + assertTrue(distanceOfReachingLeg.getMeters() > 0); + getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenBothFinishedUpwind)); + getTrackedRace().recordFix( + hunger, + new GPSFixMovingImpl(offsetMarkPos, timePointInReaching, new KnotSpeedWithBearingImpl(12, + new DegreeBearingImpl(270)))); + getTrackedRace().recordFix( + plattner, + new GPSFixMovingImpl(windwardMarkPos, timePointInReaching, new KnotSpeedWithBearingImpl(12, + new DegreeBearingImpl(270)))); + assertEquals(0., + getTrackedRace().getTrack(hunger).getEstimatedPosition(timePointInReaching, /* extrapolate */false) + .getDistance(offsetMarkPos).getMeters(), 0.00001); + assertEquals(0., + getTrackedRace().getTrack(plattner).getEstimatedPosition(timePointInReaching, /* extrapolate */false) + .getDistance(windwardMarkPos).getMeters(), 0.00001); + Distance hungersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(hunger, timePointInReaching, WindPositionMode.LEG_MIDDLE); + Distance plattnersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(plattner, timePointInReaching, WindPositionMode.LEG_MIDDLE); + assertEquals(0., hungersDistanceToLeader.getMeters(), 0.00001); + assertEquals(distanceOfReachingLeg.getMeters(), plattnersDistanceToLeader.getMeters(), 0.001); + } + + @Test + public void testDistanceWithHungerInDownwindAndPlattnerInReaching() throws NoWindException { + // give Hunger and Plattner a mark passing for the windward mark, putting both of them into the reaching leg + final MillisecondsTimePoint whenBothFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); + final MillisecondsTimePoint timePointInReaching = new MillisecondsTimePoint(whenBothFinishedUpwind.asMillis()+10000); + Position windwardMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getTo(), timePointInReaching); + Position offsetMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(1).getTo(), timePointInReaching); + Distance distanceOfReachingLeg = windwardMarkPos.getDistance(offsetMarkPos); + assertTrue(distanceOfReachingLeg.getMeters() > 0); + getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenBothFinishedUpwind)); + getTrackedRace().recordFix( + hunger, + new GPSFixMovingImpl(offsetMarkPos, timePointInReaching, new KnotSpeedWithBearingImpl(12, + new DegreeBearingImpl(270)))); + getTrackedRace().recordFix( + plattner, + new GPSFixMovingImpl(windwardMarkPos, timePointInReaching, new KnotSpeedWithBearingImpl(12, + new DegreeBearingImpl(270)))); + assertEquals(0., + getTrackedRace().getTrack(hunger).getEstimatedPosition(timePointInReaching, /* extrapolate */false) + .getDistance(offsetMarkPos).getMeters(), 0.00001); + assertEquals(0., + getTrackedRace().getTrack(plattner).getEstimatedPosition(timePointInReaching, /* extrapolate */false) + .getDistance(windwardMarkPos).getMeters(), 0.00001); + Distance hungersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(hunger, timePointInReaching, WindPositionMode.LEG_MIDDLE); + Distance plattnersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(plattner, timePointInReaching, WindPositionMode.LEG_MIDDLE); + assertEquals(0., hungersDistanceToLeader.getMeters(), 0.00001); + assertEquals(distanceOfReachingLeg.getMeters(), plattnersDistanceToLeader.getMeters(), 0.001); + } + + @Test + public void testDistanceWithHungerInDownwindAndPlattnerInUpwindWithReachingInBetween() throws NoWindException { + // give Hunger a mark passing for the windward mark, consume Plattner is stuck at the starting line, putting + // the empty reaching leg between the two; see if the reaching leg's non-windward distance is counted, and if + // Plattner's distance is accumulated correctly + final MillisecondsTimePoint whenHungerFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); + final MillisecondsTimePoint whenHungerFinishedReaching = new MillisecondsTimePoint(whenHungerFinishedUpwind.asMillis()+10000); + final MillisecondsTimePoint timePointToConsider = new MillisecondsTimePoint(whenHungerFinishedReaching.asMillis()+10000); + Position leewardPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getFrom(), timePointToConsider); + Position windwardMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getTo(), timePointToConsider); + Position offsetMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(1).getTo(), timePointToConsider); + Distance distanceOfReachingLeg = windwardMarkPos.getDistance(offsetMarkPos); + Distance windwardDistanceOfUpwindLeg = leewardPos.getDistance(windwardMarkPos); + assertTrue(distanceOfReachingLeg.getMeters() > 0); + getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenHungerFinishedUpwind, whenHungerFinishedReaching)); + getTrackedRace().recordFix( + hunger, + new GPSFixMovingImpl(offsetMarkPos, timePointToConsider, new KnotSpeedWithBearingImpl(12, + new DegreeBearingImpl(270)))); + getTrackedRace().recordFix( + plattner, + new GPSFixMovingImpl(leewardPos, timePointToConsider, new KnotSpeedWithBearingImpl(12, + new DegreeBearingImpl(0)))); + assertEquals(0., + getTrackedRace().getTrack(hunger).getEstimatedPosition(timePointToConsider, /* extrapolate */false) + .getDistance(offsetMarkPos).getMeters(), 0.00001); + assertEquals(0., + getTrackedRace().getTrack(plattner).getEstimatedPosition(timePointToConsider, /* extrapolate */false) + .getDistance(leewardPos).getMeters(), 0.00001); + Distance hungersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(hunger, timePointToConsider, WindPositionMode.LEG_MIDDLE); + Distance plattnersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(plattner, timePointToConsider, WindPositionMode.LEG_MIDDLE); + assertEquals(0., hungersDistanceToLeader.getMeters(), 0.00001); + assertEquals(distanceOfReachingLeg.getMeters()+windwardDistanceOfUpwindLeg.getMeters(), plattnersDistanceToLeader.getMeters(), 0.001); + } + + @Test + public void testWindwardDistanceToOverallLeaderWithHungerFirstAtWindwardMarkButSecondAfterReachingLeg() throws NoWindException { + // Hunger finished first upwind before Schomaeker; Schomaeker finished reaching before Hunger, leading now; + // Schoemaeker made it half through the downwind while Hunger got stuck at the offset mark + final MillisecondsTimePoint whenHungerFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); + final MillisecondsTimePoint whenSchomaekerFinishedUpwind = new MillisecondsTimePoint(whenHungerFinishedUpwind.asMillis()+1000); + final MillisecondsTimePoint whenSchomaekerFinishedReaching = new MillisecondsTimePoint(whenSchomaekerFinishedUpwind.asMillis()+10000); + final MillisecondsTimePoint whenHungerFinishedReaching = new MillisecondsTimePoint(whenSchomaekerFinishedReaching.asMillis()+1000); + final MillisecondsTimePoint timePointToConsider = new MillisecondsTimePoint(whenHungerFinishedReaching.asMillis()+10000); + + Position leewardPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getFrom(), timePointToConsider); + Position windwardMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getTo(), timePointToConsider); + Position offsetMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(1).getTo(), timePointToConsider); + Distance distanceOfReachingLeg = windwardMarkPos.getDistance(offsetMarkPos); + Distance windwardDistanceOfUpwindLeg = leewardPos.getDistance(windwardMarkPos); + assertTrue(distanceOfReachingLeg.getMeters() > 0); + getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenHungerFinishedUpwind, whenHungerFinishedReaching)); + getTrackedRace().updateMarkPassings(schomaeker, createMarkPassings(schomaeker, start, whenSchomaekerFinishedUpwind, whenSchomaekerFinishedReaching)); + getTrackedRace().recordFix( + hunger, + new GPSFixMovingImpl(offsetMarkPos, timePointToConsider, new KnotSpeedWithBearingImpl(12, + new DegreeBearingImpl(270)))); + getTrackedRace().recordFix( + schomaeker, + new GPSFixMovingImpl(offsetMarkPos.translateGreatCircle( + offsetMarkPos.getBearingGreatCircle(leewardPos), offsetMarkPos.getDistance(leewardPos) + .scale(0.5)), timePointToConsider, new KnotSpeedWithBearingImpl(12, + new DegreeBearingImpl(180)))); + getTrackedRace().recordFix( + plattner, + new GPSFixMovingImpl(leewardPos, timePointToConsider, new KnotSpeedWithBearingImpl(12, + new DegreeBearingImpl(0)))); + assertEquals(0., + getTrackedRace().getTrack(hunger).getEstimatedPosition(timePointToConsider, /* extrapolate */false) + .getDistance(offsetMarkPos).getMeters(), 0.00001); + assertEquals(0., + getTrackedRace().getTrack(plattner).getEstimatedPosition(timePointToConsider, /* extrapolate */false) + .getDistance(leewardPos).getMeters(), 0.00001); + Distance schomaekersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(schomaeker, timePointToConsider, WindPositionMode.LEG_MIDDLE); + Distance plattnersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(plattner, timePointToConsider, WindPositionMode.LEG_MIDDLE); + assertEquals(0., schomaekersDistanceToLeader.getMeters(), 0.00001); + // distance to leading Schomaeker expected to be the entire upwind distance plus the offset distance plus half the downwind + assertEquals(distanceOfReachingLeg.getMeters()+windwardDistanceOfUpwindLeg.getMeters()*1.5, plattnersDistanceToLeader.getMeters(), 0.001); + } + + @Test + public void testWindwardDistanceOnReachingLegProjectsOntoLegDirection() throws NoWindException { + final MillisecondsTimePoint whenBothFinishedUpwind = new MillisecondsTimePoint(start.asMillis()+600000); + final MillisecondsTimePoint timePointToConsider = new MillisecondsTimePoint(whenBothFinishedUpwind.asMillis()+10000); + + Position windwardMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(0).getTo(), timePointToConsider); + Position offsetMarkPos = getTrackedRace().getApproximatePosition(getTrackedRace().getRace().getCourse().getLegs().get(1).getTo(), timePointToConsider); + Distance distanceOfReachingLeg = windwardMarkPos.getDistance(offsetMarkPos); + assertTrue(distanceOfReachingLeg.getMeters() > 0); + getTrackedRace().updateMarkPassings(hunger, createMarkPassings(hunger, start, whenBothFinishedUpwind)); + getTrackedRace().updateMarkPassings(plattner, createMarkPassings(plattner, start, whenBothFinishedUpwind)); + Bearing reachingLegBearing = windwardMarkPos.getBearingGreatCircle(offsetMarkPos); + // Hunger turns left after the windward mark + getTrackedRace().recordFix( + hunger, + new GPSFixMovingImpl(windwardMarkPos.translateGreatCircle(new DegreeBearingImpl(reachingLegBearing.getDegrees()-45), + distanceOfReachingLeg.scale(1./Math.sqrt(2.))), timePointToConsider, new KnotSpeedWithBearingImpl(12, + new DegreeBearingImpl(270)))); + // Plattner turns right after the windward mark; both travel the same distance projected along the leg + getTrackedRace().recordFix( + plattner, + new GPSFixMovingImpl(windwardMarkPos.translateGreatCircle(new DegreeBearingImpl(reachingLegBearing.getDegrees()+45), + distanceOfReachingLeg.scale(1./Math.sqrt(2.))), timePointToConsider, new KnotSpeedWithBearingImpl(12, + new DegreeBearingImpl(270)))); + // with 90deg separating them, traveling 1/sqrt(2) the distance of the leg should put them the distance of the leg apart geometrically + assertEquals( + distanceOfReachingLeg.getMeters(), + getTrackedRace() + .getTrack(hunger) + .getEstimatedPosition(timePointToConsider, /* extrapolate */false) + .getDistance( + getTrackedRace().getTrack(plattner).getEstimatedPosition(timePointToConsider, /* extrapolate */ + false)).getMeters(), 0.00001); + // however, projected onto the leg their distance should be 0 + Distance plattnersDistanceToLeader = getTrackedRace().getWindwardDistanceToOverallLeader(plattner, timePointToConsider, WindPositionMode.LEG_MIDDLE); + assertEquals(0., plattnersDistanceToLeader.getMeters(), 0.00001); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveMarkPassingDataTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveMarkPassingDataTest.java index fa78cfaab45..c1bc2f1b499 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveMarkPassingDataTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveMarkPassingDataTest.java @@ -107,7 +107,7 @@ public class ReceiveMarkPassingDataTest extends AbstractTracTracLiveTest { } }, /* trackedRegattaRegistry */null, /* courseDesignUpdateURI */null, /* tracTracUsername */null, null, /* tracTracPassword */ - getEventSubscriber(), getRaceSubscriber(), ReceiverType.RACECOURSE, ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, + getEventSubscriber(), getRaceSubscriber(), /*ignoreTracTracMarkPassings*/ false, ReceiverType.RACECOURSE, ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS)) { receivers.add(r); } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveTrackingDataTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveTrackingDataTest.java index d2ffc899fb4..5846d508b3c 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveTrackingDataTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/ReceiveTrackingDataTest.java @@ -84,7 +84,7 @@ public class ReceiveTrackingDataTest extends AbstractTracTracLiveTest { } }, /* trackedRegattaRegistry */null, getTracTracRace(), null, /* courseDesignUpdateURI */ - /* tracTracUsername */null, /* tracTracPassword */null, getEventSubscriber(), getRaceSubscriber())) { + /* tracTracUsername */null, /* tracTracPassword */null, getEventSubscriber(), getRaceSubscriber(), /*ignoreTracTracMarkPassings*/ false)) { receiver.subscribe(); getRaceSubscriber().start(); } @@ -96,7 +96,7 @@ public class ReceiveTrackingDataTest extends AbstractTracTracLiveTest { public void addRaceDefinition(RaceDefinition race, DynamicTrackedRace trackedRace) { } }, /* trackedRegattaRegistry */null, getTracTracRace(), null, /* courseDesignUpdateURI */ - /* tracTracUsername */null, /* tracTracPassword */null, getEventSubscriber(), getRaceSubscriber())); + /* tracTracUsername */null, /* tracTracPassword */null, getEventSubscriber(), getRaceSubscriber(), /*ignoreTracTracMarkPassings*/ false)); } @Test diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StarbordSideOfStartLineRecognitionTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StarbordSideOfStartLineRecognitionTest.java index f69815b0ef7..334e27bbba3 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StarbordSideOfStartLineRecognitionTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/StarbordSideOfStartLineRecognitionTest.java @@ -86,7 +86,7 @@ public class StarbordSideOfStartLineRecognitionTest { private static final long serialVersionUID = -8007932232555073829L; public MockedTrackedRaceImpl() { - super(null, null, Collections. emptyList(), null, null, 0, 0, 0, 0); + super(null, null, Collections. emptyList(), null, null, 0, 0, 0, 0, false); } @Override diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java index faeaca23b4e..ac029889f68 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/TrackBasedTest.java @@ -117,7 +117,7 @@ public abstract class TrackBasedTest { DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(trackedRegatta, race, Collections. emptyList(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 0, /* millisecondsOverWhichToAverageWind */ 30000, /* millisecondsOverWhichToAverageSpeed */ 30000, - /* delay for wind estimation cache invalidation */ 0); + /* delay for wind estimation cache invalidation */ 0, /*useMarkPassingCalculator*/ false); // in this simplified artificial course, the top mark is exactly north of the right leeward gate DegreePosition topPosition = new DegreePosition(54.48, 10.24); TimePoint afterTheRace = new MillisecondsTimePoint(timePointForFixes.asMillis() + 36000000); // 10h after the fix time diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UnicodeCharactersInCompetitorNamesTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UnicodeCharactersInCompetitorNamesTest.java index fc42239e6bc..de22a3fa28b 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UnicodeCharactersInCompetitorNamesTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UnicodeCharactersInCompetitorNamesTest.java @@ -65,7 +65,8 @@ public class UnicodeCharactersInCompetitorNamesTest { + TracTracConnectionConstants.PORT_STORED), new URI( "http://tracms.traclive.dk/update_course"), /* startOfTracking */null, /* endOfTracking */null, /* delayToLiveInMillis */0l, - /* simulateWithStartTimeNow */false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, + /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings*/ + false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, "tracTest", "tracTest", "", "", new DummyTrackedRegattaRegistry()); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UpdateMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UpdateMarkPassingTest.java index 60aacd5527f..2611d968806 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UpdateMarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/UpdateMarkPassingTest.java @@ -46,7 +46,7 @@ public class UpdateMarkPassingTest { /* trackedRegatta */new DynamicTrackedRegattaImpl(new RegattaImpl("test", null, null, null, new HashSet(), false, null, "test", null)), race, Collections. emptyList(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */1000, /* millisecondsOverWhichToAverageWind */30000, - /* millisecondsOverWhichToAverageSpeed */30000); + /* millisecondsOverWhichToAverageSpeed */30000, /*useMarkPassingCalculator*/ false); TimePoint now = MillisecondsTimePoint.now(); TimePoint later = now.plus(1000); trackedRace.updateMarkPassings(competitor, Arrays.asList(new MarkPassing[] { new MarkPassingImpl(now, waypoint, competitor) })); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java index 64368e6e02e..9b6782bf4b7 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/WindTest.java @@ -1,355 +1,355 @@ -package com.sap.sailing.domain.test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.junit.Test; - -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.ControlPoint; -import com.sap.sailing.domain.base.DomainFactory; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.base.Sideline; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.base.impl.BoatClassImpl; -import com.sap.sailing.domain.base.impl.BoatImpl; -import com.sap.sailing.domain.base.impl.CompetitorImpl; -import com.sap.sailing.domain.base.impl.CourseImpl; -import com.sap.sailing.domain.base.impl.NationalityImpl; -import com.sap.sailing.domain.base.impl.PersonImpl; -import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.TeamImpl; -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.ScoringSchemeType; -import com.sap.sailing.domain.common.confidence.Weigher; -import com.sap.sailing.domain.common.confidence.impl.PositionAndTimePointWeigher; -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.MeterDistance; -import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager; -import com.sap.sailing.domain.confidence.ConfidenceFactory; -import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; -import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; -import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; -import com.sap.sailing.domain.tracking.DynamicTrackedRace; -import com.sap.sailing.domain.tracking.MarkPassing; -import com.sap.sailing.domain.tracking.Wind; -import com.sap.sailing.domain.tracking.WindTrack; -import com.sap.sailing.domain.tracking.WindWithConfidence; -import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; -import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; -import com.sap.sailing.domain.tracking.impl.EmptyWindStore; -import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; -import com.sap.sailing.domain.tracking.impl.WindImpl; -import com.sap.sailing.domain.tracking.impl.WindTrackImpl; -import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl; -import com.sap.sse.common.Color; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; -import com.sap.sse.common.Util.Pair; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class WindTest { - private static final int AVERAGING_INTERVAL_MILLIS = 30000 /* 30s averaging interval */; - - /** - * Tests that averaging also works across the 0deg mark - */ - @Test - public void testAveragingWind() throws InterruptedException { - WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true, "TestWindTrack"); - TimePoint t1 = MillisecondsTimePoint.now(); - TimePoint t2 = new MillisecondsTimePoint(t1.asMillis()+10); - TimePoint middle = new MillisecondsTimePoint((t1.asMillis()+t2.asMillis())/2); - DegreePosition pos = new DegreePosition(0, 0); - Wind wind1 = new WindImpl(pos, t1, new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(355))); - Wind wind2 = new WindImpl(pos, t2, new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(5))); - track.add(wind1); - track.add(wind2); - Wind average = track.getAveragedWind(pos, middle); - assertEquals(0., average.getBearing().getDifferenceTo(new DegreeBearingImpl(0)).getDegrees(), 0.0001); - } - - @Test - public void testMultipleWindFixesWithSameTimestampInSameWindTrack() { - WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true, "TestWindTrack"); - TimePoint now = MillisecondsTimePoint.now(); - DegreePosition pos1 = new DegreePosition(0, 0); - DegreePosition pos2 = new DegreePosition(1, 1); - Wind wind1 = new WindImpl(pos1, now, new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(0))); - Wind wind2 = new WindImpl(pos2, now, new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))); - track.add(wind1); - track.add(wind2); - track.lockForRead(); - try { - assertEquals(2, Util.size(track.getFixes())); - Set expectedWind = new HashSet(); - expectedWind.add(wind1); - expectedWind.add(wind2); - Set actualWind = new HashSet(); - for (Wind wind : track.getFixes()) { - actualWind.add(wind); - } - assertEquals(expectedWind, actualWind); - } finally { - track.unlockAfterRead(); - } - } - - @Test - public void testEmptyTrackYieldsNullAsWindEstimate() { - WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true, "TestWindTrack"); - assertNull(track.getAveragedWind(new DegreePosition(0, 0), MillisecondsTimePoint.now())); - } - - /** - * If the wind track has areas with no data, and wind information is requested for such an interval, - * it is essential to still average over the {@link #AVERAGING_INTERVAL_MILLIS} interval, even if the - * interval is further away than {@link #AVERAGING_INTERVAL_MILLIS}. - */ - @Test - public void testAveragingOfSparseWindTrack() { - WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true, "TestWindTrack"); - DegreePosition pos = new DegreePosition(0, 0); - Wind wind1 = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))); - Wind wind2 = new WindImpl(pos, new MillisecondsTimePoint(1000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))); - Wind wind3 = new WindImpl(pos, new MillisecondsTimePoint(2000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))); - Wind wind4 = new WindImpl(pos, new MillisecondsTimePoint(10000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))); - Wind wind5 = new WindImpl(pos, new MillisecondsTimePoint(30000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))); - Wind wind6 = new WindImpl(pos, new MillisecondsTimePoint(40000), new KnotSpeedWithBearingImpl(130, new DegreeBearingImpl(0))); - Wind wind7 = new WindImpl(pos, new MillisecondsTimePoint(50000), new KnotSpeedWithBearingImpl(170, new DegreeBearingImpl(0))); - track.add(wind1); - track.add(wind2); - track.add(wind3); - track.add(wind4); - track.add(wind5); - track.add(wind6); - track.add(wind7); - - // interval does bearely reach 20's burst because 0 has 0 length and 1000..30000 has 29000 length - assertEquals(20, track.getAveragedWind(pos, new MillisecondsTimePoint(1)).getKnots(), 0.00000001); - // interval uses the two fixes to the left (0, 1000)=1000 and three to the right (2000, 10000, 30000)=28000 - assertEquals(20, track.getAveragedWind(pos, new MillisecondsTimePoint(1001)).getKnots(), 0.00000001); - // in the middle of the "hole", fetches (0, 1000, 2000, 10000)=10000 and (30000, 40000)=10000, so 20000ms worth of wind - final double averageFor20000 = track.getAveragedWind(pos, new MillisecondsTimePoint(20000)).getKnots(); - // value is hard to predict exactly because time difference-based confidences rate fixes closer to 20000ms higher than those further away - assertEquals(35, averageFor20000, 5); - // right of the middle of the "hole", fetches (0, 1000, 2000, 10000)=10000 and (30000, 40000, 50000)=20000 - final double averageFor20500 = track.getAveragedWind(pos, new MillisecondsTimePoint(20500)).getKnots(); - assertEquals(37, averageFor20500, 5); - assertTrue(averageFor20500 > averageFor20000); - } - - @Test - public void testSingleElementWindTrack() { - WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true, "TestWindTrack"); - DegreePosition pos = new DegreePosition(0, 0); - Wind wind = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(123))); - track.add(wind); - Wind estimate = track.getAveragedWind(pos, new MillisecondsTimePoint(0)); - assertEquals(10, estimate.getKnots(), 0.000000001); - assertEquals(123, estimate.getBearing().getDegrees(), 0.000000001); - } - - @Test - public void testSingleElementExtrapolation() { - WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */, /* useSpeed */ true, "TestWindTrack"); - DegreePosition pos = new DegreePosition(0, 0); - Wind wind = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(123))); - track.add(wind); - // we only have one measurement; this should be extrapolated because it's our best guess - Wind estimate = track.getAveragedWind(pos, new MillisecondsTimePoint(1000)); - assertEquals(10, estimate.getKnots(), 0.000000001); - assertEquals(123, estimate.getBearing().getDegrees(), 0.000000001); - } - - @Test - public void testSingleElementExtrapolationBeyondThreshold() { - WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */, /* useSpeed */ true, "TestWindTrack"); - DegreePosition pos = new DegreePosition(0, 0); - Wind wind = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(123))); - track.add(wind); - // we only have one measurement; this should be extrapolated because it's our best guess even if - // the last measurement was longer ago than our smoothening interval - Wind estimate = track.getAveragedWind(pos, new MillisecondsTimePoint(AVERAGING_INTERVAL_MILLIS+1000)); - assertEquals(10, estimate.getKnots(), 0.000000001); - assertEquals(123, estimate.getBearing().getDegrees(), 0.000000001); - } - - @Test - public void testTwoElementWindTrackSameBearing() { - WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */, /* useSpeed */ true, "TestWindTrack"); - DegreePosition pos = new DegreePosition(0, 0); - Wind wind1 = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(100))); - track.add(wind1); - Wind wind2 = new WindImpl(pos, new MillisecondsTimePoint(1000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(100))); - track.add(wind2); - Wind estimate = track.getAveragedWind(pos, new MillisecondsTimePoint(500)); - assertEquals(15, estimate.getKnots(), 0.000000001); - assertEquals(100, estimate.getBearing().getDegrees(), 0.00000001); - } - - @Test - public void testTwoElementWindTrackDifferentBearing() { - WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */, /* useSpeed */ true, "TestWindTrack"); - DegreePosition pos = new DegreePosition(0, 0); - Wind wind1 = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(110))); - track.add(wind1); - Wind wind2 = new WindImpl(pos, new MillisecondsTimePoint(1000), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(100))); - track.add(wind2); - Wind estimate = track.getAveragedWind(pos, new MillisecondsTimePoint(2000)); - assertEquals(10, estimate.getKnots(), 0.000000001); - assertEquals(105, estimate.getBearing().getDegrees(), 0.6); // some tolerance needed because of time-based confidence - } - - @Test - public void testUsingNewerThanRequestedIfCloserThanOlder() throws ParseException { - /* - Imagine the following wind measurements: - - 2009-07-11T13:45:00.000+0200@null: 10.0kn from 278.0� avg(30000ms): 2009-07-11T13:45:00.000+0200@null: 10.0kn from 278.0° - 2009-07-11T13:45:05.000+0200@null: 10.0kn from 265.0� avg(30000ms): 2009-07-11T13:45:05.000+0200@null: 10.0kn from 265.0° - 2009-07-12T17:31:40.000+0200@null: 10.0kn from 260.0� avg(30000ms): 2009-07-12T17:31:40.000+0200@null: 10.0kn from 260.0° - - Now assume a query for 2009-07-12T17:31:38 which is closest to the newest entry but (much) more than - the averaging interval after the previous entry (2009-07-11T13:45:05.000). This test ensures that - the WindTrack uses the newer entry even though it's after the time point requested because it's - much closer, and the previous entry would be out of the averaging interval anyway. - */ - SimpleDateFormat df = new SimpleDateFormat("yyyy-DD-mm'T'hh:mm:ss"); - Wind wind1 = new WindImpl(null, new MillisecondsTimePoint(df.parse("2009-07-11T13:45:00").getTime()), - new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(98))); - Wind wind2 = new WindImpl(null, new MillisecondsTimePoint(df.parse("2009-07-11T13:45:05").getTime()), - new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(85))); - Wind wind3 = new WindImpl(null, new MillisecondsTimePoint(df.parse("2009-07-11T17:31:40").getTime()), - new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(80))); - WindTrack track = new WindTrackImpl(/* millisecondsOverWhichToAverage */ 30000, /* useSpeed */ true, "TestWindTrack"); - track.add(wind1); - track.add(wind2); - track.add(wind3); - TimePoint timePoint = new MillisecondsTimePoint(df.parse("2009-07-11T17:31:38").getTime()); - Wind result = track.getAveragedWind(null, timePoint); - // expectation: take two from left (because they are closer than AVERAGING_INTERVAL_MILLIS apart), one from right side: - assertEquals((wind1.getKnots() + wind2.getKnots() + wind3.getKnots()) / 3, result.getKnots(), 0.000000001); - assertEquals(80., result.getBearing().getDegrees(), 5); - } - - @Test - public void testWindwardDistanceForReachingLeg() { - // TODO construct a tiny course with one upwind, one reaching and one downwind leg, a race with two competitors; - // TODO create a tracked race with corresponding WEB wind source; put leader in downwind, trailer in upwind and - // TODO test that windward distance includes the full reaching leg's length - } - - /** - * See bug 943. Wind estimation should return null instead of causing exceptions in case there - * is no wind data to bootstrap the estimator with. - */ - @Test - public void testWindEstimationReturnsNullIfNoUpwindStartAndNoOtherWindDataAvailable() { - DomainFactory domainFactory = DomainFactory.INSTANCE; - Mark startFinishLeft = domainFactory.getOrCreateMark("Start/Finish left"); - Mark startFinishRight = domainFactory.getOrCreateMark("Start/Finish right"); - ControlPoint startFinish = domainFactory.createControlPointWithTwoMarks(startFinishLeft, startFinishRight, "Start/Finish"); - ControlPoint top = domainFactory.getOrCreateMark("Top"); - Waypoint w1 = domainFactory.createWaypoint(startFinish, /*passingInstruction*/ null); - Waypoint w2 = domainFactory.createWaypoint(top, /*passingInstruction*/ null); - Waypoint w3 = domainFactory.createWaypoint(startFinish, /*passingInstruction*/ null); - Competitor competitor = new CompetitorImpl(123, "Test Competitor", Color.RED, null, new TeamImpl("STG", Collections.singleton( - new PersonImpl("Test Competitor", new NationalityImpl("GER"), - /* dateOfBirth */null, "This is famous " + "Test Competitor")), new PersonImpl("Rigo van Maas", - new NationalityImpl("NED"), - /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl("Test Competitor" + "'s boat", - new BoatClassImpl("505", /* typicallyStartsUpwind */true), null)); - final BoatClass boatClass = domainFactory.getOrCreateBoatClass("ESS40"); - DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(new DynamicTrackedRegattaImpl( - new RegattaImpl(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, - RegattaImpl.getDefaultName("Test Regatta", boatClass.getName()), boatClass, /*startDate*/ null, /*endDate*/ null, - /* trackedRegattaRegistry */ null, domainFactory.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", null)), - new RaceDefinitionImpl("Test Race", - new CourseImpl("Test Course", Arrays.asList(new Waypoint[] { w1, w2, w3 })), - boatClass, Collections.singleton(competitor)), Collections. emptyList(), - EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 1000, - /* millisecondsOverWhichToAverageWind */ 30000, - /* millisecondsOverWhichToAverageSpeed */ 30000); - TimePoint start = MillisecondsTimePoint.now(); - TimePoint topMarkRounding = start.plus(30000); - TimePoint finish = topMarkRounding.plus(30000); - trackedRace.updateMarkPassings(competitor, Arrays.asList(new MarkPassing[] { - new MarkPassingImpl(start, w1, competitor), - new MarkPassingImpl(topMarkRounding, w2, competitor), - new MarkPassingImpl(finish, w3, competitor) - })); - assertFalse(boatClass.typicallyStartsUpwind()); - assertNull(trackedRace.getEstimatedWindDirection(MillisecondsTimePoint.now())); - } - - @Test - public void testWindAveragingBasedOnPosition() { - Weigher> timeWeigherThatPretendsToAlsoWeighPositions = new PositionAndTimePointWeigher( - /* halfConfidenceAfterMilliseconds */10000l, new MeterDistance(1000)); - ConfidenceBasedWindAverager> averager = ConfidenceFactory.INSTANCE - .createWindAverager(timeWeigherThatPretendsToAlsoWeighPositions); - TimePoint now = MillisecondsTimePoint.now(); - final DegreePosition p1 = new DegreePosition(0, 0); - WindWithConfidence> w1 = new WindWithConfidenceImpl<>( - new WindImpl(p1, now, new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(90))), - /* confidence */ 1.0, new Pair(p1, now), /* useSpeed */ true); - final Position p2 = p1.translateGreatCircle(new DegreeBearingImpl(0), new MeterDistance(1000)); - WindWithConfidence> w2 = new WindWithConfidenceImpl<>( - new WindImpl(p2, now, new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))), - /* confidence */ 1.0, new Pair(p2, now), /* useSpeed */ true); - List>> fixes = new ArrayList<>(); - fixes.add(w1); - fixes.add(w2); - WindWithConfidence> averageAtP1 = averager.getAverage(fixes, new Pair(p1, now)); - WindWithConfidence> averageAtP2 = averager.getAverage(fixes, new Pair(p2, now)); - // first careful assertion: at p1 where wind is from the west (to the east), wind should be further from the - // west (further to the east) than at p2 where wind is from the south (to the north). - final Bearing averageAtP1Bearing = averageAtP1.getObject().getBearing(); - assertTrue(Math.abs(averageAtP1Bearing.getDifferenceTo(w1.getObject().getBearing()).getDegrees()) < - Math.abs(averageAtP1Bearing.getDifferenceTo(w2.getObject().getBearing()).getDegrees())); - // ...and vice versa - final Bearing averageAtP2Bearing = averageAtP2.getObject().getBearing(); - assertTrue(Math.abs(averageAtP2Bearing.getDifferenceTo(w2.getObject().getBearing()).getDegrees()) < - Math.abs(averageAtP2Bearing.getDifferenceTo(w1.getObject().getBearing()).getDegrees())); - // expect the arithmetic average at the point which is in the middle between the two positions - Position middleBetweenP1AndP2 = p1.translateGreatCircle(p1.getBearingGreatCircle(p2), p1.getDistance(p2).scale(.5)); - WindWithConfidence> averageAtMiddleBetweenP1AndP2 = averager.getAverage(fixes, - new Pair(middleBetweenP1AndP2, now)); - assertEquals(15, averageAtMiddleBetweenP1AndP2.getObject().getKnots(), 0.00001); - assertEquals(45, averageAtMiddleBetweenP1AndP2.getObject().getBearing().getDegrees(), 0.00001); - - // now try again with distance 2km and demand the difference between the position-dependent readings to be greater - final Position p3 = p1.translateGreatCircle(new DegreeBearingImpl(0), new MeterDistance(2000)); - WindWithConfidence> w3 = new WindWithConfidenceImpl<>( - new WindImpl(p3, now, new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))), - /* confidence */ 1.0, new Pair(p3, now), /* useSpeed */ true); - List>> fixesFurtherApart = new ArrayList<>(); - fixesFurtherApart.add(w1); - fixesFurtherApart.add(w3); - WindWithConfidence> averageFurtherApartAtP1 = averager.getAverage(fixesFurtherApart, new Pair(p1, now)); - WindWithConfidence> averageAtP3 = averager.getAverage(fixesFurtherApart, new Pair(p3, now)); - assertTrue(averageFurtherApartAtP1.getObject().getKnots() < averageAtP1.getObject().getKnots()); - assertTrue(averageAtP3.getObject().getKnots() > averageAtP2.getObject().getKnots()); - assertTrue(Math.abs(averageFurtherApartAtP1.getObject().getBearing().getDifferenceTo(w1.getObject().getBearing()).getDegrees()) - < Math.abs(averageAtP1.getObject().getBearing().getDifferenceTo(w1.getObject().getBearing()).getDegrees())); - assertTrue(Math.abs(averageAtP3.getObject().getBearing().getDifferenceTo(w3.getObject().getBearing()).getDegrees()) - < Math.abs(averageAtP2.getObject().getBearing().getDifferenceTo(w3.getObject().getBearing()).getDegrees())); - } -} +package com.sap.sailing.domain.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.Test; + +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.ControlPoint; +import com.sap.sailing.domain.base.DomainFactory; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.Sideline; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.BoatImpl; +import com.sap.sailing.domain.base.impl.CompetitorImpl; +import com.sap.sailing.domain.base.impl.CourseImpl; +import com.sap.sailing.domain.base.impl.NationalityImpl; +import com.sap.sailing.domain.base.impl.PersonImpl; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.TeamImpl; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.ScoringSchemeType; +import com.sap.sailing.domain.common.confidence.Weigher; +import com.sap.sailing.domain.common.confidence.impl.PositionAndTimePointWeigher; +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.MeterDistance; +import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager; +import com.sap.sailing.domain.confidence.ConfidenceFactory; +import com.sap.sailing.domain.racelog.impl.EmptyRaceLogStore; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; +import com.sap.sailing.domain.regattalog.impl.EmptyRegattaLogStore; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sailing.domain.tracking.WindWithConfidence; +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; +import com.sap.sailing.domain.tracking.impl.EmptyWindStore; +import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; +import com.sap.sailing.domain.tracking.impl.WindImpl; +import com.sap.sailing.domain.tracking.impl.WindTrackImpl; +import com.sap.sailing.domain.tracking.impl.WindWithConfidenceImpl; +import com.sap.sse.common.Color; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class WindTest { + private static final int AVERAGING_INTERVAL_MILLIS = 30000 /* 30s averaging interval */; + + /** + * Tests that averaging also works across the 0deg mark + */ + @Test + public void testAveragingWind() throws InterruptedException { + WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true, "TestWindTrack"); + TimePoint t1 = MillisecondsTimePoint.now(); + TimePoint t2 = new MillisecondsTimePoint(t1.asMillis()+10); + TimePoint middle = new MillisecondsTimePoint((t1.asMillis()+t2.asMillis())/2); + DegreePosition pos = new DegreePosition(0, 0); + Wind wind1 = new WindImpl(pos, t1, new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(355))); + Wind wind2 = new WindImpl(pos, t2, new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(5))); + track.add(wind1); + track.add(wind2); + Wind average = track.getAveragedWind(pos, middle); + assertEquals(0., average.getBearing().getDifferenceTo(new DegreeBearingImpl(0)).getDegrees(), 0.0001); + } + + @Test + public void testMultipleWindFixesWithSameTimestampInSameWindTrack() { + WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true, "TestWindTrack"); + TimePoint now = MillisecondsTimePoint.now(); + DegreePosition pos1 = new DegreePosition(0, 0); + DegreePosition pos2 = new DegreePosition(1, 1); + Wind wind1 = new WindImpl(pos1, now, new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(0))); + Wind wind2 = new WindImpl(pos2, now, new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))); + track.add(wind1); + track.add(wind2); + track.lockForRead(); + try { + assertEquals(2, Util.size(track.getFixes())); + Set expectedWind = new HashSet(); + expectedWind.add(wind1); + expectedWind.add(wind2); + Set actualWind = new HashSet(); + for (Wind wind : track.getFixes()) { + actualWind.add(wind); + } + assertEquals(expectedWind, actualWind); + } finally { + track.unlockAfterRead(); + } + } + + @Test + public void testEmptyTrackYieldsNullAsWindEstimate() { + WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true, "TestWindTrack"); + assertNull(track.getAveragedWind(new DegreePosition(0, 0), MillisecondsTimePoint.now())); + } + + /** + * If the wind track has areas with no data, and wind information is requested for such an interval, + * it is essential to still average over the {@link #AVERAGING_INTERVAL_MILLIS} interval, even if the + * interval is further away than {@link #AVERAGING_INTERVAL_MILLIS}. + */ + @Test + public void testAveragingOfSparseWindTrack() { + WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true, "TestWindTrack"); + DegreePosition pos = new DegreePosition(0, 0); + Wind wind1 = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))); + Wind wind2 = new WindImpl(pos, new MillisecondsTimePoint(1000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))); + Wind wind3 = new WindImpl(pos, new MillisecondsTimePoint(2000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))); + Wind wind4 = new WindImpl(pos, new MillisecondsTimePoint(10000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))); + Wind wind5 = new WindImpl(pos, new MillisecondsTimePoint(30000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))); + Wind wind6 = new WindImpl(pos, new MillisecondsTimePoint(40000), new KnotSpeedWithBearingImpl(130, new DegreeBearingImpl(0))); + Wind wind7 = new WindImpl(pos, new MillisecondsTimePoint(50000), new KnotSpeedWithBearingImpl(170, new DegreeBearingImpl(0))); + track.add(wind1); + track.add(wind2); + track.add(wind3); + track.add(wind4); + track.add(wind5); + track.add(wind6); + track.add(wind7); + + // interval does bearely reach 20's burst because 0 has 0 length and 1000..30000 has 29000 length + assertEquals(20, track.getAveragedWind(pos, new MillisecondsTimePoint(1)).getKnots(), 0.00000001); + // interval uses the two fixes to the left (0, 1000)=1000 and three to the right (2000, 10000, 30000)=28000 + assertEquals(20, track.getAveragedWind(pos, new MillisecondsTimePoint(1001)).getKnots(), 0.00000001); + // in the middle of the "hole", fetches (0, 1000, 2000, 10000)=10000 and (30000, 40000)=10000, so 20000ms worth of wind + final double averageFor20000 = track.getAveragedWind(pos, new MillisecondsTimePoint(20000)).getKnots(); + // value is hard to predict exactly because time difference-based confidences rate fixes closer to 20000ms higher than those further away + assertEquals(35, averageFor20000, 5); + // right of the middle of the "hole", fetches (0, 1000, 2000, 10000)=10000 and (30000, 40000, 50000)=20000 + final double averageFor20500 = track.getAveragedWind(pos, new MillisecondsTimePoint(20500)).getKnots(); + assertEquals(37, averageFor20500, 5); + assertTrue(averageFor20500 > averageFor20000); + } + + @Test + public void testSingleElementWindTrack() { + WindTrack track = new WindTrackImpl(AVERAGING_INTERVAL_MILLIS, /* useSpeed */ true, "TestWindTrack"); + DegreePosition pos = new DegreePosition(0, 0); + Wind wind = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(123))); + track.add(wind); + Wind estimate = track.getAveragedWind(pos, new MillisecondsTimePoint(0)); + assertEquals(10, estimate.getKnots(), 0.000000001); + assertEquals(123, estimate.getBearing().getDegrees(), 0.000000001); + } + + @Test + public void testSingleElementExtrapolation() { + WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */, /* useSpeed */ true, "TestWindTrack"); + DegreePosition pos = new DegreePosition(0, 0); + Wind wind = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(123))); + track.add(wind); + // we only have one measurement; this should be extrapolated because it's our best guess + Wind estimate = track.getAveragedWind(pos, new MillisecondsTimePoint(1000)); + assertEquals(10, estimate.getKnots(), 0.000000001); + assertEquals(123, estimate.getBearing().getDegrees(), 0.000000001); + } + + @Test + public void testSingleElementExtrapolationBeyondThreshold() { + WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */, /* useSpeed */ true, "TestWindTrack"); + DegreePosition pos = new DegreePosition(0, 0); + Wind wind = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(123))); + track.add(wind); + // we only have one measurement; this should be extrapolated because it's our best guess even if + // the last measurement was longer ago than our smoothening interval + Wind estimate = track.getAveragedWind(pos, new MillisecondsTimePoint(AVERAGING_INTERVAL_MILLIS+1000)); + assertEquals(10, estimate.getKnots(), 0.000000001); + assertEquals(123, estimate.getBearing().getDegrees(), 0.000000001); + } + + @Test + public void testTwoElementWindTrackSameBearing() { + WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */, /* useSpeed */ true, "TestWindTrack"); + DegreePosition pos = new DegreePosition(0, 0); + Wind wind1 = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(100))); + track.add(wind1); + Wind wind2 = new WindImpl(pos, new MillisecondsTimePoint(1000), new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(100))); + track.add(wind2); + Wind estimate = track.getAveragedWind(pos, new MillisecondsTimePoint(500)); + assertEquals(15, estimate.getKnots(), 0.000000001); + assertEquals(100, estimate.getBearing().getDegrees(), 0.00000001); + } + + @Test + public void testTwoElementWindTrackDifferentBearing() { + WindTrack track = new WindTrackImpl(30000 /* 30s averaging interval */, /* useSpeed */ true, "TestWindTrack"); + DegreePosition pos = new DegreePosition(0, 0); + Wind wind1 = new WindImpl(pos, new MillisecondsTimePoint(0), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(110))); + track.add(wind1); + Wind wind2 = new WindImpl(pos, new MillisecondsTimePoint(1000), new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(100))); + track.add(wind2); + Wind estimate = track.getAveragedWind(pos, new MillisecondsTimePoint(2000)); + assertEquals(10, estimate.getKnots(), 0.000000001); + assertEquals(105, estimate.getBearing().getDegrees(), 0.6); // some tolerance needed because of time-based confidence + } + + @Test + public void testUsingNewerThanRequestedIfCloserThanOlder() throws ParseException { + /* + Imagine the following wind measurements: + + 2009-07-11T13:45:00.000+0200@null: 10.0kn from 278.0� avg(30000ms): 2009-07-11T13:45:00.000+0200@null: 10.0kn from 278.0° + 2009-07-11T13:45:05.000+0200@null: 10.0kn from 265.0� avg(30000ms): 2009-07-11T13:45:05.000+0200@null: 10.0kn from 265.0° + 2009-07-12T17:31:40.000+0200@null: 10.0kn from 260.0� avg(30000ms): 2009-07-12T17:31:40.000+0200@null: 10.0kn from 260.0° + + Now assume a query for 2009-07-12T17:31:38 which is closest to the newest entry but (much) more than + the averaging interval after the previous entry (2009-07-11T13:45:05.000). This test ensures that + the WindTrack uses the newer entry even though it's after the time point requested because it's + much closer, and the previous entry would be out of the averaging interval anyway. + */ + SimpleDateFormat df = new SimpleDateFormat("yyyy-DD-mm'T'hh:mm:ss"); + Wind wind1 = new WindImpl(null, new MillisecondsTimePoint(df.parse("2009-07-11T13:45:00").getTime()), + new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(98))); + Wind wind2 = new WindImpl(null, new MillisecondsTimePoint(df.parse("2009-07-11T13:45:05").getTime()), + new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(85))); + Wind wind3 = new WindImpl(null, new MillisecondsTimePoint(df.parse("2009-07-11T17:31:40").getTime()), + new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(80))); + WindTrack track = new WindTrackImpl(/* millisecondsOverWhichToAverage */ 30000, /* useSpeed */ true, "TestWindTrack"); + track.add(wind1); + track.add(wind2); + track.add(wind3); + TimePoint timePoint = new MillisecondsTimePoint(df.parse("2009-07-11T17:31:38").getTime()); + Wind result = track.getAveragedWind(null, timePoint); + // expectation: take two from left (because they are closer than AVERAGING_INTERVAL_MILLIS apart), one from right side: + assertEquals((wind1.getKnots() + wind2.getKnots() + wind3.getKnots()) / 3, result.getKnots(), 0.000000001); + assertEquals(80., result.getBearing().getDegrees(), 5); + } + + @Test + public void testWindwardDistanceForReachingLeg() { + // TODO construct a tiny course with one upwind, one reaching and one downwind leg, a race with two competitors; + // TODO create a tracked race with corresponding WEB wind source; put leader in downwind, trailer in upwind and + // TODO test that windward distance includes the full reaching leg's length + } + + /** + * See bug 943. Wind estimation should return null instead of causing exceptions in case there + * is no wind data to bootstrap the estimator with. + */ + @Test + public void testWindEstimationReturnsNullIfNoUpwindStartAndNoOtherWindDataAvailable() { + DomainFactory domainFactory = DomainFactory.INSTANCE; + Mark startFinishLeft = domainFactory.getOrCreateMark("Start/Finish left"); + Mark startFinishRight = domainFactory.getOrCreateMark("Start/Finish right"); + ControlPoint startFinish = domainFactory.createControlPointWithTwoMarks(startFinishLeft, startFinishRight, "Start/Finish"); + ControlPoint top = domainFactory.getOrCreateMark("Top"); + Waypoint w1 = domainFactory.createWaypoint(startFinish, /*passingInstruction*/ null); + Waypoint w2 = domainFactory.createWaypoint(top, /*passingInstruction*/ null); + Waypoint w3 = domainFactory.createWaypoint(startFinish, /*passingInstruction*/ null); + Competitor competitor = new CompetitorImpl(123, "Test Competitor", Color.RED, null, new TeamImpl("STG", Collections.singleton( + new PersonImpl("Test Competitor", new NationalityImpl("GER"), + /* dateOfBirth */null, "This is famous " + "Test Competitor")), new PersonImpl("Rigo van Maas", + new NationalityImpl("NED"), + /* dateOfBirth */null, "This is Rigo, the coach")), new BoatImpl("Test Competitor" + "'s boat", + new BoatClassImpl("505", /* typicallyStartsUpwind */true), null)); + final BoatClass boatClass = domainFactory.getOrCreateBoatClass("ESS40"); + DynamicTrackedRace trackedRace = new DynamicTrackedRaceImpl(new DynamicTrackedRegattaImpl( + new RegattaImpl(EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, + RegattaImpl.getDefaultName("Test Regatta", boatClass.getName()), boatClass, /*startDate*/ null, /*endDate*/ null, + /* trackedRegattaRegistry */ null, domainFactory.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", null)), + new RaceDefinitionImpl("Test Race", + new CourseImpl("Test Course", Arrays.asList(new Waypoint[] { w1, w2, w3 })), + boatClass, Collections.singleton(competitor)), Collections. emptyList(), + EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 1000, + /* millisecondsOverWhichToAverageWind */ 30000, + /* millisecondsOverWhichToAverageSpeed */ 30000, /*useMarkPassingCalculator*/ false); + TimePoint start = MillisecondsTimePoint.now(); + TimePoint topMarkRounding = start.plus(30000); + TimePoint finish = topMarkRounding.plus(30000); + trackedRace.updateMarkPassings(competitor, Arrays.asList(new MarkPassing[] { + new MarkPassingImpl(start, w1, competitor), + new MarkPassingImpl(topMarkRounding, w2, competitor), + new MarkPassingImpl(finish, w3, competitor) + })); + assertFalse(boatClass.typicallyStartsUpwind()); + assertNull(trackedRace.getEstimatedWindDirection(MillisecondsTimePoint.now())); + } + + @Test + public void testWindAveragingBasedOnPosition() { + Weigher> timeWeigherThatPretendsToAlsoWeighPositions = new PositionAndTimePointWeigher( + /* halfConfidenceAfterMilliseconds */10000l, new MeterDistance(1000)); + ConfidenceBasedWindAverager> averager = ConfidenceFactory.INSTANCE + .createWindAverager(timeWeigherThatPretendsToAlsoWeighPositions); + TimePoint now = MillisecondsTimePoint.now(); + final DegreePosition p1 = new DegreePosition(0, 0); + WindWithConfidence> w1 = new WindWithConfidenceImpl<>( + new WindImpl(p1, now, new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(90))), + /* confidence */ 1.0, new Pair(p1, now), /* useSpeed */ true); + final Position p2 = p1.translateGreatCircle(new DegreeBearingImpl(0), new MeterDistance(1000)); + WindWithConfidence> w2 = new WindWithConfidenceImpl<>( + new WindImpl(p2, now, new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))), + /* confidence */ 1.0, new Pair(p2, now), /* useSpeed */ true); + List>> fixes = new ArrayList<>(); + fixes.add(w1); + fixes.add(w2); + WindWithConfidence> averageAtP1 = averager.getAverage(fixes, new Pair(p1, now)); + WindWithConfidence> averageAtP2 = averager.getAverage(fixes, new Pair(p2, now)); + // first careful assertion: at p1 where wind is from the west (to the east), wind should be further from the + // west (further to the east) than at p2 where wind is from the south (to the north). + final Bearing averageAtP1Bearing = averageAtP1.getObject().getBearing(); + assertTrue(Math.abs(averageAtP1Bearing.getDifferenceTo(w1.getObject().getBearing()).getDegrees()) < + Math.abs(averageAtP1Bearing.getDifferenceTo(w2.getObject().getBearing()).getDegrees())); + // ...and vice versa + final Bearing averageAtP2Bearing = averageAtP2.getObject().getBearing(); + assertTrue(Math.abs(averageAtP2Bearing.getDifferenceTo(w2.getObject().getBearing()).getDegrees()) < + Math.abs(averageAtP2Bearing.getDifferenceTo(w1.getObject().getBearing()).getDegrees())); + // expect the arithmetic average at the point which is in the middle between the two positions + Position middleBetweenP1AndP2 = p1.translateGreatCircle(p1.getBearingGreatCircle(p2), p1.getDistance(p2).scale(.5)); + WindWithConfidence> averageAtMiddleBetweenP1AndP2 = averager.getAverage(fixes, + new Pair(middleBetweenP1AndP2, now)); + assertEquals(15, averageAtMiddleBetweenP1AndP2.getObject().getKnots(), 0.00001); + assertEquals(45, averageAtMiddleBetweenP1AndP2.getObject().getBearing().getDegrees(), 0.00001); + + // now try again with distance 2km and demand the difference between the position-dependent readings to be greater + final Position p3 = p1.translateGreatCircle(new DegreeBearingImpl(0), new MeterDistance(2000)); + WindWithConfidence> w3 = new WindWithConfidenceImpl<>( + new WindImpl(p3, now, new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))), + /* confidence */ 1.0, new Pair(p3, now), /* useSpeed */ true); + List>> fixesFurtherApart = new ArrayList<>(); + fixesFurtherApart.add(w1); + fixesFurtherApart.add(w3); + WindWithConfidence> averageFurtherApartAtP1 = averager.getAverage(fixesFurtherApart, new Pair(p1, now)); + WindWithConfidence> averageAtP3 = averager.getAverage(fixesFurtherApart, new Pair(p3, now)); + assertTrue(averageFurtherApartAtP1.getObject().getKnots() < averageAtP1.getObject().getKnots()); + assertTrue(averageAtP3.getObject().getKnots() > averageAtP2.getObject().getKnots()); + assertTrue(Math.abs(averageFurtherApartAtP1.getObject().getBearing().getDifferenceTo(w1.getObject().getBearing()).getDegrees()) + < Math.abs(averageAtP1.getObject().getBearing().getDifferenceTo(w1.getObject().getBearing()).getDegrees())); + assertTrue(Math.abs(averageAtP3.getObject().getBearing().getDifferenceTo(w3.getObject().getBearing()).getDegrees()) + < Math.abs(averageAtP2.getObject().getBearing().getDifferenceTo(w3.getObject().getBearing()).getDegrees())); + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMarkPassingTest.java index 28ee679f364..08d53b1e61c 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMarkPassingTest.java @@ -11,10 +11,9 @@ import java.net.URISyntaxException; import java.net.URL; import java.text.ParseException; import java.util.ArrayList; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.List; - -import junit.framework.Assert; +import java.util.Map; import org.junit.AfterClass; @@ -28,6 +27,8 @@ import com.sap.sailing.domain.markpassingcalculation.Candidate; import com.sap.sailing.domain.markpassingcalculation.CandidateChooser; import com.sap.sailing.domain.markpassingcalculation.CandidateFinder; import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator; +import com.sap.sailing.domain.markpassingcalculation.impl.CandidateChooserImpl; +import com.sap.sailing.domain.markpassingcalculation.impl.CandidateFinderImpl; import com.sap.sailing.domain.test.OnlineTracTracBasedTest; import com.sap.sailing.domain.test.measurements.Measurement; import com.sap.sailing.domain.test.measurements.MeasurementCase; @@ -45,8 +46,8 @@ import com.tractrac.subscription.lib.api.SubscriberInitializationException; public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest { - private LinkedHashMap> givenPasses = new LinkedHashMap<>(); - private ArrayList waypoints = new ArrayList<>(); + private Map> givenPasses = new HashMap<>(); + private List waypoints = new ArrayList<>(); private static String className; private static String simpleName; private static double totalPasses = 0; @@ -59,23 +60,24 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest { super(); className = getClass().getName(); simpleName = getClass().getSimpleName(); - } private void setUp(String raceNumber) throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { super.setUp(); - URI storedUri = new URI("file:///" + new File("resources/" + getFileName() + raceNumber + ".mtb").getCanonicalPath().replace('\\', '/')); + URI storedUri = new URI("file:///" + + new File("resources/" + getFileName() + raceNumber + ".mtb").getCanonicalPath().replace('\\', '/')); super.setUp(new URL("file:///" + new File("resources/" + getFileName() + raceNumber + ".txt").getCanonicalPath()), - /* liveUri */null, /* storedUri */storedUri, - new ReceiverType[] { ReceiverType.MARKPASSINGS, ReceiverType.RACECOURSE, ReceiverType.MARKPOSITIONS, ReceiverType.RAWPOSITIONS }); - getTrackedRace().recordWind(new WindImpl(/* position */null, MillisecondsTimePoint.now(), new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(65))), + /* liveUri */null, /* storedUri */storedUri, new ReceiverType[] { ReceiverType.MARKPASSINGS, ReceiverType.RACECOURSE, + ReceiverType.MARKPOSITIONS, ReceiverType.RAWPOSITIONS }); + getTrackedRace().recordWind( + new WindImpl(/* position */null, MillisecondsTimePoint.now(), new KnotSpeedWithBearingImpl(12, new DegreeBearingImpl(65))), new WindSourceImpl(WindSourceType.WEB)); for (Waypoint w : getRace().getCourse().getWaypoints()) { waypoints.add(w); } for (Competitor c : getRace().getCompetitors()) { - LinkedHashMap givenMarkPasses = new LinkedHashMap(); + Map givenMarkPasses = new HashMap(); for (Waypoint wp : waypoints) { MarkPassing markPassing = getTrackedRace().getMarkPassing(c, wp); givenMarkPasses.put(wp, markPassing); @@ -89,17 +91,23 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest { protected void testRace(String raceNumber) throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { setUp(raceNumber); testWholeRace(); - testMiddleOfRace(2); testMiddleOfRace(0); + testMiddleOfRace(2); } private void testWholeRace() { - LinkedHashMap> computedPasses = new LinkedHashMap<>(); + Map> computedPasses = new HashMap<>(); // Get calculatedMarkPasses long time = System.currentTimeMillis(); - final MarkPassingCalculator markPassCreator = new MarkPassingCalculator(getTrackedRace(), true); + new MarkPassingCalculator(getTrackedRace(), false); time = System.currentTimeMillis() - time; - computedPasses = markPassCreator.getAllPasses(); + + for (Competitor c : getRace().getCompetitors()) { + computedPasses.put(c, new HashMap()); + for (Waypoint w : waypoints) { + computedPasses.get(c).put(w, getTrackedRace().getMarkPassing(c, w)); + } + } // Compare computed and calculated MarkPassings final int tolerance = 10000; @@ -113,7 +121,9 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest { boolean printRight = false; boolean printWrong = true; - boolean printResult = false; + boolean printResult = true; + + System.out.println(getTrackedRace().getStartOfRace()); for (Competitor c : getRace().getCompetitors()) { numberOfCompetitors++; @@ -145,7 +155,8 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest { System.out.println("Both null" + "\n"); } } else { - long timeDelta = givenPasses.get(c).get(w).getTimePoint().asMillis() - computedPasses.get(c).get(w).getTimePoint().asMillis(); + long timeDelta = givenPasses.get(c).get(w).getTimePoint().asMillis() + - computedPasses.get(c).get(w).getTimePoint().asMillis(); if ((Math.abs(timeDelta) < tolerance)) { correctPasses++; if (printRight) { @@ -190,58 +201,58 @@ public abstract class AbstractMarkPassingTest extends OnlineTracTracBasedTest { incorrect += incorrectPasses; skipped += wronglyNotComputed; extra += wronglyComputed; - assertTrue("Expected accuracy to be at least 0.8 but was "+accuracy, accuracy >= 0.8); + assertTrue("Expected accuracy to be at least 0.8 but was " + accuracy, accuracy >= 0.8); } - private void testMiddleOfRace(int waypoint) { - CandidateFinder finder = new CandidateFinder(getTrackedRace()); - CandidateChooser chooser = new CandidateChooser(getTrackedRace()); + private void testMiddleOfRace(int zeroBasedIndexOfLastWaypointToBePassed) { + CandidateFinder finder = new CandidateFinderImpl(getTrackedRace()); + CandidateChooser chooser = new CandidateChooserImpl(getTrackedRace()); int mistakes = 0; - Waypoint start = waypoints.get(waypoint); - Waypoint second = waypoints.get(waypoint + 1); + Waypoint lastWaypointToBePassed = waypoints.get(zeroBasedIndexOfLastWaypointToBePassed); + Waypoint wayPointAfterwards = waypoints.get(zeroBasedIndexOfLastWaypointToBePassed + 1); for (Competitor c : getRace().getCompetitors()) { - TimePoint startTime; - TimePoint secondPass; - if (givenPasses.get(c).get(start) != null && givenPasses.get(c).get(second) != null) { - startTime = givenPasses.get(c).get(start).getTimePoint(); - secondPass = givenPasses.get(c).get(second).getTimePoint(); - TimePoint delta = new MillisecondsTimePoint(startTime.plus(secondPass.asMillis()).asMillis() / 2); - List fixes = new ArrayList(); - try { - getTrackedRace().getTrack(c).lockForRead(); - for (GPSFixMoving fix : getTrackedRace().getTrack(c).getFixes()) { - if (fix.getTimePoint().before(delta)) { - fixes.add(fix); + MarkPassing markPassingAfter = givenPasses.get(c).get(wayPointAfterwards); + if (markPassingAfter != null) { + if (givenPasses.get(c).get(lastWaypointToBePassed) != null && givenPasses.get(c).get(wayPointAfterwards) != null) { + TimePoint beforeNextPassing = markPassingAfter.getTimePoint().minus(20000); + List fixes = new ArrayList(); + try { + getTrackedRace().getTrack(c).lockForRead(); + for (GPSFixMoving fix : getTrackedRace().getTrack(c).getFixes()) { + if (fix.getTimePoint().before(beforeNextPassing)) { + fixes.add(fix); + } + } + } finally { + getTrackedRace().getTrack(c).unlockAfterRead(); + } + Util.Pair, Iterable> f = finder.getCandidateDeltas(c, fixes); + chooser.calculateMarkPassDeltas(c, f.getA(), f.getB()); + boolean gotPassed = true; + boolean gotOther = false; + System.out.println(c); + for (Waypoint w : getRace().getCourse().getWaypoints()) { + MarkPassing old = givenPasses.get(c).get(w); + MarkPassing newm = getTrackedRace().getMarkPassing(c, w); + System.out.println(newm); + if (waypoints.indexOf(w) <= zeroBasedIndexOfLastWaypointToBePassed) { + if ((old == null) != (newm == null)) { + gotPassed = false; + } + } else { + if (w != wayPointAfterwards && newm != null) { + gotOther = true; + } } } - } finally { - getTrackedRace().getTrack(c).unlockAfterRead(); - } - Util.Pair, Iterable> f = finder.getCandidateDeltas(c, fixes); - chooser.calculateMarkPassDeltas(c, f); - boolean gotPassed = true; - boolean gotOther = false; - // System.out.println(c); - for (Waypoint w : getRace().getCourse().getWaypoints()) { - MarkPassing old = givenPasses.get(c).get(w); - MarkPassing newm = getTrackedRace().getMarkPassing(c, w); - // System.out.println(newm); - if (waypoints.indexOf(w) <= waypoint) { - if ((old == null) != (newm == null)) { - gotPassed = false; - } - } else { - if (newm != null) { - gotOther = true; - } + if (!gotPassed || gotOther) { + mistakes++; } } - if (!gotPassed || gotOther) { - mistakes++; - } } } - Assert.assertTrue(mistakes == 0); + assertEquals(0, mistakes); + } @AfterClass diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java index 30ce75475d8..b41aa583c8a 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/AbstractMockedRaceMarkPassingTest.java @@ -1,95 +1,79 @@ -package com.sap.sailing.domain.test.markpassing; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Random; -import java.util.UUID; - -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.ControlPointWithTwoMarks; -import com.sap.sailing.domain.base.Course; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.base.RaceDefinition; -import com.sap.sailing.domain.base.Regatta; -import com.sap.sailing.domain.base.Sideline; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.base.impl.BoatClassImpl; -import com.sap.sailing.domain.base.impl.CompetitorImpl; -import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl; -import com.sap.sailing.domain.base.impl.CourseAreaImpl; -import com.sap.sailing.domain.base.impl.CourseImpl; -import com.sap.sailing.domain.base.impl.FleetImpl; -import com.sap.sailing.domain.base.impl.MarkImpl; -import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; -import com.sap.sailing.domain.base.impl.RegattaImpl; -import com.sap.sailing.domain.base.impl.SeriesImpl; -import com.sap.sailing.domain.base.impl.WaypointImpl; -import com.sap.sailing.domain.common.PassingInstruction; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.SpeedWithBearing; -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.leaderboard.impl.HighPoint; -import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; -import com.sap.sailing.domain.tracking.DynamicTrackedRace; -import com.sap.sailing.domain.tracking.GPSFixMoving; -import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; -import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; -import com.sap.sailing.domain.tracking.impl.EmptyWindStore; -import com.sap.sailing.domain.tracking.impl.GPSFixImpl; -import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class AbstractMockedRaceMarkPassingTest { - protected Competitor bob = new CompetitorImpl("Bob", "Bob", null, null, null, null); - protected Competitor joe = new CompetitorImpl("Joe", "Joe", null, null, null, null); - protected Competitor mike = new CompetitorImpl("Mike", "Mike", null, null, null, null); - - protected Mark m = new MarkImpl("black"); - protected Mark m2 = new MarkImpl("white 1"); - protected Mark m3 = new MarkImpl("white 2"); - - protected List waypoints = new ArrayList<>(); - protected DynamicTrackedRace trackedRace; - protected long time; - protected Random rnd = new Random(); - - public AbstractMockedRaceMarkPassingTest() { - ControlPointWithTwoMarks cp = new ControlPointWithTwoMarksImpl(m2, m3, "cp"); - Waypoint w1 = new WaypointImpl(cp, PassingInstruction.Line); - Waypoint w2 = new WaypointImpl(m, PassingInstruction.Port); - Waypoint w3 = new WaypointImpl(cp, PassingInstruction.Line); - final BoatClassImpl boatClass = new BoatClassImpl("boat", true); - Regatta r = new RegattaImpl(RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, - /*startDate*/ null, /*endDate*/ null, Arrays.asList(new SeriesImpl("Series", true, Arrays.asList(new FleetImpl("fleet")), - new ArrayList(), null)), true, new HighPoint(), "ID", new CourseAreaImpl("area", new UUID(5, 5))); - Course course = new CourseImpl("course", Arrays.asList(w1, w2, w3)); - RaceDefinition race = new RaceDefinitionImpl("Performance Race", course, boatClass, Arrays.asList(bob)); - trackedRace = new DynamicTrackedRaceImpl(new DynamicTrackedRegattaImpl(r), race, new ArrayList(), new EmptyWindStore(), EmptyGPSFixStore.INSTANCE, 0, 10000, 10000); - trackedRace.setStartTimeReceived(new MillisecondsTimePoint(System.currentTimeMillis() - 120000)); - List tps = Arrays.asList(new MillisecondsTimePoint(System.currentTimeMillis()), new MillisecondsTimePoint(System.currentTimeMillis() - 30000), - new MillisecondsTimePoint(System.currentTimeMillis() + 30000)); - List> pos = Arrays.asList(new Util.Pair(m, new DegreePosition(0, 0)), new Util.Pair(m2, new DegreePosition(-0.001, -0.00005)), - new Util.Pair(m3, new DegreePosition(-0.001, 0.00005))); - for (Util.Pair pair : pos) { - for (TimePoint t : tps) { - trackedRace.recordFix(pair.getA(), new GPSFixImpl(pair.getB(), t)); - } - } - for (Waypoint w : trackedRace.getRace().getCourse().getWaypoints()) { - waypoints.add(w); - } - } - - protected GPSFixMoving rndFix() { - DegreePosition position = new DegreePosition(37.8878 + rnd.nextDouble() * 0.0019, -122.268 - rnd.nextDouble() * 0.012); - TimePoint p = new MillisecondsTimePoint((long) (System.currentTimeMillis() - 300000 + (Math.random() * (7800000)))); - SpeedWithBearing speed = new KnotSpeedWithBearingImpl(rnd.nextInt(11), new DegreeBearingImpl(rnd.nextInt(360))); - - return new GPSFixMovingImpl(position, p, speed); - } -} +package com.sap.sailing.domain.test.markpassing; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.ControlPointWithTwoMarks; +import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Regatta; +import com.sap.sailing.domain.base.Sideline; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.BoatClassImpl; +import com.sap.sailing.domain.base.impl.CompetitorImpl; +import com.sap.sailing.domain.base.impl.ControlPointWithTwoMarksImpl; +import com.sap.sailing.domain.base.impl.CourseAreaImpl; +import com.sap.sailing.domain.base.impl.CourseImpl; +import com.sap.sailing.domain.base.impl.FleetImpl; +import com.sap.sailing.domain.base.impl.MarkImpl; +import com.sap.sailing.domain.base.impl.RaceDefinitionImpl; +import com.sap.sailing.domain.base.impl.RegattaImpl; +import com.sap.sailing.domain.base.impl.SeriesImpl; +import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.PassingInstruction; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.impl.DegreePosition; +import com.sap.sailing.domain.leaderboard.impl.HighPoint; +import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRaceImpl; +import com.sap.sailing.domain.tracking.impl.DynamicTrackedRegattaImpl; +import com.sap.sailing.domain.tracking.impl.EmptyWindStore; +import com.sap.sailing.domain.tracking.impl.GPSFixImpl; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class AbstractMockedRaceMarkPassingTest { + protected Competitor ron = new CompetitorImpl("Ron", "Ron", null, null, null, null); + protected Competitor tom = new CompetitorImpl("Tom", "Tom", null, null, null, null); + protected Competitor ben = new CompetitorImpl("Ben", "Ben", null, null, null, null); + + protected Mark m = new MarkImpl("Mark"); + protected Mark gate1 = new MarkImpl("Gate1"); + protected Mark gate2 = new MarkImpl("Gate2"); + protected Mark reaching = new MarkImpl("Reaching"); + + protected List waypoints = new ArrayList<>(); + + protected DynamicTrackedRace race; + + public AbstractMockedRaceMarkPassingTest() { + ControlPointWithTwoMarks cp = new ControlPointWithTwoMarksImpl(gate1, gate2, "cp"); + + Waypoint w1 = new WaypointImpl(cp, PassingInstruction.Line); + Waypoint w2 = new WaypointImpl(m, PassingInstruction.Port); + Waypoint w3 = new WaypointImpl(cp, PassingInstruction.Gate); + Waypoint w4 = new WaypointImpl(m, PassingInstruction.Starboard); + Waypoint w5 = new WaypointImpl(cp, PassingInstruction.Line); + waypoints = Arrays.asList(w1, w2, w3, w4, w5); + final BoatClassImpl boatClass = new BoatClassImpl("boat", true); + Regatta r = new RegattaImpl(RegattaImpl.getDefaultName("regatta", boatClass.getName()), boatClass, + /*startDate*/ null, /*endDate*/ null, Arrays.asList(new SeriesImpl("Series", true, Arrays.asList(new FleetImpl("fleet")), + new ArrayList(), null)), true, new HighPoint(), "ID", new CourseAreaImpl("area", new UUID(5, 5))); + Course course = new CourseImpl("course", waypoints); + RaceDefinition raceDef = new RaceDefinitionImpl("Performance Race", course, boatClass, Arrays.asList(ron, tom, ben)); + race = new DynamicTrackedRaceImpl(new DynamicTrackedRegattaImpl(r), raceDef, new ArrayList(), new EmptyWindStore(), EmptyGPSFixStore.INSTANCE, 0, 10000, 10000, /*useMarkPassingCalculator*/ false); + race.setStartTimeReceived(new MillisecondsTimePoint(10000)); + TimePoint t = new MillisecondsTimePoint(30000); + List> pos = Arrays.asList(new Util.Pair(m, new DegreePosition(0, 0)), + new Util.Pair(gate1, new DegreePosition(-0.001, -0.00005)), new Util.Pair(gate2, new DegreePosition(-0.001, 0.00005)), new Util.Pair(reaching, new DegreePosition(-0.0005, -0.0005))); + for (Util.Pair pair : pos) { + race.recordFix(pair.getA(), new GPSFixImpl(pair.getB(), t)); + } + } +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/CandidateFinderTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/CandidateFinderTest.java deleted file mode 100755 index 4074acf5ef2..00000000000 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/CandidateFinderTest.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.sap.sailing.domain.test.markpassing; - -import static org.junit.Assert.assertEquals; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.Test; - -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.markpassingcalculation.CandidateFinder; -import com.sap.sailing.domain.tracking.GPSFix; -import com.sap.sailing.domain.tracking.GPSFixMoving; -import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; -import com.sap.sse.common.Util; -import com.sap.sse.common.impl.MillisecondsTimePoint; - -public class CandidateFinderTest extends AbstractMockedRaceMarkPassingTest { - - @Test - public void test() { - GPSFixMoving fix1 = new GPSFixMovingImpl(new DegreePosition(0.000003,0.000049), - new MillisecondsTimePoint(System.currentTimeMillis() - 2000), new KnotSpeedWithBearingImpl(5, - new DegreeBearingImpl(330))); - GPSFixMoving fix2 = new GPSFixMovingImpl(new DegreePosition(0.000062,0.000029), - new MillisecondsTimePoint(System.currentTimeMillis()), new KnotSpeedWithBearingImpl(5, - new DegreeBearingImpl(270))); - GPSFixMoving fix3 = new GPSFixMovingImpl(new DegreePosition(0.000026,-0.000024), - new MillisecondsTimePoint(System.currentTimeMillis() + 2000), new KnotSpeedWithBearingImpl(5, - new DegreeBearingImpl(225))); - GPSFixMoving fix4 = new GPSFixMovingImpl(new DegreePosition(-0.000056,-0.000049), - new MillisecondsTimePoint(System.currentTimeMillis() + 4000), new KnotSpeedWithBearingImpl(5, - new DegreeBearingImpl(190))); - trackedRace.recordFix(bob, fix1); - trackedRace.recordFix(bob, fix2); - CandidateFinder finder = new CandidateFinder(trackedRace); - List fixes = new ArrayList(); - fixes.add(fix1); - fixes.add(fix2); - assertEquals(0, Util.size(finder.getCandidateDeltas(bob, fixes).getA())); - - trackedRace.recordFix(bob, fix3); - fixes.clear(); - fixes.add(fix3); - assertEquals(1, Util.size(finder.getCandidateDeltas(bob, fixes).getA())); // CTE candidate - - trackedRace.recordFix(bob, fix4); - fixes.clear(); - fixes.add(fix4); - assertEquals(1, Util.size(finder.getCandidateDeltas(bob, fixes).getA())); // Distance Candidate - } -} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/FlorianopolisMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/FlorianopolisMarkPassingTest.java index fecd9da93f9..6703e5326ee 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/FlorianopolisMarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/FlorianopolisMarkPassingTest.java @@ -17,70 +17,107 @@ public class FlorianopolisMarkPassingTest extends AbstractMarkPassingTest { } @Test - public void testRace1() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { + public void testRace3() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { testRace("3"); } @Test - public void testRace4() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { + public void testRace4() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { testRace("4"); } - /* - * Test ignored on master branch to quicken the hudson build. - * - * @Test public void testRace1() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("1"); } - * - * @Test public void testRace2() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("2"); } - * - * @Test public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("5"); } - * - * @Test public void testRace6() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("6"); } - * - * @Test public void testRace7() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("7"); } - * - * @Test public void testRace8() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("8"); } - * - * @Test public void testRace10() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("10"); } - * - * @Test public void testRace11() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("11"); } - * - * @Test public void testRace12() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("12"); } - * - * @Test public void testRace13() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("13"); } - * - * @Test public void testRace14() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("14"); } - * - * @Test public void testRace16() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("16"); } - * - * @Test public void testRace17() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("17"); } - * - * @Test public void testRace19() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("19"); } - * - * @Test public void testRace22() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("22"); } - * - * @Test public void testRace24() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("24"); } - */ + @Test + public void testRace1() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("1"); + } + + @Test + public void testRace2() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("2"); + } + + @Test + public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("5"); + } + + @Test + public void testRace6() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("6"); + } + + @Test + public void testRace7() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("7"); + } + + @Test + public void testRace8() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("8"); + } + + @Test + public void testRace10() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("10"); + } + + @Test + public void testRace11() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("11"); + } + + @Test + public void testRace12() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("12"); + } + + @Test + public void testRace13() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("13"); + } + + @Test + public void testRace17() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("17"); + } + + @Test + public void testRace19() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("19"); + } + + @Test + public void testRace22() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("22"); + } + + @Test + public void testRace24() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("24"); + } /* * Alinghi acting wierd * + * @Test public void testRace14() throws IOException, InterruptedException, URISyntaxException, ParseException, + * SubscriberInitializationException, CreateModelException { testRace("14"); } + * * @Test public void testRace23() throws IOException, InterruptedException, URISyntaxException { testRace(23); } */ @@ -90,6 +127,13 @@ public class FlorianopolisMarkPassingTest extends AbstractMarkPassingTest { * @Test public void testRace18() throws IOException, InterruptedException, URISyntaxException { testRace("18"); } */ + /* + * Fails in Hudson... + * + * @Test public void testRace16() throws IOException, InterruptedException, URISyntaxException, ParseException, + * SubscriberInitializationException, CreateModelException { testRace("16"); } + */ + /* * All mark data seems to be missing * diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/KW470MarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/KW470MarkPassingTest.java index 0642f5afb04..b0b1f4af49a 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/KW470MarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/KW470MarkPassingTest.java @@ -17,19 +17,22 @@ public class KW470MarkPassingTest extends AbstractMarkPassingTest { } @Test - public void testRace4() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { + public void testRace4() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { testRace("4"); } - /* - * Ignored on master branch - * - * @Test public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("5"); } - * - * @Test public void testRace6() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("6"); } - */ + @Test + public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("5"); + } + + @Test + public void testRace6() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("6"); + } @Override protected String getFileName() { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/Kiel2011505MarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/Kiel2011505MarkPassingTest.java index 3d3d22ce26c..3cc40b64aa6 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/Kiel2011505MarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/Kiel2011505MarkPassingTest.java @@ -16,6 +16,8 @@ public class Kiel2011505MarkPassingTest extends AbstractMarkPassingTest { super(); } + // The Start-Times are set very wrong on these races + @Ignore public void testRace2() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { testRace("2"); diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/Kiel2013505Test.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/Kiel2013505Test.java index 65efcb3ebbc..9844a4266f7 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/Kiel2013505Test.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/Kiel2013505Test.java @@ -5,7 +5,7 @@ import java.net.MalformedURLException; import java.net.URISyntaxException; import java.text.ParseException; -import org.junit.Ignore; +import org.junit.Test; import com.tractrac.model.lib.api.event.CreateModelException; import com.tractrac.subscription.lib.api.SubscriberInitializationException; @@ -16,12 +16,13 @@ public class Kiel2013505Test extends AbstractMarkPassingTest { super(); } - @Ignore + //Takes longer than 2 minutes, so the rule in AbstractTracTracLiveTest needs to be deactivated first. + + @Test public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { testRace("5"); } - @Override protected String getFileName() { return "event_20130626_KielerWoch-505_race_"; diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingCalculatorPerformanceTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingCalculatorPerformanceTest.java index 0d3e0ac9bbe..3ac56b61333 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingCalculatorPerformanceTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingCalculatorPerformanceTest.java @@ -13,14 +13,22 @@ import junit.framework.Assert; import org.junit.AfterClass; import org.junit.Test; +import com.sap.sailing.domain.common.SpeedWithBearing; +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.markpassingcalculation.Candidate; -import com.sap.sailing.domain.markpassingcalculation.CandidateChooser; import com.sap.sailing.domain.markpassingcalculation.CandidateFinder; +import com.sap.sailing.domain.markpassingcalculation.impl.CandidateChooserImpl; +import com.sap.sailing.domain.markpassingcalculation.impl.CandidateFinderImpl; +import com.sap.sailing.domain.markpassingcalculation.impl.CandidateImpl; import com.sap.sailing.domain.test.measurements.Measurement; import com.sap.sailing.domain.test.measurements.MeasurementCase; import com.sap.sailing.domain.test.measurements.MeasurementXMLFile; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; +import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; import com.sap.sse.common.impl.MillisecondsTimePoint; @@ -47,49 +55,57 @@ public class MarkPassingCalculatorPerformanceTest extends AbstractMockedRaceMark @Test public void testFinder() { - CandidateFinder f = new CandidateFinder(trackedRace); + CandidateFinder f = new CandidateFinderImpl(race); List fixesAdded = new ArrayList<>(); for (int i = 0; i < 1000; i++) { GPSFixMoving fix = rndFix(); - trackedRace.recordFix(bob, fix); + race.recordFix(ron, fix); fixesAdded.add(fix); } time = System.currentTimeMillis(); - f.getCandidateDeltas(bob, fixesAdded); + f.getCandidateDeltas(ron, fixesAdded); time = System.currentTimeMillis() - time; result.put("FinderPerformance", time); System.out.println(time); - Assert.assertTrue(time < 3000); + Assert.assertTrue(time < 5000); } @Test public void testChooser() { - long time = timeToAddCandidatesToChooser(100, 1, 25); + long time = timeToAddCandidatesToChooser(500, 1, 25); System.out.println(time); result.put("ChooserPerformance", time); - assertTrue(time < 2000); + assertTrue(time < 5000); } private long timeToAddCandidatesToChooser(int numberOfTimesAdding, int numberToAddEachTime, int numberOfRepititions) { long totalTime = 0; for (int i = 0; i < numberOfRepititions; i++) { - CandidateChooser c = new CandidateChooser(trackedRace); + CandidateChooserImpl c = new CandidateChooserImpl(race); for (int j = 0; j < numberOfTimesAdding; j++) { List newCandidates = new ArrayList<>(); for (int k = 0; k < numberToAddEachTime; k++) { newCandidates.add(randomCan()); } time = System.currentTimeMillis(); - c.calculateMarkPassDeltas(bob, new Util.Pair, Iterable>(newCandidates, new ArrayList())); + c.calculateMarkPassDeltas(ron, newCandidates, new ArrayList()); totalTime += System.currentTimeMillis() - time; } } return totalTime / numberOfRepititions; } - private Candidate randomCan() { + private CandidateImpl randomCan() { int id = rnd.nextInt(3); - return new Candidate(id + 1, new MillisecondsTimePoint((long) (rnd.nextDouble() * 200000)), - 0.5 + 0.5 * rnd.nextDouble(), Util.get(trackedRace.getRace().getCourse().getWaypoints(), id), true, true, null); + return new CandidateImpl(id + 1, new MillisecondsTimePoint((long) (rnd.nextDouble() * 200000)), + 0.5 + 0.5 * rnd.nextDouble(), Util.get(race.getRace().getCourse().getWaypoints(), id)); + } + + private GPSFixMoving rndFix() { + DegreePosition position = new DegreePosition(0.001 - rnd.nextDouble() * 0.001, + 0.0002 - rnd.nextDouble() * 0.0004); + TimePoint p = new MillisecondsTimePoint((long) (rnd.nextDouble() * 200000)); + SpeedWithBearing speed = new KnotSpeedWithBearingImpl(rnd.nextInt(11), new DegreeBearingImpl(rnd.nextInt(360))); + return new GPSFixMovingImpl(position, p, speed); } } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingWhiteBoxTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingWhiteBoxTest.java new file mode 100755 index 00000000000..b9a7bf7df93 --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/MarkPassingWhiteBoxTest.java @@ -0,0 +1,305 @@ +package com.sap.sailing.domain.test.markpassing; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.NavigableSet; + +import org.junit.Test; + +import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.base.impl.WaypointImpl; +import com.sap.sailing.domain.common.PassingInstruction; +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.markpassingcalculation.Candidate; +import com.sap.sailing.domain.markpassingcalculation.CandidateChooser; +import com.sap.sailing.domain.markpassingcalculation.CandidateFinder; +import com.sap.sailing.domain.markpassingcalculation.impl.CandidateChooserImpl; +import com.sap.sailing.domain.markpassingcalculation.impl.CandidateFinderImpl; +import com.sap.sailing.domain.markpassingcalculation.impl.CandidateImpl; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.impl.GPSFixMovingImpl; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +/** + * Tests tricky situation that can fail easily. Created with help of http://itouchmap.com/latlong.html + * + */ +public class MarkPassingWhiteBoxTest extends AbstractMockedRaceMarkPassingTest { + + @Test + public void testNormalPassingAndSuppressingPassings() { + // Normal Passing of Single mark + GPSFixMoving fix1 = new GPSFixMovingImpl(new DegreePosition(0.000003, 0.000049), new MillisecondsTimePoint( + 40000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(330))); + GPSFixMoving fix2 = new GPSFixMovingImpl(new DegreePosition(0.000062, 0.000029), new MillisecondsTimePoint( + 44000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(270))); + GPSFixMoving fix3 = new GPSFixMovingImpl(new DegreePosition(0.000026, -0.000024), new MillisecondsTimePoint( + 47000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(225))); + GPSFixMoving fix4 = new GPSFixMovingImpl(new DegreePosition(-0.000056, -0.000049), new MillisecondsTimePoint( + 50000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(190))); + race.recordFix(ron, fix1); + race.recordFix(ron, fix2); + CandidateFinder finder = new CandidateFinderImpl(race); + CandidateChooser chooser = new CandidateChooserImpl(race); + List fixes = new ArrayList(); + fixes.add(fix1); + fixes.add(fix2); + Util.Pair, Iterable> candidateDeltas = finder.getCandidateDeltas(ron, fixes); + assertEquals(0, Util.size(candidateDeltas.getA())); + chooser.calculateMarkPassDeltas(ron, candidateDeltas.getA(), candidateDeltas.getB()); + + race.recordFix(ron, fix3); + fixes.clear(); + fixes.add(fix3); + candidateDeltas = finder.getCandidateDeltas(ron, fixes); + assertEquals(2, Util.size(candidateDeltas.getA())); // CTE candidate + chooser.calculateMarkPassDeltas(ron, candidateDeltas.getA(), candidateDeltas.getB()); + + + race.recordFix(ron, fix4); + fixes.clear(); + fixes.add(fix4); + candidateDeltas = finder.getCandidateDeltas(ron, fixes); + assertEquals(2, Util.size(candidateDeltas.getA())); // Distance Candidate + chooser.calculateMarkPassDeltas(ron, candidateDeltas.getA(), candidateDeltas.getB()); + + NavigableSet markPassings = race.getMarkPassings(ron); + assertEquals(1, markPassings.size()); + chooser.suppressMarkPassings(ron, 1); + NavigableSet markPassingsAfterSupressing = race.getMarkPassings(ron); + assertEquals(0, markPassingsAfterSupressing.size()); + + } + + @Test + public void testDistance() { + // 3 fixes all on one side of crossing Bearing, therefore no XTE-Candidate + GPSFixMoving fix1 = new GPSFixMovingImpl(new DegreePosition(-0.000155, 0.000103), new MillisecondsTimePoint( + 60000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(330))); + GPSFixMoving fix2 = new GPSFixMovingImpl(new DegreePosition(0.000038, 0.000021), new MillisecondsTimePoint( + 65000), new KnotSpeedWithBearingImpl(4, new DegreeBearingImpl(280))); + GPSFixMoving fix3 = new GPSFixMovingImpl(new DegreePosition(-0.000268, 0.000135), new MillisecondsTimePoint( + 80000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(170))); + CandidateFinderImpl finder = new CandidateFinderImpl(race); + + race.recordFix(tom, fix1); + race.recordFix(tom, fix3); + List fixes = new ArrayList<>(); + fixes.add(fix1); + fixes.add(fix3); + Util.Pair, Iterable> cans = finder.getCandidateDeltas(tom, fixes); + assertEquals(Util.size(cans.getA()), 0); + assertEquals(Util.size(cans.getB()), 0); + + fixes.clear(); + race.recordFix(tom, fix2); + fixes.add(fix2); + cans = finder.getCandidateDeltas(tom, fixes); + assertEquals(Util.size(cans.getA()), 2); // 2 Distance candidates (mark is rounded twice in course) + Double probability = cans.getA().iterator().next().getProbability(); + assertTrue(probability > 0.5 && probability < 0.8); // Close but distance candidate + assertEquals(Util.size(cans.getB()), 0); + } + + @Test + public void testPastGate() { + // Competitor sails closely past one mark of gate and rounds the other mark further away + GPSFixMoving fix1 = new GPSFixMovingImpl(new DegreePosition(-0.000967, -0.000124), new MillisecondsTimePoint( + 100000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(100))); + GPSFixMoving fix2 = new GPSFixMovingImpl(new DegreePosition(-0.000989, -0.000001), new MillisecondsTimePoint( + 110000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(160))); + GPSFixMoving fix3 = new GPSFixMovingImpl(new DegreePosition(-0.001079, 0.000045), new MillisecondsTimePoint( + 115000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(90))); + GPSFixMoving fix4 = new GPSFixMovingImpl(new DegreePosition(-0.000982, 0.000143), new MillisecondsTimePoint( + 125000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(45))); + CandidateFinderImpl finder = new CandidateFinderImpl(race); + race.recordFix(tom, fix1); + race.recordFix(tom, fix2); + List fixes = new ArrayList<>(); + fixes.addAll(Arrays.asList(fix1, fix2)); + Util.Pair, Iterable> cans = finder.getCandidateDeltas(tom, fixes); + Double inFront = cans.getA().iterator().next().getProbability(); + assertTrue(Util.size(cans.getA()) == 1); + // Passing of one mark, close but wrong side and direction + assertEquals(Util.size(cans.getB()), 0); + + race.recordFix(tom, fix3); + fixes.clear(); + fixes.add(fix3); + cans = finder.getCandidateDeltas(tom, fixes); + assertEquals(5, Util.size(cans.getA())); + assertEquals(Util.size(cans.getB()), 0); + + race.recordFix(tom, fix4); + fixes.clear(); + fixes.add(fix4); + cans = finder.getCandidateDeltas(tom, fixes); + assertEquals(Util.size(cans.getB()), 0); + for (Candidate c : cans.getA()) { + if (c.getOneBasedIndexOfWaypoint() == 3) { + assertTrue(c.getProbability() > inFront); + // Passing of correct mark with greater distance + } + } + } + + @Test + public void testGateAfterLine() { + // The problem of passing a gate right after the start line is crossed, solved by the distance estimation + GPSFixMoving fix1 = new GPSFixMovingImpl(new DegreePosition(-0.001051, -0.000008), new MillisecondsTimePoint( + 10000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(5))); + GPSFixMoving fix2 = new GPSFixMovingImpl(new DegreePosition(-0.000942, 0.000022), new MillisecondsTimePoint( + 14000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(15))); + race.recordFix(tom, fix1); + race.recordFix(tom, fix2); + Candidate c1 = new CandidateImpl(1, new MillisecondsTimePoint(11000), 0.99, waypoints.get(0)); + Candidate c3 = new CandidateImpl(3, new MillisecondsTimePoint(13000), 0.99, waypoints.get(2)); + // Good candidate but bad time + CandidateChooser chooser = new CandidateChooserImpl(race); + chooser.calculateMarkPassDeltas(tom, Arrays.asList(c1, c3), new ArrayList()); + NavigableSet markPassings = race.getMarkPassings(tom); + assertEquals(markPassings.size(), 1); + assertEquals(markPassings.first().getWaypoint(), waypoints.get(0)); + } + + @Test + public void testVerySlowCompetitor() { + // TODO + } + + @Test + public void testSailingInFrontAndAroundMark() { + CandidateFinder finder = new CandidateFinderImpl(race); + GPSFixMoving fix1 = new GPSFixMovingImpl(new DegreePosition(-0.000037, -0.000126), new MillisecondsTimePoint( + 40000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(80))); + GPSFixMoving fix2 = new GPSFixMovingImpl(new DegreePosition(-0.000022, 0.000001), new MillisecondsTimePoint( + 43000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(45))); + GPSFixMoving fix3 = new GPSFixMovingImpl(new DegreePosition(0.000018, 0.000038), new MillisecondsTimePoint( + 46000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(330))); + GPSFixMoving fix4 = new GPSFixMovingImpl(new DegreePosition(0.000084, -0.000004), new MillisecondsTimePoint( + 49000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(260))); + GPSFixMoving fix5 = new GPSFixMovingImpl(new DegreePosition(0.000054, -0.000153), new MillisecondsTimePoint( + 52000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(200))); + race.recordFix(ben, fix1); + race.recordFix(ben, fix2); + race.recordFix(ben, fix3); + List fixes = new ArrayList<>(); + fixes.addAll(Arrays.asList(fix1, fix2, fix3)); + Util.Pair, Iterable> cans = finder.getCandidateDeltas(ben, fixes); + Candidate inFront = Util.get(cans.getA(), 0); + race.recordFix(ben, fix4); + race.recordFix(ben, fix5); + fixes.clear(); + fixes.addAll(Arrays.asList(fix4, fix5)); + cans = finder.getCandidateDeltas(ben, fixes); + Candidate behind = Util.get(cans.getA(), 0); + assertTrue(behind.getProbability() > inFront.getProbability()); + } + + public void fixAndUnfixMarkPassing() { + GPSFixMoving fix1 = new GPSFixMovingImpl(new DegreePosition(-0.001027, -0.000001), new MillisecondsTimePoint( + 11000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(340))); + GPSFixMoving fix2 = new GPSFixMovingImpl(new DegreePosition(-0.000396, -0.000602), new MillisecondsTimePoint( + 35000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(315))); + GPSFixMoving fix3 = new GPSFixMovingImpl(new DegreePosition(0.000164, -0.000012), new MillisecondsTimePoint( + 60000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(90))); + GPSFixMoving fix4 = new GPSFixMovingImpl(new DegreePosition(-0.000402, 0.00043), new MillisecondsTimePoint( + 85000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(135))); + GPSFixMoving fix5 = new GPSFixMovingImpl(new DegreePosition(-0.001027, -0.000001), new MillisecondsTimePoint( + 115000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(90))); + + race.recordFix(tom, fix1); + race.recordFix(tom, fix2); + race.recordFix(tom, fix3); + race.recordFix(tom, fix4); + race.recordFix(tom, fix5); + + CandidateChooser chooser = new CandidateChooserImpl(race); + Iterable waypoints = race.getRace().getCourse().getWaypoints(); + Candidate c0 = new CandidateImpl(1, new MillisecondsTimePoint(12000), 1, Util.get(waypoints, 0)); + Waypoint waypointToSet = Util.get(waypoints, 1); + Candidate c1 = new CandidateImpl(2, new MillisecondsTimePoint(60000), 1, waypointToSet); + Candidate c2 = new CandidateImpl(3, new MillisecondsTimePoint(110000), 1, Util.get(waypoints, 2)); + + chooser.calculateMarkPassDeltas(tom, Arrays.asList(c0, c1, c2), new ArrayList()); + + NavigableSet markPassingsBeforeFixing = race.getMarkPassings(tom); + MarkPassing markPassingBeforeAdding = Util.get(markPassingsBeforeFixing, 1); + assertTrue(markPassingBeforeAdding.getTimePoint().asMillis() == 60000); + + chooser.setFixedPassing(tom, 1, new MillisecondsTimePoint(58000)); + + NavigableSet markPassingsAfterFixing = race.getMarkPassings(tom); + MarkPassing markPassingAfterAdding = Util.get(markPassingsAfterFixing, 1); + assertTrue(markPassingAfterAdding.getTimePoint().asMillis() == 58000); + + chooser.setFixedPassing(tom, 1, new MillisecondsTimePoint(59000)); + + NavigableSet markPassingsAfterSecondFixing = race.getMarkPassings(tom); + MarkPassing markPassingAfterSecondAdding = Util.get(markPassingsAfterSecondFixing, 1); + assertTrue(markPassingAfterSecondAdding.getTimePoint().asMillis() == 59000); + + chooser.removeFixedPassing(tom, 1); + + NavigableSet markPassingsAfterRemoving = race.getMarkPassings(tom); + MarkPassing markPassingAfterRemoving = Util.get(markPassingsAfterRemoving, 1); + assertTrue(markPassingAfterRemoving.getTimePoint().asMillis() == 60000); + } + + @Test + public void testAddingAndRemovingWaypointToFinder() { + GPSFixMoving fix1 = new GPSFixMovingImpl(new DegreePosition(-0.00045, -0.00052), new MillisecondsTimePoint( + 40000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(200))); + GPSFixMoving fix2 = new GPSFixMovingImpl(new DegreePosition(-0.0006, -0.00052), + new MillisecondsTimePoint(44000), new KnotSpeedWithBearingImpl(5, new DegreeBearingImpl(160))); + + race.recordFix(ron, fix1); + race.recordFix(ron, fix2); + + CandidateFinder finder = new CandidateFinderImpl(race); + Waypoint newWaypoint = new WaypointImpl(reaching, PassingInstruction.Port); + Course course = race.getRace().getCourse(); + + // Before adding + Util.Pair, Iterable> allCandidatesBefore = finder.getAllCandidates(ron); + assertEquals(Util.size(allCandidatesBefore.getB()), 0); + assertEquals(Util.size(allCandidatesBefore.getA()), 0); + + // The process of adding + course.addWaypoint(4, newWaypoint); + Util.Pair, List> addedCans = finder.updateWaypoints(Arrays.asList(newWaypoint), new ArrayList(), 4).get(ron); + assertEquals(Util.size(addedCans.getA()), 1); + for (Candidate c : addedCans.getA()) { + assertEquals(c.getWaypoint(), newWaypoint); + } + + // Before removing + Util.Pair, Iterable> allCandidatesInBetween = finder.getAllCandidates(ron); + assertEquals(Util.size(allCandidatesInBetween.getA()), 1); + assertEquals(Util.size(allCandidatesInBetween.getB()), 0); + + // The Process of removing + course.removeWaypoint(4); + Util.Pair, List> removedCans = finder.updateWaypoints(new ArrayList(), Arrays.asList(newWaypoint), 4).get(ron); + assertEquals(Util.size(removedCans.getB()), 1); + for (Candidate c : removedCans.getB()) { + assertEquals(c.getWaypoint(), newWaypoint); + } + + // After removing + Util.Pair, Iterable> allCansAfterRemoving = finder.getAllCandidates(ron); + assertEquals(Util.size(allCansAfterRemoving.getB()), 0); + assertEquals(Util.size(allCansAfterRemoving.getA()), 0); + } + +} diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/OBMR2012VRMarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/OBMR2012VRMarkPassingTest.java index ef7938fed48..487bfd22c3c 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/OBMR2012VRMarkPassingTest.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/OBMR2012VRMarkPassingTest.java @@ -17,45 +17,70 @@ public class OBMR2012VRMarkPassingTest extends AbstractMarkPassingTest { } @Test - public void testRaceFr11() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { + public void testRaceFr11() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { testRace("Fr_Fleet_1_1"); } @Test - public void testRaceFr12() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { + public void testRaceFr12() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { testRace("Fr_Fleet_1_2"); } - /* - * Ignored on master branch. - * - * @Test public void testRaceFr13() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("Fr_Fleet_1_3"); } - * - * @Test public void testRaceFr21() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("Fr_Fleet_2_1"); } - * - * @Test public void testRaceFr22() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("Fr_Fleet_2_2"); } - * - * @Test public void testRaceFr23() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("Fr_Fleet_2_3"); } - * - * @Test public void testRaceSaVm11() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("Sa-Vm_Fleet_1_1"); } - * - * @Test public void testRaceSaVm12() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("Sa-Vm_Fleet_1_2"); } - * - * @Test public void testRaceSaVm13() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("Sa-Vm_Fleet_1_3"); } - * - * @Test public void testRaceSaVm21() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("Sa-Vm_Fleet_2_1"); } - * - * @Test public void testRaceSaVm22() throws IOException, InterruptedException, URISyntaxException, ParseException, SubscriberInitializationException, CreateModelException { - * testRace("Sa-Vm_Fleet_2_2"); } - */ + @Test + public void testRaceFr13() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("Fr_Fleet_1_3"); + } + + @Test + public void testRaceFr21() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("Fr_Fleet_2_1"); + } + + @Test + public void testRaceFr22() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("Fr_Fleet_2_2"); + } + + @Test + public void testRaceFr23() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("Fr_Fleet_2_3"); + } + + @Test + public void testRaceSaVm11() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("Sa-Vm_Fleet_1_1"); + } + + @Test + public void testRaceSaVm12() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("Sa-Vm_Fleet_1_2"); + } + + @Test + public void testRaceSaVm13() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("Sa-Vm_Fleet_1_3"); + } + + @Test + public void testRaceSaVm21() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("Sa-Vm_Fleet_2_1"); + } + + @Test + public void testRaceSaVm22() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("Sa-Vm_Fleet_2_2"); + } @Override protected String getExpectedEventName() { diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/StarIDM2013MarkPassingTest.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/StarIDM2013MarkPassingTest.java new file mode 100755 index 00000000000..600111324cb --- /dev/null +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/markpassing/StarIDM2013MarkPassingTest.java @@ -0,0 +1,41 @@ +package com.sap.sailing.domain.test.markpassing; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.text.ParseException; + +import org.junit.Test; + +import com.tractrac.model.lib.api.event.CreateModelException; +import com.tractrac.subscription.lib.api.SubscriberInitializationException; + +public class StarIDM2013MarkPassingTest extends AbstractMarkPassingTest { + + public StarIDM2013MarkPassingTest() throws MalformedURLException, URISyntaxException { + super(); + } + + @Test + public void testRace5() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("5"); + } + + @Test + public void testRace6() throws IOException, InterruptedException, URISyntaxException, ParseException, + SubscriberInitializationException, CreateModelException { + testRace("6"); + } + + @Override + protected String getFileName() { + return "event_20130424_IDMStarboo-Race_"; + } + + @Override + protected String getExpectedEventName() { + return "IDM Starboot 2013"; + } + +} 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 f9188fa2a8e..b9dd404787c 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 @@ -25,7 +25,6 @@ import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.base.SpeedWithConfidence; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.base.configuration.RegattaConfiguration; -import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.Position; @@ -484,7 +483,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable sidelines, WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, - DynamicRaceDefinitionSet raceDefinitionSetToUpdate) { + DynamicRaceDefinitionSet raceDefinitionSetToUpdate, boolean useMarkPassingcalculator) { return null; } }; @@ -822,21 +821,10 @@ public class MockedTrackedRace implements DynamicTrackedRace { return null; } - @Override public GPSFixStore getGPSFixStore() { return null; } - @Override - public Bearing getCrossingBearing(Waypoint w, TimePoint t) { - return null; - } - - @Override - public Util.Pair getPortAndStarboardMarks(TimePoint t, Waypoint w) { - return null; - } - @Override public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException { @@ -872,6 +860,12 @@ public class MockedTrackedRace implements DynamicTrackedRace { return null; } + @Override + public boolean isUsingMarkPassingCalculator() { + // TODO Auto-generated method stub + return false; + } + @Override public void attachRegattaLog(RegattaLog regattaLog) { } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java index aa9653d3ae4..87741525f7b 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java @@ -592,4 +592,10 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { return null; } + @Override + public boolean isUsingMarkPassingCalculator() { + // TODO Auto-generated method stub + return false; + } + } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java index e8632586471..39788f49d40 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/DomainFactory.java @@ -136,7 +136,7 @@ public interface DomainFactory { */ TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, - boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, + boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility, TrackedRegattaRegistry trackedRegattaRegistry) throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException; @@ -147,7 +147,7 @@ public interface DomainFactory { */ RaceTracker createRaceTracker(Regatta regatta, URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, - boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, + boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility, TrackedRegattaRegistry trackedRegattaRegistry) throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, @@ -173,7 +173,8 @@ public interface DomainFactory { Iterable getUpdateReceivers(DynamicTrackedRegatta trackedRegatta, long delayToLiveInMillis, Simulator simulator, WindStore windStore, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, TrackedRegattaRegistry trackedRegattaRegistry, IRace tractracRace, URI courseDesignUpdateURI, - String tracTracUsername, String tracTracPassword, IEventSubscriber eventSubscriber, IRaceSubscriber raceSubscriber); + String tracTracUsername, String tracTracPassword, IEventSubscriber eventSubscriber, + IRaceSubscriber raceSubscriber, boolean useInternalMarkPassingAlgorithm); /** * Creates a {@link RaceDefinition} from a TracTrac {@link Race} and a domain {@link Course} definition. The @@ -197,7 +198,7 @@ public interface DomainFactory { String raceName, Iterable competitors, BoatClass boatClass, Course course, Iterable sidelines, WindStore windStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, - URI courseDesignUpdateURI, UUID tracTracEventUuid, String tracTracUsername, String tracTracPassword); + URI courseDesignUpdateURI, UUID tracTracEventUuid, String tracTracUsername, String tracTracPassword, boolean ignoreTracTracMarkPassings); /** * The record may be for a single mark or a gate. If for a gate, the @@ -216,7 +217,7 @@ public interface DomainFactory { Iterable getUpdateReceivers(DynamicTrackedRegatta trackedRegatta, IRace tractracRace, WindStore windStore, long delayToLiveInMillis, Simulator simulator, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, TrackedRegattaRegistry trackedRegattaRegistry, URI courseDesignUpdateURI, String tracTracUsername, - String tracTracPassword, IEventSubscriber eventSubscriber, IRaceSubscriber raceSubscriber, ReceiverType... types); + String tracTracPassword, IEventSubscriber eventSubscriber, IRaceSubscriber raceSubscriber, boolean ignoreTracTracMarkPassings, ReceiverType... types); JSONService parseJSONURLWithRaceRecords(URL jsonURL, boolean loadClientParams) throws IOException, ParseException, org.json.simple.parser.ParseException, URISyntaxException; @@ -252,7 +253,7 @@ public interface DomainFactory { RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, - boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, + boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility); /** * Removes all knowledge about tractracRace which includes removing it from the race cache, from the diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapter.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapter.java index 77240764b88..72fcb93662e 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapter.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/TracTracAdapter.java @@ -70,7 +70,7 @@ public interface TracTracAdapter { RaceHandle addTracTracRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint trackingStartTime, TimePoint trackingEndTime, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, - long timeoutForReceivingRaceDefinitionInMilliseconds, boolean simulateWithStartTimeNow, + long timeoutForReceivingRaceDefinitionInMilliseconds, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility) throws MalformedURLException, FileNotFoundException, URISyntaxException, Exception; diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/Activator.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/Activator.java index fecda2340b6..f5e866cd48d 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/Activator.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/Activator.java @@ -13,8 +13,6 @@ public class Activator implements BundleActivator { private final TracTracAdapterFactory tracTracAdapterFactory; - private static boolean useTracTracMarkPassings = true; - public Activator() { // there is exactly one instance of the racingEventService in the whole server tracTracAdapterFactory = new TracTracAdapterFactoryImpl(); @@ -24,18 +22,8 @@ public class Activator implements BundleActivator { // register the racing service in the OSGi registry context.registerService(TracTracAdapterFactory.class.getName(), tracTracAdapterFactory, null); logger.log(Level.INFO, "Started "+context.getBundle().getSymbolicName()); - useTracTracMarkPassings = context.getProperty("tractrac.usemarkpassings") == null ? true : context.getProperty( - "tractrac.usemarkpassings").equals("false") ? false : true; } public void stop(BundleContext context) throws Exception { } - - public boolean isUseTracTracMarkPassings() { - return useTracTracMarkPassings; - } - - public static Activator getInstance() { - return new Activator(); - } } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java index 650902ae32a..6375116d53e 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/DomainFactoryImpl.java @@ -47,7 +47,6 @@ import com.sap.sailing.domain.common.Position; import com.sap.sailing.domain.common.ScoringSchemeType; import com.sap.sailing.domain.common.impl.DegreeBearingImpl; import com.sap.sailing.domain.common.impl.DegreePosition; -import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator; import com.sap.sailing.domain.racelog.RaceLogStore; import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; import com.sap.sailing.domain.racelog.tracking.GPSFixStore; @@ -402,7 +401,7 @@ public class DomainFactoryImpl implements DomainFactory { WindStore windStore, long delayToLiveInMillis, Simulator simulator, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, TrackedRegattaRegistry trackedRegattaRegistry, URI courseDesignUpdateURI, String tracTracUsername, String tracTracPassword, IEventSubscriber eventSubscriber, - IRaceSubscriber raceSubscriber, ReceiverType... types) { + IRaceSubscriber raceSubscriber, boolean useInternalMarkPassingAlgorithm, ReceiverType... types) { IEvent tractracEvent = tractracRace.getEvent(); Collection result = new ArrayList(); for (ReceiverType type : types) { @@ -411,7 +410,7 @@ public class DomainFactoryImpl implements DomainFactory { result.add(new RaceCourseReceiver(this, trackedRegatta, tractracEvent, tractracRace, windStore, raceDefinitionSetToUpdate, delayToLiveInMillis, WindTrack.DEFAULT_MILLISECONDS_OVER_WHICH_TO_AVERAGE_WIND, simulator, - courseDesignUpdateURI, tracTracUsername, tracTracPassword, eventSubscriber, raceSubscriber)); + courseDesignUpdateURI, tracTracUsername, tracTracPassword, eventSubscriber, raceSubscriber, useInternalMarkPassingAlgorithm)); break; case MARKPOSITIONS: result.add(new MarkPositionReceiver( @@ -422,7 +421,7 @@ public class DomainFactoryImpl implements DomainFactory { trackedRegatta, tractracEvent, this, simulator, eventSubscriber, raceSubscriber)); break; case MARKPASSINGS: - if (Activator.getInstance().isUseTracTracMarkPassings()) { + if (!useInternalMarkPassingAlgorithm) { result.add(new MarkPassingReceiver(trackedRegatta, tractracEvent, simulator, this, eventSubscriber, raceSubscriber)); } break; @@ -440,9 +439,10 @@ public class DomainFactoryImpl implements DomainFactory { long delayToLiveInMillis, Simulator simulator, WindStore windStore, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, TrackedRegattaRegistry trackedRegattaRegistry, IRace tractracRace, URI courseDesignUpdateURI, String tracTracUsername, - String tracTracPassword, IEventSubscriber eventSubscriber, IRaceSubscriber raceSubscriber) { + String tracTracPassword, IEventSubscriber eventSubscriber, IRaceSubscriber raceSubscriber, boolean useInternalMarkPassingAlgorithm) { return getUpdateReceivers(trackedRegatta, tractracRace, windStore, delayToLiveInMillis, simulator, - raceDefinitionSetToUpdate, trackedRegattaRegistry, courseDesignUpdateURI, tracTracUsername, tracTracPassword, eventSubscriber, raceSubscriber, + raceDefinitionSetToUpdate, trackedRegattaRegistry, courseDesignUpdateURI, tracTracUsername, + tracTracPassword, eventSubscriber, raceSubscriber, useInternalMarkPassingAlgorithm, ReceiverType.RACECOURSE, ReceiverType.MARKPASSINGS, ReceiverType.MARKPOSITIONS, ReceiverType.RACESTARTFINISH, ReceiverType.RAWPOSITIONS); } @@ -505,7 +505,7 @@ public class DomainFactoryImpl implements DomainFactory { String raceName, Iterable competitors, BoatClass boatClass, Course course, Iterable sidelines, WindStore windStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, - URI tracTracUpdateURI, UUID tracTracEventUuid, String tracTracUsername, String tracTracPassword) { + URI tracTracUpdateURI, UUID tracTracEventUuid, String tracTracUsername, String tracTracPassword, boolean ignoreTracTracMarkPassings) { synchronized (raceCache) { RaceDefinition raceDefinition = raceCache.get(raceId); if (raceDefinition == null) { @@ -520,7 +520,7 @@ public class DomainFactoryImpl implements DomainFactory { if (raceDefinition.getBoatClass() == trackedRegatta.getRegatta().getBoatClass()) { trackedRegatta.getRegatta().addRace(raceDefinition); trackedRace = createTrackedRace(trackedRegatta, raceDefinition, sidelines, windStore, EmptyGPSFixStore.INSTANCE, - delayToLiveInMillis, millisecondsOverWhichToAverageWind, raceDefinitionSetToUpdate); + delayToLiveInMillis, millisecondsOverWhichToAverageWind, raceDefinitionSetToUpdate, ignoreTracTracMarkPassings); logger.info("Added race " + raceDefinition + " to regatta " + trackedRegatta.getRegatta()); addTracTracUpdateHandlers(tracTracUpdateURI, tracTracEventUuid, tracTracUsername, tracTracPassword, raceDefinition, trackedRace); @@ -534,9 +534,6 @@ public class DomainFactoryImpl implements DomainFactory { } else { logger.info("Found existing tracked race for race "+raceName+" with ID "+raceId); } - if (!Activator.getInstance().isUseTracTracMarkPassings()) { - new MarkPassingCalculator(trackedRace, true); - } return trackedRace; } } @@ -559,11 +556,11 @@ public class DomainFactoryImpl implements DomainFactory { } private DynamicTrackedRace createTrackedRace(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable sidelines, WindStore windStore, GPSFixStore gpsFixStore, - long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, DynamicRaceDefinitionSet raceDefinitionSetToUpdate) { + long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, boolean useMarkPassingCalculator) { return trackedRegatta.createTrackedRace(race, sidelines, windStore, gpsFixStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, /* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(), - raceDefinitionSetToUpdate); + raceDefinitionSetToUpdate, useMarkPassingCalculator); } @Override @@ -638,26 +635,26 @@ public class DomainFactoryImpl implements DomainFactory { @Override public TracTracRaceTracker createRaceTracker(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, - boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, + boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility, TrackedRegattaRegistry trackedRegattaRegistry) throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException { return new TracTracRaceTrackerImpl(this, paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking, - endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, regattaLogStore, windStore, + endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry); } @Override public RaceTracker createRaceTracker(Regatta regatta, URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, long delayToLiveInMillis, - boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, + boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility, TrackedRegattaRegistry trackedRegattaRegistry) throws MalformedURLException, FileNotFoundException, URISyntaxException, CreateModelException, SubscriberInitializationException { return new TracTracRaceTrackerImpl(regatta, this, paramURL, liveURI, storedURI, courseDesignUpdateURI, - startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, + startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry); } @@ -675,11 +672,11 @@ public class DomainFactoryImpl implements DomainFactory { @Override public RaceTrackingConnectivityParameters createTrackingConnectivityParameters(URL paramURL, URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking, - long delayToLiveInMillis, boolean simulateWithStartTimeNow, RaceLogStore raceLogStore, + long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility) { return new RaceTrackingConnectivityParametersImpl(paramURL, liveURI, storedURI, courseDesignUpdateURI, - startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, + startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, this, tracTracUsername, tracTracPassword, raceStatus, raceVisibility); } diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MetadataParserImpl.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MetadataParserImpl.java index 41f3ffabc97..6972bbe22c6 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MetadataParserImpl.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/MetadataParserImpl.java @@ -17,6 +17,7 @@ import com.sap.sailing.domain.common.MarkType; import com.sap.sailing.domain.common.PassingInstruction; import com.sap.sailing.domain.tractracadapter.MetadataParser; import com.sap.sailing.domain.tractracadapter.TracTracControlPoint; +import com.sap.sse.common.Util; import com.sap.sse.common.impl.NamedImpl; /** @@ -82,10 +83,11 @@ public class MetadataParserImpl implements MetadataParser { * Parses the route metadata for additional course information * The 'passing side' for each course waypoint is encoded like this... *

-     *  Seq.1=GATE
-     *  Seq.2=PORT
-     *  Seq.3=GATE
-     *  Seq.4=STARBOARD
+     *  Seq.0=LINE
+     *  Seq.1=PORT
+     *  Seq.2=GATE
+     *  Seq.3=STARBOARD
+     *  Seq.4=LINE
      * 
*/ @Override @@ -93,16 +95,15 @@ public class MetadataParserImpl implements MetadataParser { Map result = new HashMap(); if (routeMetadataString != null) { Map routeMetadata = parseMetadata(routeMetadataString); - int i = 1; - for (TracTracControlPoint controlPoint : controlPoints) { + int start = routeMetadata.containsKey("Seq." + 0) ? 0 : 1; + for (int i = start; i < start + Util.size(controlPoints); i++) { String seqValue = routeMetadata.get("Seq." + i); - if (!controlPoint.getHasTwoPoints() && seqValue != null) { + if (seqValue != null) { final PassingInstruction passingInstructions = PassingInstruction.valueOfIgnoringCase(seqValue); if (passingInstructions != null) { - result.put(i, passingInstructions); + result.put(i - start, passingInstructions); } } - i++; } } return result; diff --git a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java index 2cfd1880a1c..114ce6d2508 100755 --- a/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java +++ b/java/com.sap.sailing.domain.tractracadapter/src/com/sap/sailing/domain/tractracadapter/impl/RaceCourseReceiver.java @@ -16,7 +16,6 @@ import com.sap.sailing.domain.base.RaceDefinition; import com.sap.sailing.domain.base.Regatta; import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.common.PassingInstruction; -import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator; import com.sap.sailing.domain.racelog.tracking.EmptyGPSFixStore; import com.sap.sailing.domain.tracking.DynamicRaceDefinitionSet; import com.sap.sailing.domain.tracking.DynamicTrackedRace; @@ -60,12 +59,13 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue courseWaypointPassingInstructions = getDomainFactory().getMetadataParser().parsePassingInstructionData(routeMetadataString, routeControlPoints); List> ttControlPoints = new ArrayList<>(); - int i = 1; + int i = 0; for (IControl cp : event.getA().getControls()) { PassingInstruction passingInstructions = courseWaypointPassingInstructions.containsKey(i) ? courseWaypointPassingInstructions.get(i) : null; ttControlPoints.add(new com.sap.sse.common.Util.Pair(ttControlPointsForAllOriginalEventControlPoints.get(cp), passingInstructions)); @@ -154,7 +155,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue(); for (Receiver receiver : domainFactory.getUpdateReceivers(getTrackedRegatta(), delayToLiveInMillis, simulator, windStore, this, trackedRegattaRegistry, tractracRace, tracTracUpdateURI, - tracTracUsername, tracTracPassword, eventSubscriber, raceSubscriber)) { + tracTracUsername, tracTracPassword, eventSubscriber, raceSubscriber, useInternalMarkPassingAlgorithm)) { receivers.add(receiver); } addListenersForStoredDataAndStartController(receivers); diff --git a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF old mode 100644 new mode 100755 index a9856674f3d..abcdb4207e4 --- a/java/com.sap.sailing.domain/META-INF/MANIFEST.MF +++ b/java/com.sap.sailing.domain/META-INF/MANIFEST.MF @@ -14,6 +14,7 @@ Export-Package: com.sap.sailing.domain.base, com.sap.sailing.domain.leaderboard.impl, com.sap.sailing.domain.leaderboard.meta, com.sap.sailing.domain.markpassingcalculation, + com.sap.sailing.domain.markpassingcalculation.impl, com.sap.sailing.domain.markpassingcalculation.splining, com.sap.sailing.domain.masterdataimport, com.sap.sailing.domain.racelog, diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java index aa862ad14cc..35b896f7133 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/CourseImpl.java @@ -33,6 +33,7 @@ import difflib.Patch; import difflib.PatchFailedException; public class CourseImpl extends NamedImpl implements Course { + private static final long serialVersionUID = -4280487649617132403L; private static final Logger logger = Logger.getLogger(CourseImpl.class.getName()); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java index 906c2e17780..61fc4571ddb 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/base/impl/RaceColumnRaceLogReplicator.java @@ -2,6 +2,7 @@ package com.sap.sailing.domain.base.impl; import java.io.Serializable; +import com.sap.sailing.domain.abstractlog.race.FixedMarkPassingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; @@ -18,6 +19,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; +import com.sap.sailing.domain.abstractlog.race.SuppressedMarkPassingsEvent; import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogCloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDefineMarkEvent; @@ -152,6 +154,16 @@ public class RaceColumnRaceLogReplicator implements RaceLogEventVisitor, Seriali notifyOnAdd(event); } + @Override + public void visit(FixedMarkPassingEvent event) { + notifyOnAdd(event); + } + + @Override + public void visit(SuppressedMarkPassingsEvent event) { + notifyOnAdd(event); + } + @Override public void visit(RaceLogAdditionalScoringInformationEvent additionalScoringInformation) { notifyOnAdd(additionalScoringInformation); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/AbstractCandidateChooser.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/AbstractCandidateChooser.java deleted file mode 100755 index f936d476f83..00000000000 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/AbstractCandidateChooser.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.sap.sailing.domain.markpassingcalculation; - -import java.util.LinkedHashMap; - -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.tracking.MarkPassing; -import com.sap.sse.common.Util; - -/** - * Takes the {@link Candidate}s from an {@link AbstractCandidateFinder} and calculates any new {@link MarkPassing}s. The - * race should then be notified about these new MarkPassings. - * - * @author Nicolas Klose - * - */ - -public interface AbstractCandidateChooser { - - public void calculateMarkPassDeltas(Competitor c, Util.Pair, Iterable> candidateDeltas); - - public LinkedHashMap> getAllPasses(); -} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/AbstractCandidateFinder.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/AbstractCandidateFinder.java deleted file mode 100755 index 6db9daf73bc..00000000000 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/AbstractCandidateFinder.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.sap.sailing.domain.markpassingcalculation; - -import java.util.LinkedHashMap; -import java.util.List; - -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.tracking.GPSFix; -import com.sap.sse.common.Util; - -/** - * A CandidateFinder converts the incoming GPSFixes of competitors and marks into {@link Candidate}s for each - * competitor. {@link #calculateFixesAffectedByNewMarkFixes(Mark, List)} determines the fixes that may have changed - * their status as {@link Candidate} after new fixes for a mark arrive. This determines those fixes that may have - * changed their status as a {@link Candidate}. {@link #getCandidateDeltas(Competitor, Iterable)} then returns the - * actual changes to the list of Candidates as a Pair of Lists, one of the new Candidates and one of the Candidates that - * should be removed. - * - * @author Nicolas Klose - * - */ - -public interface AbstractCandidateFinder { - - LinkedHashMap> calculateFixesAffectedByNewMarkFixes(Mark mark, Iterable gps); - - /** - * Before calling this method, there have to be fixes that may have changed their status as a Candidate. This is - * done by the the method {@link #calculateFixesAffectedByNewMarkFixes(Mark, Iterable)} - * - * @param c - * @return - */ - Util.Pair, Iterable> getCandidateDeltas(Competitor c, Iterable fixes); - - /** - * When initializing the calculator, the whole race until now is evaluated. For that purpose all of the Candidates - * are needed instead of just the deltas. - * - * @param c - * the competitor whose {@link Candidate}s should be returned - * @return all of the Candidates for the specified competitor - */ - Util.Pair, Iterable> getAllCandidates(Competitor c); -} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java index 8e6dd45374e..71b4aca1767 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Candidate.java @@ -2,61 +2,32 @@ package com.sap.sailing.domain.markpassingcalculation; import com.sap.sailing.domain.base.Waypoint; import com.sap.sse.common.TimePoint; +import com.sap.sailing.domain.markpassingcalculation.impl.CandidateChooserImpl; +import com.sap.sailing.domain.markpassingcalculation.impl.CandidateImpl; /** - * A possible passing of a {@link Waypoint}. It contains the {@link Waypoint} that it might be passing, a + * Represent the possible {@link MarkPassing} of a {@link Waypoint}. It contains the {@link Waypoint} it might be passing, a * {@link TimePoint}, the probability that this candidate is a passing (e.g. based on the distance to the * {@link Waypoint}) and the one-based(!) ID of this Waypoint. The ID is one based because the standard implementation - * of {@link AbstractCandidateChooser} (see {@link CandidateChooser}) uses a proxy Candidates at the end and the - * beginning of the race, the one at the beginning receives the ID 0. Candidates are created in an - * {@link AbstractCandidateFinder}, which determines where a passing could be and assigns them + * of {@link CandidateChooser} (see {@link CandidateChooserImpl}) uses a proxy Candidates at the end and the beginning + * of the race, the one at the beginning recieves the ID 0. * * @author Nicolas Klose * */ -public class Candidate implements Comparable { - private Waypoint waypoint; - private TimePoint timePoint; - private double distanceProbability; - private Integer id; - private boolean rightSide; - private boolean rightDirection; - private String type; +public interface Candidate extends Comparable { - public Candidate(int id, TimePoint timePoint, double distanceProbability, Waypoint w, boolean rightSide, boolean rightDirection, String type) { - this.waypoint = w; - this.timePoint = timePoint; - this.distanceProbability = distanceProbability; - this.id = id; - this.rightSide = rightSide; - this.rightDirection=rightDirection; - this.type = type; - } + Waypoint getWaypoint(); - public int getID() { - return id; - } + int getOneBasedIndexOfWaypoint(); - public TimePoint getTimePoint() { - return timePoint; - } + TimePoint getTimePoint(); - public Double getProbability() { - double factor= (rightSide&&rightDirection)?1:(rightSide||rightDirection)?0.7:0.4; - double cost = distanceProbability * factor; - return cost; - } + /** + * @return the probability that this actually is a passing. + */ + Double getProbability(); - public Waypoint getWaypoint() { - return waypoint; - } - - public String toString() { - return type + "-Candidate for " + id + " with cost " + getProbability() + "and Timepoint " + timePoint; - } - - @Override - public int compareTo(Candidate arg0) { - return id != arg0.getID() ? id.compareTo(arg0.getID()) : timePoint != arg0.getTimePoint() ? timePoint.compareTo(arg0.getTimePoint()) : getProbability().compareTo(arg0.getProbability()); - } + int compareTo(Candidate arg0); + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateChooser.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateChooser.java index 97318ab4b97..d2d1e2f809a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateChooser.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateChooser.java @@ -1,234 +1,30 @@ package com.sap.sailing.domain.markpassingcalculation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import java.util.logging.Level; -import java.util.logging.Logger; - import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.LegType; -import com.sap.sailing.domain.common.NoWindException; -import com.sap.sailing.domain.common.impl.MeterDistance; +import com.sap.sailing.domain.markpassingcalculation.impl.CandidateImpl; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.MarkPassing; -import com.sap.sailing.domain.tracking.TrackedLeg; -import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; -/** - * The standard implementation of {@link AbstractCandidateChooser}. A graph is created, with each {@link Candidate} as a - * vertices between two proxy Candidates, start and end. The probability of an {@link Edge} is - * equal to the probability of the two Candidates plus and estimation of the distance traveled between the them. A - * shortest path-algorithm is then used to find the most likely sequence of {@link MarkPassing}s. - * - * @author Nicolas Klose - * - */ -public class CandidateChooser implements AbstractCandidateChooser { +public interface CandidateChooser { + /** + * Calculates any new {@link MarkPassing}s and notifies the {@link DynamicTrackedRace}. + * + * @param candidateDeltas + * new {@link CandidateImpl}s and those that should be removed. + */ - private static final Logger logger = Logger.getLogger(CandidateChooser.class.getName()); + public void calculateMarkPassDeltas(Competitor c, Iterable newCans, Iterable oldCans); - private LinkedHashMap> currentMarkPasses = new LinkedHashMap<>(); - private LinkedHashMap> allEdges = new LinkedHashMap<>(); - private LinkedHashMap> candidates = new LinkedHashMap<>(); - private TimePoint raceStartTime; - private Candidate start; - private Candidate end; - private final DynamicTrackedRace race; - private final double penaltyForSkipping = 1 - Edge.getPenaltyForSkipping(); - private static double strictness = 900; // Lower = stricter; 900 + void removeWaypoints(Iterable ways); - public CandidateChooser(DynamicTrackedRace race) { - logger.setLevel(Level.INFO); - this.race = race; - raceStartTime = race.getStartOfRace()!=null?race.getStartOfRace().minus(2000):null; - start = new Candidate(/*Index*/0, raceStartTime, /*Probability*/1, /*Waypoint*/null, /*right Side*/true, /*right Direction*/true, "Proxy"); - end = new Candidate(race.getRace().getCourse().getIndexOfWaypoint(race.getRace().getCourse().getLastWaypoint()) + 2, /*TimePoint*/null, - /*Probability*/1, /*Waypoint*/null, /*right Side*/true, /*right Direction*/true, "Proxy"); - candidates = new LinkedHashMap<>(); - for (Competitor c : race.getRace().getCompetitors()) { - candidates.put(c, new TreeSet()); - currentMarkPasses.put(c, new LinkedHashMap()); - allEdges.put(c, new TreeSet()); - addCandidates(Arrays.asList(start, end), c); - } - } + void setFixedPassing(Competitor c, Integer zeroBasedIndexOfWaypoint, TimePoint t); - @Override - public LinkedHashMap> getAllPasses() { - return currentMarkPasses; - } + void removeFixedPassing(Competitor c, Integer zeroBasedIndexOfWaypoint); - @Override - public void calculateMarkPassDeltas(Competitor c, Util.Pair, Iterable> candidateDeltas) { - if (race.getStartOfRace() != null && (raceStartTime == null || !race.getStartOfRace().minus(2000).equals(raceStartTime))) { - raceStartTime = race.getStartOfRace().minus(2000); - for (Competitor com : allEdges.keySet()) { - removeCandidates(Arrays.asList(start), com); - } - start = new Candidate(0, raceStartTime, /*Probability*/1, /*Waypoint*/null, /*right Side*/true, /*right Direction*/true, "Proxy"); - for (Competitor com : allEdges.keySet()) { - addCandidates(Arrays.asList(start), com); - } - } - removeCandidates(candidateDeltas.getB(), c); - addCandidates(candidateDeltas.getA(), c); - findShortestPath(c); - } + void suppressMarkPassings(Competitor c, Integer zeroBasedIndexOfWaypoint); - private void createNewEdges(Competitor co, Iterable newCandidates) { - for (Candidate newCan : newCandidates) { - for (Candidate oldCan : candidates.get(co)) { - Candidate early = newCan; - Candidate late = oldCan; - if (oldCan.getID() < newCan.getID()) { - early = oldCan; - late = newCan; - } - if (raceStartTime != null) { - if (late == end && early != end) { - allEdges.get(co).add(new Edge(early, late, 1)); - } else if (!(early.getID() == late.getID()) && !late.getTimePoint().before(early.getTimePoint()) && estimatedDistance(co, early, late) > penaltyForSkipping) { - Edge e = new Edge(early, late, estimatedDistance(co, early, late)); - allEdges.get(co).add(e); - } - } else { - if ((late == end || early == start) && early != late) { - allEdges.get(co).add(new Edge(early, late, 1)); - } else if (!(early.getID() == late.getID()) && late.getTimePoint().after(early.getTimePoint()) && estimatedDistance(co, early, late) > penaltyForSkipping) { - allEdges.get(co).add(new Edge(early, late, estimatedDistance(co, early, late))); - } - } - } - } - } + void stopSuppressingMarkPassings(Competitor c); - private void findShortestPath(Competitor co) { - boolean changed = false; - ArrayList all = new ArrayList<>(); - for (Edge e : allEdges.get(co)) { - all.add(e); - } - LinkedHashMap> candidateWithParent = new LinkedHashMap<>(); - candidateWithParent.put(start, new Util.Pair(null, 0.0)); - Util.Pair currentMostLikelyEdge = null; - while (!candidateWithParent.containsKey(end)) { - currentMostLikelyEdge = null; - for (Edge e : all) { - if (candidateWithParent.containsKey(e.getStart())) { - Double cost = candidateWithParent.get(e.getStart()).getB() + e.getProbability(); - if (currentMostLikelyEdge == null) { - currentMostLikelyEdge = new Util.Pair(e, cost); - } else if (cost < currentMostLikelyEdge.getB()) { - currentMostLikelyEdge = new Util.Pair(e, cost); - } - } - } - if (!candidateWithParent.containsKey(currentMostLikelyEdge.getA().getEnd())) { - candidateWithParent.put(currentMostLikelyEdge.getA().getEnd(), new Util.Pair(currentMostLikelyEdge.getA().getStart(), currentMostLikelyEdge.getB())); - } - all.remove(currentMostLikelyEdge.getA()); - } - Candidate marker = candidateWithParent.get(end).getA(); - while (!(marker == start)) { - if (currentMarkPasses.get(co).get(marker.getWaypoint()) == null || !currentMarkPasses.get(co).get(marker.getWaypoint()).getTimePoint().equals(marker.getTimePoint())) { - currentMarkPasses.get(co).put(marker.getWaypoint(), new MarkPassingImpl(marker.getTimePoint(), marker.getWaypoint(), co)); - changed = true; - } - marker = candidateWithParent.get(marker).getA(); - } - if (changed) { - logger.info("New MarkPasses for " + co); - List markPassDeltas = new ArrayList<>(); - for (MarkPassing m : currentMarkPasses.get(co).values()) { - markPassDeltas.add(m); - } - race.updateMarkPassings(co, markPassDeltas); - } - } - - private double estimatedDistance(Competitor c, Candidate c1, Candidate c2) { - Distance totalEstimatedDistance = new MeterDistance(0); - Waypoint current; - if (c1.getID() == 0) { - current = race.getRace().getCourse().getFirstWaypoint(); - } else { - current = c1.getWaypoint(); - } - while (current != c2.getWaypoint()) { - TrackedLeg leg = race.getTrackedLegStartingAt(current); - totalEstimatedDistance = totalEstimatedDistance.add(estimatedDistanceOnLeg(leg, - c1.getTimePoint().plus(1 / 2 * c2.getTimePoint().minus(c1.getTimePoint().asMillis()).asMillis()))); - current = leg.getLeg().getTo(); - } - Distance actualDistance = race.getTrack(c).getDistanceTraveled(c1.getTimePoint(), c2.getTimePoint()); - double differenceInMeters = totalEstimatedDistance.getMeters() - actualDistance.getMeters(); - double sigmaSquared = 1 / (2 * Math.PI); - double factor = 1 / (Math.sqrt(sigmaSquared * 2 * Math.PI)); - double exponent = -(Math.pow(differenceInMeters / strictness, 2) / (2 * sigmaSquared)); - double result = factor * Math.pow(Math.E, exponent); - return result; - } - - private Distance estimatedDistanceOnLeg(TrackedLeg leg, TimePoint t) { - try { - if (leg.getLegType(t) == LegType.DOWNWIND) { - return leg.getGreatCircleDistance(t).scale(1.2); - } - if (leg.getLegType(t) == LegType.UPWIND) { - return leg.getGreatCircleDistance(t).scale(Math.sqrt(2)); - } - } catch (NoWindException e) { - Logger.getLogger(CandidateChooser.class.getName()).log(Level.SEVERE, - "CandidateChooser threw exception " + e.getMessage() + " while estimating the Time between two Candidates."); - } - return leg.getGreatCircleDistance(t).scale(1.3); - } - - private void addCandidates(Iterable newCandidates, Competitor co) { - for (Candidate c : newCandidates) { - if (!candidates.get(co).contains(c)) { - candidates.get(co).add(c); - } - } - createNewEdges(co, newCandidates); - } - - private void removeCandidates(Iterable wrongCandidates, Competitor co) { - for (Candidate c : wrongCandidates) { - candidates.get(co).remove(c); - List toRemove = new ArrayList<>(); - for (Edge e : allEdges.get(co)) { - if (e.getStart().equals(c) || e.getEnd().equals(c)) { - toRemove.add(e); - } - } - for (Edge e : toRemove) { - allEdges.get(co).remove(e); - } - } - } } - -/* - * @SuppressWarnings("unused") private void reEvaluateStartingEdges() { for (Competitor c : candidates.keySet()) { - * ArrayList newEdges = new ArrayList<>(); ArrayList edgesToRemove = new ArrayList<>(); for (Edge e : - * allEdges.get(c)) { if (e.getStart().getID() == 0 && e.getEnd().getID() == 1) { newEdges.add(new Edge(e.getStart(), - * e.getEnd(), numberOfCloseStarts(e.getEnd().getTimePoint()))); edgesToRemove.add(e); } } for (Edge e : edgesToRemove) - * { allEdges.get(c).remove(e); } for (Edge e : newEdges) { allEdges.get(c).add(e); } } } - * - * private double numberOfCloseStarts(TimePoint t) { double numberOfCompetitors = 0; double totalTimeDifference = 0; for - * (Competitor c : candidates.keySet()) { double closestCandidate = -1; for (Candidate ca : candidates.get(c)) { if - * (ca.getID() == 1) { if (closestCandidate == -1) { closestCandidate = Math.abs(ca.getTimePoint().asMillis() - - * t.asMillis()); } else if (closestCandidate > Math.abs(ca.getTimePoint().asMillis() - t.asMillis())) { - * closestCandidate = Math.abs(ca.getTimePoint().asMillis() - t.asMillis()); } } } if (closestCandidate != -1) { - * numberOfCompetitors++; totalTimeDifference = totalTimeDifference + closestCandidate; } } return 1 / (0.00001 * - * (totalTimeDifference / numberOfCompetitors) + 1); } - */ diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java index eceabaef3fd..a69d4a915df 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/CandidateFinder.java @@ -1,468 +1,45 @@ package com.sap.sailing.domain.markpassingcalculation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Set; -import java.util.TreeMap; -import java.util.TreeSet; -import java.util.logging.Level; -import java.util.logging.Logger; +import java.util.Map; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.PassingInstruction; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.impl.DegreeBearingImpl; -import com.sap.sailing.domain.common.impl.MeterDistance; -import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.GPSFix; -import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; /** - * The standard implemantation of {@link AbstractCandidateFinder}. There are two ways {@link Candidate}s can be created. - * First of all, all local distance minima to a waypoint are a candidate. Secondly, every time a competitor passes the - * crossing Bearing of a Waypoint a Candidate is created through linear interpolation. The probability of a Candidate - * depends on its distance to the waypoint, whether it is on the right side and if it passes in the right direction. + * Converts the incoming GPSFixes of competitors and marks into {@link Candidate}s for each competitor. * * @author Nicolas Klose - * */ -public class CandidateFinder implements AbstractCandidateFinder { +public interface CandidateFinder { - private static final Logger logger = Logger.getLogger(CandidateFinder.class.getName()); + - private LinkedHashMap>>> crossTrackErrors = new LinkedHashMap<>(); - private LinkedHashMap>> distances = new LinkedHashMap<>(); - private LinkedHashMap, Candidate>>> cteCandidates = new LinkedHashMap<>(); - private LinkedHashMap>> distanceCandidates = new LinkedHashMap<>(); - private final DynamicTrackedRace race; - private final double penaltyForSkipping = 1 - Edge.getPenaltyForSkipping(); - private final LinkedHashMap passingInstructions = new LinkedHashMap<>(); - private final Comparator comp = new Comparator() { - @Override - public int compare(GPSFix arg0, GPSFix arg1) { - return arg0.getTimePoint().compareTo(arg1.getTimePoint()); - } - }; + /** + * @param fixes + * Either new fixes or fixes that may have changed their status, e.g. as a result of new mark + * fixes. + * @return new {@link Candidate}s and those that should be removed. + */ + Util.Pair, Iterable> getCandidateDeltas(Competitor c, Iterable fixes); - private double strictness = 9; // Higher = stricter; 9 - public CandidateFinder(DynamicTrackedRace race) { - this.race = race; - for (Competitor c : race.getRace().getCompetitors()) { - crossTrackErrors.put(c, new TreeMap>>(comp)); - distances.put(c, new TreeMap>(comp)); - cteCandidates.put(c, new LinkedHashMap, Candidate>>()); - distanceCandidates.put(c, new LinkedHashMap>()); - for (Waypoint w : race.getRace().getCourse().getWaypoints()) { - cteCandidates.get(c).put(w, new LinkedHashMap, Candidate>()); - distanceCandidates.get(c).put(w, new LinkedHashMap()); - } - } - for (Waypoint w : race.getRace().getCourse().getWaypoints()) { - PassingInstruction instruction = w.getPassingInstructions(); - if (instruction == PassingInstruction.None || instruction == null) { - if (w.equals(race.getRace().getCourse().getFirstWaypoint()) || w.equals(race.getRace().getCourse().getLastWaypoint())) { - instruction = PassingInstruction.Line; - } else { - int numberofMarks = 0; - Iterator it = w.getMarks().iterator(); - while (it.hasNext()) { - it.next(); - numberofMarks++; - } - if (numberofMarks == 2) { - instruction = PassingInstruction.Gate; - } else if (numberofMarks == 1) { - instruction = PassingInstruction.Port; - } else { - instruction = PassingInstruction.None; - } - } - } - passingInstructions.put(w, instruction); - } - Edge.setNumberOfWayoints(passingInstructions.keySet().size()); - } - @Override - public Util.Pair, Iterable> getAllCandidates(Competitor c) { - Set fixes = new TreeSet(comp); - try { - race.getTrack(c).lockForRead(); - for (GPSFix fix : race.getTrack(c).getFixes()) { - fixes.add(fix); - } - } finally { - race.getTrack(c).unlockAfterRead(); - } - for (Waypoint w : passingInstructions.keySet()) { - cteCandidates.get(c).get(w).clear(); - distanceCandidates.get(c).get(w).clear(); - } - return getCandidateDeltas(c, fixes); - } + /** + * When initializing or refreshing the calculator, the whole race until now is evaluated. For that purpose all of the + * {@link Candidate}s are needed instead of just the deltas. + */ + Util.Pair, Iterable> getAllCandidates(Competitor c); + + Map, List>> updateWaypoints(Iterable addedWaypoints, Iterable removedWaypoints, Integer smallestIndex); - @Override - public LinkedHashMap> calculateFixesAffectedByNewMarkFixes(Mark mark, Iterable gps) { - TreeSet fixes = new TreeSet(comp); - for (GPSFix fix : gps) { - fixes.add(fix); - } - LinkedHashMap> affectedFixes = new LinkedHashMap<>(); - for (Competitor c : race.getRace().getCompetitors()) { - affectedFixes.put(c, new ArrayList()); - } - for (GPSFix fix : fixes) { - GPSFix end = fixes.higher(fix); - for (Competitor c : affectedFixes.keySet()) { - GPSFix comFix = crossTrackErrors.get(c).ceilingKey(fix); - if (end != null) { - while (comFix != null && comFix.getTimePoint().before(end.getTimePoint())) { - affectedFixes.get(c).add(comFix); - comFix = crossTrackErrors.get(c).higherKey(comFix); - } - } else { - while (comFix != null) { - affectedFixes.get(c).add(comFix); - comFix = crossTrackErrors.get(c).higherKey(comFix); - } - } - } - } - return affectedFixes; - } + /** + * @return The fixes for each Competitor that may have changed their status as a {@link Candidate} because of new + * mark fixes.. + */ + Map> calculateFixesAffectedByNewMarkFixes(Map> newMarkFixes); - @Override - public Util.Pair, Iterable> getCandidateDeltas(Competitor c, Iterable fixes) { - if (logger.getLevel() == Level.FINEST) { - logger.finest("Reevaluating MarkPasses for" + c); - } - Set newCans = new TreeSet<>(); - Set wrongCans = new TreeSet<>(); - LinkedHashMap, List>> distanceCandidates = checkForDistanceCandidateChanges(c, fixes); - LinkedHashMap, List>> cteCandidates = checkForCTECandidatesChanges(c, fixes); - for (Waypoint w : passingInstructions.keySet()) { - newCans.addAll(cteCandidates.get(w).getA()); - newCans.addAll(distanceCandidates.get(w).getA()); - wrongCans.addAll(distanceCandidates.get(w).getB()); - wrongCans.addAll(cteCandidates.get(w).getB()); - } - if (newCans.size() != 0 || wrongCans.size() != 0) { - logger.fine(newCans.size() + " new Candidates and " + wrongCans.size() + " removed Candidates for " + c); - } - return new Util.Pair, Iterable>(newCans, wrongCans); - } - - private LinkedHashMap, List>> checkForDistanceCandidateChanges(Competitor c, Iterable fixes) { - LinkedHashMap, List>> result = new LinkedHashMap<>(); - calculatesDistances(c, fixes); - for (Waypoint w : passingInstructions.keySet()) { - result.put(w, new Util.Pair, List>(new ArrayList(), new ArrayList())); - } - Set affectedFixes = new TreeSet(comp); - for (GPSFix fix : fixes) { - affectedFixes.add(fix); - GPSFix fixBefore = distances.get(c).lowerKey(fix); - GPSFix fixAfter = distances.get(c).higherKey(fix); - if (fixBefore != null) { - affectedFixes.add(fixBefore); - } - if (fixAfter != null) { - affectedFixes.add(fixAfter); - } - } - for (GPSFix fix : affectedFixes) { - TimePoint t = null; - Position p = null; - GPSFix fixBefore = distances.get(c).lowerKey(fix); - GPSFix fixAfter = distances.get(c).higherKey(fix); - for (Waypoint w : passingInstructions.keySet()) { - Double cost = null; - Boolean wasCan = false; - Candidate oldCan = null; - Boolean isCan = false; - Distance dis = distances.get(c).get(fix).get(w); - Distance disBefore = fixBefore != null ? distances.get(c).get(fixBefore).get(w) : null; - Distance disAfter = fixAfter != null ? distances.get(c).get(fixAfter).get(w) : null; - if (dis == null || disBefore == null || disAfter == null) { - continue; - } - if (disBefore != null && disAfter != null && dis.getMeters() < disBefore.getMeters() && dis.getMeters() < disAfter.getMeters()) { - t = fix.getTimePoint(); - p = fix.getPosition(); - cost = getDistanceLikelyhood(w, p, t); - if (cost == null) { - continue; - } - cost *= 0.9; - if (cost > penaltyForSkipping) { - isCan = true; - } - } - for (GPSFix possibleCanFix : distanceCandidates.get(c).get(w).keySet()) { - if (possibleCanFix.equals(fix)) { - wasCan = true; - oldCan = distanceCandidates.get(c).get(w).get(possibleCanFix); - break; - } - } - if (!wasCan && isCan) { - Candidate newCan = new Candidate(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t, cost, w, isOnCorrectSideOfWaypoint(w, p, t), true, "Distance"); - distanceCandidates.get(c).get(w).put(fix, newCan); - result.get(w).getA().add(newCan); - } else if (wasCan && !isCan) { - distanceCandidates.get(c).get(w).remove(fix); - result.get(w).getB().add(oldCan); - } else if (wasCan && isCan && oldCan.getProbability() != cost) { - Candidate newCan = new Candidate(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t, cost, w, isOnCorrectSideOfWaypoint(w, p, t), true, "Distance"); - distanceCandidates.get(c).get(w).put(fix, newCan); - result.get(w).getA().add(newCan); - result.get(w).getB().add(oldCan); - } - } - } - return result; - } - - private void calculatesDistances(Competitor c, Iterable fixes) { - for (GPSFix fix : fixes) { - distances.get(c).put(fix, new LinkedHashMap()); - for (Waypoint w : passingInstructions.keySet()) { - Distance distance = calculateDistance(fix.getPosition(), w, fix.getTimePoint()); - distances.get(c).get(fix).put(w, distance); - } - } - } - - private LinkedHashMap, List>> checkForCTECandidatesChanges(Competitor c, Iterable fixes) { - calculateCrossTrackErrors(c, fixes); - LinkedHashMap, List>> result = new LinkedHashMap<>(); - for (Waypoint w : passingInstructions.keySet()) { - result.put(w, new Util.Pair, List>(new ArrayList(), new ArrayList())); - } - for (GPSFix fix : fixes) { - GPSFix fixBefore = crossTrackErrors.get(c).lowerKey(fix); - GPSFix fixAfter = crossTrackErrors.get(c).higherKey(fix); - for (Waypoint w : passingInstructions.keySet()) { - List> oldCandidates = new ArrayList<>(); - Set> newCandidates = new TreeSet>(new Comparator>() { - @Override - public int compare(List arg0, List arg1) { - int firstFix = arg0.get(0).getTimePoint().compareTo(arg1.get(0).getTimePoint()); - return firstFix != 0 ? firstFix : arg0.get(1).getTimePoint().compareTo(arg1.get(1).getTimePoint()); - } - }); - for (List fixPair : cteCandidates.get(c).get(w).keySet()) { - if (fixPair.contains(fix)) { - oldCandidates.add(fixPair); - } - } - if ((crossTrackErrors.get(c).get(fix).get(w).getA() != null && crossTrackErrors.get(c).get(fix).get(w).getA() == 0) - || (crossTrackErrors.get(c).get(fix).get(w).getB() != null && crossTrackErrors.get(c).get(fix).get(w).getB() == 0)) { - newCandidates.add(Arrays.asList(fix, fix)); - } else { - if (fixAfter != null && crossTrackErrorSignChanges(fix, fixAfter, w, c)) { - newCandidates.add(Arrays.asList(fix, fixAfter)); - } - if (fixBefore != null && crossTrackErrorSignChanges(fix, fixBefore, w, c)) { - newCandidates.add(Arrays.asList(fixBefore, fix)); - } - } - for (List canFixes : newCandidates) { - Candidate newCan = createCandidate(c, canFixes.get(0), canFixes.get(1), w); - if (oldCandidates.contains(canFixes)) { - oldCandidates.remove(canFixes); - if (newCan.getProbability() != cteCandidates.get(c).get(w).get(canFixes).getProbability()) { - result.get(w).getB().add(cteCandidates.get(c).get(w).get(canFixes)); - cteCandidates.get(c).get(w).remove(canFixes); - if (newCan.getProbability() > penaltyForSkipping) { - result.get(w).getA().add(newCan); - cteCandidates.get(c).get(w).put(canFixes, newCan); - } - } - } else { - if (newCan.getProbability() > penaltyForSkipping) { - result.get(w).getA().add(newCan); - cteCandidates.get(c).get(w).put(canFixes, newCan); - } - } - } - for (List badCanFixes : oldCandidates) { - result.get(w).getB().add(cteCandidates.get(c).get(w).get(badCanFixes)); - cteCandidates.get(c).get(w).remove(badCanFixes); - } - } - } - return result; - } - - private void calculateCrossTrackErrors(Competitor c, Iterable fixes) { - for (GPSFix fix : fixes) { - crossTrackErrors.get(c).put(fix, new LinkedHashMap>()); - for (Waypoint w : passingInstructions.keySet()) { - Iterator it = w.getMarks().iterator(); - List marks = new ArrayList(); - while (it.hasNext()) { - marks.add(it.next()); - } - Util.Pair ctes; - Position fixPos = fix.getPosition(); - Position markPos = race.getOrCreateTrack(marks.get(0)).getEstimatedPosition(fix.getTimePoint(), false); - Bearing crossingBearing = race.getCrossingBearing(w, fix.getTimePoint()); - Double cte1 = null; - Double cte2 = null; - if (crossingBearing != null) { - cte1 = fixPos.crossTrackError(markPos, crossingBearing).getMeters(); - if (passingInstructions.get(w) == PassingInstruction.Gate) { - cte2 = fixPos.crossTrackError(race.getOrCreateTrack(marks.get(1)).getEstimatedPosition(fix.getTimePoint(), false), crossingBearing).getMeters(); - - } - } - ctes = new Util.Pair(cte1, cte2); - crossTrackErrors.get(c).get(fix).put(w, ctes); - } - } - } - - private boolean crossTrackErrorSignChanges(GPSFix fix, GPSFix fix2, Waypoint w, Competitor c) { - Double cte = crossTrackErrors.get(c).get(fix).get(w).getA(); - Double cte2 = crossTrackErrors.get(c).get(fix2).get(w).getA(); - if (cte!=null&&cte2!=null&&( cte< 0) != (cte2<= 0)) { - return true; - } else if (passingInstructions.get(w) == PassingInstruction.Gate){ - cte = crossTrackErrors.get(c).get(fix).get(w).getB(); - cte2 = crossTrackErrors.get(c).get(fix2).get(w).getB(); - if (cte!=null&&cte2!=null&&( cte< 0) != (cte2<= 0)) { - return true; - } - } - return false; - } - - private Candidate createCandidate(Competitor c, GPSFix fix1, GPSFix fix2, Waypoint w) { - double cte1 = crossTrackErrors.get(c).get(fix1).get(w).getA(); - double cte2 = crossTrackErrors.get(c).get(fix2).get(w).getA(); - if ((cte1 < 0 && cte2 < 0) || (cte1 > 0 && cte2 > 0)) { - cte1 = crossTrackErrors.get(c).get(fix1).get(w).getB(); - cte2 = crossTrackErrors.get(c).get(fix2).get(w).getB(); - } - TimePoint start = fix1.getTimePoint(); - long differenceInMillis = fix2.getTimePoint().asMillis() - fix1.getTimePoint().asMillis(); - double ratio = (Math.abs(cte1) / (Math.abs(cte1) + Math.abs(cte2))); - TimePoint t = start.plus((long) (differenceInMillis * ratio)); - Position p = race.getTrack(c).getEstimatedPosition(t, true); - double cost = getDistanceLikelyhood(w, p, t); - return new Candidate(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t, cost, w, isOnCorrectSideOfWaypoint(w, p, t), passesInTheRightDirection(w, cte1, cte2), "CTE"); - } - - private boolean isOnCorrectSideOfWaypoint(Waypoint w, Position p, TimePoint t) { - boolean result = true; - if (passingInstructions.get(w) == PassingInstruction.Port || passingInstructions.get(w) == PassingInstruction.Starboard - || passingInstructions.get(w) == PassingInstruction.FixedBearing) { - final Position estimatedMarkPosition = race.getOrCreateTrack(w.getMarks().iterator().next()).getEstimatedPosition(t, true); - if (estimatedMarkPosition != null) { - result = p.crossTrackError(estimatedMarkPosition, - race.getCrossingBearing(w, t).add(new DegreeBearingImpl(90))).getMeters() < 0; - } else { - // TODO - } - } else if (passingInstructions.get(w) == PassingInstruction.Gate) { - // TODO - } else if (passingInstructions.get(w) == PassingInstruction.Offset) { - // TODO - } - return result; - } - - private boolean passesInTheRightDirection(Waypoint w, double cte1, double cte2) { - boolean result = true; - if (passingInstructions.get(w) == PassingInstruction.Port) { - result = cte1 > cte2 ? true : false; - } else if (passingInstructions.get(w) == PassingInstruction.Starboard) { - result = cte1 < cte2 ? true : false; - } else if (passingInstructions.get(w) == PassingInstruction.Line) { - result = cte1 > cte2 ? true : false; - } else if (passingInstructions.get(w) == PassingInstruction.Gate) { - // TODO - } else if (passingInstructions.get(w) == PassingInstruction.Offset) { - // TODO - } else if (passingInstructions.get(w) == PassingInstruction.FixedBearing) { - // TODO - } - if (result == false) { - System.currentTimeMillis(); - } - return result; - } - - private Double getDistanceLikelyhood(Waypoint w, Position p, TimePoint t) { - Distance distance = calculateDistance(p, w, t); - Distance legLength = getLegLength(t, w); - if (legLength != null) { - double result = 1 / (strictness * Math.abs(distance.getMeters() / legLength.getMeters()) + 1); - // Auch NormalVerteilung??! - return result; - } - return null; - } - - private Distance getLegLength(TimePoint t, Waypoint w) { - if (w == race.getRace().getCourse().getFirstWaypoint()) { - return race.getTrackedLegStartingAt(w).getGreatCircleDistance(t); - } else if (w == race.getRace().getCourse().getLastWaypoint()) { - return race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t); - } else { - Distance before = race.getTrackedLegStartingAt(w).getGreatCircleDistance(t); - Distance after = race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t); - if (after != null && before != null) { - return new MeterDistance(before.add(after).getMeters() / 2); - } - return null; - } - } - - private Distance calculateDistance(Position p, Waypoint w, TimePoint t) { - Distance distance = null; - PassingInstruction instruction = passingInstructions.get(w); - Position p1 = null; - Position p2 = null; - - if (instruction == PassingInstruction.Line) { - Util.Pair pos = race.getPortAndStarboardMarks(t, w); - p1 = pos.getA() == null ? null : race.getOrCreateTrack(pos.getA()).getEstimatedPosition(t, false); - p2 = pos.getB() == null ? null : race.getOrCreateTrack(pos.getB()).getEstimatedPosition(t, false); - } else { - int i = 1; - for (Mark m : w.getMarks()) { - Position markPos = race.getOrCreateTrack(m).getEstimatedPosition(t, false); - if (i == 1) { - p1 = markPos; - } else { - p2 = markPos; - } - i++; - } - } - if (instruction.equals(PassingInstruction.Port) || instruction.equals(PassingInstruction.Starboard) || instruction.equals(PassingInstruction.Offset)) { - distance = p1 != null ? p.getDistance(p1) : null; - } - if (instruction.equals(PassingInstruction.Line)) { - distance = (p1 != null && p2 != null) ? p.getDistanceToLine(p1, p2) : p1 != null ? p.getDistance(p1) : p2 != null ? p.getDistance(p1) : null; - } - if (instruction.equals(PassingInstruction.Gate)) { - Distance d1 = p1 != null ? p.getDistance(p1) : null; - Distance d2 = p2 != null ? p.getDistance(p2) : null; - distance = d1 != null && d2 != null ? (d1.getMeters() < d2.getMeters() ? d1 : d2) : d1 == null ? d2 : d1; - } - return distance; - } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Edge.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Edge.java deleted file mode 100755 index 801b7566ffa..00000000000 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/Edge.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.sap.sailing.domain.markpassingcalculation; - -import com.sap.sailing.domain.base.Waypoint; - -/** - * Represent the passage between two {@link Candidate}s, start and end. - * {@link #getProbability()} returns the probability of this passage being correct, consisting of the probability of - * each Candidate, the probability of the passage (e.g. an estimate whether the time between the candidates matches the - * distance between their {@link Waypoint}s), and a penalty for skipping waypoints, which is possible if a tracker fails - * or a competitor aborts but should be avoided if possible. - * - * @author Nicolas Klose - * - */ - -public class Edge implements Comparable { - private Candidate start; - private Candidate end; - private static int numberOfWaypoints; - private static double penaltyForSkipped = 0.7; - private static double penaltyForSkippedToEnd = 0.6; - private double timeEstimationOrCloseStartsProbability; - - public Edge(Candidate start, Candidate end, double timeEstimationOrStartAnalysis) { - this.start = start; - this.end = end; - this.timeEstimationOrCloseStartsProbability = timeEstimationOrStartAnalysis; - } - - public static double getPenaltyForSkipping() { - return penaltyForSkipped; - } - - public static void setNumberOfWayoints(int number) { - numberOfWaypoints = number; - } - - public Double getProbability() { - double penalty = end.getID() == numberOfWaypoints + 1 ? penaltyForSkippedToEnd : penaltyForSkipped; - return 1-(start.getProbability() * end.getProbability() * timeEstimationOrCloseStartsProbability) + 2 * penalty * (end.getID() - start.getID() - 1); - } - - public Candidate getStart() { - return start; - } - - public Candidate getEnd() { - return end; - } - - public String toString() { - return "From ID " + start.getID() + " to " + end.getID() + ": " + getProbability(); - } - - @Override - public int compareTo(Edge o) { - return start != o.getStart() ? start.compareTo(o.getStart()) : end != o.getEnd() ? end.compareTo(o.getEnd()) : getProbability().compareTo(o.getProbability()); - } -} \ No newline at end of file diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java index 84db016bb74..40f1d6c143a 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingCalculator.java @@ -1,16 +1,15 @@ package com.sap.sailing.domain.markpassingcalculation; import java.util.ArrayList; -import java.util.Comparator; -import java.util.LinkedHashMap; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; -import java.util.TreeSet; import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -20,128 +19,184 @@ import java.util.logging.Logger; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Mark; import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.markpassingcalculation.impl.CandidateChooserImpl; +import com.sap.sailing.domain.markpassingcalculation.impl.CandidateFinderImpl; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sse.common.TimePoint; import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.Util.Triple; import com.sap.sse.util.impl.ThreadFactoryWithPriority; /** - * Calculates the {@link MarkPassing}s for a {@link DynamicTrackedRace} using a {@link AbstractCandidateFinder} and a - * {@link AbstractCandidateChooser}. The finder evaluates the fixes and finds possible MarkPassings as {@link Candidate}s. - * The chooser then finds the most likely sequence of {@link Candidate}s and creates the {@link MarkPassing}s for - * this sequence. If listen is true, a {@link MarkPassingUpdateListener} is initialized which puts new - * fixes into a queue. Additionally a new thread is started, which takes the fixes out of the queue and sorts them so - * that each object (Competitor or Mark) has a list of Fixes in combinedFixes. If suspended is - * false, the new Fixes are handed to the executor as FutureTasks, first to calculate the affected Fixes - * and then the actual MarkPassings (See {@link CandidateFinder} and {@link CandidateChooser}). Then the listening - * process begins again with the queue being emptied. This continues until the end Object is put in the - * queue by the {@link MarkPassingUpdateListener}, signaling that the race is over. + * Calculates the {@link MarkPassing}s for a {@link DynamicTrackedRace} using an {@link CandidateFinder} and an + * {@link CandidateChooser}. The finder evaluates the fixes and finds possible MarkPassings as {@link Candidate}s . The + * chooser than finds the most likely sequence of {@link Candidate}s and updates the race with new {@link MarkPassing}s + * for this sequence. Upon calling the constructor {@link #MarkPassingCalculator(DynamicTrackedRace, boolean)} this + * happens for the current state of the race. In addition, for live races, the listen parameter of the + * constructor should be true. Then a {@link MarkPassingUpdateListener} is initialized which puts new fixes into a queue + * as {@link StorePositionUpdateStrategy}. A new thread will also be started to evaluate the new fixes (See + * {@link CandidateFinder} and {@link CandidateChooser}). This continues until the {@link MarkPassingUpdateListener} + * signals that the race is over (after {@link #stop()} is called. * * @author Nicolas Klose * */ public class MarkPassingCalculator { + private final DynamicTrackedRace race; + private CandidateFinder finder; + private CandidateChooser chooser; private static final Logger logger = Logger.getLogger(MarkPassingCalculator.class.getName()); - private final static Util.Pair end = new Util.Pair(null, null); - private final static ExecutorService executor = new ThreadPoolExecutor(/* corePoolSize */Math.max(Runtime.getRuntime() - .availableProcessors() - 1, 3), + private final MarkPassingUpdateListener listener; + private final static ExecutorService executor = new ThreadPoolExecutor(/* corePoolSize */Math.max(Runtime + .getRuntime().availableProcessors() - 1, 3), /* maximumPoolSize */Math.max(Runtime.getRuntime().availableProcessors() - 1, 3), /* keepAliveTime */60, TimeUnit.SECONDS, /* workQueue */new LinkedBlockingQueue(), new ThreadFactoryWithPriority(Thread.NORM_PRIORITY - 1)); - private MarkPassingUpdateListener listener; - private AbstractCandidateFinder finder; - private AbstractCandidateChooser chooser; private boolean suspended = false; public MarkPassingCalculator(DynamicTrackedRace race, boolean listen) { if (listen) { - listener = new MarkPassingUpdateListener(race, end); + listener = new MarkPassingUpdateListener(race); + } else { + listener = null; } - finder = new CandidateFinder(race); - chooser = new CandidateChooser(race); + this.race = race; + finder = new CandidateFinderImpl(race); + chooser = new CandidateChooserImpl(race); for (Competitor c : race.getRace().getCompetitors()) { - chooser.calculateMarkPassDeltas(c, finder.getAllCandidates(c)); + Util.Pair, Iterable> allCandidates = finder.getAllCandidates(c); + chooser.calculateMarkPassDeltas(c, allCandidates.getA(), allCandidates.getB()); } if (listen) { - new Thread(new Listen(), "MarkPassingCalculator listener for race " + race.getRace().getName()).start(); + new Thread(new Listen(), "MarkPassingCalculator for race " + race.getRace().getName()).start(); } } + /** + * Waits until an object is in the queue, then drains it entirely. After that, the fixes are sorted by the object + * they are tracking in competitorFixes and markFixes. Finally, if suspended + * is false, new passing are computed. + * + * @author Nicolas Klose + * + */ private class Listen implements Runnable { @Override public void run() { - logger.fine("MarkPassingCalculator is listening for new Fixes."); + logger.fine("MarkPassingCalculator is listening"); boolean finished = false; - LinkedHashMap> combinedFixes = new LinkedHashMap<>(); + Map> competitorFixes = new HashMap<>(); + Map> markFixes = new HashMap<>(); + List addedWaypoints = new ArrayList<>(); + List removedWaypoints = new ArrayList<>(); + Integer smallestChangedWaypointIndex = null; + List> fixedMarkPassings = new ArrayList<>(); + List> removedFixedMarkPassings = new ArrayList<>(); + List> suppressedMarkPassings = new ArrayList<>(); + List unsuppressedMarkPassings = new ArrayList<>(); while (!finished) { - List> allNewFixes = new ArrayList>(); + logger.finer("MPC is checking the queue"); + List allNewFixInsertions = new ArrayList<>(); try { - allNewFixes.add(listener.getQueue().take()); + allNewFixInsertions.add(listener.getQueue().take()); } catch (InterruptedException e) { logger.log(Level.SEVERE, "MarkPassingCalculator threw exception " + e.getMessage() + " while waiting for new GPSFixes"); } - listener.getQueue().drainTo(allNewFixes); - for (Util.Pair fix : allNewFixes) { - if (fix == end) { + listener.getQueue().drainTo(allNewFixInsertions); + logger.finer("MPC recieved "+ allNewFixInsertions.size()+" new updates."); + for (StorePositionUpdateStrategy fixInsertion : allNewFixInsertions) { + if (listener.isEndMarker(fixInsertion)) { + logger.info("Stopping "+MarkPassingCalculator.this+"'s listener"); finished = true; - continue; + } else { + fixInsertion.storePositionUpdate(competitorFixes, markFixes, addedWaypoints, removedWaypoints, + smallestChangedWaypointIndex, fixedMarkPassings, removedFixedMarkPassings, + suppressedMarkPassings, unsuppressedMarkPassings); } - if (!combinedFixes.containsKey(fix.getA())) { - combinedFixes.put(fix.getA(), new ArrayList()); - } - combinedFixes.get(fix.getA()).add(fix.getB()); } if (!suspended) { - logger.finest("MarkPassingCalculator evaluating fixes for " + combinedFixes.size() + " objects."); - computeMarkPasses(combinedFixes); - combinedFixes.clear(); + if (smallestChangedWaypointIndex != null) { + Map, List>> candidateDeltas = finder + .updateWaypoints(addedWaypoints, removedWaypoints, smallestChangedWaypointIndex); + chooser.removeWaypoints(removedWaypoints); + for (Entry, List>> entry : candidateDeltas + .entrySet()) { + Util.Pair, List> pair = entry.getValue(); + chooser.calculateMarkPassDeltas(entry.getKey(), pair.getA(), pair.getB()); + } + } + updateManuallySetMarkPassings(fixedMarkPassings, removedFixedMarkPassings, suppressedMarkPassings, + unsuppressedMarkPassings); + computeMarkPasses(competitorFixes, markFixes); + competitorFixes.clear(); + markFixes.clear(); + addedWaypoints.clear(); + removedWaypoints.clear(); + fixedMarkPassings.clear(); + removedFixedMarkPassings.clear(); + suppressedMarkPassings.clear(); + unsuppressedMarkPassings.clear(); } } } - - private void computeMarkPasses(LinkedHashMap> combinedFixes) { - - LinkedHashMap> comFixes = new LinkedHashMap<>(); - Comparator com = new Comparator(){ - @Override - public int compare(GPSFix arg0, GPSFix arg1) { - return arg0.getTimePoint().compareTo(arg1.getTimePoint()); - } - }; - List>>> markTasks = new ArrayList<>(); - for (Object o : combinedFixes.keySet()) { - if (o instanceof Mark) { - FutureTask>> task = new FutureTask<>(new FixesAffectedByNewMarkFixes((Mark)o, combinedFixes.get(o))); - markTasks.add(task); - executor.submit(task); - } - if (o instanceof Competitor){ - if(!comFixes.keySet().contains(o)){ - comFixes.put((Competitor) o, new TreeSet(com)); - } - comFixes.get(o).addAll(combinedFixes.get(o)); - } + + private void updateManuallySetMarkPassings(List> fixedMarkPassings, + List> removedMarkPassings, + List> suppressedMarkPassings, List unsuppressedMarkPassings) { + logger.finest("Updating manually edited MarkPassings"); + for (Pair pair : suppressedMarkPassings) { + chooser.suppressMarkPassings(pair.getA(), pair.getB()); } - for(FutureTask>> task : markTasks){ - LinkedHashMap> fixes = new LinkedHashMap<>(); - try { - fixes = task.get(); - } catch (InterruptedException | ExecutionException e) { - logger.severe("Threw Exception " + e.getMessage()+" while calculating fixes affected by new Mark Fixes."); - } - for (Competitor c : fixes.keySet()){ - if(!comFixes.keySet().contains(c)){ - comFixes.put((Competitor) c, new TreeSet(com)); + for (Competitor c : unsuppressedMarkPassings) { + chooser.stopSuppressingMarkPassings(c); + } + + for (Pair pair : removedMarkPassings) { + chooser.removeFixedPassing(pair.getA(), pair.getB()); + } + for (Triple triple : fixedMarkPassings) { + chooser.setFixedPassing(triple.getA(), triple.getB(), triple.getC()); + } + } + + /** + * The calculation has two steps. For every mark with new fixes those competitor fixes are calculated that may + * have changed their status as {@link Candidate} (see {@code FixesAffectedByNewMarkFixes}). Then, the + * {@link CandidateFinder} uses those fixes along with any new competitor fixes to calculate any new or wrong + * Candidates. These are passed to the {@link CandidateChooser} to calculate any new {@link MarkPassing}s (see + * {@link ComputeMarkPassings}). + * + */ + private void computeMarkPasses(Map> newCompetitorFixes, + Map> newMarkFixes) { + logger.finer("Calculating markpassings with " + newCompetitorFixes.size() + " new competitor Fixes and " + + newMarkFixes.size() + "new mark fixes."); + Map> combinedCompetitorFixes = new HashMap<>(); + + for (Entry> competitorEntry : newCompetitorFixes.entrySet()) { + Set fixesForCompetitor = new HashSet<>(); + combinedCompetitorFixes.put(competitorEntry.getKey(), fixesForCompetitor); + fixesForCompetitor.addAll(competitorEntry.getValue()); + } + if (!newMarkFixes.isEmpty()) { + for (Entry> fixesAffectedByNewMarkFixes : finder + .calculateFixesAffectedByNewMarkFixes(newMarkFixes).entrySet()) { + Set fixes = combinedCompetitorFixes.get(fixesAffectedByNewMarkFixes.getKey()); + if (fixes == null) { + fixes = new HashSet<>(); + combinedCompetitorFixes.put(fixesAffectedByNewMarkFixes.getKey(), fixes); } - comFixes.get(c).addAll(fixes.get(c)); + fixes.addAll(fixesAffectedByNewMarkFixes.getValue()); } } List> tasks = new ArrayList<>(); - for (Competitor c : comFixes.keySet()) { - tasks.add(Executors.callable(new ComputeMarkPassings(c, comFixes.get(c)))); + for (Entry> c : combinedCompetitorFixes.entrySet()) { + tasks.add(Executors.callable(new ComputeMarkPassings(c.getKey(), c.getValue()))); } try { executor.invokeAll(tasks); @@ -152,40 +207,67 @@ public class MarkPassingCalculator { private class ComputeMarkPassings implements Runnable { Competitor c; - Set fixes; - public ComputeMarkPassings(Competitor c, Set fixes) { + Iterable fixes; + + public ComputeMarkPassings(Competitor c, Iterable fixes) { this.c = c; this.fixes = fixes; } + @Override public void run() { - chooser.calculateMarkPassDeltas(c, finder.getCandidateDeltas(c, fixes)); + logger.finer("Calculating MarkPassings for " + c + " (" + Util.size(fixes) + " new fixes)"); + Util.Pair, Iterable> candidateDeltas = finder.getCandidateDeltas(c, + fixes); + logger.finer("Received " + Util.size(candidateDeltas.getA()) + " new Candidates and will remove " + + Util.size(candidateDeltas.getB()) + " old Candidates for " + c); + chooser.calculateMarkPassDeltas(c, candidateDeltas.getA(), candidateDeltas.getB()); } } - private class FixesAffectedByNewMarkFixes implements Callable>> { - Mark m; - Iterable fixes; - public FixesAffectedByNewMarkFixes(Mark m, Iterable fixes) { - this.m = m; - this.fixes = fixes; - } - @Override - public LinkedHashMap> call() { - return finder.calculateFixesAffectedByNewMarkFixes((Mark) m, fixes); - } - } } + /** + * Only suspends the actual calculation. Even when suspended all incoming fixes are added to the queue and sorted. + */ public void suspend() { + logger.finest("Suspended MarkPassingCalculator"); suspended = true; } + /** + * An empty object is written to the queue to ensure that any fixes that have been removed from the queue are + * evaluated even if nothing else arrives after this is called. + */ public void resume() { + logger.finest("Resumed MarkPassingCalculator"); suspended = false; + listener.getQueue().add(new StorePositionUpdateStrategy() { + @Override + public void storePositionUpdate(Map> competitorFixes, + Map> markFixes, List addedWaypoints, List removedWaypoints, + Integer smallestChangedWaypointIndex, + List> fixedMarkPassings, + List> removedMarkPassings, + List> suppressedMarkPassings, List unSuppressedMarkPassings) { + } + }); } - public LinkedHashMap> getAllPasses() { - return chooser.getAllPasses(); + public void stop() { + listener.stop(); + } + + public void recalculateEverything() { + finder = new CandidateFinderImpl(race); + chooser = new CandidateChooserImpl(race); + for (Competitor c : race.getRace().getCompetitors()) { + Util.Pair, Iterable> allCandidates = finder.getAllCandidates(c); + chooser.calculateMarkPassDeltas(c, allCandidates.getA(), allCandidates.getB()); + } + } + + public MarkPassingUpdateListener getListener() { + return listener; } } \ No newline at end of file diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java index eb9e97dbffa..4cb819cb46e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/MarkPassingUpdateListener.java @@ -1,53 +1,180 @@ package com.sap.sailing.domain.markpassingcalculation; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.Logger; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.common.TrackedRaceStatusEnum; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.GPSFix; import com.sap.sailing.domain.tracking.GPSFixMoving; -import com.sap.sailing.domain.tracking.TrackedRace; -import com.sap.sailing.domain.tracking.TrackedRaceStatus; import com.sap.sailing.domain.tracking.impl.AbstractRaceChangeListener; -import com.sap.sse.common.Util; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.Util.Triple; /** * Listens for changes that might affect the MarkPassingCalculator: new Fixes of a Competitor or a Mark and when the * race status changes to finished. New Fixes are put in queue to be evaluated by the {@link MarkPassingCalculator} and - * the end object is passed through to signalise the end of the race. + * the end object is passed through to signal the end of the race. * * @author Nicolas Klose * */ public class MarkPassingUpdateListener extends AbstractRaceChangeListener { - private final LinkedBlockingQueue> queue; - private final Util.Pair end; + private static final Logger logger = Logger.getLogger(MarkPassingUpdateListener.class.getName()); + private LinkedBlockingQueue queue; + private final StorePositionUpdateStrategy endMarker = new StorePositionUpdateStrategy() { + @Override + public void storePositionUpdate(Map> competitorFixes, Map> markFixes, + List addedWaypoints, List removedWaypoints, Integer smallestChangedWaypointIndex, + List> fixedMarkPassings, List> removedMarkPassings, + List> suppressedMarkPassings, List unSuppressedMarkPassings) { + } + }; - public MarkPassingUpdateListener(TrackedRace race, Util.Pair end) { - race.addListener(this); + /** + * Adds itself automatically as a Listener on the race and its course. + */ + public MarkPassingUpdateListener(DynamicTrackedRace race) { queue = new LinkedBlockingQueue<>(); - this.end = end; + race.addListener(this); + race.getRace().getCourse().addCourseListener(this); } - public LinkedBlockingQueue> getQueue() { + public BlockingQueue getQueue() { return queue; } @Override - public void competitorPositionChanged(GPSFixMoving fix, Competitor item) { - queue.add(new Util.Pair(item, fix)); + public void competitorPositionChanged(final GPSFixMoving fix, final Competitor competitor) { + queue.add(new StorePositionUpdateStrategy() { + @Override + public void storePositionUpdate(Map> competitorFixes, Map> markFixes, + List addedWaypoints, List removedWaypoints, Integer smallestChangedWaypointIndex, + List> fixedMarkPassings, List> removedMarkPassings, + List> suppressedMarkPassings, List unSuppressedMarkPassings) { + List list = competitorFixes.get(competitor); + if (list == null) { + list = new ArrayList<>(); + competitorFixes.put(competitor, list); + } + list.add(fix); + } + }); } @Override - public void markPositionChanged(GPSFix fix, Mark mark, boolean firstInTrack) { - queue.add(new Util.Pair(mark, fix)); + public void markPositionChanged(final GPSFix fix, final Mark mark, boolean firstInTrack) { + queue.add(new StorePositionUpdateStrategy() { + @Override + public void storePositionUpdate(Map> competitorFixes, Map> markFixes, + List addedWaypoints, List removedWaypoints, Integer smallestChangedWaypointIndex, + List> fixedMarkPassings, List> removedMarkPassings, + List> suppressedMarkPassings, List unSuppressedMarkPassings) { + List list = markFixes.get(mark); + if (list == null) { + list = new ArrayList<>(); + markFixes.put(mark, list); + } + list.add(fix); + } + }); + } + + public boolean isEndMarker(StorePositionUpdateStrategy endMarkerCandidate) { + return endMarkerCandidate == endMarker; + } + + public void stop() { + logger.info("Stopping "+this); + queue.add(endMarker); } @Override - public void statusChanged(TrackedRaceStatus newStatus, TrackedRaceStatus oldStatus) { - if (newStatus.getStatus() == TrackedRaceStatusEnum.FINISHED) { - queue.add(end); - } + public void waypointAdded(final int zeroBasedIndex, final Waypoint waypointThatGotAdded) { + queue.add(new StorePositionUpdateStrategy() { + @Override + public void storePositionUpdate(Map> competitorFixes, Map> markFixes, + List addedWaypoints, List removedWaypoints, Integer smallestChangedWaypointIndex, + List> fixedMarkPassings, List> removedMarkPassings, + List> suppressedMarkPassings, List unSuppressedMarkPassings) { + addedWaypoints.add(waypointThatGotAdded); + if (smallestChangedWaypointIndex == null || smallestChangedWaypointIndex > zeroBasedIndex) { + smallestChangedWaypointIndex = zeroBasedIndex; + } + } + }); + } + + @Override + public void waypointRemoved(final int zeroBasedIndex, final Waypoint waypointThatGotRemoved) { + queue.add(new StorePositionUpdateStrategy() { + @Override + public void storePositionUpdate(Map> competitorFixes, Map> markFixes, + List addedWaypoints, List removedWaypoints, Integer smallestChangedWaypointIndex, + List> fixedMarkPassings, List> removedMarkPassings, + List> suppressedMarkPassings, List unSuppressedMarkPassings) { + removedWaypoints.add(waypointThatGotRemoved); + if (smallestChangedWaypointIndex == null || smallestChangedWaypointIndex > zeroBasedIndex) { + smallestChangedWaypointIndex = zeroBasedIndex; + } + } + }); + } + + public void addFixedPassing(final Competitor c, final Integer zeroBasedIndexOfWaypoint, final TimePoint timePointOfFixedPassing) { + queue.add(new StorePositionUpdateStrategy() { + @Override + public void storePositionUpdate(Map> competitorFixes, Map> markFixes, + List addedWaypoints, List removedWaypoints, Integer smallestChangedWaypointIndex, + List> fixedMarkPassings, List> removedMarkPassings, + List> suppressedMarkPassings, List unSuppressedMarkPassings) { + fixedMarkPassings.add(new Triple(c, zeroBasedIndexOfWaypoint, timePointOfFixedPassing)); + } + }); + } + + public void removeFixedPassing(final Competitor c, final Integer zeroBasedIndexOfWaypoint) { + queue.add(new StorePositionUpdateStrategy() { + @Override + public void storePositionUpdate(Map> competitorFixes, Map> markFixes, + List addedWaypoints, List removedWaypoints, Integer smallestChangedWaypointIndex, + List> fixedMarkPassings, List> removedMarkPassings, + List> suppressedMarkPassings, List unSuppressedMarkPassings) { + removedMarkPassings.add(new Pair(c, zeroBasedIndexOfWaypoint)); + } + + }); + } + + public void addSuppressedPassing(final Competitor c, final Integer zeroBasedIndexOfWaypoint) { + queue.add(new StorePositionUpdateStrategy() { + @Override + public void storePositionUpdate(Map> competitorFixes, Map> markFixes, + List addedWaypoints, List removedWaypoints, Integer smallestChangedWaypointIndex, + List> fixedMarkPassings, List> removedMarkPassings, + List> suppressedMarkPassings, List unSuppressedMarkPassings) { + suppressedMarkPassings.add(new Pair(c, zeroBasedIndexOfWaypoint)); + + } + }); + } + + public void removeSuppressedPassing(final Competitor c) { + queue.add(new StorePositionUpdateStrategy() { + @Override + public void storePositionUpdate(Map> competitorFixes, Map> markFixes, + List addedWaypoints, List removedWaypoints, Integer smallestChangedWaypointIndex, + List> fixedMarkPassings, List> removedMarkPassings, + List> suppressedMarkPassings, List unSuppressedMarkPassings) { + unSuppressedMarkPassings.add(c); + } + }); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/StorePositionUpdateStrategy.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/StorePositionUpdateStrategy.java new file mode 100755 index 00000000000..f4f9929b4f5 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/StorePositionUpdateStrategy.java @@ -0,0 +1,27 @@ +package com.sap.sailing.domain.markpassingcalculation; + +import java.util.List; +import java.util.Map; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.Util.Triple; + +/** + * Is queued between {@link MarkPassingCalculator} and {@link MarkPassingUpdateListener}. + */ +public interface StorePositionUpdateStrategy { + + /** + * Adds its fix either to competitorFixes or markFixes, depending what the fix is for. If + * no fixes exist yet for that object, a new entry is created. + */ + void storePositionUpdate(Map> competitorFixes, Map> markFixes, + List addedWaypoints, List removedWaypoints, Integer smallestChangedWaypointIndex, + List> fixedMarkPassings, List> removedMarkPassings, + List> suppressedMarkPassings, List unSuppressedMarkPassings); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java new file mode 100755 index 00000000000..6690e21c278 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateChooserImpl.java @@ -0,0 +1,457 @@ +package com.sap.sailing.domain.markpassingcalculation.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.impl.MeterDistance; +import com.sap.sailing.domain.markpassingcalculation.Candidate; +import com.sap.sailing.domain.markpassingcalculation.CandidateChooser; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.impl.MarkPassingImpl; +import com.sap.sse.common.Duration; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +/** + * The standard implementation of {@link CandidateChooser}. A graph is created, with each {@link Candidate} as a + * vertices, all between two proxy Candidates, start and end using {@link Edge}s. These are + * only created if the both the waypoints and the the timepoints are in chronological order and the distance-based + * estimation is good enough. They are saved in allEdges, a map in which every candidate is the key to a + * list of all the edges that start at this candidate. The shortest path between the proxy-Candidates is the most likely + * sequence of {@link MarkPassing}s. Every time new candidates arrive, the start time of the race is checked. If it has + * changed, the proxy start and all edges containing it are updated. + * + * @author Nicolas Klose + * + */ +public class CandidateChooserImpl implements CandidateChooser { + // TODO what is the meaning of this constant? + private static final int MILLISECONDS_BEFORE_STARTTIME = 5000; + private final static double MINIMUM_PROBABILITY = 1 - Edge.getPenaltyForSkipping(); + + private static final Logger logger = Logger.getLogger(CandidateChooserImpl.class.getName()); + + private Map> currentMarkPasses = new HashMap<>(); + private Map>> allEdges = new HashMap<>(); + private Map> candidates = new HashMap<>(); + private Map> fixedPassings = new HashMap<>(); + private Map suppressedPassings = new HashMap<>(); + private TimePoint raceStartTime; + + /** + * An artificial proxy candidate that comes before the start mark passing. Its time point is set to + * {@link #MILLISECONDS_BEFORE_STARTTIME} milliseconds before the race start time or null + * in case the race start time is not known. + */ + private final CandidateWithSettableTime start; + private final Candidate end; + private final DynamicTrackedRace race; + + public CandidateChooserImpl(DynamicTrackedRace race) { + this.race = race; + raceStartTime = race.getStartOfRace() != null ? race.getStartOfRace().minus(MILLISECONDS_BEFORE_STARTTIME) : null; + start = new CandidateWithSettableTime(/* Index */0, raceStartTime, /* Probability */1, /* Waypoint */null); + end = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(race.getRace().getCourse().getLastWaypoint()) + 2, /* TimePoint */ + null, + /* Probability */1, /* Waypoint */null); + candidates = new HashMap<>(); + List startAndEnd = Arrays.asList(start, end); + for (Competitor c : race.getRace().getCompetitors()) { + candidates.put(c, new TreeSet()); + currentMarkPasses.put(c, new HashMap()); + TreeSet fixedPasses = new TreeSet(new Comparator() { + @Override + public int compare(Candidate o1, Candidate o2) { + return o1.getOneBasedIndexOfWaypoint() - o2.getOneBasedIndexOfWaypoint(); + } + }); + fixedPassings.put(c, fixedPasses); + allEdges.put(c, new HashMap>()); + fixedPasses.addAll(startAndEnd); + addCandidates(c, startAndEnd); + + } + } + + @Override + public void calculateMarkPassDeltas(Competitor c, Iterable newCans, Iterable oldCans) { + TimePoint startOfRace = race.getStartOfRace(); + if (startOfRace != null) { + if (raceStartTime == null || !startOfRace.minus(MILLISECONDS_BEFORE_STARTTIME).equals(raceStartTime)) { + raceStartTime = startOfRace.minus(MILLISECONDS_BEFORE_STARTTIME); + List startList = new ArrayList<>(); + startList.add(start); + for (Competitor com : candidates.keySet()) { + removeCandidates(com, startList); + } + start.setTimePoint(raceStartTime); + for (Competitor com : allEdges.keySet()) { + addCandidates(com, startList); + } + } + } + removeCandidates(c, oldCans); + addCandidates(c, newCans); + findShortestPath(c); + } + + @Override + public void removeWaypoints(Iterable ways) { + for (Competitor c : currentMarkPasses.keySet()) { + for (Waypoint w : ways) { + currentMarkPasses.get(c).remove(w); + } + } + } + + @Override + public void setFixedPassing(Competitor c, Integer zeroBasedIndexOfWaypoint, TimePoint t) { + Candidate fixedCan = new CandidateImpl(zeroBasedIndexOfWaypoint + 1, t, 1, Util.get(race.getRace().getCourse().getWaypoints(), zeroBasedIndexOfWaypoint)); + NavigableSet fixed = fixedPassings.get(c); + if (!fixed.add(fixedCan)) { + Candidate old = fixed.ceiling(fixedCan); + fixed.remove(old); + removeCandidates(c, Arrays.asList(old)); + fixed.add(fixedCan); + } + addCandidates(c, Arrays.asList(fixedCan)); + findShortestPath(c); + } + + @Override + public void removeFixedPassing(Competitor c, Integer zeroBasedIndexOfWaypoint) { + Candidate toRemove = null; + for (Candidate can : fixedPassings.get(c)) { + if (can.getOneBasedIndexOfWaypoint() - 1 == zeroBasedIndexOfWaypoint) { + toRemove = can; + break; + } + } + fixedPassings.get(c).remove(toRemove); + removeCandidates(c, Arrays.asList(toRemove)); + findShortestPath(c); + } + + @Override + public void suppressMarkPassings(Competitor c, Integer zeroBasedIndexOfWaypoint) { + suppressedPassings.put(c, zeroBasedIndexOfWaypoint); + findShortestPath(c); + } + + @Override + public void stopSuppressingMarkPassings(Competitor c) { + suppressedPassings.remove(c); + findShortestPath(c); + } + + private void createNewEdges(Competitor c, Iterable newCandidates) { + final Boolean isGateStart = race.isGateStart(); + Map> edges = allEdges.get(c); + for (Candidate newCan : newCandidates) { + for (Candidate oldCan : candidates.get(c)) { + final Candidate early; + final Candidate late; + if (oldCan.getOneBasedIndexOfWaypoint() < newCan.getOneBasedIndexOfWaypoint()) { + early = oldCan; + late = newCan; + } else if (oldCan.getOneBasedIndexOfWaypoint() > newCan.getOneBasedIndexOfWaypoint()) { + late = oldCan; + early = newCan; + } else { + continue; // don't create edge from/to same waypoint + } + + final double estimatedDistanceProbability; + final double startTimingProbability; + if (early == start) { + // An edge starting at the start proxy node. If the late candidate is for a start mark passing, + // determine a probability not based on distance traveled but based on the + // time difference between scheduled start time and candidate's time point. If the "late" candidate + // is not for the start mark/line, meaning that mark passings including the actual start are + // skipped, as usual use getDistanceEstimationBasedProbability assuming a start mark passing at + // the race's start time. + if (isGateStart == Boolean.TRUE || start.getTimePoint() == null) { // TODO for gate start read gate timing and scale probability accordingly + startTimingProbability = 1; // no start time point known; all candidate time points equally likely + estimatedDistanceProbability = 1; // can't tell distance sailed either because we don't know the start time + } else { + if (late.getWaypoint() == race.getRace().getCourse().getFirstWaypoint()) { + // no skips; going from the start proxy node to a candidate for the start mark passing; + // calculate the probability for the start being the start given its timing and multiply + // with the estimation for the distance-based probability: + final Duration timeGapBetweenStartOfRaceAndCandidateTimePoint = early.getTimePoint() + .plus(MILLISECONDS_BEFORE_STARTTIME).until(late.getTimePoint()); + // Being MILLISECONDS_BEFORE_STARTTIME off means a probability of 1/2; being twice this time + // off means 1/3, and so on + startTimingProbability = (double) MILLISECONDS_BEFORE_STARTTIME + / (double) (MILLISECONDS_BEFORE_STARTTIME + Math + .abs(timeGapBetweenStartOfRaceAndCandidateTimePoint.asMillis())); + estimatedDistanceProbability = 1; + } else { + startTimingProbability = 0.1; // can't really tell how well the start time was matched when + // we don't have a start candidate + estimatedDistanceProbability = late == end ? 1 : getDistanceEstimationBasedProbability(c, early, late); + } + } + } else { + startTimingProbability = 1; // no penalty for any start time difference because this edge doesn't cover a start + if (late == end) { + // final edge; we don't know anything about distances for the end proxy node + estimatedDistanceProbability = 1; + } else { + estimatedDistanceProbability = getDistanceEstimationBasedProbability(c, early, late); + } + } + // If one of the candidates is fixed, the edge is always created unless they travel backwards in time. + // Otherwise the edge is only created if the distance estimation, which can be calculated as long as the + // candidates are not the proxy and or start is close enough to the actual distance sailed. + final NavigableSet fixed = fixedPassings.get(c); + // TODO this comparison does not exactly implement the condition "if distance is more likely than skipping" + if (travelingForwardInTimeOrUnknown(early, late) && + (fixed.contains(early) || fixed.contains(late) || estimatedDistanceProbability > MINIMUM_PROBABILITY)) { + addEdge(edges, new Edge(early, late, startTimingProbability * estimatedDistanceProbability, race.getRace().getCourse())); + } + } + } + } + + private boolean travelingForwardInTimeOrUnknown(Candidate early, Candidate late) { + return early.getTimePoint() == null || late.getTimePoint() == null || early.getTimePoint().before(late.getTimePoint()); + } + + private void addEdge(Map> edges, Edge e) { + logger.finest("Adding "+ e.toString()); + Set edgeSet = edges.get(e.getStart()); + if (edgeSet == null) { + edgeSet = new HashSet<>(); + edges.put(e.getStart(), edgeSet); + } + edgeSet.add(e); + } + + /** + * Calculates the most likely series of {@link MarkPassings} using the edges in {@link allEdges}. These are saved in + * {@link #currentMarkPasses} and the {@link DynamicTrackedRace} is + * {@link DynamicTrackedRace#updateMarkPassings(Competitor, Iterable) notified}.

+ * + * The algorithm works out optimal solutions between fixed mark passings. By default, start and end proxy + * candidates are the only fixed elements. If more fixed elements are provided, the algorithm solves the + * optimization problem separately for each segment and concatenates the solutions. + */ + private void findShortestPath(Competitor c) { + Map> allCompetitorEdges = allEdges.get(c); + SortedSet mostLikelyCandidates = new TreeSet<>(); + NavigableSet fixedPasses = fixedPassings.get(c); + Candidate startOfFixedInterval = fixedPasses.first(); + Candidate endOfFixedInterval = fixedPasses.higher(startOfFixedInterval); + Integer zeroBasedIndexOfWaypoint = suppressedPassings.get(c); + Integer oneBasedIndexOfSuppressedWaypoint = zeroBasedIndexOfWaypoint != null ? zeroBasedIndexOfWaypoint + 1 : end + .getOneBasedIndexOfWaypoint(); + while (endOfFixedInterval != null) { + if (oneBasedIndexOfSuppressedWaypoint <= endOfFixedInterval.getOneBasedIndexOfWaypoint()) { + endOfFixedInterval = end; + } + NavigableSet> currentEdgesCheapestFirst = new TreeSet<>(new Comparator>() { + @Override + public int compare(Util.Pair o1, Util.Pair o2) { + int result = o1.getB().compareTo(o2.getB()); + return result != 0 ? result : o1.getA().compareTo(o2.getA()); + } + }); + Map> candidateWithParentAndSmallestTotalCost = new HashMap<>(); + int indexOfEndOfFixedInterval = endOfFixedInterval.getOneBasedIndexOfWaypoint(); + + boolean endFound = false; + currentEdgesCheapestFirst.add(new Util.Pair(new Edge(new CandidateImpl(-1, null, /* estimated distance probability */ 1, null), startOfFixedInterval, + 0, race.getRace().getCourse()), 0.0)); + while (!endFound) { + Util.Pair cheapestEdgeWithCost = currentEdgesCheapestFirst.pollFirst(); + Edge currentCheapestEdge = cheapestEdgeWithCost.getA(); + Double currentCheapestCost = cheapestEdgeWithCost.getB(); + // If the shortest path to this candidate is already known the new edge is not added. + if (!candidateWithParentAndSmallestTotalCost.containsKey(currentCheapestEdge.getEnd())) { + // The cheapest edge taking us to currentCheapestEdge.getEnd() is found. Remember it. + candidateWithParentAndSmallestTotalCost.put(currentCheapestEdge.getEnd(), new Util.Pair( + currentCheapestEdge.getStart(), currentCheapestCost)); + logger.finest("Added "+ currentCheapestEdge + "as cheapest edge for " + c); + endFound = currentCheapestEdge.getEnd() == endOfFixedInterval; + if (!endFound) { + // the end of the segment was not yet found; add edges leading away from + // currentCheapestEdge.getEnd(), summing up their cost with the cost required + // to reach currentCheapestEdge.getEnd() + Set edgesForNewCandidate = allCompetitorEdges.get(currentCheapestEdge.getEnd()); + for (Edge e : edgesForNewCandidate) { + int oneBasedIndexOfEndOfEdge = e.getEnd().getOneBasedIndexOfWaypoint(); + // only add edge if it stays within the current segment, not exceeding + // the next fixed mark passing + if (oneBasedIndexOfEndOfEdge <= indexOfEndOfFixedInterval + && (oneBasedIndexOfEndOfEdge < oneBasedIndexOfSuppressedWaypoint || e.getEnd() == end)) { + currentEdgesCheapestFirst.add(new Util.Pair(e, currentCheapestCost + e.getCost())); + } + } + } + } + } + Candidate marker = candidateWithParentAndSmallestTotalCost.get(endOfFixedInterval).getA(); + while (marker.getOneBasedIndexOfWaypoint() > 0) { + mostLikelyCandidates.add(marker); + marker = candidateWithParentAndSmallestTotalCost.get(marker).getA(); + } + startOfFixedInterval = endOfFixedInterval; + endOfFixedInterval = fixedPasses.higher(endOfFixedInterval); + } + boolean changed = false; + Map currentPasses = currentMarkPasses.get(c); + if (currentPasses.size() != mostLikelyCandidates.size()) { + changed = true; + } else { + for (Candidate can : mostLikelyCandidates) { + MarkPassing currentPassing = currentPasses.get(can.getWaypoint()); + if (currentPassing == null || currentPassing.getTimePoint().compareTo(can.getTimePoint()) != 0) { + changed = true; + break; + } + } + } + if (changed) { + currentPasses.clear(); + List newMarkPassings = new ArrayList<>(); + for (Candidate can : mostLikelyCandidates) { + if (can != start && can != end) { + MarkPassingImpl newMarkPassing = new MarkPassingImpl(can.getTimePoint(), can.getWaypoint(), c); + currentPasses.put(newMarkPassing.getWaypoint(), newMarkPassing); + newMarkPassings.add(newMarkPassing); + } + } + logger.fine("Updating MarkPasses for " + c + " in case "+race.getRace().getName()); + race.updateMarkPassings(c, newMarkPassings); + } + } + + /** + * The distance between waypoints is used to estimate the distance that should be covered between these two + * candidates. This estimation is then compared to the distance actually sailed. A distance smaller than the + * estimation is (aside from a small tolerance) impossible, a distance larger get increasingly unlikely. + */ + private double getDistanceEstimationBasedProbability(Competitor c, Candidate c1, Candidate c2) { + final double result; + assert c1.getOneBasedIndexOfWaypoint() < c2.getOneBasedIndexOfWaypoint(); + assert c2 != end; + Waypoint first; + final TimePoint middleOfc1Andc2 = new MillisecondsTimePoint(c1.getTimePoint().plus(c2.getTimePoint().asMillis()).asMillis() / 2); + if (c1.getOneBasedIndexOfWaypoint() == 0) { + first = race.getRace().getCourse().getFirstWaypoint(); + } else { + first = c1.getWaypoint(); + } + final Waypoint second = c2.getWaypoint(); + Distance totalGreatCircleDistance = getTotalGreatCircleDistanceBetweenWaypoints(first, second, middleOfc1Andc2); + Distance actualDistanceTraveled = race.getTrack(c).getDistanceTraveled(c1.getTimePoint(), c2.getTimePoint()); + result = getProbabilityOfActualDistanceGivenGreatCircleDistance(totalGreatCircleDistance, actualDistanceTraveled); + return result; + } + + /** + * Based on a direct great-circle distance between waypoints and an actual distance sailed, determines how likely it + * is that this distance sailed could have happened between those waypoints. For a reaching leg, this would be based + * on a straight comparison of the numbers. However, with upwind and downwind legs and boats not going from mark to + * mark on a great circle segment, distances sailed will exceed the great line circle distances. + *

+ * + * A smaller distance than great circle from mark to mark is very unlikely, somewhere between the distance estimated + * and double that is likely and anything greater than that gradually becomes unlikely. + * + * @return a number between 0 and 1 with 1 representing a "fair chance" that the actual distance sailed could have + * been sailed for the given great circle distance; 1 is returned for actual distances being in the range of + * 1..2 times the great circle distance. Actual distances outside this interval reduce probability linearly + * for smaller distances (gradient 3.5) and varies with the square root for distances that exceed twice the + * great circle distance. + */ + private double getProbabilityOfActualDistanceGivenGreatCircleDistance(Distance totalGreatCircleDistance, + Distance actualDistanceTraveled) { + final double result; + double differenceInMeters = actualDistanceTraveled.getMeters() - totalGreatCircleDistance.getMeters(); + double ratio = differenceInMeters / totalGreatCircleDistance.getMeters(); + // A smaller distance than great circle from mark to mark is very unlikely, somewhere between the distance + // estimated and double that is likely and anything greater than that gradually becomes unlikely + if (ratio < 0) { + // TODO shouldn't these factors be constants in the class header for easy fine-tuning? + result = 3.5 * ratio + 1; + } else if (ratio > 1) { + result = Math.sqrt((-ratio + 3) / 2); + } else { + result = 1; + } + return result; + } + + private Distance getTotalGreatCircleDistanceBetweenWaypoints(Waypoint first, final Waypoint second, + final TimePoint timePoint) { + Distance totalGreatCircleDistance = new MeterDistance(0); + boolean legsAreBetweenCandidates = false; + for (TrackedLeg leg : race.getTrackedLegs()) { + Waypoint from = leg.getLeg().getFrom(); + if (from == second) { + break; + } + if (from == first) { + legsAreBetweenCandidates = true; + } + if (legsAreBetweenCandidates) { + totalGreatCircleDistance = totalGreatCircleDistance.add(leg.getGreatCircleDistance(timePoint)); + } + } + return totalGreatCircleDistance; + } + + private void addCandidates(Competitor c, Iterable newCandidates) { + for (Candidate can : newCandidates) { + candidates.get(c).add(can); + } + createNewEdges(c, newCandidates); + } + + private void removeCandidates(Competitor c, Iterable wrongCandidates) { + for (Candidate can : wrongCandidates) { + logger.finest("Removing all edges containing " + can.toString() + "of "+ c); + candidates.get(c).remove(can); + Map> edges = allEdges.get(c); + edges.remove(can); + for (Set set : edges.values()) { + for (Iterator i = set.iterator(); i.hasNext();) { + final Edge e = i.next(); + if (e.getStart().equals(can) || e.getEnd().equals(can)) { + i.remove(); + } + } + } + } + } + + private class CandidateWithSettableTime extends CandidateImpl { + + public CandidateWithSettableTime(int oneBasedIndexOfWaypoint, TimePoint p, double distanceProbability, Waypoint w) { + super(oneBasedIndexOfWaypoint, p, distanceProbability, w); + } + + public void setTimePoint(TimePoint t) { + p = t; + } + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java new file mode 100755 index 00000000000..bac75e3c77e --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java @@ -0,0 +1,869 @@ +package com.sap.sailing.domain.markpassingcalculation.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeSet; +import java.util.logging.Logger; + +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.Leg; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.Bearing; +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.PassingInstruction; +import com.sap.sailing.domain.common.Position; +import com.sap.sailing.domain.common.impl.DegreeBearingImpl; +import com.sap.sailing.domain.common.impl.MeterDistance; +import com.sap.sailing.domain.markpassingcalculation.Candidate; +import com.sap.sailing.domain.markpassingcalculation.CandidateFinder; +import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; + +/** + * The standard implemantation of {@link CandidateFinder}. There are two kinds of {@link Candidate}s. First of all, + * every time a competitor passes the crossing-bearing of a waypoint, a candidate is created using linear interpolation + * to estimate the exact time the bearing was crossed. Secondly, all local distance minima to a waypoint are candidates. + * The probability of a candidate depends on its distance , whether it is on the right side and if it passes in the + * right direction of its waypoint. + * + * @author Nicolas Klose + * + */ +public class CandidateFinderImpl implements CandidateFinder { + + // TODO Parallelization? + + private final int strictnessOfDistanceBasedProbability = 8; + private final double penaltyForWrongSide = 0.7; + private final double penaltyForWrongDirection = 0.7; + private final double penaltyForDistanceCandidates = 0.7; + + private static final Logger logger = Logger.getLogger(CandidateFinderImpl.class.getName()); + private Map>>> distanceCache = new LinkedHashMap<>(); + private Map>>> xteCache = new LinkedHashMap<>(); + + private Map, Candidate>>> xteCandidates = new HashMap<>(); + private Map>> distanceCandidates = new HashMap<>(); + private final DynamicTrackedRace race; + private final double penaltyForSkipping = 1 - Edge.getPenaltyForSkipping(); + private final Map passingInstructions = new LinkedHashMap<>(); + private final Comparator comp = new Comparator() { + @Override + public int compare(GPSFix arg0, GPSFix arg1) { + return arg0.getTimePoint().compareTo(arg1.getTimePoint()); + } + }; + + public CandidateFinderImpl(DynamicTrackedRace race) { + this.race = race; + RaceDefinition raceDefinition = race.getRace(); + for (Competitor c : raceDefinition.getCompetitors()) { + xteCache.put(c, new LimitedLinkedHashMap>>(25)); + distanceCache.put(c, new LimitedLinkedHashMap>>(25)); + xteCandidates.put(c, new HashMap, Candidate>>()); + distanceCandidates.put(c, new HashMap>()); + } + } + + private class LimitedLinkedHashMap extends LinkedHashMap { + + private static final long serialVersionUID = 1L; + private int limit; + + public LimitedLinkedHashMap(int limit) { + super(); + this.limit = limit; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return this.size() > limit; + } + } + + @Override + public Util.Pair, Iterable> getAllCandidates(Competitor c) { + Set fixes = getAllFixes(c); + distanceCache.get(c).clear(); + xteCache.get(c).clear(); + synchronized (xteCandidates) { + xteCandidates.get(c).clear(); + } + synchronized (distanceCandidates) { + distanceCandidates.get(c).clear(); + } + return getCandidateDeltas(c, fixes); + } + + @Override + public Map> calculateFixesAffectedByNewMarkFixes(Map> markFixes) { + // TODO Right now creates on time stretch between the 2 outside markfixes + Map> affectedFixes = new HashMap<>(); + TimePoint start = null; + TimePoint end = null; + for (Entry> fixes : markFixes.entrySet()) { + for (GPSFix fix : fixes.getValue()) { + Util.Pair timePoints = race.getOrCreateTrack(fixes.getKey()) + .getEstimatedPositionTimePeriodAffectedBy(fix); + TimePoint newStart = timePoints.getA(); + TimePoint newEnd = timePoints.getB(); + start = start == null || start.after(newStart) ? newStart : start; + end = end == null || end.before(newEnd) ? newEnd : end; + } + } + for (Competitor c : race.getRace().getCompetitors()) { + List comFixes = new ArrayList<>(); + DynamicGPSFixTrack track = race.getTrack(c); + GPSFix comFix = track.getFirstFixAtOrAfter(start); + if (comFix != null) { + if (end != null) { + while (comFix != null && !comFix.getTimePoint().after(end)) { + comFixes.add(comFix); + comFix = track.getFirstFixAfter(comFix.getTimePoint()); + } + } else { + while (comFix != null) { + comFixes.add(comFix); + comFix = track.getFirstFixAfter(comFix.getTimePoint()); + } + } + } + if (!comFixes.isEmpty()) { + affectedFixes.put(c, comFixes); + } + } + return affectedFixes; + } + + @Override + public Util.Pair, Iterable> getCandidateDeltas(Competitor c, Iterable fixes) { + + List newCans = new ArrayList<>(); + List wrongCans = new ArrayList<>(); + Util.Pair, List> distanceCandidates = checkForDistanceCandidateChanges(c, fixes, + race.getRace().getCourse().getWaypoints()); + Util.Pair, List> xteCandidates = checkForXTECandidatesChanges(c, fixes, race + .getRace().getCourse().getWaypoints()); + logger.finest(distanceCandidates.getA().size() + " new Distance Candidates, " + xteCandidates.getA().size() + + " new XTE Candidates, " + distanceCandidates.getB().size() + " removed distance Candidates and " + + xteCandidates.getB().size() + " removed XTE Candidates."); + newCans.addAll(xteCandidates.getA()); + newCans.addAll(distanceCandidates.getA()); + wrongCans.addAll(xteCandidates.getB()); + wrongCans.addAll(distanceCandidates.getB()); + return new Util.Pair, Iterable>(newCans, wrongCans); + } + + private Map, List>> invalidateAfterCourseChange(int indexOfChange) { + Map, List>> result = new HashMap<>(); + Course course = race.getRace().getCourse(); + for (Competitor c : race.getRace().getCompetitors()) { + distanceCache.get(c).clear(); + xteCache.get(c).clear(); + } + List changedWaypoints = new ArrayList<>(); + for (Waypoint w : course.getWaypoints()) { + if (course.getIndexOfWaypoint(w) > indexOfChange - 2) { + changedWaypoints.add(w); + } + } + for (Competitor c : race.getRace().getCompetitors()) { + List badCans = new ArrayList<>(); + List newCans = new ArrayList<>(); + for (Waypoint w : changedWaypoints) { + Map, Candidate> xteCans = getXteCandidates(c, w); + badCans.addAll(xteCans.values()); + xteCans.clear(); + Map distanceCans = getDistanceCandidates(c, w); + badCans.addAll(distanceCans.values()); + distanceCans.clear(); + } + Set allFixes = getAllFixes(c); + newCans.addAll(checkForDistanceCandidateChanges(c, allFixes, changedWaypoints).getA()); + newCans.addAll(checkForXTECandidatesChanges(c, allFixes, changedWaypoints).getA()); + result.put(c, new Util.Pair, List>(newCans, badCans)); + } + return result; + } + + @Override + public Map, List>> updateWaypoints( + Iterable addedWaypoints, Iterable removedWaypoints, Integer smallestIndex) { + Map> removedWaypointCandidates = removeWaypoints(removedWaypoints); + Map, List>> newAndUpdatedCandidates = invalidateAfterCourseChange(smallestIndex); + for (Entry> entry : removedWaypointCandidates.entrySet()) { + newAndUpdatedCandidates.get(entry.getKey()).getB().addAll(entry.getValue()); + } + return newAndUpdatedCandidates; + } + + private Map> removeWaypoints(Iterable waypoints) { + // TODO Clear caches? + Map> result = new HashMap<>(); + for (Competitor c : race.getRace().getCompetitors()) { + result.put(c, new ArrayList()); + } + for (Waypoint w : waypoints) { + passingInstructions.remove(w); + for (Entry> entry : result.entrySet()) { + Competitor c = entry.getKey(); + List badCans = entry.getValue(); + badCans.addAll(getXteCandidates(c, w).values()); + synchronized (xteCandidates) { + xteCandidates.get(c).remove(w); + } + badCans.addAll(getDistanceCandidates(c, w).values()); + synchronized (distanceCandidates) { + distanceCandidates.get(c).remove(w); + } + } + } + return result; + } + + private Map getDistanceCandidates(Competitor c, Waypoint w) { + synchronized (distanceCandidates) { + Map result = distanceCandidates.get(c).get(w); + if (result == null) { + result = new HashMap<>(); + distanceCandidates.get(c).put(w, result); + } + return result; + } + } + + private Map, Candidate> getXteCandidates(Competitor c, Waypoint w) { + synchronized (xteCandidates) { + Map, Candidate> result = xteCandidates.get(c).get(w); + if (result == null) { + result = new HashMap<>(); + xteCandidates.get(c).put(w, result); + } + return result; + } + } + + private PassingInstruction determinePassingInstructions(Waypoint w) { + final Waypoint firstWaypoint = race.getRace().getCourse().getFirstWaypoint(); + final Waypoint lastWaypoint = race.getRace().getCourse().getLastWaypoint(); + PassingInstruction instruction = w.getPassingInstructions(); + if ((w.equals(firstWaypoint) || w.equals(lastWaypoint)) && instruction == PassingInstruction.Gate) { + instruction = PassingInstruction.Line; + } + if (instruction == PassingInstruction.None || instruction == null) { + if (w.equals(firstWaypoint) || w.equals(lastWaypoint)) { + instruction = PassingInstruction.Line; + } else { + int numberofMarks = 0; + Iterator it = w.getMarks().iterator(); + while (it.hasNext()) { + it.next(); + numberofMarks++; + } + if (numberofMarks == 2) { + instruction = PassingInstruction.Gate; + } else if (numberofMarks == 1) { + instruction = PassingInstruction.Port; + } else { + instruction = PassingInstruction.None; + } + } + } + return instruction; + } + + private Set getAllFixes(Competitor c) { + Set fixes = new TreeSet(comp); + DynamicGPSFixTrack track = race.getTrack(c); + try { + track.lockForRead(); + for (GPSFix fix : track.getFixes( + race.getStartOfTracking()==null?TimePoint.BeginningOfTime:race.getStartOfTracking(), + /* fromInclusive */ true, + race.getEndOfTracking()==null?TimePoint.EndOfTime:race.getEndOfTracking(), + /* toInclusive */ true)) { + fixes.add(fix); + } + } finally { + track.unlockAfterRead(); + } + return fixes; + } + + /** + * For each fix the distance to each waypoint is calculated. Then the fix is checked for being a candidate. + */ + private Util.Pair, List> checkForDistanceCandidateChanges(Competitor c, + Iterable fixes, Iterable waypoints) { + Util.Pair, List> result = new Util.Pair, List>( + new ArrayList(), new ArrayList()); + TreeSet affectedFixes = new TreeSet(comp); + GPSFixTrack track = race.getTrack(c); + for (GPSFix fix : fixes) { + affectedFixes.add(fix); + GPSFix fixBefore; + GPSFix fixAfter; + try { + track.lockForRead(); + TimePoint timePoint = fix.getTimePoint(); + fixBefore = track.getLastFixBefore(timePoint); + fixAfter = track.getFirstFixAfter(timePoint); + } finally { + track.unlockAfterRead(); + } + if (fixBefore != null) { + affectedFixes.add(fixBefore); + } + if (fixAfter != null) { + affectedFixes.add(fixAfter); + } + } + for (GPSFix fix : affectedFixes) { + TimePoint t = null; + Position p = null; + GPSFix fixBefore; + GPSFix fixAfter; + try { + track.lockForRead(); + TimePoint timePoint = fix.getTimePoint(); + fixBefore = track.getLastFixBefore(timePoint); + fixAfter = track.getFirstFixAfter(timePoint); + } finally { + track.unlockAfterRead(); + } + if (fixBefore != null && fixAfter != null) { + Map> fixDistances = getDistances(c, fix); + Map> fixDistancesBefore = getDistances(c, fixBefore); + Map> fixDistancesAfter = getDistances(c, fixAfter); + for (Waypoint w : waypoints) { + Boolean wasCan = false; + Boolean isCan = false; + Candidate oldCan = null; + Double probability = null; + List waypointDistances = fixDistances.get(w); + List waypointDistancesBefore = fixDistancesBefore.get(w); + List waypointDistancesAfter = fixDistancesAfter.get(w); + // due to course changes, waypoints that exist in the waypoints collection may not have a corresponding + // key in passingInstructions' key set which is the basis for the waypoints for which getDistances(...) + // computes results; so we have to check for null here: + if (waypointDistances != null && waypointDistancesBefore != null && waypointDistancesAfter != null) { + Iterator disIter = waypointDistances.iterator(); + Iterator disBeforeIter = waypointDistancesBefore.iterator(); + Iterator disAfterIter = waypointDistancesAfter.iterator(); + boolean portMark = true; + while (disIter.hasNext() && disBeforeIter.hasNext() && disAfterIter.hasNext()) { + Distance dis = disIter.next(); + Distance disBefore = disBeforeIter.next(); + Distance disAfter = disAfterIter.next(); + if (dis != null && disBefore != null && disAfter != null) { + if (Math.abs(dis.getMeters()) < Math.abs(disBefore.getMeters()) + && Math.abs(dis.getMeters()) < Math.abs(disAfter.getMeters())) { + t = fix.getTimePoint(); + p = fix.getPosition(); + Double newProbability = getDistanceBasedProbability(w, t, dis); + if (newProbability != null) { + newProbability *= isOnCorrectSideOfWaypoint(w, p, t, portMark) ? penaltyForDistanceCandidates + : penaltyForDistanceCandidates * penaltyForWrongSide; + if (newProbability > penaltyForSkipping + && (probability == null || newProbability > probability)) { + isCan = true; + probability = newProbability; + } + } + } + } + portMark = false; + } + oldCan = getDistanceCandidates(c, w).get(fix); + if (oldCan != null) { + wasCan = true; + } + if (!wasCan && isCan) { + Candidate newCan = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t, + probability, w); + getDistanceCandidates(c, w).put(fix, newCan); + result.getA().add(newCan); + logger.finest("Added distance" + newCan.toString() + "for " + c); + } else if (wasCan && !isCan) { + getDistanceCandidates(c, w).remove(fix); + result.getB().add(oldCan); + } else if (wasCan && isCan && oldCan.getProbability() != probability) { + Candidate newCan = new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t, + probability, w); + getDistanceCandidates(c, w).put(fix, newCan); + result.getA().add(newCan); + logger.finest("Added distance" + newCan.toString() + "for " + c); + result.getB().add(oldCan); + } + } + } + } + } + return result; + } + + private Map> getDistances(Competitor c, GPSFix fix) { + // TODO Possibly for specific waypoints + Map> result = distanceCache.get(c).get(fix); + if (result == null) { + // Else calculate distances and put them into the cache + result = new LinkedHashMap<>(); + for (Waypoint w : getWaypoints()) { + List distances = calculateDistance(fix.getPosition(), w, fix.getTimePoint()); + result.put(w, distances); + } + distanceCache.get(c).put(fix, result); + } + return result; + } + + /** + * For each fix the cross-track error(s) to each waypoint are calculated. Then all Util.Pairs of fixes are checked + * for being a candidate. + * + * @param waypointAsList + */ + private Util.Pair, List> checkForXTECandidatesChanges(Competitor c, + Iterable fixes, Iterable waypoints) { + Util.Pair, List> result = new Util.Pair, List>( + new ArrayList(), new ArrayList()); + DynamicGPSFixTrack track = race.getTrack(c); + for (GPSFix fix : fixes) { + TimePoint t = fix.getTimePoint(); + GPSFix fixBefore; + GPSFix fixAfter; + track.lockForRead(); + try { + fixBefore = track.getLastFixBefore(t); + fixAfter = track.getFirstFixAfter(t); + } finally { + track.unlockAfterRead(); + } + Map> xtesBefore = null; + Map> xtesAfter = null; + TimePoint tBefore = null; + TimePoint tAfter = null; + if (fixBefore != null) { + xtesBefore = getXTE(c, fixBefore); + tBefore = fixBefore.getTimePoint(); + } + if (fixAfter != null) { + xtesAfter = getXTE(c, fixAfter); + tAfter = fixAfter.getTimePoint(); + } + Map> xtes = getXTE(c, fix); + for (Waypoint w : waypoints) { + List> oldCandidates = new ArrayList<>(); + Map, Candidate> newCandidates = new HashMap, Candidate>(); + Map, Candidate> waypointCandidates = getXteCandidates(c, w); + for (List fixPair : waypointCandidates.keySet()) { + if (fixPair.contains(fix)) { + oldCandidates.add(fixPair); + } + } + List wayPointXTEs = xtes.get(w); + int size = wayPointXTEs.size(); + if (size > 0) { + Double xte = wayPointXTEs.get(0).getMeters(); + if (xte == 0) { + newCandidates.put(Arrays.asList(fix, fix), createCandidate(c, 0, 0, t, t, w, true)); + } else { + if (fixAfter != null && xtesAfter != null && !xtesAfter.get(w).isEmpty()) { + Double xteAfter = xtesAfter.get(w).get(0).getMeters(); + if (xteAfter != null && xte < 0 != xteAfter <= 0) { + newCandidates.put(Arrays.asList(fix, fixAfter), + createCandidate(c, xte, xteAfter, t, tAfter, w, true)); + } + } + if (fixBefore != null && !xtesBefore.get(w).isEmpty()) { + Double xteBefore = xtesBefore.get(w).get(0).getMeters(); + if (xte < 0 != xteBefore <= 0) { + newCandidates.put(Arrays.asList(fixBefore, fix), + createCandidate(c, xteBefore, xte, tBefore, t, w, true)); + } + } + } + } + if (size > 1) { + Double xte = wayPointXTEs.get(1).getMeters(); + if (xte == 0) { + newCandidates.put(Arrays.asList(fix, fix), createCandidate(c, 0, 0, t, t, w, false)); + } else { + if (fixAfter != null && xtesAfter != null) { + Double xteAfter = xtesAfter.get(w).get(1).getMeters(); + if (xte < 0 != xteAfter <= 0) { + newCandidates.put(Arrays.asList(fix, fixAfter), + createCandidate(c, xte, xteAfter, t, tAfter, w, false)); + } + } + if (fixBefore != null) { + Double xteBefore = xtesBefore.get(w).get(1).getMeters(); + if (xte < 0 != xteBefore <= 0) { + newCandidates.put(Arrays.asList(fixBefore, fix), + createCandidate(c, xteBefore, xte, tBefore, t, w, false)); + } + } + } + } + for (Entry, Candidate> candidateWithFixes : newCandidates.entrySet()) { + Candidate newCan = candidateWithFixes.getValue(); + List canFixes = candidateWithFixes.getKey(); + if (oldCandidates.contains(canFixes)) { + oldCandidates.remove(canFixes); + Candidate oldCan = waypointCandidates.get(canFixes); + if (newCan.compareTo(oldCan) != 0) { + result.getB().add(oldCan); + waypointCandidates.remove(canFixes); + if (newCan.getProbability() > penaltyForSkipping) { + result.getA().add(newCan); + logger.finest("Added XTE " + newCan.toString() + "for " + c); + waypointCandidates.put(canFixes, newCan); + } + } + } else { + if (newCan.getProbability() > penaltyForSkipping) { + result.getA().add(newCan); + logger.finest("Added XTE " + newCan.toString() + "for " + c); + waypointCandidates.put(canFixes, newCan); + } + } + } + for (List badCanFixes : oldCandidates) { + result.getB().add(waypointCandidates.get(badCanFixes)); + waypointCandidates.remove(badCanFixes); + } + } + } + return result; + } + + private Map> getXTE(Competitor c, GPSFix fix) { + Map> result = xteCache.get(c).get(fix); + if (result == null) { + result = new HashMap<>(); + Position p = fix.getPosition(); + TimePoint t = fix.getTimePoint(); + for (Waypoint w : getWaypoints()) { + List distances = new ArrayList<>(); + result.put(w, distances); + for (Util.Pair crossingInfo : getCrossingInformation(w, t)) { + if (crossingInfo.getA() != null && crossingInfo.getB() != null) { + distances.add(p.crossTrackError(crossingInfo.getA(), crossingInfo.getB())); + } + } + } + xteCache.get(c).put(fix, result); + } + return result; + } + + private Iterable getWaypoints() { + return race.getRace().getCourse().getWaypoints(); + } + + /** + * Calculates the cross-track error of each fix to the position and crossing bearing of each waypoint. Gates have + * two of these and lines always go from the port mark to the starboard mark. + * + * + * /* private void calculateCrossTrackErrors(Competitor c, Iterable fixes, Iterable + * waypointsToCalculate) { for (GPSFix fix : fixes) { Position fixPos = fix.getPosition(); TimePoint t = + * fix.getTimePoint(); Map> waypointXTE = new HashMap>(); + * crossTrackErrors.get(c).put(fix, waypointXTE); for (Waypoint w : race.getRace().getCourse().getWaypoints()) { + * List xte = new ArrayList<>(); waypointXTE.put(w, xte); for (Util.Pair crossingInfo : + * getCrossingInformation(w, t)) { xte.add(fixPos.crossTrackError(crossingInfo.getA(), + * crossingInfo.getB()).getMeters()); } } } } + */ + + private Candidate createCandidate(Competitor c, double xte1, double xte2, TimePoint t1, TimePoint t2, Waypoint w, + Boolean portMark) { + long differenceInMillis = t2.asMillis() - t1.asMillis(); + double ratio = (Math.abs(xte1) / (Math.abs(xte1) + Math.abs(xte2))); + TimePoint t = t1.plus((long) (differenceInMillis * ratio)); + Position p = race.getTrack(c).getEstimatedPosition(t, false); + List distances = calculateDistance(p, w, t); + Distance d = portMark ? distances.get(0) : distances.get(1); + double cost = getDistanceBasedProbability(w, t, d); + cost = isOnCorrectSideOfWaypoint(w, p, t, portMark) ? cost : cost * penaltyForWrongSide; + cost = passesInTheRightDirection(w, xte1, xte2, portMark) ? cost : cost * penaltyForWrongDirection; + return new CandidateImpl(race.getRace().getCourse().getIndexOfWaypoint(w) + 1, t, cost, w); + } + + /** + * Determines whether a candidate is on the correct side of a waypoint. This is defined by the crossing information. + * The cross-track error of p to the crossing Position and the crossing Bearing rotated by 90deg need + * to be negative. If the passing instructions are {@link PassingInstruction#Line}, it checks whether the boat + * passed between the two marks. + */ + private boolean isOnCorrectSideOfWaypoint(Waypoint w, Position p, TimePoint t, boolean portMark) { + boolean result = true; + PassingInstruction instruction = getPassingInstructions(w); + if (instruction == PassingInstruction.Line) { + List pos = new ArrayList<>(); + for (Mark m : w.getMarks()) { + Position po = race.getOrCreateTrack(m).getEstimatedPosition(t, false); + if (po == null) { + return true; + } + pos.add(po); + } + Bearing diff1 = pos.get(0).getBearingGreatCircle(p) + .getDifferenceTo(pos.get(0).getBearingGreatCircle(pos.get(1))); + Bearing diff2 = pos.get(1).getBearingGreatCircle(p) + .getDifferenceTo(pos.get(1).getBearingGreatCircle(pos.get(0))); + if (Math.abs(diff1.getDegrees()) > 90 || Math.abs(diff2.getDegrees()) > 90) { + result = false; + } + + } else { + Mark m = null; + if (instruction == PassingInstruction.Port || instruction == PassingInstruction.Starboard + || instruction == PassingInstruction.FixedBearing) { + m = w.getMarks().iterator().next(); + } else if (instruction == PassingInstruction.Gate) { + Util.Pair pair = getPortAndStarboardMarks(t, w); + m = portMark ? pair.getA() : pair.getB(); + } + if (m != null) { + Util.Pair crossingInfo = Util.get(getCrossingInformation(w, t), portMark ? 0 : 1); + result = p.crossTrackError(crossingInfo.getA(), crossingInfo.getB().add(new DegreeBearingImpl(90))) + .getMeters() < 0; + } + } + return result; + } + + /** + * Determines whether a candidate passes a waypoint in the right direction. Can only be applied to XTE-Candidates. + * For marks passed on port, the cross-track error should switch from positive to negative and vice versa. Lines are + * also from positive to negative as the cross-track error to a line is always positive when approaching it from the + * correct side. + */ + private boolean passesInTheRightDirection(Waypoint w, double xte1, double xte2, boolean portMark) { + boolean result = true; + PassingInstruction instruction = getPassingInstructions(w); + if (instruction == PassingInstruction.Port || instruction == PassingInstruction.Line + || (instruction == PassingInstruction.Gate && portMark)) { + result = xte1 > xte2 ? true : false; + } else if (instruction == PassingInstruction.Starboard || (instruction == PassingInstruction.Gate && !portMark)) { + result = xte1 < xte2 ? true : false; + } + return result; + } + + /** + * @return a probability based on the distance to w and the average leg lengths before and after. + */ + private Double getDistanceBasedProbability(Waypoint w, TimePoint t, Distance distance) { + Distance legLength = getLegLength(t, w); + if (legLength != null) { + double result = 1 / (strictnessOfDistanceBasedProbability/* Raising this will make is stricter */ + * Math.abs(distance.getMeters() / legLength.getMeters()) + 1); + return result; + } + return null; + } + + /** + * @return an average of the estimated legs before and after w. + */ + private Distance getLegLength(TimePoint t, Waypoint w) { + Course course = race.getRace().getCourse(); + if (w == course.getFirstWaypoint()) { + return race.getTrackedLegStartingAt(w).getGreatCircleDistance(t); + } else if (w == course.getLastWaypoint()) { + return race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t); + } else { + Distance before = race.getTrackedLegStartingAt(w).getGreatCircleDistance(t); + Distance after = race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t); + if (after != null && before != null) { + return new MeterDistance(before.add(after).getMeters() / 2); + } + return null; + } + } + + /** + * If m is contained in lineMarks, the distance to that line is calculated, else simply + * the distance to the m. + */ + private List calculateDistance(Position p, Waypoint w, TimePoint t) { + List distances = new ArrayList<>(); + PassingInstruction instruction = getPassingInstructions(w); + boolean singleMark = false; + switch (instruction) { + case Port: + singleMark = true; + break; + case Starboard: + singleMark = true; + break; + case FixedBearing: + singleMark = true; + break; + case Gate: + Util.Pair posGate = getPortAndStarboardMarks(t, w); + if (posGate.getA() != null) { + Position portGatePosition = race.getOrCreateTrack(posGate.getA()).getEstimatedPosition(t, false); + distances.add(portGatePosition != null ? p.getDistance(portGatePosition) : null); + } else { + distances.add(null); + } + if (posGate.getB() != null) { + Position starboardGatePosition = race.getOrCreateTrack(posGate.getB()).getEstimatedPosition(t, false); + distances.add(starboardGatePosition != null ? p.getDistance(starboardGatePosition) : null); + } else { + distances.add(null); + } + break; + case Line: + Util.Pair posLine = getPortAndStarboardMarks(t, w); + if (posLine.getA() != null && posLine.getB() != null) { + Position portLinePosition = race.getOrCreateTrack(posLine.getA()).getEstimatedPosition(t, false); + Position starboardLinePosition = race.getOrCreateTrack(posLine.getB()).getEstimatedPosition(t, false); + distances.add((portLinePosition != null && starboardLinePosition != null) ? p.getDistanceToLine( + portLinePosition, starboardLinePosition) : portLinePosition != null ? p + .getDistance(portLinePosition) : starboardLinePosition != null ? p + .getDistance(portLinePosition) : null); + } + break; + case Offset: + // TODO + break; + case None: + break; + default: + break; + } + if (singleMark) { + Position markPosition = race.getOrCreateTrack(w.getMarks().iterator().next()) + .getEstimatedPosition(t, false); + distances.add(markPosition != null ? p.getDistance(markPosition) : null); + } + return distances; + } + + private PassingInstruction getPassingInstructions(Waypoint w) { + final PassingInstruction result; + if (passingInstructions.containsKey(w)) { + result = passingInstructions.get(w); + } else { + result = determinePassingInstructions(w); + passingInstructions.put(w, result); + } + return result; + } + + /** + * @return all possible ways to pass a waypoint, described as a position and a bearing. The line out of those two + * must be crossed. + */ + private Iterable> getCrossingInformation(Waypoint w, TimePoint t) { + List> result = new ArrayList<>(); + PassingInstruction instruction = getPassingInstructions(w); + if (instruction == PassingInstruction.Line) { + Util.Pair marks = getPortAndStarboardMarks(t, w); + Position portPosition = null; + Bearing b = null; + Mark portMark = marks.getA(); + Mark starBoardMark = marks.getB(); + if (portMark != null && starBoardMark != null) { + portPosition = race.getOrCreateTrack(portMark).getEstimatedPosition(t, false); + Position starboardPosition = race.getOrCreateTrack(starBoardMark).getEstimatedPosition(t, false); + if (portPosition != null && starboardPosition != null) { + b = portPosition.getBearingGreatCircle(starboardPosition); + result.add(new Util.Pair(portPosition, b)); + } + } + } else if (instruction == PassingInstruction.Gate) { + Position before = race.getApproximatePosition(race.getTrackedLegFinishingAt(w).getLeg().getFrom(), t); + Position after = race.getApproximatePosition(race.getTrackedLegStartingAt(w).getLeg().getTo(), t); + Util.Pair pos = getPortAndStarboardMarks(t, w); + Mark portMark = pos.getA(); + if (portMark != null) { + Position portPosition = race.getOrCreateTrack(portMark).getEstimatedPosition(t, false); + Bearing crossingPort = before.getBearingGreatCircle(portPosition).middle( + after.getBearingGreatCircle(portPosition)); + result.add(new Util.Pair(portPosition, crossingPort)); + } + Mark starboardMark = pos.getB(); + if (starboardMark != null) { + Position starboardPosition = race.getOrCreateTrack(starboardMark).getEstimatedPosition(t, false); + Bearing crossingStarboard = before.getBearingGreatCircle(starboardPosition).middle( + after.getBearingGreatCircle(starboardPosition)); + result.add(new Util.Pair(starboardPosition, crossingStarboard)); + } + } else { + Bearing b = null; + Position p; + if (instruction == PassingInstruction.FixedBearing) { + b = w.getFixedBearing(); + } else { + // TODO Mark at start or end of race + Bearing before = race.getTrackedLegFinishingAt(w).getLegBearing(t); + Bearing after = race.getTrackedLegStartingAt(w).getLegBearing(t); + if (before != null && after != null) { + b = before.middle(after.reverse()); + } + } + p = race.getOrCreateTrack(w.getMarks().iterator().next()).getEstimatedPosition(t, false); + result.add(new Util.Pair(p, b)); + } + return result; + } + + /** + * @return the marks of a waypoint with two marks in the order port, starboard (when approching the waypoint from + * the direction of the waypoint beforehand. + */ + private Util.Pair getPortAndStarboardMarks(TimePoint t, Waypoint w) { + List markPositions = new ArrayList(); + for (Mark mark : w.getMarks()) { + final Position estimatedMarkPosition = race.getOrCreateTrack(mark).getEstimatedPosition(t, /* extrapolate */ false); + if (estimatedMarkPosition == null) { + return new Util.Pair(null, null); + } + markPositions.add(estimatedMarkPosition); + } + final List legs = race.getRace().getCourse().getLegs(); + final int indexOfWaypoint = race.getRace().getCourse().getIndexOfWaypoint(w); + if (indexOfWaypoint < 0) { + return new Util.Pair(null, null); + } + final boolean isStartLine = indexOfWaypoint == 0; + final Bearing legDeterminingDirectionBearing = race.getTrackedLeg( + legs.get(isStartLine ? 0 : indexOfWaypoint - 1)).getLegBearing(t); + if (legDeterminingDirectionBearing == null) { + return new Util.Pair(null, null); + } + Distance crossTrackErrorOfMark0OnLineFromMark1ToNextWaypoint = markPositions.get(0).crossTrackError( + markPositions.get(1), legDeterminingDirectionBearing); + final Mark starboardMarkWhileApproachingLine; + final Mark portMarkWhileApproachingLine; + if (crossTrackErrorOfMark0OnLineFromMark1ToNextWaypoint.getMeters() < 0) { + portMarkWhileApproachingLine = Util.get(w.getMarks(), 0); + starboardMarkWhileApproachingLine = Util.get(w.getMarks(), 1); + } else { + portMarkWhileApproachingLine = Util.get(w.getMarks(), 1); + starboardMarkWhileApproachingLine = Util.get(w.getMarks(), 0); + } + return new Util.Pair(portMarkWhileApproachingLine, starboardMarkWhileApproachingLine); + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateImpl.java new file mode 100755 index 00000000000..87bfb5d438a --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateImpl.java @@ -0,0 +1,55 @@ +package com.sap.sailing.domain.markpassingcalculation.impl; + +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.markpassingcalculation.Candidate; +import com.sap.sse.common.TimePoint; + +public class CandidateImpl implements Candidate { + private final Waypoint w; + protected TimePoint p; + private final double probability; + private final Integer oneBasedIndexOfWaypoint; + + public CandidateImpl(int oneBasedIndexOfWaypoint, TimePoint p, double probability, Waypoint w) { + this.w = w; + this.p = p; + this.probability = probability; + this.oneBasedIndexOfWaypoint = oneBasedIndexOfWaypoint; + + } + + @Override + public int getOneBasedIndexOfWaypoint() { + return oneBasedIndexOfWaypoint; + } + + @Override + public TimePoint getTimePoint() { + return p; + } + + @Override + public Double getProbability() { + + return probability; + } + + @Override + public Waypoint getWaypoint() { + return w; + } + + public String toString() { + return "Candidate for waypoint " + oneBasedIndexOfWaypoint + " with cost " + getProbability() + " and Timepoint " + p; + } + + @Override + public int compareTo(CandidateImpl arg0) { + return compareTo((Candidate) arg0); + } + + @Override + public int compareTo(Candidate arg0) { + return oneBasedIndexOfWaypoint != arg0.getOneBasedIndexOfWaypoint() ? oneBasedIndexOfWaypoint.compareTo(arg0.getOneBasedIndexOfWaypoint()) : p != arg0.getTimePoint() ? p.compareTo(arg0.getTimePoint()) : getProbability().compareTo(arg0.getProbability()); + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/Edge.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/Edge.java new file mode 100755 index 00000000000..2c3783aa16f --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/Edge.java @@ -0,0 +1,70 @@ +package com.sap.sailing.domain.markpassingcalculation.impl; + +import com.sap.sailing.domain.base.Course; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.markpassingcalculation.Candidate; +import com.sap.sse.common.Util; + +/** + * Represent the passage between two {@link Candidate}s, start and end. {@link #getCost()} + * returns the probability of this passage being correct, consisting of the probability of each Candidate, the + * probability of the passage between them (an estimate whether the distance sailed between the candidates matches the + * distance between their {@link Waypoint}s), and a penalty for skipping waypoints, which might happen if a tracker + * fails or a competitor aborts but should be avoided if possible and is therefore unlikely. + * + * @author Nicolas Klose + * + */ + +public class Edge implements Comparable { + private final Candidate start; + private final Candidate end; + // TODO what is the meaning of this constant? + private final static double penaltyForSkipped = 0.7; + // TODO what is the meaning of this constant? + private final static double penaltyForSkippedToEnd = 0.6; + private final double estimatedDistanceAndStartTimingProbability; + private final Course course; + + public Edge(Candidate start, Candidate end, double estimatedDistanceAndStartTimingProbability, Course course) { + this.course = course; + this.start = start; + this.end = end; + this.estimatedDistanceAndStartTimingProbability = estimatedDistanceAndStartTimingProbability; + } + + public static double getPenaltyForSkipping() { + return penaltyForSkipped; + } + + /** + * The cost for skipping a waypoint is 2*{@link #penaltyForSkipped} but is reduced to {@link #penaltyForSkippedToEnd} when + * skipping to the end. The reason for preferring skips to the end proxy node is that in live situations where the course + * hasn't been completed yet it is required to skip to the end. Additional cost comes from the inverse probability of + * the product of the start node, end node and distance-based probabilities. If these probabilities multiply to zero, + * an additional cost of 1.0 is added to any skip penalty. + */ + public Double getCost() { + double penalty = end.getOneBasedIndexOfWaypoint() == Util.size(course.getWaypoints()) + 1 ? penaltyForSkippedToEnd : penaltyForSkipped; + return 1 - (start.getProbability() * end.getProbability() * estimatedDistanceAndStartTimingProbability) + 2 * penalty + * (end.getOneBasedIndexOfWaypoint() - start.getOneBasedIndexOfWaypoint() - 1); + } + + public Candidate getStart() { + return start; + } + + public Candidate getEnd() { + return end; + } + + public String toString() { + return "Edge from Waypoint " + start.getOneBasedIndexOfWaypoint() + " to " + end.getOneBasedIndexOfWaypoint() + ": " + getCost(); + } + + // TODO Only for Debugging! + @Override + public int compareTo(Edge o) { + return start != o.getStart() ? start.compareTo(o.getStart()) : end != o.getEnd() ? end.compareTo(o.getEnd()) : getCost().compareTo(o.getCost()); + } +} \ No newline at end of file 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 0c13b707efa..7a3b945348f 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 @@ -3,12 +3,9 @@ package com.sap.sailing.domain.tracking; import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseBase; import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.WindSource; import com.sap.sailing.domain.common.racelog.Flags; import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; public interface DynamicTrackedRace extends TrackedRace { void recordFix(Competitor competitor, GPSFixMoving fix); @@ -103,8 +100,4 @@ public interface DynamicTrackedRace extends TrackedRace { void invalidateStartTime(); void invalidateEndTime(); - - Bearing getCrossingBearing(Waypoint w, TimePoint t); - - Util.Pair getPortAndStarboardMarks(TimePoint t, Waypoint w); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRegatta.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRegatta.java index 8e591df0c87..039fee7d3db 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRegatta.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/DynamicTrackedRegatta.java @@ -1,8 +1,6 @@ package com.sap.sailing.domain.tracking; import com.sap.sailing.domain.base.RaceDefinition; -import com.sap.sailing.domain.base.Sideline; -import com.sap.sailing.domain.racelog.tracking.GPSFixStore; public interface DynamicTrackedRegatta extends TrackedRegatta { @@ -16,14 +14,4 @@ public interface DynamicTrackedRegatta extends TrackedRegatta { * for race. See also {@link #getTrackedRace(RaceDefinition)} for a blocking variant. */ DynamicTrackedRace getExistingTrackedRace(RaceDefinition race); - - /** - * @param raceDefinitionSetToUpdate - * may be null which means that no update will be fired to any - * {@link DynamicRaceDefinitionSet}. - */ - DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable sidelines, - WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, - long millisecondsOverWhichToAverageSpeed, DynamicRaceDefinitionSet raceDefinitionSetToUpdate); - } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java index eeb6962e398..8c5151b6aca 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java @@ -751,4 +751,6 @@ public interface TrackedRace extends Serializable { */ Distance getAdditionalGateStartDistance(Competitor competitor, TimePoint timePoint); + boolean isUsingMarkPassingCalculator(); + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegatta.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegatta.java index bdffd02721c..227a8a216f5 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegatta.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRegatta.java @@ -44,7 +44,7 @@ public interface TrackedRegatta extends Serializable { */ DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable sidelines, WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, - DynamicRaceDefinitionSet raceDefinitionSetToUpdate); + DynamicRaceDefinitionSet raceDefinitionSetToUpdate, boolean useInternalMarkPassingAlgorithm); /** * Obtains the tracked race for race. Blocks until the tracked race has been created 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 5e0ebb3ce6f..bf747d1fbc5 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 @@ -1,883 +1,814 @@ -package com.sap.sailing.domain.tracking.impl; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.ConcurrentModificationException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.NavigableSet; -import java.util.Set; -import java.util.function.Consumer; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.sap.sailing.domain.abstractlog.race.RaceLog; -import com.sap.sailing.domain.base.BoatClass; -import com.sap.sailing.domain.base.Competitor; -import com.sap.sailing.domain.base.CourseBase; -import com.sap.sailing.domain.base.Leg; -import com.sap.sailing.domain.base.Mark; -import com.sap.sailing.domain.base.RaceDefinition; -import com.sap.sailing.domain.base.Sideline; -import com.sap.sailing.domain.base.Waypoint; -import com.sap.sailing.domain.common.Bearing; -import com.sap.sailing.domain.common.Distance; -import com.sap.sailing.domain.common.PassingInstruction; -import com.sap.sailing.domain.common.Position; -import com.sap.sailing.domain.common.TimingConstants; -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.racelog.Flags; -import com.sap.sailing.domain.racelog.tracking.GPSFixStore; -import com.sap.sailing.domain.tracking.CourseDesignChangedListener; -import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; -import com.sap.sailing.domain.tracking.DynamicTrackedRace; -import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; -import com.sap.sailing.domain.tracking.GPSFix; -import com.sap.sailing.domain.tracking.GPSFixMoving; -import com.sap.sailing.domain.tracking.GPSFixTrack; -import com.sap.sailing.domain.tracking.GPSTrackListener; -import com.sap.sailing.domain.tracking.MarkPassing; -import com.sap.sailing.domain.tracking.RaceAbortedListener; -import com.sap.sailing.domain.tracking.RaceChangeListener; -import com.sap.sailing.domain.tracking.StartTimeChangedListener; -import com.sap.sailing.domain.tracking.TrackedLeg; -import com.sap.sailing.domain.tracking.TrackedRaceStatus; -import com.sap.sailing.domain.tracking.TrackedRegatta; -import com.sap.sailing.domain.tracking.Wind; -import com.sap.sailing.domain.tracking.WindStore; -import com.sap.sailing.domain.tracking.WindTrack; -import com.sap.sse.common.TimePoint; -import com.sap.sse.common.Util; -import com.sap.sse.common.impl.MillisecondsTimePoint; -import com.sap.sse.concurrent.LockUtil; -import com.sap.sse.concurrent.NamedReentrantReadWriteLock; - -public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements -DynamicTrackedRace, GPSTrackListener { - private static final long serialVersionUID = 1092726918239676958L; - - private static final Logger logger = Logger.getLogger(DynamicTrackedRaceImpl.class.getName()); - - private transient Set listeners; - - private boolean raceIsKnownToStartUpwind; - - private boolean delayToLiveInMillisFixed; - - private transient DynamicTrackedRaceLogListener logListener; - - private transient Set courseDesignChangedListeners; - private transient Set startTimeChangedListeners; - private transient Set raceAbortedListeners; - - public DynamicTrackedRaceImpl(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable sidelines, - WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, - long delayForCacheInvalidationOfWindEstimation) { - super(trackedRegatta, race, sidelines, windStore, gpsFixStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, - delayForCacheInvalidationOfWindEstimation); - this.logListener = new DynamicTrackedRaceLogListener(this); - this.courseDesignChangedListeners = new HashSet<>(); - this.startTimeChangedListeners = new HashSet<>(); - this.raceAbortedListeners = new HashSet<>(); - this.raceIsKnownToStartUpwind = race.getBoatClass().typicallyStartsUpwind(); - if (!raceIsKnownToStartUpwind) { - Set windSourcesToExclude = new HashSet(); - for (WindSource windSourceToExclude : getWindSourcesToExclude()) { - windSourcesToExclude.add(windSourceToExclude); - } - windSourcesToExclude.add(new WindSourceImpl(WindSourceType.COURSE_BASED)); - setWindSourcesToExclude(windSourcesToExclude); - } - for (Competitor competitor : getRace().getCompetitors()) { - DynamicGPSFixTrack track = getTrack(competitor); - track.addListener(this); - } - // default wind tracks are observed because they are created by the superclass constructor using - // createWindTrack which adds this object as a listener - } - - /** - * After de-serialization sets a valid {@link #listeners} collection which is transient and therefore - * hasn't been serialized. - */ - private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { - ois.defaultReadObject(); - listeners = new HashSet(); - logListener = new DynamicTrackedRaceLogListener(this); - courseDesignChangedListeners = new HashSet<>(); - startTimeChangedListeners = new HashSet<>(); - raceAbortedListeners = new HashSet<>(); - } - - /** - * {@link #raceIsKnownToStartUpwind} (see also {@link #raceIsKnownToStartUpwind()}) is initialized based on the race's - * {@link RaceDefinition#getBoatClass()} boat class's {@link BoatClass#typicallyStartsUpwind()} result. It can be changed - * using {@link #setRaceIsKnownToStartUpwind(boolean)}. Uses millisecondsOverWhichToAverageWind/2 for the - * delayForCacheInvalidationOfWindEstimation argument of the constructor.

- * - * Loading wind tracks from the windStore happens asynchronously which means that when the constructor returns, - * the caller cannot assume that all wind tracks have yet been loaded completely. The caller may call {@link #waitUntilLoadingFromWindStoreComplete()} - * to wait until all persistent wind sources have been successfully and completely loaded. - */ - public DynamicTrackedRaceImpl(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable sidelines, - WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, - long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed) { - this(trackedRegatta, race, sidelines, windStore, gpsFixStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, - millisecondsOverWhichToAverageWind/2); - } - - @Override - public void recordFix(Competitor competitor, GPSFixMoving fix) { - DynamicGPSFixTrack track = getTrack(competitor); - if (track != null) { - if (logger != null && logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) { - logger.finest(""+competitor.getName() + ": " + fix); - } - track.addGPSFix(fix); // the track notifies this tracked race which in turn notifies its listeners - } - } - - @Override - public void setStatus(TrackedRaceStatus newStatus) { - TrackedRaceStatus oldStatus = getStatus(); - super.setStatus(newStatus); - notifyListeners(newStatus, oldStatus); - } - - @Override - public void recordFix(Mark mark, GPSFix fix) { - if ((getStartOfTracking() == null || getStartOfTracking().compareTo(fix.getTimePoint()) <= 0) && - (getEndOfTracking() == null || getEndOfTracking().compareTo(fix.getTimePoint()) >= 0)) { - getOrCreateTrack(mark).addGPSFix(fix); - } - } - - @Override - public void setMillisecondsOverWhichToAverageSpeed(long millisecondsOverWhichToAverageSpeed) { - this.millisecondsOverWhichToAverageSpeed = millisecondsOverWhichToAverageSpeed; - for (Competitor competitor : getRace().getCompetitors()) { - getTrack(competitor).setMillisecondsOverWhichToAverage(millisecondsOverWhichToAverageSpeed); - } - for (Waypoint waypoint : getRace().getCourse().getWaypoints()) { - for (Mark mark : waypoint.getMarks()) { - getOrCreateTrack(mark).setMillisecondsOverWhichToAverage(millisecondsOverWhichToAverageSpeed); - } - } - updated(/* time point */null); - triggerManeuverCacheRecalculationForAllCompetitors(); - } - - @Override - public void setMillisecondsOverWhichToAverageWind(long millisecondsOverWhichToAverageWind) { - long oldMillisecondsOverWhichToAverageWind = this.millisecondsOverWhichToAverageWind; - this.millisecondsOverWhichToAverageWind = millisecondsOverWhichToAverageWind; - for (WindSource windSource : getWindSources()) { - getOrCreateWindTrack(windSource).setMillisecondsOverWhichToAverage(millisecondsOverWhichToAverageWind); - } - updated(/* time point */null); - triggerManeuverCacheRecalculationForAllCompetitors(); - notifyListenersWindAveragingChanged(oldMillisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageWind); - } - - - @Override - public void setAndFixDelayToLiveInMillis(long delayToLiveInMillis) { - if (getDelayToLiveInMillis() != delayToLiveInMillis) { - super.setDelayToLiveInMillis(delayToLiveInMillis); - notifyListenersDelayToLiveChanged(delayToLiveInMillis); - } - delayToLiveInMillisFixed = true; - } - - @Override - public void setDelayToLiveInMillis(long delayToLiveInMillis) { - if (!delayToLiveInMillisFixed && getDelayToLiveInMillis() != delayToLiveInMillis) { - super.setDelayToLiveInMillis(delayToLiveInMillis); - notifyListenersDelayToLiveChanged(delayToLiveInMillis); - } - } - - @Override - public DynamicGPSFixTrack getTrack(Competitor competitor) { - return (DynamicGPSFixTrack) super.getTrack(competitor); - } - - @Override - public DynamicGPSFixTrack getOrCreateTrack(Mark mark) { - return (DynamicGPSFixTrack) super.getOrCreateTrack(mark); - } - - /** - * In addition to creating the track which is performed by the superclass implementation, this implementation registers - * a {@link GPSTrackListener} with the mark's track and {@link #notifyListeners(GPSFix, Mark, boolean) notifies the listeners} - * about updates. The {@link #updated(TimePoint)} method is not called with the mark fix's time point because - * mark fixes may be received also from marks that don't belong to this race. - */ - @Override - protected DynamicGPSFixTrackImpl createMarkTrack(Mark mark) { - final DynamicGPSFixTrackImpl result = super.createMarkTrack(mark); - result.addListener(new GPSTrackListener() { - private static final long serialVersionUID = -2855787105725103732L; - - @Override - public void gpsFixReceived(GPSFix fix, Mark mark, boolean firstFixInTrack) { - triggerManeuverCacheRecalculationForAllCompetitors(); - notifyListeners(fix, mark, firstFixInTrack); - } - - @Override - public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, - long newMillisecondsOverWhichToAverage) { - // nobody can currently listen for the change of the mark speed averaging because mark speed is not a value used - } - - @Override - public boolean isTransient() { - return false; - } - }); - return result; - } - - /** - * Callers iterating over the result need to synchronize on the resulting collection while iterating - * to avoid {@link ConcurrentModificationException}s. - */ - private Set getListeners() { - if (listeners == null) { - listeners = new HashSet(); - } - return listeners; - } - - @Override - public void addListener(RaceChangeListener listener) { - synchronized (getListeners()) { - getListeners().add(listener); - } - } - - @Override - public void invalidateStartTime() { - TimePoint oldStartOfRace = getStartOfRace(); - super.invalidateStartTime(); - TimePoint newStartOfRace = getStartOfRace(); - if (!Util.equalsWithNull(oldStartOfRace, newStartOfRace)) { - notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace); - } - } - - @Override - public void addListener(RaceChangeListener listener, final boolean notifyAboutWindFixesAlreadyLoaded, - final boolean notifyAboutGPSFixesAlreadyLoaded) { - if (notifyAboutWindFixesAlreadyLoaded) { - LockUtil.lockForRead(getLoadingFromWindStoreLock()); - } - if (notifyAboutGPSFixesAlreadyLoaded) { - LockUtil.lockForRead(getLoadingFromGPSFixStoreLock()); - } - try { - addListener(listener); - if (notifyAboutWindFixesAlreadyLoaded) { - // Now notify all wind fixes we can get from the race by now. TrackedRace.getWindSource() delivers all wind - // sources known so far. If there is a wind track being loaded, it will be separately notified later by - // DynamicTrackedRaceImpl.createWindTrack(...). - // Holding the serialization lock - for (WindSource windSource : getWindSources()) { - if (windSource.getType().canBeStored()) { - WindTrack windTrack = getOrCreateWindTrack(windSource); - // replicate all wind fixes that may have been loaded by the wind store - windTrack.lockForRead(); - try { - for (Wind wind : windTrack.getRawFixes()) { - listener.windDataReceived(wind, windSource); - } - } finally { - windTrack.unlockAfterRead(); - } - } - } - } - if (notifyAboutGPSFixesAlreadyLoaded) { - for (Mark mark : getMarks()) { - GPSFixTrack markTrack = getOrCreateTrack(mark); - markTrack.lockForRead(); - try { - boolean firstInTrack = true; - for (GPSFix fix : markTrack.getRawFixes()) { - listener.markPositionChanged(fix, mark, firstInTrack); - firstInTrack = false; - } - } finally { - markTrack.unlockAfterRead(); - } - } - - for (Competitor competitor : getRace().getCompetitors()) { - GPSFixTrack competitorTrack = getTrack(competitor); - competitorTrack.lockForRead(); - try { - for (GPSFixMoving fix : competitorTrack.getRawFixes()) { - listener.competitorPositionChanged(fix, competitor); - } - } finally { - competitorTrack.unlockAfterRead(); - } - } - } - } finally { - if (notifyAboutWindFixesAlreadyLoaded) { - LockUtil.unlockAfterRead(getLoadingFromWindStoreLock()); - } - if (notifyAboutGPSFixesAlreadyLoaded) { - LockUtil.unlockAfterRead(getLoadingFromGPSFixStoreLock()); - } - } - } - - @Override - public void removeListener(RaceChangeListener listener) { - synchronized (getListeners()) { - getListeners().remove(listener); - } - } - - @Override - public void setWindSourcesToExclude(Iterable windSourcesToExclude) { - super.setWindSourcesToExclude(windSourcesToExclude); - notifyListenersWindSourcesToExcludeChanged(windSourcesToExclude); - } - - private void notifyListeners(Consumer notifyAction) { - RaceChangeListener[] listeners; - synchronized (getListeners()) { - listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); - } - for (RaceChangeListener listener : listeners) { - try { - notifyAction.accept(listener); - } catch (Exception e) { - logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); - logger.log(Level.SEVERE, "notifyListeners(Consumer notifyAction", e); - } - } - } - - private void notifyListenersWindSourcesToExcludeChanged(Iterable windSourcesToExclude) { - notifyListeners(listener -> listener.windSourcesToExcludeChanged(windSourcesToExclude)); - } - - private void notifyListenersStartOfTrackingChanged(TimePoint startOfTracking) { - notifyListeners(listener -> listener.startOfTrackingChanged(startOfTracking)); - } - - private void notifyListenersEndOfTrackingChanged(TimePoint endOfTracking) { - notifyListeners(listener -> listener.endOfTrackingChanged(endOfTracking)); - } - - private void notifyListenersStartTimeReceivedChanged(TimePoint startTimeReceived) { - notifyListeners(listener -> listener.startTimeReceivedChanged(startTimeReceived)); - } - - private void notifyListenersStartOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) { - notifyListeners(listener -> listener.startOfRaceChanged(oldStartOfRace, newStartOfRace)); - } - - private void notifyListenersWaypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) { - notifyListeners(listener -> listener.waypointAdded(zeroBasedIndex, waypointThatGotAdded)); - } - - private void notifyListenersWaypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) { - notifyListeners(listener -> listener.waypointRemoved(zeroBasedIndex, waypointThatGotRemoved)); - } - - @Override - public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) { - super.waypointAdded(zeroBasedIndex, waypointThatGotAdded); - notifyListenersWaypointAdded(zeroBasedIndex, waypointThatGotAdded); - } - - @Override - public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) { - super.waypointRemoved(zeroBasedIndex, waypointThatGotRemoved); - notifyListenersWaypointRemoved(zeroBasedIndex, waypointThatGotRemoved); - } - - private void notifyListeners(GPSFix fix, Mark mark, boolean firstInTrack) { - notifyListeners(listener -> listener.markPositionChanged(fix, mark, firstInTrack)); - } - - private void notifyListeners(GPSFixMoving fix, Competitor competitor) { - notifyListeners(listener -> listener.competitorPositionChanged(fix, competitor)); - } - - private void notifyListeners(TrackedRaceStatus status, TrackedRaceStatus oldStatus) { - notifyListeners(listener -> listener.statusChanged(status, oldStatus)); - } - - private void notifyListeners(Wind wind, WindSource windSource) { - notifyListeners(listener -> listener.windDataReceived(wind, windSource)); - } - - private void notifyListenersSpeedAveragingChanged(long oldMillisecondsOverWhichToAverageSpeed, long newMillisecondsOverWhichToAverageSpeed) { - notifyListeners(listener -> listener.speedAveragingChanged(oldMillisecondsOverWhichToAverageSpeed, newMillisecondsOverWhichToAverageSpeed)); - } - - private void notifyListenersWindAveragingChanged(long oldMillisecondsOverWhichToAverageWind, long newMillisecondsOverWhichToAverageWind) { - notifyListeners(listener -> listener.windAveragingChanged(oldMillisecondsOverWhichToAverageWind, newMillisecondsOverWhichToAverageWind)); - } - - private void notifyListenersDelayToLiveChanged(long delayToLiveInMillis) { - notifyListeners(listener -> listener.delayToLiveChanged(delayToLiveInMillis)); - } - - private void notifyListenersWindRemoved(Wind wind, WindSource windSource) { - notifyListeners(listener -> listener.windDataRemoved(wind, windSource)); - } - - private void notifyListeners(Competitor competitor, Map oldMarkPassings, Iterable markPassings) { - notifyListeners(listener -> listener.markPassingReceived(competitor, oldMarkPassings, markPassings)); - } - - @Override - public void updateMarkPassings(Competitor competitor, Iterable markPassings) { - LockUtil.lockForRead(getSerializationLock()); // keep serializer from reading the mark passings collections - try { - Map oldMarkPassings = new HashMap(); - MarkPassing oldStartMarkPassing = null; - TimePoint oldStartOfRace = getStartOfRace(); // getStartOfRace() may respond with a new result already after - // updating the mark passings - boolean requiresStartTimeUpdate = true; - final NavigableSet markPassingsForCompetitor = getMarkPassings(competitor); - lockForRead(markPassingsForCompetitor); - try { - for (MarkPassing oldMarkPassing : markPassingsForCompetitor) { - if (oldStartMarkPassing == null) { - oldStartMarkPassing = oldMarkPassing; - } - oldMarkPassings.put(oldMarkPassing.getWaypoint(), oldMarkPassing); - } - } finally { - unlockAfterRead(markPassingsForCompetitor); - } - final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassingsForCompetitor); - TimePoint timePointOfLatestEvent = new MillisecondsTimePoint(0); - // Make sure that clearMarkPassings and the re-adding of the mark passings are non-interruptible by readers. - // Note that the write lock for the mark passings in order per waypoint is obtained inside - // clearMarkPassings(...) as well as inside the subsequent for-loop. It is important to always first obtain the mark passings lock - // for the competitor mark passings before obtaining the lock for the mark passings in order for the waypoint to avoid - // deadlocks. - LockUtil.lockForWrite(markPassingsLock); - try { - clearMarkPassings(competitor); - for (MarkPassing markPassing : markPassings) { - // try to find corresponding old start mark passing - if (oldStartMarkPassing != null - && markPassing.getWaypoint().equals(oldStartMarkPassing.getWaypoint())) { - if (markPassing.getTimePoint() != null && oldStartMarkPassing.getTimePoint() != null - && markPassing.getTimePoint().equals(oldStartMarkPassing.getTimePoint())) { - requiresStartTimeUpdate = false; - } - } - if (!Util.contains(getRace().getCourse().getWaypoints(), markPassing.getWaypoint())) { - StringBuilder courseWaypointsWithID = new StringBuilder(); - boolean first = true; - for (Waypoint courseWaypoint : getRace().getCourse().getWaypoints()) { - if (first) { - first = false; - } else { - courseWaypointsWithID.append(" -> "); - } - courseWaypointsWithID.append(courseWaypoint.toString()); - courseWaypointsWithID.append(" (ID="); - courseWaypointsWithID.append(courseWaypoint.getId()); - courseWaypointsWithID.append(")"); - } - logger.severe("Received mark passing " + markPassing + " for race " + getRace() - + " for waypoint ID" + markPassing.getWaypoint().getId() - + " but the waypoint does not exist in course " + courseWaypointsWithID); - } else { - markPassingsForCompetitor.add(markPassing); - } - Collection markPassingsInOrderForWaypoint = getOrCreateMarkPassingsInOrderAsNavigableSet(markPassing - .getWaypoint()); - final NamedReentrantReadWriteLock markPassingsLock2 = getMarkPassingsLock(markPassingsInOrderForWaypoint); - LockUtil.lockForWrite(markPassingsLock2); - try { - // The mark passings of competitor have been removed by the call to - // clearMarkPassings(competitor) above - // from both, the collection that holds the mark passings by waypoint and the one that holds the - // mark passings per competitor; so we can simply add here: - markPassingsInOrderForWaypoint.add(markPassing); - } finally { - LockUtil.unlockAfterWrite(markPassingsLock2); - } - if (markPassing.getTimePoint().compareTo(timePointOfLatestEvent) > 0) { - timePointOfLatestEvent = markPassing.getTimePoint(); - } - } - } finally { - LockUtil.unlockAfterWrite(markPassingsLock); - } - updated(timePointOfLatestEvent); - triggerManeuverCacheRecalculation(competitor); - // update the race times like start, end and the leg times - if (requiresStartTimeUpdate) { - TimePoint interimsStartOfRace = getStartOfRace(); - invalidateStartTime(); - TimePoint newStartOfRace = getStartOfRace(); - if (Util.equalsWithNull(interimsStartOfRace, newStartOfRace) - && !Util.equalsWithNull(oldStartOfRace, newStartOfRace)) { - // invalidateStartTime() will not have thrown a startOfRaceChanged event notification because it - // already saw the new - // start of race time; we have to throw the notification here: - notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace); - } - } - invalidateMarkPassingTimes(); - invalidateEndTime(); - // notify *after* all mark passings have been re-established; should avoid flicker - notifyListeners(competitor, oldMarkPassings, markPassings); - } finally { - LockUtil.unlockAfterRead(getSerializationLock()); - } - } - - @Override - public Iterable getMarkPassingsInOrder(Waypoint waypoint) { - return (NavigableSet) super.getMarkPassingsInOrder(waypoint); - } - - @Override - public void lockForRead(Iterable markPassings) { - getRace().getCourse().lockForRead(); - LockUtil.lockForRead(getMarkPassingsLock(markPassings)); - } - - @Override - public void unlockAfterRead(Iterable markPassings) { - LockUtil.unlockAfterRead(getMarkPassingsLock(markPassings)); - getRace().getCourse().unlockAfterRead(); - } - - /** - * Removes all mark passings of competitor from both, the {@link #markPassingsForCompetitor} - * and the {@link #markPassingsForWaypoint} collections. - */ - private void clearMarkPassings(Competitor competitor) { - NavigableSet markPassings = getMarkPassings(competitor); - final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassings); - LockUtil.lockForWrite(markPassingsLock); - try { - Iterator mpIter = markPassings.iterator(); - while (mpIter.hasNext()) { - MarkPassing mp = mpIter.next(); - mpIter.remove(); - Collection markPassingsInOrder = getMarkPassingsInOrderAsNavigableSet(mp.getWaypoint()); - LockUtil.lockForWrite(getMarkPassingsLock(markPassingsInOrder)); - try { - markPassingsInOrder.remove(mp); - } finally { - LockUtil.unlockAfterWrite(getMarkPassingsLock(markPassingsInOrder)); - } - } - } finally { - LockUtil.unlockAfterWrite(markPassingsLock); - } - } - - @Override - public void setStartTimeReceived(TimePoint startTimeReceived) { - if (!Util.equalsWithNull(startTimeReceived, getStartTimeReceived())) { - TimePoint oldStartOfRace = getStartOfRace(); - super.setStartTimeReceived(startTimeReceived); - notifyListenersStartTimeReceivedChanged(getStartTimeReceived()); - TimePoint newStartOfRace = getStartOfRace(); - if (!Util.equalsWithNull(oldStartOfRace, newStartOfRace)) { - notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace); - } - } - } - - @Override - public void setStartOfTrackingReceived(TimePoint startOfTrackingReceived) { - if (!Util.equalsWithNull(startOfTrackingReceived, getStartOfTracking())) { - super.setStartOfTrackingReceived(startOfTrackingReceived); - notifyListenersStartOfTrackingChanged(getStartOfTracking()); - } - } - - @Override - public void setEndOfTrackingReceived(TimePoint endOfTrackingReceived) { - if (!Util.equalsWithNull(endOfTrackingReceived, getEndOfTracking())) { - super.setEndOfTrackingReceived(endOfTrackingReceived); - notifyListenersEndOfTrackingChanged(getEndOfTracking()); - } - } - - /** - * In addition to calling the superclass implementation, for a stored wind track whose fixes were loaded by this - * call, all listeners are notified about these existing wind fixes using their - * {@link RaceChangeListener#windDataReceived(Wind, WindSource)} callback method. In particular this replicates all - * wind fixes that may have been loaded from the wind store for the new track. - */ - @Override - protected WindTrack createWindTrack(WindSource windSource, long delayForWindEstimationCacheInvalidation) { - WindTrack result = super.createWindTrack(windSource, delayForWindEstimationCacheInvalidation); - if (windSource.getType().canBeStored()) { - // replicate all wind fixes that may have been loaded by the wind store - result.lockForRead(); - try { - for (Wind wind : result.getRawFixes()) { - notifyListeners(wind, windSource); // Note that this doesn't notify the track's listeners but the tracked race's listeners. - } // In particular, the wind store won't receive events (again) for the wind fixes it already has. - } finally { - result.unlockAfterRead(); - } - } - return result; - } - - @Override - public boolean recordWind(Wind wind, WindSource windSource) { - final boolean result; - // 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(); - TimePoint endOfRace = getEndOfRace(); - TimePoint endOfTracking = getEndOfTracking(); - if ((startOfTracking == null || !startOfTracking.minus(TrackedRaceImpl.TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).after(wind.getTimePoint()) || - (startOfRace != null && !startOfRace.minus(TrackedRaceImpl.TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).after(wind.getTimePoint()))) - && - (endOfTracking == null || endOfTracking.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).after(wind.getTimePoint()) || - (endOfRace != null && endOfRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).after(wind.getTimePoint())))) { - result = getOrCreateWindTrack(windSource).add(wind); - updated(/* time point */null); // wind events shouldn't advance race time - triggerManeuverCacheRecalculationForAllCompetitors(); - notifyListeners(wind, windSource); - } else { - result = false; - } - return result; - } - - @Override - public void removeWind(Wind wind, WindSource windSource) { - getOrCreateWindTrack(windSource).remove(wind); - updated(/* time point */null); // wind events shouldn't advance race time - triggerManeuverCacheRecalculationForAllCompetitors(); - notifyListenersWindRemoved(wind, windSource); - } - - @Override - public void gpsFixReceived(GPSFixMoving fix, Competitor competitor, boolean firstFixInTrack) { - updated(fix.getTimePoint()); - triggerManeuverCacheRecalculation(competitor); - notifyListeners(fix, competitor); - } - - @Override - public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) { - notifyListenersSpeedAveragingChanged(oldMillisecondsOverWhichToAverage, newMillisecondsOverWhichToAverage); - } - - @Override - public boolean isTransient() { - return false; - } - - @Override - protected TrackedLeg createTrackedLeg(Leg leg) { - return new TrackedLegImpl(this, leg, getRace().getCompetitors()); - } - - @Override - public long getMillisecondsOverWhichToAverageSpeed() { - long result = 0; // default in case there is no competitor - Iterator compIter = getRace().getCompetitors().iterator(); - if (compIter.hasNext()) { - DynamicGPSFixTrack someTrack = getTrack(compIter.next()); - result = someTrack.getMillisecondsOverWhichToAverageSpeed(); - } - return result; - } - - @Override - public long getMillisecondsOverWhichToAverageWind() { - long result = 0; // default in case there is no competitor - for (WindSource windSource : getWindSources()) { - WindTrack someTrack = getOrCreateWindTrack(windSource); - result = someTrack.getMillisecondsOverWhichToAverageWind(); - } - return result; - } - - @Override - public DynamicTrackedRegatta getTrackedRegatta() { - return (DynamicTrackedRegatta) super.getTrackedRegatta(); - } - - @Override - public void setRaceIsKnownToStartUpwind(boolean raceIsKnownToStartUpwind) { - this.raceIsKnownToStartUpwind = raceIsKnownToStartUpwind; - } - - @Override - public boolean raceIsKnownToStartUpwind() { - return raceIsKnownToStartUpwind; - } - - @Override - public void attachRaceLog(RaceLog raceLog) { - super.attachRaceLog(raceLog); - logListener.addTo(raceLog); - } - - @Override - public void detachRaceLog(Serializable identifier) { - RaceLog attachedRaceLog = attachedRaceLogs.get(identifier); - if (attachedRaceLog != null) { - logListener.removeFrom(attachedRaceLog); - } - super.detachRaceLog(identifier); - } - - @Override - public void addCourseDesignChangedListener(CourseDesignChangedListener listener) { - this.courseDesignChangedListeners.add(listener); - } - - @Override - public void onCourseDesignChangedByRaceCommittee(CourseBase newCourseDesign) { - try { - for (CourseDesignChangedListener courseDesignChangedListener : courseDesignChangedListeners) { - courseDesignChangedListener.courseDesignChanged(newCourseDesign); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - @Override - public void onStartTimeChangedByRaceCommittee(TimePoint newStartTime) { - logger.info("Start time of race "+getRace().getName()+" updated by race committee to "+newStartTime); - try { - for (StartTimeChangedListener startTimeChangedListener : startTimeChangedListeners) { - startTimeChangedListener.startTimeChanged(newStartTime); - } - } catch (IOException e) { - logger.log(Level.INFO, "Exception trying to notify race status change listeners about start time change", e); - } - } - - @Override - public void onAbortedByRaceCommittee(Flags flag) { - try { - for (RaceAbortedListener raceAbortedListener : raceAbortedListeners) { - raceAbortedListener.raceAborted(flag); - } - } catch (IOException e) { - logger.log(Level.INFO, "Exception trying to notify race status change listeners about start time change", e); - } - } - - @Override - public void addStartTimeChangedListener(StartTimeChangedListener listener) { - this.startTimeChangedListeners.add(listener); - } - - @Override - public void addRaceAbortedListener(RaceAbortedListener listener) { - this.raceAbortedListeners.add(listener); - } - - /** - * @return The Bearing of a line starting at w that needs to be crossed to pass a mark. - */ - @Override - public Bearing getCrossingBearing(Waypoint w, TimePoint t) { - Bearing result = null; - PassingInstruction instruction = w.getPassingInstructions(); - if (instruction == PassingInstruction.None || instruction == null) { - if (w.equals(getRace().getCourse().getFirstWaypoint()) || w.equals(getRace().getCourse().getLastWaypoint())) { - instruction = PassingInstruction.Line; - } else { - int numberofMarks = 0; - Iterator it = w.getMarks().iterator(); - while (it.hasNext()) { - it.next(); - numberofMarks++; - } - if (numberofMarks == 2) { - instruction = PassingInstruction.Gate; - } else if (numberofMarks == 1) { - instruction = PassingInstruction.Port; - } - } - } - if (instruction == PassingInstruction.FixedBearing) { - result = w.getFixedBearing(); - } else if (instruction == PassingInstruction.Gate || instruction == PassingInstruction.Port || instruction == PassingInstruction.Starboard) { - Bearing before = getTrackedLegFinishingAt(w).getLegBearing(t); - Bearing after = getTrackedLegStartingAt(w).getLegBearing(t); - if (before != null && after != null) { - result = before.middle(after.reverse()); - } - } else if (instruction == PassingInstruction.Line) { - com.sap.sse.common.Util.Pair pos = getPortAndStarboardMarks(t, w); - if (pos.getA() != null && pos.getB() != null) { - Position portPosition = getOrCreateTrack(pos.getA()).getEstimatedPosition(t, false); - Position starboardPosition = getOrCreateTrack(pos.getB()).getEstimatedPosition(t, false); - if (portPosition != null && starboardPosition != null) { - result = portPosition.getBearingGreatCircle(starboardPosition); - } - } - } else if (instruction == PassingInstruction.Offset) { - // TODO Bug 1712 - } - return result; - } - @Override - public com.sap.sse.common.Util.Pair getPortAndStarboardMarks(TimePoint t, Waypoint w) { - List markPositions = new ArrayList(); - for (Mark lineMark : w.getMarks()) { - final Position estimatedMarkPosition = getOrCreateTrack(lineMark).getEstimatedPosition(t, /* extrapolate */ - false); - if (estimatedMarkPosition == null) { - return new com.sap.sse.common.Util.Pair(null,null); - } - markPositions.add(estimatedMarkPosition); - } - final List legs = getRace().getCourse().getLegs(); - final int indexOfWaypoint = getRace().getCourse().getIndexOfWaypoint(w); - final boolean isStartLine = indexOfWaypoint == 0; - final Bearing legDeterminingDirectionBearing = getTrackedLeg(legs.get(isStartLine ? 0 : indexOfWaypoint - 1)).getLegBearing(t); - if (legDeterminingDirectionBearing == null) { - return new com.sap.sse.common.Util.Pair(null, null); - } - Distance crossTrackErrorOfMark0OnLineFromMark1ToNextWaypoint = markPositions.get(0).crossTrackError(markPositions.get(1), legDeterminingDirectionBearing); - final Mark starboardMarkWhileApproachingLine; - final Mark portMarkWhileApproachingLine; - if (crossTrackErrorOfMark0OnLineFromMark1ToNextWaypoint.getMeters() < 0) { - portMarkWhileApproachingLine = Util.get(w.getMarks(), 0); - starboardMarkWhileApproachingLine = Util.get(w.getMarks(), 1); - } else { - portMarkWhileApproachingLine = Util.get(w.getMarks(), 1); - starboardMarkWhileApproachingLine = Util.get(w.getMarks(), 0); - } - return new com.sap.sse.common.Util.Pair(portMarkWhileApproachingLine, starboardMarkWhileApproachingLine); - } - - @Override - public DynamicGPSFixTrack getTrack(Mark mark) { - return (DynamicGPSFixTrack) super.getTrack(mark); - } -} +package com.sap.sailing.domain.tracking.impl; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.Serializable; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.NavigableSet; +import java.util.Set; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.sap.sailing.domain.abstractlog.race.RaceLog; +import com.sap.sailing.domain.base.BoatClass; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.domain.base.CourseBase; +import com.sap.sailing.domain.base.Leg; +import com.sap.sailing.domain.base.Mark; +import com.sap.sailing.domain.base.RaceDefinition; +import com.sap.sailing.domain.base.Sideline; +import com.sap.sailing.domain.base.Waypoint; +import com.sap.sailing.domain.common.TimingConstants; +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.racelog.Flags; +import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator; +import com.sap.sailing.domain.racelog.tracking.GPSFixStore; +import com.sap.sailing.domain.tracking.CourseDesignChangedListener; +import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; +import com.sap.sailing.domain.tracking.DynamicTrackedRace; +import com.sap.sailing.domain.tracking.DynamicTrackedRegatta; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.domain.tracking.GPSFixMoving; +import com.sap.sailing.domain.tracking.GPSFixTrack; +import com.sap.sailing.domain.tracking.GPSTrackListener; +import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.RaceAbortedListener; +import com.sap.sailing.domain.tracking.RaceChangeListener; +import com.sap.sailing.domain.tracking.StartTimeChangedListener; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedRaceStatus; +import com.sap.sailing.domain.tracking.TrackedRegatta; +import com.sap.sailing.domain.tracking.Wind; +import com.sap.sailing.domain.tracking.WindStore; +import com.sap.sailing.domain.tracking.WindTrack; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util; +import com.sap.sse.common.impl.MillisecondsTimePoint; +import com.sap.sse.concurrent.LockUtil; +import com.sap.sse.concurrent.NamedReentrantReadWriteLock; + +public class DynamicTrackedRaceImpl extends TrackedRaceImpl implements +DynamicTrackedRace, GPSTrackListener { + private static final long serialVersionUID = 1092726918239676958L; + + private static final Logger logger = Logger.getLogger(DynamicTrackedRaceImpl.class.getName()); + + private transient Set listeners; + + private boolean raceIsKnownToStartUpwind; + + private boolean delayToLiveInMillisFixed; + + private transient DynamicTrackedRaceLogListener logListener; + + private transient Set courseDesignChangedListeners; + private transient Set startTimeChangedListeners; + private transient Set raceAbortedListeners; + + public DynamicTrackedRaceImpl(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable sidelines, + WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, + long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, + long delayForCacheInvalidationOfWindEstimation, boolean useInternalMarkPassingAlgorithm) { + super(trackedRegatta, race, sidelines, windStore, gpsFixStore, delayToLiveInMillis, + millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, + delayForCacheInvalidationOfWindEstimation, useInternalMarkPassingAlgorithm); + this.logListener = new DynamicTrackedRaceLogListener(this); + if (markPassingCalculator != null) { + logListener.setMarkPassingUpdateListener(markPassingCalculator.getListener()); + } + this.courseDesignChangedListeners = new HashSet<>(); + this.startTimeChangedListeners = new HashSet<>(); + this.raceAbortedListeners = new HashSet<>(); + this.raceIsKnownToStartUpwind = race.getBoatClass().typicallyStartsUpwind(); + if (markPassingCalculator != null) { + logListener.setMarkPassingUpdateListener(markPassingCalculator.getListener()); + } + if (!raceIsKnownToStartUpwind) { + Set windSourcesToExclude = new HashSet(); + for (WindSource windSourceToExclude : getWindSourcesToExclude()) { + windSourcesToExclude.add(windSourceToExclude); + } + windSourcesToExclude.add(new WindSourceImpl(WindSourceType.COURSE_BASED)); + setWindSourcesToExclude(windSourcesToExclude); + } + for (Competitor competitor : getRace().getCompetitors()) { + DynamicGPSFixTrack track = getTrack(competitor); + track.addListener(this); + } + // default wind tracks are observed because they are created by the superclass constructor using + // createWindTrack which adds this object as a listener + } + + /** + * After de-serialization sets a valid {@link #listeners} collection which is transient and therefore + * hasn't been serialized. + */ + private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { + ois.defaultReadObject(); + listeners = new HashSet(); + logListener = new DynamicTrackedRaceLogListener(this); + courseDesignChangedListeners = new HashSet<>(); + startTimeChangedListeners = new HashSet<>(); + raceAbortedListeners = new HashSet<>(); + } + + /** + * {@link #raceIsKnownToStartUpwind} (see also {@link #raceIsKnownToStartUpwind()}) is initialized based on the race's + * {@link RaceDefinition#getBoatClass()} boat class's {@link BoatClass#typicallyStartsUpwind()} result. It can be changed + * using {@link #setRaceIsKnownToStartUpwind(boolean)}. Uses millisecondsOverWhichToAverageWind/2 for the + * delayForCacheInvalidationOfWindEstimation argument of the constructor.

+ * + * Loading wind tracks from the windStore happens asynchronously which means that when the constructor returns, + * the caller cannot assume that all wind tracks have yet been loaded completely. The caller may call {@link #waitUntilLoadingFromWindStoreComplete()} + * to wait until all persistent wind sources have been successfully and completely loaded. + */ + public DynamicTrackedRaceImpl(TrackedRegatta trackedRegatta, RaceDefinition race, Iterable sidelines, + WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, + long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, boolean useInternalMarkPassingAlgorithm) { + this(trackedRegatta, race, sidelines, windStore, gpsFixStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, + millisecondsOverWhichToAverageWind/2, useInternalMarkPassingAlgorithm); + } + + @Override + public void recordFix(Competitor competitor, GPSFixMoving fix) { + DynamicGPSFixTrack track = getTrack(competitor); + if (track != null) { + if (logger != null && logger.getLevel() != null && logger.getLevel().equals(Level.FINEST)) { + logger.finest(""+competitor.getName() + ": " + fix); + } + track.addGPSFix(fix); // the track notifies this tracked race which in turn notifies its listeners + } + } + + @Override + public void setStatus(TrackedRaceStatus newStatus) { + TrackedRaceStatus oldStatus = getStatus(); + super.setStatus(newStatus); + notifyListeners(newStatus, oldStatus); + } + + @Override + public void recordFix(Mark mark, GPSFix fix) { + if ((getStartOfTracking() == null || getStartOfTracking().compareTo(fix.getTimePoint()) <= 0) && + (getEndOfTracking() == null || getEndOfTracking().compareTo(fix.getTimePoint()) >= 0)) { + getOrCreateTrack(mark).addGPSFix(fix); + } + } + + @Override + public void setMillisecondsOverWhichToAverageSpeed(long millisecondsOverWhichToAverageSpeed) { + this.millisecondsOverWhichToAverageSpeed = millisecondsOverWhichToAverageSpeed; + for (Competitor competitor : getRace().getCompetitors()) { + getTrack(competitor).setMillisecondsOverWhichToAverage(millisecondsOverWhichToAverageSpeed); + } + for (Waypoint waypoint : getRace().getCourse().getWaypoints()) { + for (Mark mark : waypoint.getMarks()) { + getOrCreateTrack(mark).setMillisecondsOverWhichToAverage(millisecondsOverWhichToAverageSpeed); + } + } + updated(/* time point */null); + triggerManeuverCacheRecalculationForAllCompetitors(); + } + + @Override + public void setMillisecondsOverWhichToAverageWind(long millisecondsOverWhichToAverageWind) { + long oldMillisecondsOverWhichToAverageWind = this.millisecondsOverWhichToAverageWind; + this.millisecondsOverWhichToAverageWind = millisecondsOverWhichToAverageWind; + for (WindSource windSource : getWindSources()) { + getOrCreateWindTrack(windSource).setMillisecondsOverWhichToAverage(millisecondsOverWhichToAverageWind); + } + updated(/* time point */null); + triggerManeuverCacheRecalculationForAllCompetitors(); + notifyListenersWindAveragingChanged(oldMillisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageWind); + } + + + @Override + public void setAndFixDelayToLiveInMillis(long delayToLiveInMillis) { + if (getDelayToLiveInMillis() != delayToLiveInMillis) { + super.setDelayToLiveInMillis(delayToLiveInMillis); + notifyListenersDelayToLiveChanged(delayToLiveInMillis); + } + delayToLiveInMillisFixed = true; + } + + @Override + public void setDelayToLiveInMillis(long delayToLiveInMillis) { + if (!delayToLiveInMillisFixed && getDelayToLiveInMillis() != delayToLiveInMillis) { + super.setDelayToLiveInMillis(delayToLiveInMillis); + notifyListenersDelayToLiveChanged(delayToLiveInMillis); + } + } + + @Override + public DynamicGPSFixTrack getTrack(Competitor competitor) { + return (DynamicGPSFixTrack) super.getTrack(competitor); + } + + @Override + public DynamicGPSFixTrack getOrCreateTrack(Mark mark) { + return (DynamicGPSFixTrack) super.getOrCreateTrack(mark); + } + + /** + * In addition to creating the track which is performed by the superclass implementation, this implementation registers + * a {@link GPSTrackListener} with the mark's track and {@link #notifyListeners(GPSFix, Mark, boolean) notifies the listeners} + * about updates. The {@link #updated(TimePoint)} method is not called with the mark fix's time point because + * mark fixes may be received also from marks that don't belong to this race. + */ + @Override + protected DynamicGPSFixTrackImpl createMarkTrack(Mark mark) { + final DynamicGPSFixTrackImpl result = super.createMarkTrack(mark); + result.addListener(new GPSTrackListener() { + private static final long serialVersionUID = -2855787105725103732L; + + @Override + public void gpsFixReceived(GPSFix fix, Mark mark, boolean firstFixInTrack) { + triggerManeuverCacheRecalculationForAllCompetitors(); + notifyListeners(fix, mark, firstFixInTrack); + } + + @Override + public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, + long newMillisecondsOverWhichToAverage) { + // nobody can currently listen for the change of the mark speed averaging because mark speed is not a value used + } + + @Override + public boolean isTransient() { + return false; + } + }); + return result; + } + + /** + * Callers iterating over the result need to synchronize on the resulting collection while iterating + * to avoid {@link ConcurrentModificationException}s. + */ + private Set getListeners() { + if (listeners == null) { + listeners = new HashSet(); + } + return listeners; + } + + @Override + public void addListener(RaceChangeListener listener) { + synchronized (getListeners()) { + getListeners().add(listener); + } + } + + @Override + public void invalidateStartTime() { + TimePoint oldStartOfRace = getStartOfRace(); + super.invalidateStartTime(); + TimePoint newStartOfRace = getStartOfRace(); + if (!Util.equalsWithNull(oldStartOfRace, newStartOfRace)) { + notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace); + } + } + + @Override + public void addListener(RaceChangeListener listener, final boolean notifyAboutWindFixesAlreadyLoaded, + final boolean notifyAboutGPSFixesAlreadyLoaded) { + if (notifyAboutWindFixesAlreadyLoaded) { + LockUtil.lockForRead(getLoadingFromWindStoreLock()); + } + if (notifyAboutGPSFixesAlreadyLoaded) { + LockUtil.lockForRead(getLoadingFromGPSFixStoreLock()); + } + try { + addListener(listener); + if (notifyAboutWindFixesAlreadyLoaded) { + // Now notify all wind fixes we can get from the race by now. TrackedRace.getWindSource() delivers all wind + // sources known so far. If there is a wind track being loaded, it will be separately notified later by + // DynamicTrackedRaceImpl.createWindTrack(...). + // Holding the serialization lock + for (WindSource windSource : getWindSources()) { + if (windSource.getType().canBeStored()) { + WindTrack windTrack = getOrCreateWindTrack(windSource); + // replicate all wind fixes that may have been loaded by the wind store + windTrack.lockForRead(); + try { + for (Wind wind : windTrack.getRawFixes()) { + listener.windDataReceived(wind, windSource); + } + } finally { + windTrack.unlockAfterRead(); + } + } + } + } + if (notifyAboutGPSFixesAlreadyLoaded) { + for (Mark mark : getMarks()) { + GPSFixTrack markTrack = getOrCreateTrack(mark); + markTrack.lockForRead(); + try { + boolean firstInTrack = true; + for (GPSFix fix : markTrack.getRawFixes()) { + listener.markPositionChanged(fix, mark, firstInTrack); + firstInTrack = false; + } + } finally { + markTrack.unlockAfterRead(); + } + } + + for (Competitor competitor : getRace().getCompetitors()) { + GPSFixTrack competitorTrack = getTrack(competitor); + competitorTrack.lockForRead(); + try { + for (GPSFixMoving fix : competitorTrack.getRawFixes()) { + listener.competitorPositionChanged(fix, competitor); + } + } finally { + competitorTrack.unlockAfterRead(); + } + } + } + } finally { + if (notifyAboutWindFixesAlreadyLoaded) { + LockUtil.unlockAfterRead(getLoadingFromWindStoreLock()); + } + if (notifyAboutGPSFixesAlreadyLoaded) { + LockUtil.unlockAfterRead(getLoadingFromGPSFixStoreLock()); + } + } + } + + @Override + public void removeListener(RaceChangeListener listener) { + synchronized (getListeners()) { + getListeners().remove(listener); + } + } + + @Override + public void setWindSourcesToExclude(Iterable windSourcesToExclude) { + super.setWindSourcesToExclude(windSourcesToExclude); + notifyListenersWindSourcesToExcludeChanged(windSourcesToExclude); + } + + private void notifyListeners(Consumer notifyAction) { + RaceChangeListener[] listeners; + synchronized (getListeners()) { + listeners = getListeners().toArray(new RaceChangeListener[getListeners().size()]); + } + for (RaceChangeListener listener : listeners) { + try { + notifyAction.accept(listener); + } catch (Exception e) { + logger.log(Level.SEVERE, "RaceChangeListener " + listener + " threw exception " + e.getMessage()); + logger.log(Level.SEVERE, "notifyListeners(Consumer notifyAction", e); + } + } + } + + private void notifyListenersWindSourcesToExcludeChanged(Iterable windSourcesToExclude) { + notifyListeners(listener -> listener.windSourcesToExcludeChanged(windSourcesToExclude)); + } + + private void notifyListenersStartOfTrackingChanged(TimePoint startOfTracking) { + notifyListeners(listener -> listener.startOfTrackingChanged(startOfTracking)); + } + + private void notifyListenersEndOfTrackingChanged(TimePoint endOfTracking) { + notifyListeners(listener -> listener.endOfTrackingChanged(endOfTracking)); + } + + private void notifyListenersStartTimeReceivedChanged(TimePoint startTimeReceived) { + notifyListeners(listener -> listener.startTimeReceivedChanged(startTimeReceived)); + } + + private void notifyListenersStartOfRaceChanged(TimePoint oldStartOfRace, TimePoint newStartOfRace) { + notifyListeners(listener -> listener.startOfRaceChanged(oldStartOfRace, newStartOfRace)); + } + + private void notifyListenersWaypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) { + notifyListeners(listener -> listener.waypointAdded(zeroBasedIndex, waypointThatGotAdded)); + } + + private void notifyListenersWaypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) { + notifyListeners(listener -> listener.waypointRemoved(zeroBasedIndex, waypointThatGotRemoved)); + } + + @Override + public void waypointAdded(int zeroBasedIndex, Waypoint waypointThatGotAdded) { + super.waypointAdded(zeroBasedIndex, waypointThatGotAdded); + notifyListenersWaypointAdded(zeroBasedIndex, waypointThatGotAdded); + } + + @Override + public void waypointRemoved(int zeroBasedIndex, Waypoint waypointThatGotRemoved) { + super.waypointRemoved(zeroBasedIndex, waypointThatGotRemoved); + notifyListenersWaypointRemoved(zeroBasedIndex, waypointThatGotRemoved); + } + + private void notifyListeners(GPSFix fix, Mark mark, boolean firstInTrack) { + notifyListeners(listener -> listener.markPositionChanged(fix, mark, firstInTrack)); + } + + private void notifyListeners(GPSFixMoving fix, Competitor competitor) { + notifyListeners(listener -> listener.competitorPositionChanged(fix, competitor)); + } + + private void notifyListeners(TrackedRaceStatus status, TrackedRaceStatus oldStatus) { + notifyListeners(listener -> listener.statusChanged(status, oldStatus)); + } + + private void notifyListeners(Wind wind, WindSource windSource) { + notifyListeners(listener -> listener.windDataReceived(wind, windSource)); + } + + private void notifyListenersSpeedAveragingChanged(long oldMillisecondsOverWhichToAverageSpeed, long newMillisecondsOverWhichToAverageSpeed) { + notifyListeners(listener -> listener.speedAveragingChanged(oldMillisecondsOverWhichToAverageSpeed, newMillisecondsOverWhichToAverageSpeed)); + } + + private void notifyListenersWindAveragingChanged(long oldMillisecondsOverWhichToAverageWind, long newMillisecondsOverWhichToAverageWind) { + notifyListeners(listener -> listener.windAveragingChanged(oldMillisecondsOverWhichToAverageWind, newMillisecondsOverWhichToAverageWind)); + } + + private void notifyListenersDelayToLiveChanged(long delayToLiveInMillis) { + notifyListeners(listener -> listener.delayToLiveChanged(delayToLiveInMillis)); + } + + private void notifyListenersWindRemoved(Wind wind, WindSource windSource) { + notifyListeners(listener -> listener.windDataRemoved(wind, windSource)); + } + + private void notifyListeners(Competitor competitor, Map oldMarkPassings, Iterable markPassings) { + notifyListeners(listener -> listener.markPassingReceived(competitor, oldMarkPassings, markPassings)); + } + + @Override + public void updateMarkPassings(Competitor competitor, Iterable markPassings) { + LockUtil.lockForRead(getSerializationLock()); // keep serializer from reading the mark passings collections + try { + Map oldMarkPassings = new HashMap(); + MarkPassing oldStartMarkPassing = null; + TimePoint oldStartOfRace = getStartOfRace(); // getStartOfRace() may respond with a new result already after + // updating the mark passings + boolean requiresStartTimeUpdate = true; + final NavigableSet markPassingsForCompetitor = getMarkPassings(competitor); + lockForRead(markPassingsForCompetitor); + try { + for (MarkPassing oldMarkPassing : markPassingsForCompetitor) { + if (oldStartMarkPassing == null) { + oldStartMarkPassing = oldMarkPassing; + } + oldMarkPassings.put(oldMarkPassing.getWaypoint(), oldMarkPassing); + } + } finally { + unlockAfterRead(markPassingsForCompetitor); + } + final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassingsForCompetitor); + TimePoint timePointOfLatestEvent = new MillisecondsTimePoint(0); + // Make sure that clearMarkPassings and the re-adding of the mark passings are non-interruptible by readers. + // Note that the write lock for the mark passings in order per waypoint is obtained inside + // clearMarkPassings(...) as well as inside the subsequent for-loop. It is important to always first obtain the mark passings lock + // for the competitor mark passings before obtaining the lock for the mark passings in order for the waypoint to avoid + // deadlocks. + LockUtil.lockForWrite(markPassingsLock); + try { + clearMarkPassings(competitor); + for (MarkPassing markPassing : markPassings) { + // try to find corresponding old start mark passing + if (oldStartMarkPassing != null + && markPassing.getWaypoint().equals(oldStartMarkPassing.getWaypoint())) { + if (markPassing.getTimePoint() != null && oldStartMarkPassing.getTimePoint() != null + && markPassing.getTimePoint().equals(oldStartMarkPassing.getTimePoint())) { + requiresStartTimeUpdate = false; + } + } + if (!Util.contains(getRace().getCourse().getWaypoints(), markPassing.getWaypoint())) { + StringBuilder courseWaypointsWithID = new StringBuilder(); + boolean first = true; + for (Waypoint courseWaypoint : getRace().getCourse().getWaypoints()) { + if (first) { + first = false; + } else { + courseWaypointsWithID.append(" -> "); + } + courseWaypointsWithID.append(courseWaypoint.toString()); + courseWaypointsWithID.append(" (ID="); + courseWaypointsWithID.append(courseWaypoint.getId()); + courseWaypointsWithID.append(")"); + } + logger.severe("Received mark passing " + markPassing + " for race " + getRace() + + " for waypoint ID" + markPassing.getWaypoint().getId() + + " but the waypoint does not exist in course " + courseWaypointsWithID); + } else { + markPassingsForCompetitor.add(markPassing); + } + Collection markPassingsInOrderForWaypoint = getOrCreateMarkPassingsInOrderAsNavigableSet(markPassing + .getWaypoint()); + final NamedReentrantReadWriteLock markPassingsLock2 = getMarkPassingsLock(markPassingsInOrderForWaypoint); + LockUtil.lockForWrite(markPassingsLock2); + try { + // The mark passings of competitor have been removed by the call to + // clearMarkPassings(competitor) above + // from both, the collection that holds the mark passings by waypoint and the one that holds the + // mark passings per competitor; so we can simply add here: + markPassingsInOrderForWaypoint.add(markPassing); + } finally { + LockUtil.unlockAfterWrite(markPassingsLock2); + } + if (markPassing.getTimePoint().compareTo(timePointOfLatestEvent) > 0) { + timePointOfLatestEvent = markPassing.getTimePoint(); + } + } + } finally { + LockUtil.unlockAfterWrite(markPassingsLock); + } + updated(timePointOfLatestEvent); + triggerManeuverCacheRecalculation(competitor); + // update the race times like start, end and the leg times + if (requiresStartTimeUpdate) { + TimePoint interimsStartOfRace = getStartOfRace(); + invalidateStartTime(); + TimePoint newStartOfRace = getStartOfRace(); + if (Util.equalsWithNull(interimsStartOfRace, newStartOfRace) + && !Util.equalsWithNull(oldStartOfRace, newStartOfRace)) { + // invalidateStartTime() will not have thrown a startOfRaceChanged event notification because it + // already saw the new + // start of race time; we have to throw the notification here: + notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace); + } + } + invalidateMarkPassingTimes(); + invalidateEndTime(); + // notify *after* all mark passings have been re-established; should avoid flicker + notifyListeners(competitor, oldMarkPassings, markPassings); + } finally { + LockUtil.unlockAfterRead(getSerializationLock()); + } + } + + @Override + public Iterable getMarkPassingsInOrder(Waypoint waypoint) { + return (NavigableSet) super.getMarkPassingsInOrder(waypoint); + } + + @Override + public void lockForRead(Iterable markPassings) { + getRace().getCourse().lockForRead(); + LockUtil.lockForRead(getMarkPassingsLock(markPassings)); + } + + @Override + public void unlockAfterRead(Iterable markPassings) { + LockUtil.unlockAfterRead(getMarkPassingsLock(markPassings)); + getRace().getCourse().unlockAfterRead(); + } + + /** + * Removes all mark passings of competitor from both, the {@link #markPassingsForCompetitor} + * and the {@link #markPassingsForWaypoint} collections. + */ + private void clearMarkPassings(Competitor competitor) { + NavigableSet markPassings = getMarkPassings(competitor); + final NamedReentrantReadWriteLock markPassingsLock = getMarkPassingsLock(markPassings); + LockUtil.lockForWrite(markPassingsLock); + try { + Iterator mpIter = markPassings.iterator(); + while (mpIter.hasNext()) { + MarkPassing mp = mpIter.next(); + mpIter.remove(); + Collection markPassingsInOrder = getMarkPassingsInOrderAsNavigableSet(mp.getWaypoint()); + LockUtil.lockForWrite(getMarkPassingsLock(markPassingsInOrder)); + try { + markPassingsInOrder.remove(mp); + } finally { + LockUtil.unlockAfterWrite(getMarkPassingsLock(markPassingsInOrder)); + } + } + } finally { + LockUtil.unlockAfterWrite(markPassingsLock); + } + } + + @Override + public void setStartTimeReceived(TimePoint startTimeReceived) { + if (!Util.equalsWithNull(startTimeReceived, getStartTimeReceived())) { + TimePoint oldStartOfRace = getStartOfRace(); + super.setStartTimeReceived(startTimeReceived); + notifyListenersStartTimeReceivedChanged(getStartTimeReceived()); + TimePoint newStartOfRace = getStartOfRace(); + if (!Util.equalsWithNull(oldStartOfRace, newStartOfRace)) { + notifyListenersStartOfRaceChanged(oldStartOfRace, newStartOfRace); + } + } + } + + @Override + public void setStartOfTrackingReceived(TimePoint startOfTrackingReceived) { + if (!Util.equalsWithNull(startOfTrackingReceived, getStartOfTracking())) { + super.setStartOfTrackingReceived(startOfTrackingReceived); + notifyListenersStartOfTrackingChanged(getStartOfTracking()); + } + } + + @Override + public void setEndOfTrackingReceived(TimePoint endOfTrackingReceived) { + if (!Util.equalsWithNull(endOfTrackingReceived, getEndOfTracking())) { + super.setEndOfTrackingReceived(endOfTrackingReceived); + notifyListenersEndOfTrackingChanged(getEndOfTracking()); + } + } + + /** + * In addition to calling the superclass implementation, for a stored wind track whose fixes were loaded by this + * call, all listeners are notified about these existing wind fixes using their + * {@link RaceChangeListener#windDataReceived(Wind, WindSource)} callback method. In particular this replicates all + * wind fixes that may have been loaded from the wind store for the new track. + */ + @Override + protected WindTrack createWindTrack(WindSource windSource, long delayForWindEstimationCacheInvalidation) { + WindTrack result = super.createWindTrack(windSource, delayForWindEstimationCacheInvalidation); + if (windSource.getType().canBeStored()) { + // replicate all wind fixes that may have been loaded by the wind store + result.lockForRead(); + try { + for (Wind wind : result.getRawFixes()) { + notifyListeners(wind, windSource); // Note that this doesn't notify the track's listeners but the tracked race's listeners. + } // In particular, the wind store won't receive events (again) for the wind fixes it already has. + } finally { + result.unlockAfterRead(); + } + } + return result; + } + + @Override + public boolean recordWind(Wind wind, WindSource windSource) { + final boolean result; + // 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(); + TimePoint endOfRace = getEndOfRace(); + TimePoint endOfTracking = getEndOfTracking(); + if ((startOfTracking == null || !startOfTracking.minus(TrackedRaceImpl.TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).after(wind.getTimePoint()) || + (startOfRace != null && !startOfRace.minus(TrackedRaceImpl.TIME_BEFORE_START_TO_TRACK_WIND_MILLIS).after(wind.getTimePoint()))) + && + (endOfTracking == null || endOfTracking.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).after(wind.getTimePoint()) || + (endOfRace != null && endOfRace.plus(TimingConstants.IS_LIVE_GRACE_PERIOD_IN_MILLIS).after(wind.getTimePoint())))) { + result = getOrCreateWindTrack(windSource).add(wind); + updated(/* time point */null); // wind events shouldn't advance race time + triggerManeuverCacheRecalculationForAllCompetitors(); + notifyListeners(wind, windSource); + } else { + result = false; + } + return result; + } + + @Override + public void removeWind(Wind wind, WindSource windSource) { + getOrCreateWindTrack(windSource).remove(wind); + updated(/* time point */null); // wind events shouldn't advance race time + triggerManeuverCacheRecalculationForAllCompetitors(); + notifyListenersWindRemoved(wind, windSource); + } + + @Override + public void gpsFixReceived(GPSFixMoving fix, Competitor competitor, boolean firstFixInTrack) { + updated(fix.getTimePoint()); + triggerManeuverCacheRecalculation(competitor); + notifyListeners(fix, competitor); + } + + @Override + public void speedAveragingChanged(long oldMillisecondsOverWhichToAverage, long newMillisecondsOverWhichToAverage) { + notifyListenersSpeedAveragingChanged(oldMillisecondsOverWhichToAverage, newMillisecondsOverWhichToAverage); + } + + @Override + public boolean isTransient() { + return false; + } + + @Override + protected TrackedLeg createTrackedLeg(Leg leg) { + return new TrackedLegImpl(this, leg, getRace().getCompetitors()); + } + + @Override + public long getMillisecondsOverWhichToAverageSpeed() { + long result = 0; // default in case there is no competitor + Iterator compIter = getRace().getCompetitors().iterator(); + if (compIter.hasNext()) { + DynamicGPSFixTrack someTrack = getTrack(compIter.next()); + result = someTrack.getMillisecondsOverWhichToAverageSpeed(); + } + return result; + } + + @Override + public long getMillisecondsOverWhichToAverageWind() { + long result = 0; // default in case there is no competitor + for (WindSource windSource : getWindSources()) { + WindTrack someTrack = getOrCreateWindTrack(windSource); + result = someTrack.getMillisecondsOverWhichToAverageWind(); + } + return result; + } + + @Override + public DynamicTrackedRegatta getTrackedRegatta() { + return (DynamicTrackedRegatta) super.getTrackedRegatta(); + } + + @Override + public void setRaceIsKnownToStartUpwind(boolean raceIsKnownToStartUpwind) { + this.raceIsKnownToStartUpwind = raceIsKnownToStartUpwind; + } + + @Override + public boolean raceIsKnownToStartUpwind() { + return raceIsKnownToStartUpwind; + } + + @Override + public void attachRaceLog(RaceLog raceLog) { + super.attachRaceLog(raceLog); + logListener.addTo(raceLog); + } + + @Override + public void detachRaceLog(Serializable identifier) { + RaceLog attachedRaceLog = attachedRaceLogs.get(identifier); + if (attachedRaceLog != null) { + logListener.removeFrom(attachedRaceLog); + } + super.detachRaceLog(identifier); + } + + @Override + public void addCourseDesignChangedListener(CourseDesignChangedListener listener) { + this.courseDesignChangedListeners.add(listener); + } + + @Override + public void onCourseDesignChangedByRaceCommittee(CourseBase newCourseDesign) { + try { + for (CourseDesignChangedListener courseDesignChangedListener : courseDesignChangedListeners) { + courseDesignChangedListener.courseDesignChanged(newCourseDesign); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void onStartTimeChangedByRaceCommittee(TimePoint newStartTime) { + logger.info("Start time of race "+getRace().getName()+" updated by race committee to "+newStartTime); + try { + for (StartTimeChangedListener startTimeChangedListener : startTimeChangedListeners) { + startTimeChangedListener.startTimeChanged(newStartTime); + } + } catch (IOException e) { + logger.log(Level.INFO, "Exception trying to notify race status change listeners about start time change", e); + } + } + + @Override + public void onAbortedByRaceCommittee(Flags flag) { + try { + for (RaceAbortedListener raceAbortedListener : raceAbortedListeners) { + raceAbortedListener.raceAborted(flag); + } + } catch (IOException e) { + logger.log(Level.INFO, "Exception trying to notify race status change listeners about start time change", e); + } + } + + @Override + public void addStartTimeChangedListener(StartTimeChangedListener listener) { + this.startTimeChangedListeners.add(listener); + } + + @Override + public void addRaceAbortedListener(RaceAbortedListener listener) { + this.raceAbortedListeners.add(listener); + } + + @Override + protected MarkPassingCalculator createMarkPassingCalculator() { + return new MarkPassingCalculator(this, true); + } + + @Override + public DynamicGPSFixTrack getTrack(Mark mark) { + return (DynamicGPSFixTrack) super.getTrack(mark); + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java old mode 100644 new mode 100755 index cfd7b02e66f..83f51b27525 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRaceLogListener.java @@ -1,43 +1,58 @@ package com.sap.sailing.domain.tracking.impl; +import java.util.HashSet; +import java.util.Set; import java.util.logging.Logger; +import com.sap.sailing.domain.abstractlog.race.FixedMarkPassingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogPassChangeEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; +import com.sap.sailing.domain.abstractlog.race.SuppressedMarkPassingsEvent; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.AbortingFlagFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.LastPublishedCourseDesignFinder; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.MarkPassingDataFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.StartTimeFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.WindFixesFinder; import com.sap.sailing.domain.abstractlog.race.impl.BaseRaceLogEventVisitor; +import com.sap.sailing.domain.base.Competitor; import com.sap.sailing.domain.base.CourseBase; 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.racelog.RaceLogRaceStatus; +import com.sap.sailing.domain.markpassingcalculation.MarkPassingUpdateListener; import com.sap.sailing.domain.tracking.DynamicTrackedRace; import com.sap.sailing.domain.tracking.Wind; import com.sap.sse.common.TimePoint; +import com.sap.sse.common.Util.Triple; /** * TODO: this class could be a good place to leverage more information about a race containing in the {@link RaceLog}. * This includes for example the {@link RaceLogRaceStatus} indicating the current race's start. */ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { - + private static final Logger logger = Logger.getLogger(DynamicTrackedRaceLogListener.class.getName()); + private Set raceLogs = new HashSet<>(); + private DynamicTrackedRace trackedRace; - + private final WindSource raceCommitteeWindSource; private LastPublishedCourseDesignFinder courseDesignFinder; private StartTimeFinder startTimeFinder; private AbortingFlagFinder abortingFlagFinder; + private MarkPassingDataFinder markPassingDataFinder; + private MarkPassingUpdateListener markPassingUpdateListener; + public DynamicTrackedRaceLogListener(DynamicTrackedRace trackedRace) { this.trackedRace = trackedRace; raceCommitteeWindSource = new WindSourceImpl(WindSourceType.RACECOMMITTEE); @@ -45,9 +60,10 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { public void addTo(RaceLog raceLog) { if (raceLog == null) { - logger.severe("Trying to add "+this+" as listener to a null race log for tracked race "+trackedRace.getRace()); + logger.severe("Trying to add " + this + " as listener to a null race log for tracked race " + trackedRace.getRace()); } else { raceLog.addListener(this); + raceLogs.add(raceLog); trackedRace.invalidateStartTime(); trackedRace.invalidateEndTime(); courseDesignFinder = new LastPublishedCourseDesignFinder(raceLog); @@ -55,12 +71,22 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { abortingFlagFinder = new AbortingFlagFinder(raceLog); initializeWindTrack(raceLog); analyze(); + if (markPassingUpdateListener != null) { + markPassingDataFinder = new MarkPassingDataFinder(raceLog); + analyzeMarkPassings(); + } } } + public void setMarkPassingUpdateListener(MarkPassingUpdateListener listener) { + markPassingUpdateListener = listener; + } + /** * Retrieves all wind fixes available in the race log and adds them to the wind track for RACECOMMITTEEE - * @param raceLog The race log from which the available wind fixes shall be retrieved. + * + * @param raceLog + * The race log from which the available wind fixes shall be retrieved. */ private void initializeWindTrack(RaceLog raceLog) { WindFixesFinder windFixesFinder = new WindFixesFinder(raceLog); @@ -80,12 +106,18 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { trackedRace.invalidateStartTime(); removeAllWindFixesFromWindTrack(raceLog); raceLog.removeListener(this); + raceLogs.remove(raceLog); + if (markPassingUpdateListener != null) { + removeMarkPassingEvents(); + } } } /** * Removes all wind fixes that are in the RaceLog from the RACECOMMITTEE wind source - * @param raceLog the RaceLog of which the wind fixes shall be removed + * + * @param raceLog + * the RaceLog of which the wind fixes shall be removed */ private void removeAllWindFixesFromWindTrack(RaceLog raceLog) { WindFixesFinder windFixesFinder = new WindFixesFinder(raceLog); @@ -94,6 +126,16 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { } } + private void removeMarkPassingEvents() { + for (Triple triple : markPassingDataFinder.analyze()) { + if (triple.getC() == null) { + markPassingUpdateListener.removeSuppressedPassing(triple.getA()); + } else { + markPassingUpdateListener.removeFixedPassing(triple.getA(), triple.getB()); + } + } + } + private void analyze() { analyzeCourseDesign(null); } @@ -110,27 +152,39 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { // ... onCourseDesignChangedByRaceCommittee() might be called more than once. trackedRace.onCourseDesignChangedByRaceCommittee(courseDesign); } else { - logger.info("Could not find any course design update on race log of " + trackedRace.getRace().getName() + "! Not sending out any events."); + logger.info("Could not find any course design update on race log of " + trackedRace.getRace().getName() + + "! Not sending out any events."); } } - + private void analyzeStartTime(TimePoint startTimeProvidedByEvent) { /* start time will be set by StartTimeFinder in TrackedRace.getStartTime() */ trackedRace.invalidateStartTime(); - + TimePoint startTime = startTimeFinder.analyze(); if (startTime == null) { startTime = startTimeProvidedByEvent; } - + if (startTime != null) { /* invoke listeners with received start time, this will also trigger tractrac update */ trackedRace.onStartTimeChangedByRaceCommittee(startTime); } else { - logger.info("Could not find any valid start time on race log of " + trackedRace.getRace().getName() + "! Not sending out any events."); + logger.info("Could not find any valid start time on race log of " + trackedRace.getRace().getName() + + "! Not sending out any events."); } } - + + private void analyzeMarkPassings() { + for (Triple triple : markPassingDataFinder.analyze()) { + if (triple.getC() == null) { + markPassingUpdateListener.addSuppressedPassing(triple.getA(), triple.getB()); + } else { + markPassingUpdateListener.addFixedPassing(triple.getA(), triple.getB(), triple.getC()); + } + } + } + @Override public void visit(RaceLogPassChangeEvent event) { trackedRace.invalidateStartTime(); // this will notify RaceStateListeners in case the start time changes by the event @@ -158,4 +212,46 @@ public class DynamicTrackedRaceLogListener extends BaseRaceLogEventVisitor { // add the wind fix to the race committee WindTrack trackedRace.recordWind(event.getWindFix(), raceCommitteeWindSource); } + + @Override + public void visit(FixedMarkPassingEvent event) { + if (markPassingUpdateListener != null) { + markPassingUpdateListener.addFixedPassing(event.getInvolvedBoats().get(0), event.getZeroBasedIndexOfPassedWaypoint(), + event.getTimePointOfFixedPassing()); + } + } + + @Override + public void visit(SuppressedMarkPassingsEvent event) { + if (markPassingUpdateListener != null) { + markPassingUpdateListener.addSuppressedPassing(event.getInvolvedBoats().get(0), + event.getZeroBasedIndexOfFirstSuppressedWaypoint()); + } + } + + @Override + public void visit(RaceLogRevokeEvent event) { + if (markPassingUpdateListener != null) { + RaceLogEvent revokedEvent = null; + for (RaceLog log : raceLogs) { + try { + log.lockForRead(); + revokedEvent = log.getEventById(event.getRevokedEventId()); + if (revokedEvent != null) { + break; + } + } finally { + log.unlockAfterRead(); + } + + } + if (revokedEvent instanceof SuppressedMarkPassingsEvent) { + markPassingUpdateListener.removeSuppressedPassing(revokedEvent.getInvolvedBoats().get(0)); + } + if (revokedEvent instanceof FixedMarkPassingEvent) { + markPassingUpdateListener.removeFixedPassing(revokedEvent.getInvolvedBoats().get(0), + ((FixedMarkPassingEvent) revokedEvent).getZeroBasedIndexOfPassedWaypoint()); + } + } + } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRegattaImpl.java index 82ebc9ca541..1bf8d0ac359 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DynamicTrackedRegattaImpl.java @@ -35,8 +35,8 @@ public class DynamicTrackedRegattaImpl extends TrackedRegattaImpl implements Dyn @Override public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable sidelines, WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, - DynamicRaceDefinitionSet raceDefinitionSetToUpdate) { + DynamicRaceDefinitionSet raceDefinitionSetToUpdate, boolean useMarkPassingCalculator) { return (DynamicTrackedRace) super.createTrackedRace(raceDefinition, sidelines, windStore, gpsFixStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, - millisecondsOverWhichToAverageSpeed, raceDefinitionSetToUpdate); + millisecondsOverWhichToAverageSpeed, raceDefinitionSetToUpdate, useMarkPassingCalculator); } } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index 3a99253d461..1280cb62078 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -93,6 +93,7 @@ import com.sap.sailing.domain.common.racelog.tracking.TransformationException; import com.sap.sailing.domain.common.scalablevalue.impl.ScalablePosition; import com.sap.sailing.domain.confidence.ConfidenceBasedWindAverager; import com.sap.sailing.domain.confidence.ConfidenceFactory; +import com.sap.sailing.domain.markpassingcalculation.MarkPassingCalculator; import com.sap.sailing.domain.racelog.tracking.GPSFixStore; import com.sap.sailing.domain.racelogtracking.DeviceMapping; import com.sap.sailing.domain.tracking.DynamicGPSFixTrack; @@ -103,6 +104,7 @@ import com.sap.sailing.domain.tracking.GPSTrackListener; import com.sap.sailing.domain.tracking.LineDetails; import com.sap.sailing.domain.tracking.Maneuver; import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.RaceListener; import com.sap.sailing.domain.tracking.Track; import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; @@ -239,6 +241,8 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl private transient Map> directionFromStartToNextMarkCache; + protected final MarkPassingCalculator markPassingCalculator; + private final ConcurrentHashMap> markTracks; private final Map courseSidelines; @@ -308,9 +312,11 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl */ private transient ShortTimeWindCache shortTimeWindCache; - public TrackedRaceImpl(final TrackedRegatta trackedRegatta, RaceDefinition race, final Iterable sidelines, final WindStore windStore, final GPSFixStore gpsFixStore, + public TrackedRaceImpl(final TrackedRegatta trackedRegatta, RaceDefinition race, + final Iterable sidelines, final WindStore windStore, final GPSFixStore gpsFixStore, long delayToLiveInMillis, final long millisecondsOverWhichToAverageWind, - long millisecondsOverWhichToAverageSpeed, long delayForWindEstimationCacheInvalidation) { + long millisecondsOverWhichToAverageSpeed, long delayForWindEstimationCacheInvalidation, + boolean useInternalMarkPassingAlgorithm) { super(race, trackedRegatta, windStore, millisecondsOverWhichToAverageWind); raceStates = new WeakHashMap<>(); shortTimeWindCache = new ShortTimeWindCache(this, millisecondsOverWhichToAverageWind / 2); @@ -421,6 +427,20 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl getOrCreateWindTrack(trackBasedWindSource, delayForWindEstimationCacheInvalidation)); competitorRankings = createCompetitorRankingsCache(); competitorRankingsLocks = createCompetitorRankingsLockMap(); + if (useInternalMarkPassingAlgorithm){ + markPassingCalculator = createMarkPassingCalculator(); + this.trackedRegatta.addRaceListener(new RaceListener() { + @Override + public void raceAdded(TrackedRace trackedRace) {} + @Override + public void raceRemoved(TrackedRace trackedRace) { + // stop mark passing calculator when tracked race is removed: + markPassingCalculator.stop(); + } + }); + } else { + markPassingCalculator = null; + } // now wait until wind loading has at least started; then we know that the serialization lock is safely held by the loader try { waitUntilLoadingFromWindStoreComplete(); @@ -2760,15 +2780,26 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } else if (oldStatus == TrackedRaceStatusEnum.LOADING && newStatus.getStatus() != TrackedRaceStatusEnum.LOADING) { resumeAllCachesNotUpdatingWhileLoading(); } - + if (newStatus.getStatus() == TrackedRaceStatusEnum.FINISHED) { + // no more new data can be expected; stop mark passing calculator if one is being used + if (isUsingMarkPassingCalculator()) { + markPassingCalculator.stop(); + } + } } private void suspendAllCachesNotUpdatingWhileLoading() { + if (markPassingCalculator != null) { + markPassingCalculator.suspend(); + } crossTrackErrorCache.suspend(); maneuverCache.suspend(); } private void resumeAllCachesNotUpdatingWhileLoading() { + if (markPassingCalculator != null) { + markPassingCalculator.resume(); + } crossTrackErrorCache.resume(); maneuverCache.resume(); } @@ -3298,6 +3329,13 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl public GPSFixStore getGPSFixStore() { return gpsFixStore; } + + protected abstract MarkPassingCalculator createMarkPassingCalculator(); + + @Override + public boolean isUsingMarkPassingCalculator() { + return markPassingCalculator!=null; + } @Override public Position getCenterOfCourse(TimePoint at) { diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java index 7ee9077c398..32532ee476e 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRegattaImpl.java @@ -171,10 +171,11 @@ public class TrackedRegattaImpl implements TrackedRegatta { @Override public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable sidelines, WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, - DynamicRaceDefinitionSet raceDefinitionSetToUpdate) { + DynamicRaceDefinitionSet raceDefinitionSetToUpdate, boolean useInternalMarkPassingAlgorithm) { logger.log(Level.INFO, "Creating DynamicTrackedRaceImpl for RaceDefinition " + raceDefinition.getName()); DynamicTrackedRaceImpl result = new DynamicTrackedRaceImpl(this, raceDefinition, sidelines, - windStore, gpsFixStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed); + windStore, gpsFixStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, + /*useMarkPassingCalculator*/ useInternalMarkPassingAlgorithm); // adding the raceDefinition to the raceDefinitionSetToUpdate BEFORE calling addTrackedRace helps those who // are called back by RaceListener.raceAdded(TrackedRace) and who then expect the update to have happened if (raceDefinitionSetToUpdate != null) { diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java index 18f7842172e..f9c0928e151 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/GwtTestCaseColumnToggling.java @@ -95,7 +95,7 @@ public class GwtTestCaseColumnToggling extends GWTTestCase { + TracTracConnectionConstants.PORT_TUNNEL_STORED : "tcp://" + TracTracConnectionConstants.HOST_NAME + ":" + TracTracConnectionConstants.PORT_STORED, COURSE_DESIGN_UPDATE_URI, - false, false, /* simulateWithStartTimeNow */ false, TRACTRAC_USERNAME, TRACTRAC_PASSWORD, new AsyncCallback() { + false, false, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings */ false, TRACTRAC_USERNAME, TRACTRAC_PASSWORD, new AsyncCallback() { @Override public void onFailure(Throwable caught) { 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 f621c43dffb..609d56ff739 100755 --- 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 @@ -23,7 +23,6 @@ import com.sap.sailing.domain.base.Sideline; import com.sap.sailing.domain.base.SpeedWithConfidence; import com.sap.sailing.domain.base.Waypoint; import com.sap.sailing.domain.base.configuration.RegattaConfiguration; -import com.sap.sailing.domain.common.Bearing; import com.sap.sailing.domain.common.Distance; import com.sap.sailing.domain.common.NoWindException; import com.sap.sailing.domain.common.Position; @@ -478,8 +477,9 @@ public class MockedTrackedRace implements DynamicTrackedRace { @Override public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable sidelines, WindStore windStore, - GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, - DynamicRaceDefinitionSet raceDefinitionSetToUpdate) { + GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, + long millisecondsOverWhichToAverageSpeed, DynamicRaceDefinitionSet raceDefinitionSetToUpdate, + boolean useMarkPassingCalculator) { return null; } }; @@ -821,16 +821,6 @@ public class MockedTrackedRace implements DynamicTrackedRace { return null; } - @Override - public Bearing getCrossingBearing(Waypoint w, TimePoint t) { - return null; - } - - @Override - public Util.Pair getPortAndStarboardMarks(TimePoint t, Waypoint w) { - return null; - } - @Override public Distance getAverageSignedCrossTrackError(Competitor competitor, TimePoint timePoint, boolean waitForLatestAnalysis) throws NoWindException { @@ -866,10 +856,17 @@ public class MockedTrackedRace implements DynamicTrackedRace { return null; } + @Override + public boolean isUsingMarkPassingCalculator() { + // TODO Auto-generated method stub + return false; + } + @Override public void attachRegattaLog(RegattaLog regattaLog) { } + @Override public void waitForLoadingFromGPSFixStoreToFinishRunning(RegattaLog fromRegattaLog) throws InterruptedException { } diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java index 7803176ca7c..b46db9f5858 100755 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/SailingServiceImplMock.java @@ -75,7 +75,7 @@ public class SailingServiceImplMock extends SailingServiceImpl { public RaceHandle addSwissTimingRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, String raceID, String raceName, String raceDescription, BoatClass boatClass, String hostname, int port, StartList startList, RaceLogStore logStore, - RegattaLogStore regattaLogStore, long timeoutInMilliseconds) throws InterruptedException, + RegattaLogStore regattaLogStore, long timeoutInMilliseconds, boolean useInternalMarkPassingAlgorithm) throws InterruptedException, UnknownHostException, IOException, ParseException, Exception { // TODO Auto-generated method stub return null; diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch index 96766428576..7c55b6a1b87 100644 --- a/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT Remote Profiling (Run-Mode only).launch @@ -1,60 +1,60 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch index e615bad9252..949b0f66cee 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT sdm.launch @@ -1,76 +1,76 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/SailingGWT.launch b/java/com.sap.sailing.gwt.ui/SailingGWT.launch index 5d06d981c92..ce32176a98c 100755 --- a/java/com.sap.sailing.gwt.ui/SailingGWT.launch +++ b/java/com.sap.sailing.gwt.ui/SailingGWT.launch @@ -1,74 +1,74 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalytics.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalytics.java index 169f4dd0a5b..5decb1a1230 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalytics.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalytics.java @@ -20,6 +20,7 @@ import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.impl.HyperlinkImpl; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.LeaderboardDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; @@ -99,7 +100,7 @@ public class RegattaAnalytics extends Composite implements LeaderboardUpdateList } public void createRegattaAnalyticsViewer(final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor, - final Timer timer, final LeaderboardSettings leaderboardSettings, final RaceIdentifier preselectedRace, + final Timer timer, final LeaderboardSettings leaderboardSettings, final RegattaAndRaceIdentifier preselectedRace, final String leaderboardGroupName, String leaderboardName, final ErrorReporter errorReporter, final UserAgentDetails userAgent, boolean showRaceDetails, boolean autoExpandLastRaceColumn, boolean showOverallLeaderboard) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java old mode 100644 new mode 100755 index 9b0590d8434..8711d0ae49e --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/regattaanalytics/RegattaAnalyticsDataManager.java @@ -5,7 +5,7 @@ import java.util.Iterator; import java.util.List; import com.sap.sailing.domain.common.DetailType; -import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; @@ -52,7 +52,7 @@ public class RegattaAnalyticsDataManager { this.multiCompetitorChart = null; } - public LeaderboardPanel createLeaderboardPanel(final LeaderboardSettings leaderboardSettings, final RaceIdentifier preselectedRace, + public LeaderboardPanel createLeaderboardPanel(final LeaderboardSettings leaderboardSettings, final RegattaAndRaceIdentifier preselectedRace, final String leaderboardGroupName, String leaderboardName, boolean showRaceDetails, boolean autoExpandLastRaceColumn) { if(leaderboardPanel == null) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalytics.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalytics.java index be1c4184f59..aedd0e0a078 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalytics.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalytics.java @@ -21,6 +21,7 @@ import com.google.gwt.user.client.ui.impl.HyperlinkImpl; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.LeaderboardNameConstants; import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.FleetDTO; import com.sap.sailing.domain.common.dto.LeaderboardDTO; import com.sap.sailing.domain.common.dto.RaceColumnDTO; @@ -114,7 +115,7 @@ public class EventSeriesAnalytics extends Composite implements LeaderboardUpdate } public void createSeriesAnalyticsViewer(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, - Timer timer, LeaderboardSettings leaderboardSettings, String preselectedLeaderboardName, RaceIdentifier preselectedRace, + Timer timer, LeaderboardSettings leaderboardSettings, String preselectedLeaderboardName, RegattaAndRaceIdentifier preselectedRace, String leaderboardGroupName, String metaLeaderboardName, ErrorReporter errorReporter, UserAgentDetails userAgent, boolean showRaceDetails, boolean autoExpandLastRaceColumn, boolean showSeriesLeaderboards) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java old mode 100644 new mode 100755 index 35f0d588f16..47e144665cf --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/event/seriesanalytics/EventSeriesAnalyticsDataManager.java @@ -6,6 +6,7 @@ import java.util.List; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.dto.CompetitorDTO; import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; @@ -56,7 +57,7 @@ public class EventSeriesAnalyticsDataManager { this.multiCompetitorChart = null; } - public LeaderboardPanel createOverallLeaderboardPanel(final LeaderboardSettings leaderboardSettings, final RaceIdentifier preselectedRace, + public LeaderboardPanel createOverallLeaderboardPanel(final LeaderboardSettings leaderboardSettings, final RegattaAndRaceIdentifier preselectedRace, final String leaderboardGroupName, String leaderboardName, boolean showRaceDetails, boolean autoExpandLastRaceColumn) { if(overallLeaderboardPanel == null) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java index 0eb19bac8e6..c9914124b47 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaActivity.java @@ -13,7 +13,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.LeaderboardType; -import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.gwt.common.client.i18n.TextMessages; import com.sap.sailing.gwt.home.client.shared.placeholder.Placeholder; @@ -97,7 +97,7 @@ public class RegattaActivity extends AbstractActivity { } long delayBetweenAutoAdvancesInMilliseconds = 3000l; - final RaceIdentifier preselectedRace = getPreselectedRace(Window.Location.getParameterMap()); + final RegattaAndRaceIdentifier preselectedRace = getPreselectedRace(Window.Location.getParameterMap()); // make a single live request as the default but don't continue to play by default Timer timer = new Timer(PlayModes.Live, PlayStates.Paused, delayBetweenAutoAdvancesInMilliseconds); final LeaderboardSettings leaderboardSettings = createLeaderboardSettingsFromURLParameters(Window.Location.getParameterMap()); @@ -188,8 +188,8 @@ public class RegattaActivity extends AbstractActivity { return result; } - private RaceIdentifier getPreselectedRace(Map> parameterMap) { - RaceIdentifier result; + private RegattaAndRaceIdentifier getPreselectedRace(Map> parameterMap) { + RegattaAndRaceIdentifier result; if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_RACE_NAME) && parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME).size() == 1 && parameterMap.containsKey(LeaderboardUrlSettings.PARAM_REGATTA_NAME) && parameterMap.get(LeaderboardUrlSettings.PARAM_REGATTA_NAME).size() == 1) { result = new RegattaNameAndRaceName(parameterMap.get(LeaderboardUrlSettings.PARAM_REGATTA_NAME).get(0), parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME).get(0)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaAnalyticsView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaAnalyticsView.java old mode 100644 new mode 100755 index 3af3bc6ae7c..98bb8a8158b --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaAnalyticsView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/RegattaAnalyticsView.java @@ -1,7 +1,7 @@ package com.sap.sailing.gwt.home.client.place.regatta; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; import com.sap.sse.gwt.client.ErrorReporter; @@ -13,7 +13,7 @@ public interface RegattaAnalyticsView { Widget asWidget(); void createRegattaAnalyticsViewer(final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor, - final Timer timer, final LeaderboardSettings leaderboardSettings, final RaceIdentifier preselectedRace, + final Timer timer, final LeaderboardSettings leaderboardSettings, final RegattaAndRaceIdentifier preselectedRace, final String leaderboardGroupName, String leaderboardName, final ErrorReporter errorReporter, final UserAgentDetails userAgent, boolean showRaceDetails, boolean autoExpandLastRaceColumn, boolean showOverallLeaderboard); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/TabletAndDesktopRegattaView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/TabletAndDesktopRegattaView.java index 9c20efd1237..13c56e0ca9f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/TabletAndDesktopRegattaView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/regatta/TabletAndDesktopRegattaView.java @@ -5,7 +5,7 @@ import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.gwt.home.client.app.HomePlacesNavigator; import com.sap.sailing.gwt.home.client.place.event.regattaanalytics.RegattaAnalytics; import com.sap.sailing.gwt.home.client.place.regatta.RegattaPlace.RegattaNavigationTabs; @@ -34,7 +34,7 @@ public class TabletAndDesktopRegattaView extends Composite implements RegattaAna } public void createRegattaAnalyticsViewer(final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor, - final Timer timer, final LeaderboardSettings leaderboardSettings, final RaceIdentifier preselectedRace, + final Timer timer, final LeaderboardSettings leaderboardSettings, final RegattaAndRaceIdentifier preselectedRace, final String leaderboardGroupName, String leaderboardName, final ErrorReporter errorReporter, final UserAgentDetails userAgent, boolean showRaceDetails, boolean autoExpandLastRaceColumn, boolean showOverallLeaderboard) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java index eeb3714d515..c334e748a1f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesActivity.java @@ -13,7 +13,7 @@ import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.LeaderboardType; -import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.gwt.common.client.i18n.TextMessages; import com.sap.sailing.gwt.home.client.shared.placeholder.Placeholder; @@ -97,7 +97,7 @@ public class SeriesActivity extends AbstractActivity { } long delayBetweenAutoAdvancesInMilliseconds = 3000l; - final RaceIdentifier preselectedRace = getPreselectedRace(Window.Location.getParameterMap()); + final RegattaAndRaceIdentifier preselectedRace = getPreselectedRace(Window.Location.getParameterMap()); // make a single live request as the default but don't continue to play by default Timer timer = new Timer(PlayModes.Live, PlayStates.Paused, delayBetweenAutoAdvancesInMilliseconds); final LeaderboardSettings leaderboardSettings = createLeaderboardSettingsFromURLParameters(Window.Location.getParameterMap()); @@ -189,8 +189,8 @@ public class SeriesActivity extends AbstractActivity { return result; } - private RaceIdentifier getPreselectedRace(Map> parameterMap) { - RaceIdentifier result; + private RegattaAndRaceIdentifier getPreselectedRace(Map> parameterMap) { + RegattaAndRaceIdentifier result; if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_RACE_NAME) && parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME).size() == 1 && parameterMap.containsKey(LeaderboardUrlSettings.PARAM_REGATTA_NAME) && parameterMap.get(LeaderboardUrlSettings.PARAM_REGATTA_NAME).size() == 1) { result = new RegattaNameAndRaceName(parameterMap.get(LeaderboardUrlSettings.PARAM_REGATTA_NAME).get(0), parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME).get(0)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesAnalyticsView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesAnalyticsView.java index 2865cdba159..55668d8eb6c 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesAnalyticsView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/SeriesAnalyticsView.java @@ -1,7 +1,7 @@ package com.sap.sailing.gwt.home.client.place.series; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; import com.sap.sse.gwt.client.ErrorReporter; @@ -13,7 +13,7 @@ public interface SeriesAnalyticsView { Widget asWidget(); void createSeriesAnalyticsViewer(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, - Timer timer, LeaderboardSettings leaderboardSettings, String preselectedLeaderboardName, RaceIdentifier preselectedRace, + Timer timer, LeaderboardSettings leaderboardSettings, String preselectedLeaderboardName, RegattaAndRaceIdentifier preselectedRace, String leaderboardGroupName, String metaLeaderboardName, ErrorReporter errorReporter, UserAgentDetails userAgent, boolean showRaceDetails, boolean autoExpandLastRaceColumn, boolean showSeriesLeaderboards); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/TabletAndDesktopSeriesView.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/TabletAndDesktopSeriesView.java old mode 100644 new mode 100755 index e3b529584fa..05faff70aee --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/TabletAndDesktopSeriesView.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/home/client/place/series/TabletAndDesktopSeriesView.java @@ -5,7 +5,7 @@ import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.Widget; -import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.gwt.home.client.app.HomePlacesNavigator; import com.sap.sailing.gwt.home.client.place.event.seriesanalytics.EventSeriesAnalytics; import com.sap.sailing.gwt.home.client.place.series.SeriesPlace.SeriesNavigationTabs; @@ -34,7 +34,7 @@ public class TabletAndDesktopSeriesView extends Composite implements SeriesAnaly } public void createSeriesAnalyticsViewer(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, - Timer timer, LeaderboardSettings leaderboardSettings, String preselectedLeaderboardName, RaceIdentifier preselectedRace, + Timer timer, LeaderboardSettings leaderboardSettings, String preselectedLeaderboardName, RegattaAndRaceIdentifier preselectedRace, String leaderboardGroupName, String metaLeaderboardName, ErrorReporter errorReporter, UserAgentDetails userAgent, boolean showRaceDetails, boolean autoExpandLastRaceColumn, boolean showSeriesLeaderboards) { seriesAnalytics.createSeriesAnalyticsViewer(sailingService, asyncActionsExecutor, timer, leaderboardSettings, preselectedLeaderboardName, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java index 496fa5fb7e9..c3544fab81d 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/CompetitorPanel.java @@ -295,7 +295,7 @@ public class CompetitorPanel extends SimplePanel { @Override public void onSuccess(Void result) { - Window.alert(stringMessages.sendingMailsSuccessfull()); + Window.alert(stringMessages.sendingMailsSuccessful()); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementPanel.java index 009cb53ccc1..704a8fab902 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/RaceLogTrackingEventManagementPanel.java @@ -495,7 +495,7 @@ public class RaceLogTrackingEventManagementPanel extends AbstractLeaderboardConf @Override public void onSuccess(Void result) { - Window.alert(stringMessages.sendingMailsSuccessfull()); + Window.alert(stringMessages.sendingMailsSuccessful()); } }); } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java index 9e67f798875..5b74c7fb005 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingEventManagementPanel.java @@ -224,6 +224,16 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane declinationCheckbox.setWordWrap(false); declinationCheckbox.setValue(true); trackableRacesPanel.add(declinationCheckbox); + + final CheckBox simulateWithStartTimeNow = new CheckBox(stringMessages.simulateWithStartTimeNow()); + simulateWithStartTimeNow.setWordWrap(false); + simulateWithStartTimeNow.setValue(false); + trackableRacesPanel.add(simulateWithStartTimeNow); + + final CheckBox useInternalMarkPassingAlgorithmCheckbox = new CheckBox(stringMessages.useInternalAlgorithm()); + useInternalMarkPassingAlgorithmCheckbox.setWordWrap(false); + useInternalMarkPassingAlgorithmCheckbox.setValue(Boolean.FALSE); + trackableRacesPanel.add(useInternalMarkPassingAlgorithmCheckbox); // text box for filtering the cell table HorizontalPanel filterPanel = new HorizontalPanel(); @@ -291,7 +301,8 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane btnTrack.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - trackSelectedRaces(trackWindCheckbox.getValue(), declinationCheckbox.getValue()); + trackSelectedRaces(trackWindCheckbox.getValue(), declinationCheckbox.getValue(), + useInternalMarkPassingAlgorithmCheckbox.getValue()); } }); } @@ -429,7 +440,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane }); } - private void trackSelectedRaces(boolean trackWind, boolean correctWindByDeclination) { + private void trackSelectedRaces(boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) { String hostname = hostnameTextbox.getValue(); Integer port = portIntegerbox.getValue(); final List selectedRaces = new ArrayList(); @@ -449,7 +460,7 @@ public class SwissTimingEventManagementPanel extends AbstractEventManagementPane sailingService.trackWithSwissTiming( /* regattaToAddTo */ regattaIdentifier, selectedRaces, hostname, port, trackWind, correctWindByDeclination, - new AsyncCallback() { + useInternalMarkPassingAlgorithm, new AsyncCallback() { @Override public void onFailure(Throwable caught) { errorReporter.reportError("Error trying to register races " + selectedRaces + " for tracking: " diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingReplayConnectorPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingReplayConnectorPanel.java index 57b003f7550..6193d9f8a6d 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingReplayConnectorPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/SwissTimingReplayConnectorPanel.java @@ -224,11 +224,11 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane declinationCheckbox.setValue(true); trackPanel.add(declinationCheckbox); - final CheckBox simulateWithStartTimeNow = new CheckBox(stringMessages.simulateWithStartTimeNow()); - simulateWithStartTimeNow.setWordWrap(false); - simulateWithStartTimeNow.setValue(false); - trackPanel.add(simulateWithStartTimeNow); - + final CheckBox useInternalMarkPassingAlgorithmCheckbox = new CheckBox(stringMessages.useInternalAlgorithm()); + useInternalMarkPassingAlgorithmCheckbox.setWordWrap(false); + useInternalMarkPassingAlgorithmCheckbox.setValue(Boolean.FALSE); + trackPanel.add(useInternalMarkPassingAlgorithmCheckbox); + trackedRacesPanel.add(trackedRacesListComposite); HorizontalPanel racesButtonPanel = new HorizontalPanel(); @@ -240,7 +240,8 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane btnTrack.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { - trackSelectedRaces(trackWindCheckbox.getValue(), declinationCheckbox.getValue(), simulateWithStartTimeNow.getValue()); + trackSelectedRaces(trackWindCheckbox.getValue(), declinationCheckbox.getValue(), + useInternalMarkPassingAlgorithmCheckbox.getValue()); } }); @@ -337,7 +338,7 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane }); } - private void trackSelectedRaces(boolean trackWind, boolean correctWindByDeclination, final boolean simulateWithStartTimeNow) { + private void trackSelectedRaces(boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) { RegattaDTO selectedRegatta = getSelectedRegatta(); RegattaIdentifier regattaIdentifier = null; if (selectedRegatta != null) { @@ -353,7 +354,7 @@ public class SwissTimingReplayConnectorPanel extends AbstractEventManagementPane } if (checkBoatClassOK(selectedRegatta, selectedRaces)) { sailingService.replaySwissTimingRace(regattaIdentifier, selectedRaces, trackWind, correctWindByDeclination, - simulateWithStartTimeNow, new AsyncCallback() { + useInternalMarkPassingAlgorithm, new AsyncCallback() { @Override public void onFailure(Throwable caught) { errorReporter.reportError("Error trying to register races " + selectedRaces diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java index 221479d016f..411331c30cd 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/adminconsole/TracTracEventManagementPanel.java @@ -279,11 +279,15 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { simulateWithStartTimeNowCheckBox.ensureDebugId("SimulateWithStartTimeNowCheckBox"); simulateWithStartTimeNowCheckBox.setWordWrap(false); simulateWithStartTimeNowCheckBox.setValue(Boolean.FALSE); + final CheckBox ignoreTracTracMarkPassingsCheckbox = new CheckBox(stringMessages.useInternalAlgorithm()); + ignoreTracTracMarkPassingsCheckbox.setWordWrap(false); + ignoreTracTracMarkPassingsCheckbox.setValue(Boolean.FALSE); layoutTable.setWidget(1, 0, trackSettingsLabel); layoutTable.setWidget(1, 1, trackWindCheckBox); layoutTable.setWidget(2, 1, correctWindCheckBox); layoutTable.setWidget(3, 1, simulateWithStartTimeNowCheckBox); + layoutTable.setWidget(4, 1, ignoreTracTracMarkPassingsCheckbox); // Filter Label racesFilterLabel = new Label(stringMessages.filterRacesByName() + ":"); @@ -305,8 +309,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { } }; racesFilterablePanel.getTextBox().ensureDebugId("TrackableRacesFilterTextBox"); - layoutTable.setWidget(4, 0, racesFilterLabel); - layoutTable.setWidget(4, 1, racesFilterablePanel); + layoutTable.setWidget(5, 0, racesFilterLabel); + layoutTable.setWidget(5, 1, racesFilterablePanel); // Races TextColumn regattaNameColumn = new TextColumn() { @@ -379,8 +383,8 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { raceList.addDataDisplay(racesTable); - layoutTable.setWidget(5, 0, racesTable); - cellFormatter.setColSpan(5, 0, 2); + layoutTable.setWidget(6, 0, racesTable); + cellFormatter.setColSpan(6, 0, 2); Button startTrackingButton = new Button(stringMessages.startTracking()); startTrackingButton.ensureDebugId("StartTrackingButton"); @@ -388,11 +392,11 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { @Override public void onClick(ClickEvent event) { trackSelectedRaces(trackWindCheckBox.getValue(), correctWindCheckBox.getValue(), - simulateWithStartTimeNowCheckBox.getValue()); + simulateWithStartTimeNowCheckBox.getValue(), ignoreTracTracMarkPassingsCheckbox.getValue()); } }); - layoutTable.setWidget(6, 1, startTrackingButton); + layoutTable.setWidget(7, 1, startTrackingButton); trackableRacesPanel.setContentWidget(layoutTable); @@ -542,7 +546,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { })); } - private void trackSelectedRaces(boolean trackWind, boolean correctWind, final boolean simulateWithStartTimeNow) { + private void trackSelectedRaces(boolean trackWind, boolean correctWind, final boolean simulateWithStartTimeNow, boolean ignoreTracTracMarkPassings) { String liveURI = liveURITextBox.getValue(); String storedURI = storedURITextBox.getValue(); String courseDesignUpdateURI = tracTracUpdateURITextBox.getValue(); @@ -568,7 +572,7 @@ public class TracTracEventManagementPanel extends AbstractEventManagementPanel { } if (checkBoatClassOK(selectedRegatta, selectedRaces)) { sailingService.trackWithTracTrac(regattaIdentifier, selectedRaces, liveURI, storedURI, - courseDesignUpdateURI, trackWind, correctWind, simulateWithStartTimeNow, tractracUsername, + courseDesignUpdateURI, trackWind, correctWind, simulateWithStartTimeNow, ignoreTracTracMarkPassings, tractracUsername, tractracPassword, new MarkedAsyncCallback(new AsyncCallback() { @Override public void onFailure(Throwable caught) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java index a1d879b6e76..ca05d7ce36f 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/RaceTimePanel.java @@ -211,15 +211,17 @@ public class RaceTimePanel extends TimePanel implements R } } + /** + * Expected to be called before {@link #lastRaceTimesInfo} has been updated to newRaceTimesInfo + */ private void updateLegMarkers(RaceTimesInfoDTO newRaceTimesInfo) { boolean requiresMarkerUpdate = true; - // updating the sliderbar markers requires a lot of time, therefore we need to do this only if required - if(lastRaceTimesInfo != null && lastRaceTimesInfo.markPassingTimes.size() == newRaceTimesInfo.markPassingTimes.size()) { + if (lastRaceTimesInfo != null && lastRaceTimesInfo.markPassingTimes.size() == newRaceTimesInfo.markPassingTimes.size()) { requiresMarkerUpdate = false; int numberOfLegs = newRaceTimesInfo.markPassingTimes.size(); - for(int i = 0; i < numberOfLegs; i++) { - if(newRaceTimesInfo.markPassingTimes.get(i).firstPassingDate != lastRaceTimesInfo.markPassingTimes.get(i).firstPassingDate) { + for (int i = 0; i < numberOfLegs; i++) { + if (!Util.equalsWithNull(newRaceTimesInfo.markPassingTimes.get(i).firstPassingDate, lastRaceTimesInfo.markPassingTimes.get(i).firstPassingDate)) { requiresMarkerUpdate = true; break; } @@ -245,18 +247,18 @@ public class RaceTimePanel extends TimePanel implements R private void redrawAllMarkers(RaceTimesInfoDTO newRaceTimesInfo) { List markPassingTimes = newRaceTimesInfo.getMarkPassingTimes(); timeSlider.clearMarkers(); - if(newRaceTimesInfo.startOfRace != null) { + if (newRaceTimesInfo.startOfRace != null) { long markerTime = newRaceTimesInfo.startOfRace.getTime(); - if(!timeSlider.isZoomed() || (timeSlider.isZoomed() && markerTime > timeSlider.getMinValue() && markerTime < timeSlider.getMaxValue())) { + if (!timeSlider.isZoomed() || (timeSlider.isZoomed() && markerTime > timeSlider.getMinValue() && markerTime < timeSlider.getMaxValue())) { timeSlider.addMarker("S", new Double(markerTime)); } } int markPassingCounter = 1; for (MarkPassingTimesDTO markPassingTimesDTO: markPassingTimes) { // ignore the start mark passing - if(markPassingCounter > 1 && markPassingTimesDTO.firstPassingDate != null) { + if (markPassingCounter > 1 && markPassingTimesDTO.firstPassingDate != null) { long markerTime = markPassingTimesDTO.firstPassingDate.getTime(); - if(!timeSlider.isZoomed() || (timeSlider.isZoomed() && markerTime > timeSlider.getMinValue() && markerTime < timeSlider.getMaxValue())) { + if (!timeSlider.isZoomed() || (timeSlider.isZoomed() && markerTime > timeSlider.getMinValue() && markerTime < timeSlider.getMaxValue())) { timeSlider.addMarker(markPassingTimesDTO.getName(), new Double(markerTime)); } } @@ -264,7 +266,7 @@ public class RaceTimePanel extends TimePanel implements R } if (newRaceTimesInfo.endOfRace != null) { long markerTime = newRaceTimesInfo.endOfRace.getTime(); - if(!timeSlider.isZoomed() || (timeSlider.isZoomed() && markerTime > timeSlider.getMinValue() && markerTime < timeSlider.getMaxValue())) { + if (!timeSlider.isZoomed() || (timeSlider.isZoomed() && markerTime > timeSlider.getMinValue() && markerTime < timeSlider.getMaxValue())) { timeSlider.addMarker("E", new Double(markerTime)); } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java index 978f6b12aee..d4a77523666 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingService.java @@ -111,13 +111,14 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS Util.Pair> listTracTracRacesInEvent(String eventJsonURL, boolean listHiddenRaces) throws Exception; void trackWithTracTrac(RegattaIdentifier regattaToAddTo, Iterable rrs, String liveURI, - String storedURI, String courseDesignUpdateURI, boolean trackWind, boolean correctWindByDeclination, boolean simulateWithStartTimeNow, String tracTracUsername, String tracTracPassword) throws Exception; + String storedURI, String courseDesignUpdateURI, boolean trackWind, boolean correctWindByDeclination, + boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword) throws Exception; void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable rrs, String hostname, int port, - boolean trackWind, boolean correctWindByDeclination) throws Exception; + boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) throws Exception; void replaySwissTimingRace(RegattaIdentifier regattaIdentifier, Iterable replayRaces, - boolean trackWind, boolean correctWindByDeclination, boolean simulateWithStartTimeNow); + boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm); void storeTracTracConfiguration(String name, String jsonURL, String liveDataURI, String storedDataURI, String courseDesignUpdateURI, String tracTracUsername, String tracTracPassword) throws Exception; @@ -559,6 +560,23 @@ public interface SailingService extends RemoteService, FileStorageManagementGwtS PolarSheetsXYDiagramData createXYDiagramForBoatClass(String itemText); + Map getCompetitorMarkPassings(RegattaAndRaceIdentifier race, CompetitorDTO competitorDTO); + + /** + * Obtains fixed mark passings and mark passing suppressions from the race log identified by + * leaderboardName, raceColumnDTO and fleet. The result contains + * pairs of zero-based waypoint numbers and times where null represents a suppressed mark + * passing and a valid {@link Date} objects represents a fixed mark passing. + */ + Map getCompetitorRaceLogMarkPassingData(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet, + CompetitorDTO competitor); + + void updateSuppressedMarkPassings(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet, + Integer newZeroBasedIndexOfSuppressedMarkPassing, CompetitorDTO competitorDTO); + + void updateFixedMarkPassing(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet, Integer indexOfWaypoint, + Date dateOfMarkPassing, CompetitorDTO competitorDTO); + void setCompetitorRegistrations(String leaderboardName, Set competitors) throws DoesNotHaveRegattaLogException; diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java index fc25f44888b..94afd2f3715 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/SailingServiceAsync.java @@ -110,22 +110,25 @@ public interface SailingServiceAsync extends BuildVersionRetriever, FileStorageM * @param simulateWithStartTimeNow * if true, the connector will adjust the time stamps of all events received such that the * first mark passing for the first waypoint will be set to "now." It will delay the forwarding of all - * events received such that they seem to be sent in "real-time." So, more or less the time points attached - * to the events sent to the receivers will again approximate the wall time. + * events received such that they seem to be sent in "real-time." So, more or less the time points + * attached to the events sent to the receivers will again approximate the wall time. + * @param useInternalMarkPassingAlgorithm + * whether or not to ignore the TracTrac-provided mark passings; if true, a separate mark + * passing calculator is used, and the TracTrac-provided ones are ignored. * @param storedURImay * be null or the empty string in which case the server will use the * {@link TracTracRaceRecordDTO#storedURI} from the rr race record. */ void trackWithTracTrac(RegattaIdentifier regattaToAddTo, Iterable rrs, String liveURI, String storedURI, String courseDesignUpdateURI, boolean trackWind, boolean correctWindByDeclination, - boolean simulateWithStartTimeNow, String tracTracUsername, String tracTracPassword, AsyncCallback callback); + boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword, AsyncCallback callback); void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable rrs, String hostname, int port, boolean trackWind, boolean correctWindByDeclination, - AsyncCallback asyncCallback); + boolean useInternalMarkPassingAlgorithm, AsyncCallback asyncCallback); void replaySwissTimingRace(RegattaIdentifier regattaIdentifier, Iterable replayRaces, - boolean trackWind, boolean correctWindByDeclination, boolean simulateWithStartTimeNow, + boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm, AsyncCallback asyncCallback); void getPreviousTracTracConfigurations(AsyncCallback> callback); @@ -505,8 +508,7 @@ public interface SailingServiceAsync extends BuildVersionRetriever, FileStorageM void reloadRaceLog(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet, AsyncCallback asyncCallback); - void getRaceLog(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet, - AsyncCallback callback); + void getRaceLog(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet, AsyncCallback callback); void getRegattaLog(String leaderboardName, AsyncCallback callback); @@ -683,10 +685,22 @@ public interface SailingServiceAsync extends BuildVersionRetriever, FileStorageM * @param manage2SailJsonUrl the URL pointing to a Manage2Sail JSON document that contains the link to the XRR document */ void getRegattas(String manage2SailJsonUrl, AsyncCallback> asyncCallback); + + void getCompetitorMarkPassings(RegattaAndRaceIdentifier race, CompetitorDTO competitorDTO, + AsyncCallback> callback); + + void getCompetitorRaceLogMarkPassingData(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet, CompetitorDTO competitor, + AsyncCallback> callback); + + void updateSuppressedMarkPassings(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet, + Integer newZeroBasedIndexOfSuppressedMarkPassing, CompetitorDTO competitorDTO, AsyncCallback callback); void createRegattaStructure(Iterable regattaNames, EventDTO newEvent, AsyncCallback asyncCallback); + void updateFixedMarkPassing(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet, Integer indexOfWaypoint, + Date dateOfMarkPassing, CompetitorDTO competitorDTO, AsyncCallback callback); + void getActiveFileStorageServiceName(AsyncCallback callback); void inviteCompetitorsForTrackingViaEmail(String serverUrlWithoutTrailingSlash, EventDTO event, 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 b3a01899045..edf735d92c4 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 @@ -1147,12 +1147,24 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c String scoringSchemeHighPointEssOverallDescription(); String timeBetweenRaceStartAndCompetitorStartInSeconds(); String timeBetweenRaceStartAndCompetitorStartInSecondsTooltip(); + String useInternalAlgorithm(); + String editMarkPassings(); + String removeFixedPassings(); + String setFixedPassing(); + String setSuppressedPassing(); + String removeSuppressedPassing(); + String errorRemovingFixedPassing(String message); + String errorSettingFixedPassing(String message); + String errorSuppressingPassing(String message); + String errorRemovingSupressedPassing(String message); + String errorTryingToObtainMarkPassing(String message); + String errorTryingToObtainRaceLogMarkPassingData(String message); String email(); String pleaseEnterAnEmail(); String inviteCompetitors(); String notAllCompetitorsProvideEmail(); String sendingMailsFailed(); - String sendingMailsSuccessfull(); + String sendingMailsSuccessful(); String selectEventForInvitation(); String imageURL(); String upload(); @@ -1196,4 +1208,6 @@ public interface StringMessages extends com.sap.sse.gwt.client.StringMessages, c String dashboardTeam(); String dashboardDistanceToLineInM(); String dashboardSpeedInKts(); + String fixedMarkPassing(); + String suppressedMarkPassing(); } 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 02f775c48d3..741f565a315 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 @@ -1127,12 +1127,24 @@ active=Active scoringSchemeHighPointEssOverallDescription=Score in points. Winner of an act scores 10 points, 2nd - 9 points, .... Any tie in the overall Extreme Sailing Series is broken in favour of the competitor with the most Act wins. If that does not break the tie, the result in the last Act will be used. timeBetweenRaceStartAndCompetitorStartInSeconds=Start delay timeBetweenRaceStartAndCompetitorStartInSecondsTooltip=Time between the start of the race and the time the competitor passed the start waypoint. Negative time suggests OCS. +useInternalAlgorithm=Use internal mark passing algorithm +editMarkPassings=Edit Mark Passings +removeFixedPassings=Remove fixed mark passing +setFixedPassing=Set time as mark passing +setSuppressedPassing=Suppress mark passing starting with current +removeSuppressedPassing=Remove suppressed mark passing +errorRemovingFixedPassing=Error removing fixed mark passing: {0} +errorSettingFixedPassing=Error setting fixed mark passing: {0} +errorSuppressingPassing=Error suppressing mark passings: {0} +errorRemovingSupressedPassing=Error removing suppressed mark passings: {0} +errorTryingToObtainMarkPassing=Error trying to obtain mark passings: {0} +errorTryingToObtainRaceLogMarkPassingData=Error trying to obtain race log mark passing data: {0} email=E-Mail pleaseEnterAnEmail=Please enter an E-Mail. inviteCompetitors=Invite competitors notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail sendingMailsFailed=Sending Mails failed -sendingMailsSuccessfull=Successfully sent E-Mails +sendingMailsSuccessful=Successfully sent E-Mails selectEventForInvitation=Select Event for Invitation imageURL=Image URL upload=Upload @@ -1173,3 +1185,5 @@ dashboardRankAtFirstMark=Rank at first mark dashboardTeam=Team dashboardDistanceToLineInM=Distance to line in m dashboardSpeedInKts=Speed in kts +fixedMarkPassing=(fixed) +suppressedMarkPassing=(suppressed) \ 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 4a76857da88..98702c89693 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 @@ -1117,12 +1117,24 @@ active=Aktiv scoringSchemeHighPointEssOverallDescription=High Point System, der Gewinner eines Acts erhält 10 Punkte, der Zweite 9 und so weiter. timeBetweenRaceStartAndCompetitorStartInSeconds=Startverzug timeBetweenRaceStartAndCompetitorStartInSecondsTooltip=Zeit seit dem Start der Wettfahrt, nach der der Teilnehmer die Startmarke passiert hat. Negative Werte suggerieren einen Frühstart. +useInternalAlgorithmus=Internen Algorithmus für Tonnenumrundungen verwenden +editMarkPassings=Tonnenumrundungen editieren +removeFixedPassings=Gesetze Umrundungszeit entfernen +setFixedPassing=Zeit als Umrundungszeit setzen +setSuppressedPassing=Umrundungen unterdrücken +removeSuppressedPassing=Unterdrückte Umrundungszeiten entfernen +errorRemovingFixedPassing=Fehler beim Entfernen der gesetzten Umrundung: {0} +errorSettingFixedPassing=Fehler beim Setzen der Umrundungszeit: {0} +errorSuppressingPassing=Fehler beim Unterdrücken von Umrundungen: {0} +errorRemovingSupressedPassing=Fehler beim Entfernen von unterdrückten Umrundungen: {0} +errorTryingToObtainMarkPassing=Fehler beim Laden der Umrundungszeiten: {0} +errorTryingToObtainRaceLogMarkPassingData=Fehler beim Laden der Umrundungsinformation aus dem RaceLog: {0} email=E-Mail pleaseEnterAnEmail=Bitte geben Sie eine E-Mail ein. inviteCompetitors=Teilnehmer einladen notAllCompetitorsProvideEmail=Nicht alle Teilnehmer haben eine E-Mail angegeben sendingMailsFailed=Versenden der E-Mails fehlgeschlagen -sendingMailsSuccessfull=Versenden der E-Mails war erfolgreich! +sendingMailsSuccessful=Versenden der E-Mails war erfolgreich! selectEventForInvitation=Event für Einladung auswählen imageURL=Bild-URL upload=Hochladen @@ -1163,3 +1175,5 @@ dashboardRankAtFirstMark=Position an erster Bahnmarke dashboardTeam=Team dashboardDistanceToLineInM=Distanz zur Linie in m dashboardSpeedInKts=Geschwindigkeit in kts +fixedMarkPassing=(fest) +suppressedMarkPassing=(unterdrückt) \ 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_ru.properties b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties index 9de11cb83a4..b83373c7142 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/StringMessages_ru.properties @@ -1066,12 +1066,24 @@ active=Active scoringSchemeHighPointEssOverallDescription=Score in points. Winner of an act scores 10 points, 2nd - 9 points, .... Any tie in the overall Extreme Sailing Series is broken in favour of the competitor with the most Act wins. If that does not break the tie, the result in the last Act will be used. timeBetweenRaceStartAndCompetitorStartInSeconds=Start delay timeBetweenRaceStartAndCompetitorStartInSecondsTooltip=Time between the start of the race and the time the competitor passed the start waypoint. Negative time suggests OCS. +useInternalAlgorithm=Use internal mark passing algorithm +editMarkPassings=Edit Mark Passings +removeFixedPassings=Remove fixed mark passing +setFixedPassing=Set time as mark passing +setSuppressedPassing=Suppress mark passing starting with current +removeSuppressedPassing=Remove suppressed mark passing +errorRemovingFixedPassing=Error removing fixed mark passing: {0} +errorSettingFixedPassing=Error setting fixed mark passing: {0} +errorSuppressingPassing=Error suppressing mark passings: {0} +errorRemovingSupressedPassing=Error removing suppressed mark passings: {0} +errorTryingToObtainMarkPassing=Error trying to obtain mark passings: {0} +errorTryingToObtainRaceLogMarkPassingData=Error trying to obtain race log mark passing data: {0} email=�маль pleaseEnterAnEmail=Пожалуй�та, введите �маль inviteCompetitors=приглашать Уча�тники notAllCompetitorsProvideEmail=Not all competitors provide an E-Mail sendingMailsFailed=Sending Mails failed -sendingMailsSuccessfull=Successfully sent E-Mails +sendingMailsSuccessful=Successfully sent E-Mails selectEventForInvitation=Select Event for Invitation imageURL=Image URL upload=Upload @@ -1112,3 +1124,5 @@ dashboardRankAtFirstMark=Rank at first mark dashboardTeam=Team dashboardDistanceToLineInM=Distance to line in m dashboardSpeedInKts=Speed in kts +fixedMarkPassing=(fixed) +suppressedMarkPassing=(suppressed) \ No newline at end of file diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java new file mode 100644 index 00000000000..2b2714aadab --- /dev/null +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/client/shared/charts/EditMarkPassingsPanel.java @@ -0,0 +1,468 @@ +package com.sap.sailing.gwt.ui.client.shared.charts; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.google.gwt.cell.client.AbstractCell; +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.safehtml.shared.SafeHtml; +import com.google.gwt.safehtml.shared.SafeHtmlBuilder; +import com.google.gwt.user.cellview.client.CellTable; +import com.google.gwt.user.cellview.client.Column; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.AbsolutePanel; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.HorizontalPanel; +import com.google.gwt.user.client.ui.VerticalPanel; +import com.google.gwt.user.client.ui.Widget; +import com.google.gwt.view.client.ListDataProvider; +import com.google.gwt.view.client.SelectionChangeEvent; +import com.google.gwt.view.client.SelectionChangeEvent.Handler; +import com.google.gwt.view.client.SingleSelectionModel; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.domain.common.dto.LeaderboardDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionChangeListener; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionProvider; +import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.shared.components.Component; +import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialogComponent; +import com.sap.sailing.gwt.ui.shared.RaceCourseDTO; +import com.sap.sailing.gwt.ui.shared.WaypointDTO; +import com.sap.sse.common.Util; +import com.sap.sse.common.Util.Pair; +import com.sap.sse.common.filter.Filter; +import com.sap.sse.common.filter.FilterSet; +import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.async.AsyncAction; +import com.sap.sse.gwt.client.async.AsyncActionsExecutor; +import com.sap.sse.gwt.client.player.Timer; + +public class EditMarkPassingsPanel extends AbsolutePanel implements Component, RaceSelectionChangeListener, + CompetitorSelectionChangeListener { + + private static class AnchorCell extends AbstractCell { + @Override + public void render(com.google.gwt.cell.client.Cell.Context context, SafeHtml safeHtml, SafeHtmlBuilder sb) { + sb.append(safeHtml); + } + } + + private final SailingServiceAsync sailingService; + private RegattaAndRaceIdentifier raceIdentifier; + private final AsyncActionsExecutor asyncExecutor; + private final ErrorReporter errorReporter; + private final StringMessages stringMessages; + private final CompetitorSelectionProvider competitorSelectionModel; + private String leaderboardName; + private RaceColumnDTO column; + + private CompetitorDTO competitor; + + /** + * The fixed mark passings; keys are zero-based waypoint indexes + */ + private Map currentCompetitorEdits = new HashMap<>(); + + private Integer zeroBasedIndexOfFirstSuppressedWaypoint; + + /** + * The waypoints table uses pairs of zero-based waypoint numbers and current passing times. + */ + private final CellTable> wayPointSelectionTable; + private final ListDataProvider> waypointList; + private final SingleSelectionModel> waypointSelectionModel; + private List currentWaypoints; + + private final Button setTimeAsMarkPassingsButton; + private final Button removeFixedMarkPassingsButton; + private final Button suppressPassingsButton; + private final Button removeSuppressedPassingButton; + + public EditMarkPassingsPanel(final SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, + final RegattaAndRaceIdentifier raceIdentifier, final StringMessages stringMessages, + final CompetitorSelectionProvider competitorSelectionModel, final ErrorReporter errorReporter, + final Timer timer) { + this.sailingService = sailingService; + this.raceIdentifier = raceIdentifier; + this.asyncExecutor = asyncActionsExecutor; + this.errorReporter = errorReporter; + this.competitorSelectionModel = competitorSelectionModel; + this.stringMessages = stringMessages; + competitorSelectionModel.addCompetitorSelectionChangeListener(this); + + setVisible(false); + + // Waypoint list + currentWaypoints = new ArrayList<>(); + waypointList = new ListDataProvider<>(); + waypointSelectionModel = new SingleSelectionModel>(); + waypointSelectionModel.addSelectionChangeHandler(new Handler() { + @Override + public void onSelectionChange(SelectionChangeEvent event) { + Pair selectedObject = waypointSelectionModel.getSelectedObject(); + if (selectedObject != null) { + Date timePoint = selectedObject.getB(); + if (timePoint != null) { + timer.setTime(timePoint.getTime()); + } + } + enableButtons(); + } + }); + wayPointSelectionTable = new CellTable>(); + wayPointSelectionTable.addColumn(new Column, SafeHtml>(new AnchorCell()) { + @Override + public SafeHtml getValue(final Util.Pair object) { + return new SafeHtml() { + private static final long serialVersionUID = 1L; + + @Override + public String asString() { + return currentWaypoints.get(object.getA()).getName(); + } + }; + } + }, stringMessages.waypoint()); + final DateTimeFormat timeFormat = DateTimeFormat.getFormat("HH:mm:ss"); + wayPointSelectionTable.addColumn(new Column, SafeHtml>(new AnchorCell()) { + @Override + public SafeHtml getValue(final Pair object) { + final Date date; + date = object.getB(); + return new SafeHtml() { + private static final long serialVersionUID = 1L; + + @Override + public String asString() { + // TODO this is really unclean (Problem: no calendar) + String string; + if (date != null) { + string = timeFormat.format(date); + if (currentCompetitorEdits.containsKey(object.getA())) { + string += " "+stringMessages.fixedMarkPassing(); + } + } else if (zeroBasedIndexOfFirstSuppressedWaypoint != null + && !(object.getA() < zeroBasedIndexOfFirstSuppressedWaypoint)) { + string = " "+stringMessages.suppressedMarkPassing(); + } else { + string = ""; + } + return string; + } + }; + } + }, stringMessages.markPassing()); + + waypointList.addDataDisplay(wayPointSelectionTable); + wayPointSelectionTable.setSelectionModel(waypointSelectionModel); + + // Buttons for fixing + removeFixedMarkPassingsButton = new Button(stringMessages.removeFixedPassings()); + removeFixedMarkPassingsButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + asyncExecutor.execute(new AsyncAction() { + @Override + public void execute(AsyncCallback callback) { + sailingService.updateFixedMarkPassing(leaderboardName, column, column.getFleet(raceIdentifier), + waypointSelectionModel.getSelectedObject().getA(), null, competitor, callback); + } + }, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(stringMessages.errorRemovingFixedPassing(caught.getMessage())); + } + + @Override + public void onSuccess(Void result) { + refillList(); + } + }); + } + }); + setTimeAsMarkPassingsButton = new Button(stringMessages.setFixedPassing()); + setTimeAsMarkPassingsButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + asyncExecutor.execute(new AsyncAction() { + @Override + public void execute(AsyncCallback callback) { + sailingService.updateFixedMarkPassing(leaderboardName, column, column.getFleet(raceIdentifier), + waypointSelectionModel.getSelectedObject().getA(), timer.getTime(), competitor, + callback); + } + }, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(stringMessages.errorSettingFixedPassing(caught.getMessage())); + } + + @Override + public void onSuccess(Void result) { + refillList(); + } + }); + } + }); + + // Button for suppressing + suppressPassingsButton = new Button(stringMessages.setSuppressedPassing()); + suppressPassingsButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + asyncExecutor.execute(new AsyncAction() { + @Override + public void execute(AsyncCallback callback) { + sailingService.updateSuppressedMarkPassings(leaderboardName, column, + column.getFleet(raceIdentifier), waypointSelectionModel.getSelectedObject().getA(), + competitor, callback); + } + }, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(stringMessages.errorSuppressingPassing(caught.getMessage())); + } + + @Override + public void onSuccess(Void result) { + refillList(); + } + }); + } + }); + + removeSuppressedPassingButton = new Button(stringMessages.removeSuppressedPassing()); + removeSuppressedPassingButton.addClickHandler(new ClickHandler() { + @Override + public void onClick(ClickEvent event) { + asyncExecutor.execute(new AsyncAction() { + @Override + public void execute(AsyncCallback callback) { + sailingService.updateSuppressedMarkPassings(leaderboardName, column, + column.getFleet(raceIdentifier), null, competitor, callback); + } + }, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(stringMessages.errorRemovingSupressedPassing(caught.getMessage())); + } + + @Override + public void onSuccess(Void result) { + refillList(); + } + }); + } + }); + refreshWaypoints(); + HorizontalPanel tableAndButtons = new HorizontalPanel(); + add(tableAndButtons, 0, 0); + tableAndButtons.setSpacing(3); + tableAndButtons.add(wayPointSelectionTable); + VerticalPanel buttonPanel = new VerticalPanel(); + buttonPanel.setSpacing(3); + tableAndButtons.add(buttonPanel); + buttonPanel.add(setTimeAsMarkPassingsButton); + buttonPanel.add(removeFixedMarkPassingsButton); + buttonPanel.add(suppressPassingsButton); + buttonPanel.add(removeSuppressedPassingButton); + enableButtons(); + } + + @Override + public void addedToSelection(CompetitorDTO competitor) { + processCompetitorSelectionChange(); + } + + @Override + public void removedFromSelection(CompetitorDTO competitor) { + processCompetitorSelectionChange(); + } + + private void processCompetitorSelectionChange() { + waypointSelectionModel.clear(); + if (Util.size(competitorSelectionModel.getSelectedCompetitors()) == 1) { + refillList(); + } else { + disableEditing(); + } + } + + private void disableEditing() { + waypointList.getList().clear(); + clearInfo(); + } + + private void refillList() { + clearInfo(); + competitor = competitorSelectionModel.getSelectedCompetitors().iterator().next(); + // Get current mark passings + asyncExecutor.execute(new AsyncAction>() { + @Override + public void execute(AsyncCallback> callback) { + sailingService.getCompetitorMarkPassings(raceIdentifier, competitor, callback); + } + }, new AsyncCallback>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(stringMessages.errorTryingToObtainMarkPassing(caught.getMessage())); + } + + @Override + public void onSuccess(Map result) { + List> newMarkPassings = new ArrayList<>(); + for (WaypointDTO waypoint : currentWaypoints) { + int index = currentWaypoints.indexOf(waypoint); + newMarkPassings.add(new Util.Pair(index, result.get(index))); + } + waypointList.getList().clear(); + waypointList.getList().addAll(newMarkPassings); + } + }); + // Get current edits + asyncExecutor.execute(new AsyncAction>() { + @Override + public void execute(AsyncCallback> callback) { + sailingService.getCompetitorRaceLogMarkPassingData(leaderboardName, column, + column.getFleet(raceIdentifier), competitor, callback); + } + }, new AsyncCallback>() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(stringMessages.errorTryingToObtainRaceLogMarkPassingData(caught.getMessage())); + } + + @Override + public void onSuccess(Map result) { + for (Entry data : result.entrySet()) { + if (data.getValue() == null) { + zeroBasedIndexOfFirstSuppressedWaypoint = data.getKey(); + } else { + currentCompetitorEdits.put(data.getKey(), data.getValue()); + } + } + enableButtons(); + wayPointSelectionTable.redraw(); + } + }); + } + + /** + * Removes all competitor selection-specific data, particularly the fixed and suppressed mark + * passings and the reference to the competitor selected. + */ + private void clearInfo() { + currentCompetitorEdits.clear(); + competitor = null; + zeroBasedIndexOfFirstSuppressedWaypoint = null; + } + + private void refreshWaypoints() { + asyncExecutor.execute(new AsyncAction() { + @Override + public void execute(AsyncCallback callback) { + sailingService.getRaceCourse(raceIdentifier, new Date(), callback); + } + }, new AsyncCallback() { + @Override + public void onFailure(Throwable caught) { + errorReporter.reportError(stringMessages.errorTryingToObtainRaceCourse(caught.getMessage())); + } + + @Override + public void onSuccess(RaceCourseDTO result) { + currentWaypoints = result.waypoints; + } + }); + } + + private void enableButtons() { + setTimeAsMarkPassingsButton.setEnabled(false); + removeFixedMarkPassingsButton.setEnabled(false); + suppressPassingsButton.setEnabled(false); + removeSuppressedPassingButton.setEnabled(false); + if (Util.size(competitorSelectionModel.getSelectedCompetitors()) == 1) { + if (zeroBasedIndexOfFirstSuppressedWaypoint != null) { + removeSuppressedPassingButton.setEnabled(true); + } + Pair selectedWaypoint = waypointSelectionModel.getSelectedObject(); + if (selectedWaypoint != null) { + setTimeAsMarkPassingsButton.setEnabled(true); + suppressPassingsButton.setEnabled(true); + if (currentCompetitorEdits.containsKey(selectedWaypoint.getA())) { + removeFixedMarkPassingsButton.setEnabled(true); + } + } + } + } + + @Override + public void onRaceSelectionChange(List selectedRaces) { + raceIdentifier = selectedRaces.iterator().next(); + refreshWaypoints(); + } + + public void setLeaderboardNameAndColumn(LeaderboardDTO leaderboard) { + if (leaderboard != null) { + leaderboardName = leaderboard.name; + for (RaceColumnDTO columnDTO : leaderboard.getRaceList()) { + if (columnDTO.containsRace(raceIdentifier)) { + column = columnDTO; + } + } + } + } + + @Override + public Widget getEntryWidget() { + return this; + } + + @Override + public boolean hasSettings() { + return false; + } + + @Override + public SettingsDialogComponent getSettingsDialogComponent() { + return null; + } + + @Override + public void updateSettings(Void newSettings) { + } + + @Override + public String getLocalizedShortName() { + return null; + } + + @Override + public String getDependentCssClassName() { + return null; + } + + @Override + public void filterChanged(FilterSet> oldFilterSet, + FilterSet> newFilterSet) { + } + + @Override + public void competitorsListChanged(Iterable competitors) { + } + + @Override + public void filteredCompetitorsListChanged(Iterable filteredCompetitors) { + } + +} diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java index 3cfcf4c1124..ffcd2a51caf 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardEntryPoint.java @@ -19,7 +19,7 @@ import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.Widget; import com.sap.sailing.domain.common.DetailType; import com.sap.sailing.domain.common.LeaderboardType; -import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.domain.common.RegattaNameAndRaceName; import com.sap.sailing.domain.common.dto.AbstractLeaderboardDTO; import com.sap.sailing.gwt.ui.client.AbstractSailingEntryPoint; @@ -148,7 +148,7 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { } ScrollPanel contentScrollPanel = new ScrollPanel(); long delayBetweenAutoAdvancesInMilliseconds = 3000l; - final RaceIdentifier preselectedRace = getPreselectedRace(Window.Location.getParameterMap()); + final RegattaAndRaceIdentifier preselectedRace = getPreselectedRace(Window.Location.getParameterMap()); // make a single live request as the default but don't continue to play by default Timer timer = new Timer(PlayModes.Live, PlayStates.Paused, delayBetweenAutoAdvancesInMilliseconds); final LeaderboardSettings leaderboardSettings = createLeaderboardSettingsFromURLParameters(Window.Location.getParameterMap()); @@ -182,8 +182,8 @@ public class LeaderboardEntryPoint extends AbstractSailingEntryPoint { mainPanel.add(contentScrollPanel); } - private RaceIdentifier getPreselectedRace(Map> parameterMap) { - RaceIdentifier result; + private RegattaAndRaceIdentifier getPreselectedRace(Map> parameterMap) { + RegattaAndRaceIdentifier result; if (parameterMap.containsKey(LeaderboardUrlSettings.PARAM_RACE_NAME) && parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME).size() == 1 && parameterMap.containsKey(LeaderboardUrlSettings.PARAM_REGATTA_NAME) && parameterMap.get(LeaderboardUrlSettings.PARAM_REGATTA_NAME).size() == 1) { result = new RegattaNameAndRaceName(parameterMap.get(LeaderboardUrlSettings.PARAM_REGATTA_NAME).get(0), parameterMap.get(LeaderboardUrlSettings.PARAM_RACE_NAME).get(0)); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java index 8123e58ad50..df90a69666a 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardPanel.java @@ -1655,7 +1655,7 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } public LeaderboardPanel(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, - LeaderboardSettings settings, boolean isEmbedded, RaceIdentifier preSelectedRace, + LeaderboardSettings settings, boolean isEmbedded, RegattaAndRaceIdentifier preSelectedRace, CompetitorSelectionProvider competitorSelectionProvider, String leaderboardGroupName, String leaderboardName, ErrorReporter errorReporter, final StringMessages stringMessages, final UserAgentDetails userAgent, boolean showRaceDetails) { @@ -1668,9 +1668,9 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS } public LeaderboardPanel(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, - LeaderboardSettings settings, boolean isEmbedded, RaceIdentifier preSelectedRace, + LeaderboardSettings settings, boolean isEmbedded, RegattaAndRaceIdentifier preSelectedRace, CompetitorSelectionProvider competitorSelectionProvider, Timer timer, String leaderboardGroupName, - String leaderboardName, ErrorReporter errorReporter, final StringMessages stringMessages, + String leaderboardName, final ErrorReporter errorReporter, final StringMessages stringMessages, final UserAgentDetails userAgent, boolean showRaceDetails, CompetitorFilterPanel competitorSearchTextBox, boolean showSelectionCheckbox, RaceTimesInfoProvider optionalRaceTimesInfoProvider, boolean autoExpandLastRaceColumn, boolean adjustTimerDelay, boolean autoApplyTopNFilter, boolean showCompetitorFilterStatus) { @@ -1781,9 +1781,10 @@ public class LeaderboardPanel extends SimplePanel implements TimeListener, PlayS contentPanel = new VerticalPanel(); contentPanel.setStyleName(STYLE_LEADERBOARD_CONTENT); - busyIndicator = new SimpleBusyIndicator(false, 0.8f); busyIndicator.ensureDebugId("BusyIndicator"); + + // the information panel if (!isEmbedded) { Widget toolbarPanel = createToolbarPanel(); diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardViewer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardViewer.java index 20d2bf1248d..bf8fd369748 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardViewer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/LeaderboardViewer.java @@ -6,7 +6,7 @@ import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.FlowPanel; import com.sap.sailing.domain.common.DetailType; -import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -27,7 +27,7 @@ public class LeaderboardViewer extends AbstractLeaderboardViewer { private LeaderboardPanel overallLeaderboardPanel; public LeaderboardViewer(final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor, - final Timer timer, final LeaderboardSettings leaderboardSettings, final RaceIdentifier preselectedRace, + final Timer timer, final LeaderboardSettings leaderboardSettings, final RegattaAndRaceIdentifier preselectedRace, final String leaderboardGroupName, String leaderboardName, final ErrorReporter errorReporter, final StringMessages stringMessages, final UserAgentDetails userAgent, boolean showRaceDetails, boolean hideToolbar, boolean autoExpandLastRaceColumn, boolean showCharts, DetailType chartDetailType, boolean showOverallLeaderboard) { @@ -39,7 +39,7 @@ public class LeaderboardViewer extends AbstractLeaderboardViewer { private LeaderboardViewer(CompetitorSelectionModel competitorSelectionModel, final SailingServiceAsync sailingService, final AsyncActionsExecutor asyncActionsExecutor, - final Timer timer, final LeaderboardSettings leaderboardSettings, final RaceIdentifier preselectedRace, + final Timer timer, final LeaderboardSettings leaderboardSettings, final RegattaAndRaceIdentifier preselectedRace, final String leaderboardGroupName, String leaderboardName, final ErrorReporter errorReporter, final StringMessages stringMessages, final UserAgentDetails userAgent, boolean showRaceDetails, boolean hideToolbar, boolean autoExpandLastRaceColumn, boolean showCharts, DetailType chartDetailType, diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java index 1c9a3b6a772..513cb150fef 100644 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/leaderboard/MetaLeaderboardViewer.java @@ -4,7 +4,7 @@ import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Label; import com.sap.sailing.domain.common.DetailType; -import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; import com.sap.sailing.gwt.ui.client.SailingServiceAsync; import com.sap.sailing.gwt.ui.client.StringMessages; @@ -27,7 +27,7 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer { public MetaLeaderboardViewer(SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, - Timer timer, LeaderboardSettings leaderboardSettings, String preselectedLeaderboardName, RaceIdentifier preselectedRace, + Timer timer, LeaderboardSettings leaderboardSettings, String preselectedLeaderboardName, RegattaAndRaceIdentifier preselectedRace, String leaderboardGroupName, String metaLeaderboardName, ErrorReporter errorReporter, StringMessages stringMessages, UserAgentDetails userAgent, boolean showRaceDetails, boolean hideToolbar, boolean autoExpandLastRaceColumn, boolean showCharts, DetailType chartDetailType, boolean showSeriesLeaderboards) { @@ -39,7 +39,7 @@ public class MetaLeaderboardViewer extends AbstractLeaderboardViewer { private MetaLeaderboardViewer(CompetitorSelectionModel competitorSelectionModel, SailingServiceAsync sailingService, AsyncActionsExecutor asyncActionsExecutor, Timer timer, LeaderboardSettings leaderboardSettings, - String preselectedLeaderboardName, RaceIdentifier preselectedRace, String leaderboardGroupName, + String preselectedLeaderboardName, RegattaAndRaceIdentifier preselectedRace, String leaderboardGroupName, String metaLeaderboardName, ErrorReporter errorReporter, StringMessages stringMessages, UserAgentDetails userAgent, boolean showRaceDetails, boolean hideToolbar, boolean autoExpandLastRaceColumn, boolean showCharts, DetailType chartDetailType, boolean showSeriesLeaderboards) { diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java index 22528da56dc..3f28b71bcaf 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/RaceBoardPanel.java @@ -1,442 +1,450 @@ -package com.sap.sailing.gwt.ui.raceboard; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.google.gwt.core.shared.GWT; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Style.Unit; -import com.google.gwt.i18n.client.DateTimeFormat; -import com.google.gwt.user.client.Command; -import com.google.gwt.user.client.Window; -import com.google.gwt.user.client.ui.Anchor; -import com.google.gwt.user.client.ui.FlowPanel; -import com.google.gwt.user.client.ui.Label; -import com.google.gwt.user.client.ui.MenuBar; -import com.google.gwt.user.client.ui.SimplePanel; -import com.google.gwt.user.client.ui.UIObject; -import com.sap.sailing.domain.common.LeaderboardNameConstants; -import com.sap.sailing.domain.common.RaceIdentifier; -import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; -import com.sap.sailing.domain.common.dto.CompetitorDTO; -import com.sap.sailing.domain.common.dto.FleetDTO; -import com.sap.sailing.domain.common.dto.LeaderboardDTO; -import com.sap.sailing.domain.common.dto.RaceColumnDTO; -import com.sap.sailing.domain.common.dto.RaceDTO; -import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; -import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory; -import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel; -import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener; -import com.sap.sailing.gwt.ui.client.MediaServiceAsync; -import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; -import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; -import com.sap.sailing.gwt.ui.client.RaceTimePanel; -import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; -import com.sap.sailing.gwt.ui.client.RegattasDisplayer; -import com.sap.sailing.gwt.ui.client.SailingServiceAsync; -import com.sap.sailing.gwt.ui.client.StringMessages; -import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent; -import com.sap.sailing.gwt.ui.client.media.PopupPositionProvider; -import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart; -import com.sap.sailing.gwt.ui.client.shared.charts.WindChart; -import com.sap.sailing.gwt.ui.client.shared.charts.WindChartSettings; -import com.sap.sailing.gwt.ui.client.shared.components.Component; -import com.sap.sailing.gwt.ui.client.shared.components.ComponentViewer; -import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; -import com.sap.sailing.gwt.ui.client.shared.filter.FilterWithUI; -import com.sap.sailing.gwt.ui.client.shared.filter.LeaderboardFetcher; -import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap; -import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapResources; -import com.sap.sailing.gwt.ui.leaderboard.CompetitorFilterPanel; -import com.sap.sailing.gwt.ui.leaderboard.ExplicitRaceColumnSelectionWithPreselectedRace; -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; -import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory; -import com.sap.sailing.gwt.ui.shared.EventDTO; -import com.sap.sailing.gwt.ui.shared.RegattaDTO; -import com.sap.sse.common.filter.FilterSet; -import com.sap.sse.gwt.client.ErrorReporter; -import com.sap.sse.gwt.client.URLEncoder; -import com.sap.sse.gwt.client.async.AsyncActionsExecutor; -import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel; -import com.sap.sse.gwt.client.player.Timer; -import com.sap.sse.gwt.client.useragent.UserAgentDetails; -import com.sap.sse.security.ui.client.UserService; - -/** - * A view showing a list of components visualizing a race from the regattas announced by calls to {@link #fillRegattas(List)}. - * The race selection is provided by a {@link RaceSelectionProvider} for which this is a {@link RaceSelectionChangeListener listener}. - * {@link RaceIdentifier}-based race selection changes are converted to {@link RaceDTO} objects using the {@link #racesByIdentifier} - * map maintained during {@link #fillRegattas(List)}. The race selection provider is expected to be single selection only. - * - * @author Frank Mittag, Axel Uhl (d043530) - * - */ -public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, RaceSelectionChangeListener, LeaderboardUpdateListener, PopupPositionProvider { - private final SailingServiceAsync sailingService; - private final MediaServiceAsync mediaService; - private final EventDTO event; - private final StringMessages stringMessages; - private final ErrorReporter errorReporter; - private final RaceBoardViewConfiguration raceboardViewConfiguration; - private String raceBoardName; - - /** - * Updated upon each {@link #fillRegattas(List)} - */ - private final Map racesByIdentifier; - - private final List componentViewers; - private RaceTimePanel timePanel; - private final Timer timer; - private final RaceSelectionProvider raceSelectionProvider; - private final UserAgentDetails userAgent; - private final CompetitorSelectionModel competitorSelectionModel; - private final TimeRangeWithZoomModel timeRangeWithZoomModel; - private final RegattaAndRaceIdentifier selectedRaceIdentifier; - - private final String leaderboardName; - private final LeaderboardPanel leaderboardPanel; - private WindChart windChart; - private MultiCompetitorRaceChart competitorChart; - - /** - * The component viewer in ONESCREEN view mode. null if in CASCADE view mode - */ - private SideBySideComponentViewer leaderboardAndMapViewer; - - private final AsyncActionsExecutor asyncActionsExecutor; - - private final RaceTimesInfoProvider raceTimesInfoProvider; - private RaceMap raceMap; - - private final FlowPanel raceInformationHeader; - private final FlowPanel regattaAndRaceTimeInformationHeader; - private boolean currentRaceHasBeenSelectedOnce; - - private static final RaceMapResources raceMapResources = GWT.create(RaceMapResources.class); - - /** - * @param event - * an optional event that can be used for "back"-navigation in case the race board shows a race in the - * context of an event; may be null. - */ - public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService, - UserService userService, AsyncActionsExecutor asyncActionsExecutor, - Timer timer, RaceSelectionProvider theRaceSelectionProvider, String leaderboardName, - String leaderboardGroupName, EventDTO event, RaceBoardViewConfiguration raceboardViewConfiguration, - ErrorReporter errorReporter, final StringMessages stringMessages, - UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls) { - this.sailingService = sailingService; - this.mediaService = mediaService; - this.stringMessages = stringMessages; - this.raceboardViewConfiguration = raceboardViewConfiguration; - this.raceSelectionProvider = theRaceSelectionProvider; - this.raceTimesInfoProvider = raceTimesInfoProvider; - this.errorReporter = errorReporter; - this.userAgent = userAgent; - this.timer = timer; - this.event = event; - this.currentRaceHasBeenSelectedOnce = false; - this.leaderboardName = leaderboardName; - raceSelectionProvider.addRaceSelectionChangeListener(this); - racesByIdentifier = new HashMap(); - selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next(); - this.setRaceBoardName(selectedRaceIdentifier.getRaceName()); - this.asyncActionsExecutor = asyncActionsExecutor; - FlowPanel mainPanel = new FlowPanel(); - mainPanel.setSize("100%", "100%"); - setWidget(mainPanel); - raceInformationHeader = new FlowPanel(); - raceInformationHeader.setStyleName("RegattaRaceInformation-Header"); - regattaAndRaceTimeInformationHeader = new FlowPanel(); - regattaAndRaceTimeInformationHeader.setStyleName("RegattaAndRaceTime-Header"); - timeRangeWithZoomModel = new TimeRangeWithZoomModel(); - componentViewers = new ArrayList(); - competitorSelectionModel = new CompetitorSelectionModel(/* hasMultiSelection */ true); - - raceMapResources.combinedWindPanelStyle().ensureInjected(); - raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer, - competitorSelectionModel, stringMessages, showMapControls, getConfiguration().isShowViewStreamlets(), getConfiguration().isShowViewSimulation(), selectedRaceIdentifier, raceMapResources.combinedWindPanelStyle()); - CompetitorFilterPanel competitorSearchTextBox = new CompetitorFilterPanel(competitorSelectionModel, stringMessages, raceMap, - new LeaderboardFetcher() { - @Override - public LeaderboardDTO getLeaderboard() { - return leaderboardPanel.getLeaderboard(); - } - }, selectedRaceIdentifier); - raceMap.getLeftHeaderPanel().add(raceInformationHeader); - raceMap.getRightHeaderPanel().add(regattaAndRaceTimeInformationHeader); - - leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName, competitorSearchTextBox); - leaderboardPanel.setTitle(stringMessages.leaderboard()); - leaderboardPanel.getElement().getStyle().setMarginLeft(6, Unit.PX); - leaderboardPanel.getElement().getStyle().setMarginTop(4, Unit.PX); - createOneScreenView(leaderboardName, leaderboardGroupName, event, mainPanel, showMapControls, raceMap, userService); // initializes the raceMap field - leaderboardPanel.addLeaderboardUpdateListener(this); - // in case the URL configuration contains the name of a competitors filter set we try to activate it - // FIXME the competitorsFilterSets has now moved to CompetitorSearchTextBox (which should probably be renamed); pass on the parameters to the LeaderboardPanel and see what it does with it - if (raceboardViewConfiguration.getActiveCompetitorsFilterSetName() != null) { - for (FilterSet> filterSet : competitorSearchTextBox.getCompetitorsFilterSets() - .getFilterSets()) { - if (filterSet.getName().equals(raceboardViewConfiguration.getActiveCompetitorsFilterSetName())) { - competitorSearchTextBox.getCompetitorsFilterSets().setActiveFilterSet(filterSet); - break; - } - } - } - timePanel = new RaceTimePanel(timer, timeRangeWithZoomModel, stringMessages, raceTimesInfoProvider, - raceboardViewConfiguration.isCanReplayDuringLiveRaces()); - timeRangeWithZoomModel.addTimeZoomChangeListener(timePanel); - raceTimesInfoProvider.addRaceTimesInfoProviderListener(timePanel); - raceSelectionProvider.addRaceSelectionChangeListener(timePanel); - timePanel.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); - } - - /** - * @param event an optional event; may be null or else can be used to show some context information in - * the {@link GlobalNavigationPanel}. - */ - private void createOneScreenView(String leaderboardName, String leaderboardGroupName, EventDTO event, FlowPanel mainPanel, - boolean showMapControls, RaceMap raceMap, UserService userService) { - // create the default leaderboard and select the right race - raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap); - raceMap.onRaceSelectionChange(Collections.singletonList(selectedRaceIdentifier)); - List> components = new ArrayList>(); - competitorChart = new MultiCompetitorRaceChart(sailingService, asyncActionsExecutor, competitorSelectionModel, raceSelectionProvider, - timer, timeRangeWithZoomModel, stringMessages, errorReporter, true, true, leaderboardGroupName, leaderboardName); - competitorChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); - competitorChart.getEntryWidget().setTitle(stringMessages.competitorCharts()); - competitorChart.setVisible(false); - components.add(competitorChart); - windChart = new WindChart(sailingService, raceSelectionProvider, timer, timeRangeWithZoomModel, new WindChartSettings(), - stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true); - windChart.setVisible(false); - windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); - windChart.getEntryWidget().setTitle(stringMessages.windChart()); - components.add(windChart); - boolean autoSelectMedia = getConfiguration().isAutoSelectMedia(); - MediaPlayerManagerComponent mediaPlayerManagerComponent = new MediaPlayerManagerComponent( - selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, userService, stringMessages, - errorReporter, userAgent, this, autoSelectMedia); - leaderboardAndMapViewer = new SideBySideComponentViewer(leaderboardPanel, raceMap, mediaPlayerManagerComponent, components, stringMessages); - componentViewers.add(leaderboardAndMapViewer); - for (ComponentViewer componentViewer : componentViewers) { - mainPanel.add(componentViewer.getViewerWidget()); - } - boolean showLeaderboard = getConfiguration().isShowLeaderboard(); - if (Document.get().getClientWidth() <= 1024) { - showLeaderboard = false; - } - setLeaderboardVisible(showLeaderboard); - setWindChartVisible(getConfiguration().isShowWindChart()); - setCompetitorChartVisible(getConfiguration().isShowCompetitorsChart()); - // make sure to load leaderboard data for filtering to work - if (!showLeaderboard) { - leaderboardPanel.setVisible(true); - leaderboardPanel.setVisible(false); - } - } - - @SuppressWarnings("unused") - private void addSettingsMenuItem(MenuBar settingsMenu, final Component component) { - if (component.hasSettings()) { - settingsMenu.addItem(component.getLocalizedShortName(), new Command() { - public void execute() { - new SettingsDialog(component, stringMessages).show(); - } - }); - } - } - - private LeaderboardPanel createLeaderboardPanel(String leaderboardName, String leaderboardGroupName, CompetitorFilterPanel competitorSearchTextBox) { - LeaderboardSettings leaderBoardSettings = LeaderboardSettingsFactory.getInstance() - .createNewSettingsForPlayMode(timer.getPlayMode(), - /* nameOfRaceToSort */ selectedRaceIdentifier.getRaceName(), - /* nameOfRaceColumnToShow */ null, /* nameOfRaceToShow */ selectedRaceIdentifier.getRaceName(), - new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier), /* showRegattaRank */ false, - /*showCompetitorSailIdColumn*/true, /*showCompetitorFullNameColumn*/true); - return new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderBoardSettings, selectedRaceIdentifier != null, selectedRaceIdentifier, - competitorSelectionModel, timer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages, - userAgent, /* showRaceDetails */ true, competitorSearchTextBox, - /* showSelectionCheckbox */ true, raceTimesInfoProvider, /* autoExpandLastRaceColumn */ false, - /* don't adjust the timer's delay from the leaderboard; control it solely from the RaceTimesInfoProvider */ false, - /*autoApplyTopNFilter*/ false, /*showCompetitorFilterStatus*/ false); - } - - private void setComponentVisible(ComponentViewer componentViewer, Component component, boolean visible) { - component.setVisible(visible); - componentViewer.forceLayout(); - } - - /** - * Sets the collapsable panel for the leaderboard open or close, if in CASCADE view mode.
- * Displays or hides the leaderboard, if in ONESCREEN view mode.

- * - * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. - * - * @param visible true if the leaderboard shall be open/visible - */ - public void setLeaderboardVisible(boolean visible) { - setComponentVisible(leaderboardAndMapViewer, leaderboardPanel, visible); - } - - /** - * Sets the collapsable panel for the wind chart open or close, if in CASCADE view mode.
- * Displays or hides the wind chart, if in ONESCREEN view mode.

- * - * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. - * - * @param visible true if the wind chart shall be open/visible - */ - public void setWindChartVisible(boolean visible) { - setComponentVisible(leaderboardAndMapViewer, windChart, visible); - } - - /** - * Sets the collapsable panel for the competitor chart open or close, if in CASCADE view mode.
- * Displays or hides the competitor chart, if in ONESCREEN view mode.

- * - * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. - * - * @param visible true if the competitor chart shall be open/visible - */ - public void setCompetitorChartVisible(boolean visible) { - setComponentVisible(leaderboardAndMapViewer, competitorChart, visible); - } - - public RaceTimePanel getTimePanel() { - return timePanel; - } - - protected SailingServiceAsync getSailingService() { - return sailingService; - } - - protected String getRaceBoardName() { - return raceBoardName; - } - - protected void setRaceBoardName(String raceBoardName) { - this.raceBoardName = raceBoardName; - } - - protected ErrorReporter getErrorReporter() { - return errorReporter; - } - - @Override - public void fillRegattas(Iterable regattas) { - racesByIdentifier.clear(); - for (RegattaDTO regatta : regattas) { - for (RaceDTO race : regatta.races) { - if (race != null && race.getRaceIdentifier() != null) { - racesByIdentifier.put(race.getRaceIdentifier(), race); - } - } - } - } - - @Override - public void onRaceSelectionChange(List selectedRaces) { - } - - public RaceBoardViewConfiguration getConfiguration() { - return raceboardViewConfiguration; - } - - @Override - public void updatedLeaderboard(LeaderboardDTO leaderboard) { - leaderboardAndMapViewer.setLeftComponentWidth(leaderboardPanel.getContentPanel().getOffsetWidth()); - } - - @Override - public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) { - if (!currentRaceHasBeenSelectedOnce) { - FleetDTO fleet = raceColumn.getFleet(raceIdentifier); - String seriesName = raceColumn.getSeriesName(); - if (LeaderboardNameConstants.DEFAULT_SERIES_NAME.equals(seriesName)) { - seriesName = ""; - } - String fleetForRaceName = fleet==null?"":fleet.getName(); - if (fleetForRaceName.equals(LeaderboardNameConstants.DEFAULT_FLEET_NAME)) { - fleetForRaceName = ""; - } else { - fleetForRaceName = " - "+fleetForRaceName; - } - final Label raceNameLabel = new Label(stringMessages.race() + " " + raceColumn.getRaceColumnName()); - raceNameLabel.setStyleName("RaceName-Label"); - final Label raceAdditionalInformationLabel = new Label(seriesName + fleetForRaceName); - raceAdditionalInformationLabel.setStyleName("RaceSeriesAndFleet-Label"); - raceInformationHeader.clear(); - raceInformationHeader.add(raceNameLabel); - raceInformationHeader.add(raceAdditionalInformationLabel); - final Anchor regattaNameAnchor = new Anchor(raceIdentifier.getRegattaName()); - if (event != null) { - // we don't use the EntryPointLinkFactory here, because of lacking support for Places - String debugParam = Window.Location.getParameter("gwt.codesvr"); - String link = "/gwt/Home.html"; - if (debugParam != null && !debugParam.isEmpty()) { - link += "?gwt.codesvr=" + debugParam; - } - link += "#EventPlace:eventId="+event.id.toString(); - link += "&navigationTab=Regatta&leaderboardName=" + URLEncoder.encode(leaderboardName); - regattaNameAnchor.setHref(link); - } else { - String leaderboardGroupNameParam = Window.Location.getParameter("leaderboardGroupName"); - if(leaderboardGroupNameParam != null) { - Map leaderboardGroupLinkParameters = new HashMap(); - leaderboardGroupLinkParameters.put("showRaceDetails", "true"); - leaderboardGroupLinkParameters.put("leaderboardGroupName", leaderboardGroupNameParam); - String leaderBoardGroupLink = EntryPointLinkFactory.createLeaderboardGroupLink(leaderboardGroupLinkParameters); - regattaNameAnchor.setHref(leaderBoardGroupLink); - } else { - // fallback - regattaNameAnchor.setHref("javascript:window.history.back();"); - } - } - regattaNameAnchor.setStyleName("RegattaName-Anchor"); - - // TODO: Strange behavior... check -// Window.addResizeHandler(new ResizeHandler() { -// @Override -// public void onResize(ResizeEvent event) { -// int headerPanelWidth = raceMap.getRightHeaderPanel().getOffsetWidth() - 150; // 150px is the width of the sapLogo -// int raceNameAndFleetLabelWidth = raceInformationHeader.getOffsetWidth(); -// int regattaAnchorWidth = regattaNameAnchor.getOffsetWidth(); -// boolean overlap = raceNameAndFleetLabelWidth + regattaAnchorWidth > headerPanelWidth; -// raceInformationHeader.setVisible(!overlap); -// } -// }); - - Label raceTimeLabel = computeRaceInformation(raceColumn, fleet); - raceTimeLabel.setStyleName("RaceTime-Label"); - regattaAndRaceTimeInformationHeader.clear(); - regattaAndRaceTimeInformationHeader.add(regattaNameAnchor); - regattaAndRaceTimeInformationHeader.add(raceTimeLabel); - currentRaceHasBeenSelectedOnce = true; - } - } - - @Override - public UIObject getXPositionUiObject() { - return timePanel; - } - - @Override - public UIObject getYPositionUiObject() { - return timePanel; - } - - private Label computeRaceInformation(RaceColumnDTO raceColumn, FleetDTO fleet) { - Label raceInformationLabel = new Label(); - raceInformationLabel.setStyleName("Race-Time-Label"); - DateTimeFormat formatter = DateTimeFormat.getFormat("E d/M/y"); - raceInformationLabel.setText(formatter.format(raceColumn.getStartDate(fleet))); - return raceInformationLabel; - } -} - +package com.sap.sailing.gwt.ui.raceboard; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gwt.core.shared.GWT; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.i18n.client.DateTimeFormat; +import com.google.gwt.user.client.Command; +import com.google.gwt.user.client.Window; +import com.google.gwt.user.client.ui.Anchor; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.MenuBar; +import com.google.gwt.user.client.ui.SimplePanel; +import com.google.gwt.user.client.ui.UIObject; +import com.sap.sailing.domain.common.LeaderboardNameConstants; +import com.sap.sailing.domain.common.RaceIdentifier; +import com.sap.sailing.domain.common.RegattaAndRaceIdentifier; +import com.sap.sailing.domain.common.dto.CompetitorDTO; +import com.sap.sailing.domain.common.dto.FleetDTO; +import com.sap.sailing.domain.common.dto.LeaderboardDTO; +import com.sap.sailing.domain.common.dto.RaceColumnDTO; +import com.sap.sailing.domain.common.dto.RaceDTO; +import com.sap.sailing.gwt.ui.client.CompetitorSelectionModel; +import com.sap.sailing.gwt.ui.client.EntryPointLinkFactory; +import com.sap.sailing.gwt.ui.client.GlobalNavigationPanel; +import com.sap.sailing.gwt.ui.client.LeaderboardUpdateListener; +import com.sap.sailing.gwt.ui.client.MediaServiceAsync; +import com.sap.sailing.gwt.ui.client.RaceSelectionChangeListener; +import com.sap.sailing.gwt.ui.client.RaceSelectionProvider; +import com.sap.sailing.gwt.ui.client.RaceTimePanel; +import com.sap.sailing.gwt.ui.client.RaceTimesInfoProvider; +import com.sap.sailing.gwt.ui.client.RegattasDisplayer; +import com.sap.sailing.gwt.ui.client.SailingServiceAsync; +import com.sap.sailing.gwt.ui.client.StringMessages; +import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent; +import com.sap.sailing.gwt.ui.client.media.PopupPositionProvider; +import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel; +import com.sap.sailing.gwt.ui.client.shared.charts.MultiCompetitorRaceChart; +import com.sap.sailing.gwt.ui.client.shared.charts.WindChart; +import com.sap.sailing.gwt.ui.client.shared.charts.WindChartSettings; +import com.sap.sailing.gwt.ui.client.shared.components.Component; +import com.sap.sailing.gwt.ui.client.shared.components.ComponentViewer; +import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; +import com.sap.sailing.gwt.ui.client.shared.filter.FilterWithUI; +import com.sap.sailing.gwt.ui.client.shared.filter.LeaderboardFetcher; +import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMap; +import com.sap.sailing.gwt.ui.client.shared.racemap.RaceMapResources; +import com.sap.sailing.gwt.ui.leaderboard.CompetitorFilterPanel; +import com.sap.sailing.gwt.ui.leaderboard.ExplicitRaceColumnSelectionWithPreselectedRace; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettings; +import com.sap.sailing.gwt.ui.leaderboard.LeaderboardSettingsFactory; +import com.sap.sailing.gwt.ui.shared.EventDTO; +import com.sap.sailing.gwt.ui.shared.RegattaDTO; +import com.sap.sse.common.filter.FilterSet; +import com.sap.sse.gwt.client.ErrorReporter; +import com.sap.sse.gwt.client.URLEncoder; +import com.sap.sse.gwt.client.async.AsyncActionsExecutor; +import com.sap.sse.gwt.client.player.TimeRangeWithZoomModel; +import com.sap.sse.gwt.client.player.Timer; +import com.sap.sse.gwt.client.useragent.UserAgentDetails; +import com.sap.sse.security.ui.client.UserService; + +/** + * A view showing a list of components visualizing a race from the regattas announced by calls to {@link #fillRegattas(List)}. + * The race selection is provided by a {@link RaceSelectionProvider} for which this is a {@link RaceSelectionChangeListener listener}. + * {@link RaceIdentifier}-based race selection changes are converted to {@link RaceDTO} objects using the {@link #racesByIdentifier} + * map maintained during {@link #fillRegattas(List)}. The race selection provider is expected to be single selection only. + * + * @author Frank Mittag, Axel Uhl (d043530) + * + */ +public class RaceBoardPanel extends SimplePanel implements RegattasDisplayer, RaceSelectionChangeListener, LeaderboardUpdateListener, PopupPositionProvider { + private final SailingServiceAsync sailingService; + private final MediaServiceAsync mediaService; + private final EventDTO event; + private final StringMessages stringMessages; + private final ErrorReporter errorReporter; + private final RaceBoardViewConfiguration raceboardViewConfiguration; + private String raceBoardName; + + /** + * Updated upon each {@link #fillRegattas(List)} + */ + private final Map racesByIdentifier; + + private final List componentViewers; + private RaceTimePanel timePanel; + private final Timer timer; + private final RaceSelectionProvider raceSelectionProvider; + private final UserAgentDetails userAgent; + private final CompetitorSelectionModel competitorSelectionModel; + private final TimeRangeWithZoomModel timeRangeWithZoomModel; + private final RegattaAndRaceIdentifier selectedRaceIdentifier; + + private final String leaderboardName; + private final LeaderboardPanel leaderboardPanel; + private WindChart windChart; + private MultiCompetitorRaceChart competitorChart; + private EditMarkPassingsPanel editMarkPassingPanel; + + /** + * The component viewer in ONESCREEN view mode. null if in CASCADE view mode + */ + private SideBySideComponentViewer leaderboardAndMapViewer; + + private final AsyncActionsExecutor asyncActionsExecutor; + + private final RaceTimesInfoProvider raceTimesInfoProvider; + private RaceMap raceMap; + + private final FlowPanel raceInformationHeader; + private final FlowPanel regattaAndRaceTimeInformationHeader; + private boolean currentRaceHasBeenSelectedOnce; + + private static final RaceMapResources raceMapResources = GWT.create(RaceMapResources.class); + + /** + * @param event + * an optional event that can be used for "back"-navigation in case the race board shows a race in the + * context of an event; may be null. + */ + public RaceBoardPanel(SailingServiceAsync sailingService, MediaServiceAsync mediaService, + UserService userService, AsyncActionsExecutor asyncActionsExecutor, + Timer timer, RaceSelectionProvider theRaceSelectionProvider, String leaderboardName, + String leaderboardGroupName, EventDTO event, RaceBoardViewConfiguration raceboardViewConfiguration, + ErrorReporter errorReporter, final StringMessages stringMessages, + UserAgentDetails userAgent, RaceTimesInfoProvider raceTimesInfoProvider, boolean showMapControls) { + this.sailingService = sailingService; + this.mediaService = mediaService; + this.stringMessages = stringMessages; + this.raceboardViewConfiguration = raceboardViewConfiguration; + this.raceSelectionProvider = theRaceSelectionProvider; + this.raceTimesInfoProvider = raceTimesInfoProvider; + this.errorReporter = errorReporter; + this.userAgent = userAgent; + this.timer = timer; + this.event = event; + this.currentRaceHasBeenSelectedOnce = false; + this.leaderboardName = leaderboardName; + raceSelectionProvider.addRaceSelectionChangeListener(this); + racesByIdentifier = new HashMap(); + selectedRaceIdentifier = raceSelectionProvider.getSelectedRaces().iterator().next(); + this.setRaceBoardName(selectedRaceIdentifier.getRaceName()); + this.asyncActionsExecutor = asyncActionsExecutor; + FlowPanel mainPanel = new FlowPanel(); + mainPanel.setSize("100%", "100%"); + setWidget(mainPanel); + raceInformationHeader = new FlowPanel(); + raceInformationHeader.setStyleName("RegattaRaceInformation-Header"); + regattaAndRaceTimeInformationHeader = new FlowPanel(); + regattaAndRaceTimeInformationHeader.setStyleName("RegattaAndRaceTime-Header"); + timeRangeWithZoomModel = new TimeRangeWithZoomModel(); + componentViewers = new ArrayList(); + competitorSelectionModel = new CompetitorSelectionModel(/* hasMultiSelection */ true); + + raceMapResources.combinedWindPanelStyle().ensureInjected(); + raceMap = new RaceMap(sailingService, asyncActionsExecutor, errorReporter, timer, + competitorSelectionModel, stringMessages, showMapControls, getConfiguration().isShowViewStreamlets(), getConfiguration().isShowViewSimulation(), selectedRaceIdentifier, raceMapResources.combinedWindPanelStyle()); + CompetitorFilterPanel competitorSearchTextBox = new CompetitorFilterPanel(competitorSelectionModel, stringMessages, raceMap, + new LeaderboardFetcher() { + @Override + public LeaderboardDTO getLeaderboard() { + return leaderboardPanel.getLeaderboard(); + } + }, selectedRaceIdentifier); + raceMap.getLeftHeaderPanel().add(raceInformationHeader); + raceMap.getRightHeaderPanel().add(regattaAndRaceTimeInformationHeader); + + leaderboardPanel = createLeaderboardPanel(leaderboardName, leaderboardGroupName, competitorSearchTextBox); + leaderboardPanel.setTitle(stringMessages.leaderboard()); + leaderboardPanel.getElement().getStyle().setMarginLeft(6, Unit.PX); + leaderboardPanel.getElement().getStyle().setMarginTop(4, Unit.PX); + createOneScreenView(leaderboardName, leaderboardGroupName, event, mainPanel, showMapControls, raceMap, userService); // initializes the raceMap field + leaderboardPanel.addLeaderboardUpdateListener(this); + // in case the URL configuration contains the name of a competitors filter set we try to activate it + // FIXME the competitorsFilterSets has now moved to CompetitorSearchTextBox (which should probably be renamed); pass on the parameters to the LeaderboardPanel and see what it does with it + if (raceboardViewConfiguration.getActiveCompetitorsFilterSetName() != null) { + for (FilterSet> filterSet : competitorSearchTextBox.getCompetitorsFilterSets() + .getFilterSets()) { + if (filterSet.getName().equals(raceboardViewConfiguration.getActiveCompetitorsFilterSetName())) { + competitorSearchTextBox.getCompetitorsFilterSets().setActiveFilterSet(filterSet); + break; + } + } + } + timePanel = new RaceTimePanel(timer, timeRangeWithZoomModel, stringMessages, raceTimesInfoProvider, + raceboardViewConfiguration.isCanReplayDuringLiveRaces()); + timeRangeWithZoomModel.addTimeZoomChangeListener(timePanel); + raceTimesInfoProvider.addRaceTimesInfoProviderListener(timePanel); + raceSelectionProvider.addRaceSelectionChangeListener(timePanel); + timePanel.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); + } + + /** + * @param event an optional event; may be null or else can be used to show some context information in + * the {@link GlobalNavigationPanel}. + */ + private void createOneScreenView(String leaderboardName, String leaderboardGroupName, EventDTO event, FlowPanel mainPanel, + boolean showMapControls, RaceMap raceMap, UserService userService) { + // create the default leaderboard and select the right race + raceTimesInfoProvider.addRaceTimesInfoProviderListener(raceMap); + raceMap.onRaceSelectionChange(Collections.singletonList(selectedRaceIdentifier)); + List> components = new ArrayList>(); + competitorChart = new MultiCompetitorRaceChart(sailingService, asyncActionsExecutor, competitorSelectionModel, raceSelectionProvider, + timer, timeRangeWithZoomModel, stringMessages, errorReporter, true, true, leaderboardGroupName, leaderboardName); + competitorChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); + competitorChart.getEntryWidget().setTitle(stringMessages.competitorCharts()); + competitorChart.setVisible(false); + components.add(competitorChart); + windChart = new WindChart(sailingService, raceSelectionProvider, timer, timeRangeWithZoomModel, new WindChartSettings(), + stringMessages, asyncActionsExecutor, errorReporter, /* compactChart */ true); + windChart.setVisible(false); + windChart.onRaceSelectionChange(raceSelectionProvider.getSelectedRaces()); + windChart.getEntryWidget().setTitle(stringMessages.windChart()); + components.add(windChart); + editMarkPassingPanel = new EditMarkPassingsPanel(sailingService, asyncActionsExecutor, selectedRaceIdentifier, + stringMessages, competitorSelectionModel, errorReporter, timer); + editMarkPassingPanel.setLeaderboardNameAndColumn(leaderboardPanel.getLeaderboard()); + editMarkPassingPanel.getEntryWidget().setTitle(stringMessages.editMarkPassings()); + components.add(editMarkPassingPanel); + boolean autoSelectMedia = getConfiguration().isAutoSelectMedia(); + MediaPlayerManagerComponent mediaPlayerManagerComponent = new MediaPlayerManagerComponent( + selectedRaceIdentifier, raceTimesInfoProvider, timer, mediaService, userService, stringMessages, + errorReporter, userAgent, this, autoSelectMedia); + leaderboardAndMapViewer = new SideBySideComponentViewer(leaderboardPanel, raceMap, mediaPlayerManagerComponent, components, stringMessages, userService, editMarkPassingPanel); + componentViewers.add(leaderboardAndMapViewer); + for (ComponentViewer componentViewer : componentViewers) { + mainPanel.add(componentViewer.getViewerWidget()); + } + boolean showLeaderboard = getConfiguration().isShowLeaderboard(); + if (Document.get().getClientWidth() <= 1024) { + showLeaderboard = false; + } + setLeaderboardVisible(showLeaderboard); + setWindChartVisible(getConfiguration().isShowWindChart()); + setCompetitorChartVisible(getConfiguration().isShowCompetitorsChart()); + // make sure to load leaderboard data for filtering to work + if (!showLeaderboard) { + leaderboardPanel.setVisible(true); + leaderboardPanel.setVisible(false); + } + } + + @SuppressWarnings("unused") + private void addSettingsMenuItem(MenuBar settingsMenu, final Component component) { + if (component.hasSettings()) { + settingsMenu.addItem(component.getLocalizedShortName(), new Command() { + public void execute() { + new SettingsDialog(component, stringMessages).show(); + } + }); + } + } + + private LeaderboardPanel createLeaderboardPanel(String leaderboardName, String leaderboardGroupName, CompetitorFilterPanel competitorSearchTextBox) { + LeaderboardSettings leaderBoardSettings = LeaderboardSettingsFactory.getInstance() + .createNewSettingsForPlayMode(timer.getPlayMode(), + /* nameOfRaceToSort */ selectedRaceIdentifier.getRaceName(), + /* nameOfRaceColumnToShow */ null, /* nameOfRaceToShow */ selectedRaceIdentifier.getRaceName(), + new ExplicitRaceColumnSelectionWithPreselectedRace(selectedRaceIdentifier), /* showRegattaRank */ false, + /*showCompetitorSailIdColumn*/true, /*showCompetitorFullNameColumn*/true); + return new LeaderboardPanel(sailingService, asyncActionsExecutor, leaderBoardSettings, selectedRaceIdentifier != null, selectedRaceIdentifier, + competitorSelectionModel, timer, leaderboardGroupName, leaderboardName, errorReporter, stringMessages, + userAgent, /* showRaceDetails */ true, competitorSearchTextBox, + /* showSelectionCheckbox */ true, raceTimesInfoProvider, /* autoExpandLastRaceColumn */ false, + /* don't adjust the timer's delay from the leaderboard; control it solely from the RaceTimesInfoProvider */ false, + /*autoApplyTopNFilter*/ false, /*showCompetitorFilterStatus*/ false); + } + + private void setComponentVisible(ComponentViewer componentViewer, Component component, boolean visible) { + component.setVisible(visible); + componentViewer.forceLayout(); + } + + /** + * Sets the collapsable panel for the leaderboard open or close, if in CASCADE view mode.
+ * Displays or hides the leaderboard, if in ONESCREEN view mode.

+ * + * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. + * + * @param visible true if the leaderboard shall be open/visible + */ + public void setLeaderboardVisible(boolean visible) { + setComponentVisible(leaderboardAndMapViewer, leaderboardPanel, visible); + } + + /** + * Sets the collapsable panel for the wind chart open or close, if in CASCADE view mode.
+ * Displays or hides the wind chart, if in ONESCREEN view mode.

+ * + * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. + * + * @param visible true if the wind chart shall be open/visible + */ + public void setWindChartVisible(boolean visible) { + setComponentVisible(leaderboardAndMapViewer, windChart, visible); + } + + /** + * Sets the collapsable panel for the competitor chart open or close, if in CASCADE view mode.
+ * Displays or hides the competitor chart, if in ONESCREEN view mode.

+ * + * The race board should be completely rendered before this method is called, or a few exceptions could be thrown. + * + * @param visible true if the competitor chart shall be open/visible + */ + public void setCompetitorChartVisible(boolean visible) { + setComponentVisible(leaderboardAndMapViewer, competitorChart, visible); + } + + public RaceTimePanel getTimePanel() { + return timePanel; + } + + protected SailingServiceAsync getSailingService() { + return sailingService; + } + + protected String getRaceBoardName() { + return raceBoardName; + } + + protected void setRaceBoardName(String raceBoardName) { + this.raceBoardName = raceBoardName; + } + + protected ErrorReporter getErrorReporter() { + return errorReporter; + } + + @Override + public void fillRegattas(Iterable regattas) { + racesByIdentifier.clear(); + for (RegattaDTO regatta : regattas) { + for (RaceDTO race : regatta.races) { + if (race != null && race.getRaceIdentifier() != null) { + racesByIdentifier.put(race.getRaceIdentifier(), race); + } + } + } + } + + @Override + public void onRaceSelectionChange(List selectedRaces) { + } + + public RaceBoardViewConfiguration getConfiguration() { + return raceboardViewConfiguration; + } + + @Override + public void updatedLeaderboard(LeaderboardDTO leaderboard) { + leaderboardAndMapViewer.setLeftComponentWidth(leaderboardPanel.getContentPanel().getOffsetWidth()); + editMarkPassingPanel.setLeaderboardNameAndColumn(leaderboard); + } + + @Override + public void currentRaceSelected(RaceIdentifier raceIdentifier, RaceColumnDTO raceColumn) { + if (!currentRaceHasBeenSelectedOnce) { + FleetDTO fleet = raceColumn.getFleet(raceIdentifier); + String seriesName = raceColumn.getSeriesName(); + if (LeaderboardNameConstants.DEFAULT_SERIES_NAME.equals(seriesName)) { + seriesName = ""; + } + String fleetForRaceName = fleet==null?"":fleet.getName(); + if (fleetForRaceName.equals(LeaderboardNameConstants.DEFAULT_FLEET_NAME)) { + fleetForRaceName = ""; + } else { + fleetForRaceName = " - "+fleetForRaceName; + } + final Label raceNameLabel = new Label(stringMessages.race() + " " + raceColumn.getRaceColumnName()); + raceNameLabel.setStyleName("RaceName-Label"); + final Label raceAdditionalInformationLabel = new Label(seriesName + fleetForRaceName); + raceAdditionalInformationLabel.setStyleName("RaceSeriesAndFleet-Label"); + raceInformationHeader.clear(); + raceInformationHeader.add(raceNameLabel); + raceInformationHeader.add(raceAdditionalInformationLabel); + final Anchor regattaNameAnchor = new Anchor(raceIdentifier.getRegattaName()); + if (event != null) { + // we don't use the EntryPointLinkFactory here, because of lacking support for Places + String debugParam = Window.Location.getParameter("gwt.codesvr"); + String link = "/gwt/Home.html"; + if (debugParam != null && !debugParam.isEmpty()) { + link += "?gwt.codesvr=" + debugParam; + } + link += "#EventPlace:eventId="+event.id.toString(); + link += "&navigationTab=Regatta&leaderboardName=" + URLEncoder.encode(leaderboardName); + regattaNameAnchor.setHref(link); + } else { + String leaderboardGroupNameParam = Window.Location.getParameter("leaderboardGroupName"); + if(leaderboardGroupNameParam != null) { + Map leaderboardGroupLinkParameters = new HashMap(); + leaderboardGroupLinkParameters.put("showRaceDetails", "true"); + leaderboardGroupLinkParameters.put("leaderboardGroupName", leaderboardGroupNameParam); + String leaderBoardGroupLink = EntryPointLinkFactory.createLeaderboardGroupLink(leaderboardGroupLinkParameters); + regattaNameAnchor.setHref(leaderBoardGroupLink); + } else { + // fallback + regattaNameAnchor.setHref("javascript:window.history.back();"); + } + } + regattaNameAnchor.setStyleName("RegattaName-Anchor"); + + // TODO: Strange behavior... check +// Window.addResizeHandler(new ResizeHandler() { +// @Override +// public void onResize(ResizeEvent event) { +// int headerPanelWidth = raceMap.getRightHeaderPanel().getOffsetWidth() - 150; // 150px is the width of the sapLogo +// int raceNameAndFleetLabelWidth = raceInformationHeader.getOffsetWidth(); +// int regattaAnchorWidth = regattaNameAnchor.getOffsetWidth(); +// boolean overlap = raceNameAndFleetLabelWidth + regattaAnchorWidth > headerPanelWidth; +// raceInformationHeader.setVisible(!overlap); +// } +// }); + + Label raceTimeLabel = computeRaceInformation(raceColumn, fleet); + raceTimeLabel.setStyleName("RaceTime-Label"); + regattaAndRaceTimeInformationHeader.clear(); + regattaAndRaceTimeInformationHeader.add(regattaNameAnchor); + regattaAndRaceTimeInformationHeader.add(raceTimeLabel); + currentRaceHasBeenSelectedOnce = true; + } + } + + @Override + public UIObject getXPositionUiObject() { + return timePanel; + } + + @Override + public UIObject getYPositionUiObject() { + return timePanel; + } + + private Label computeRaceInformation(RaceColumnDTO raceColumn, FleetDTO fleet) { + Label raceInformationLabel = new Label(); + raceInformationLabel.setStyleName("Race-Time-Label"); + DateTimeFormat formatter = DateTimeFormat.getFormat("E d/M/y"); + raceInformationLabel.setText(formatter.format(raceColumn.getStartDate(fleet))); + return raceInformationLabel; + } +} + diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java index d6bbb997bbb..3aca89eaab3 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/raceboard/SideBySideComponentViewer.java @@ -15,17 +15,23 @@ import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.WidgetCollection; +import com.sap.sailing.domain.common.security.Roles; import com.sap.sailing.gwt.ui.client.StringMessages; import com.sap.sailing.gwt.ui.client.media.MediaManagementControl; import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager; import com.sap.sailing.gwt.ui.client.media.MediaPlayerManager.PlayerChangeListener; import com.sap.sailing.gwt.ui.client.media.MediaPlayerManagerComponent; import com.sap.sailing.gwt.ui.client.media.MediaSingleSelectionControl; +import com.sap.sailing.gwt.ui.client.shared.charts.EditMarkPassingsPanel; import com.sap.sailing.gwt.ui.client.shared.components.Component; import com.sap.sailing.gwt.ui.client.shared.components.ComponentViewer; import com.sap.sailing.gwt.ui.client.shared.components.SettingsDialog; import com.sap.sailing.gwt.ui.leaderboard.LeaderboardPanel; import com.sap.sse.common.Util.Pair; +import com.sap.sse.security.shared.DefaultRoles; +import com.sap.sse.security.ui.client.UserService; +import com.sap.sse.security.ui.client.UserStatusEventHandler; +import com.sap.sse.security.ui.shared.UserDTO; /** * Component Viewer that uses a {@link TouchSplitLayoutPanel} to display its components. @@ -33,7 +39,7 @@ import com.sap.sse.common.Util.Pair; * TODO: Refactor to make sure it is really only performing operations that are related to view. Currently it is digging * too deep into components and setting titles or even creating video buttons. */ -public class SideBySideComponentViewer implements ComponentViewer { +public class SideBySideComponentViewer implements ComponentViewer, UserStatusEventHandler { private static final int DEFAULT_SOUTH_SPLIT_PANEL_HEIGHT = 200; private final int MIN_LEADERBOARD_WIDTH = 432; // works well for 505 and ESS @@ -59,6 +65,7 @@ public class SideBySideComponentViewer implements ComponentViewer { private final StringMessages stringMessages; private final Button mediaSelectionButton; private final Button mediaManagementButton; + private final EditMarkPassingsPanel markPassingsPanel; private LayoutPanel mainPanel; @@ -68,13 +75,16 @@ public class SideBySideComponentViewer implements ComponentViewer { public SideBySideComponentViewer(final LeaderboardPanel leftComponentP, final Component rightComponentP, final MediaPlayerManagerComponent mediaPlayerManagerComponent, List> components, - final StringMessages stringMessages) { + final StringMessages stringMessages, UserService userService, EditMarkPassingsPanel markPassingsPanel) { this.stringMessages = stringMessages; this.leftComponent = leftComponentP; this.rightComponent = rightComponentP; this.components = components; this.mediaSelectionButton = createMediaSelectionButton(mediaPlayerManagerComponent); this.mediaManagementButton = createMediaManagementButton(mediaPlayerManagerComponent); + this.markPassingsPanel = markPassingsPanel; + + userService.addUserStatusEventHandler(this); mediaPlayerManagerComponent.setPlayerChangeListener(new PlayerChangeListener() { @@ -146,6 +156,7 @@ public class SideBySideComponentViewer implements ComponentViewer { additionalVerticalButtons.add(new Pair(mediaManagementButton, "managemedia")); } + onUserStatusChange(userService.getCurrentUser()); // ensure that toggle buttons are positioned right splitLayoutPanel.lastComponentHasBeenAdded(this, panelForMapAndHorizontalToggleButtons, @@ -276,4 +287,18 @@ public class SideBySideComponentViewer implements ComponentViewer { } layoutForLeftComponentForcedOnce = true; } + + @Override + public void onUserStatusChange(UserDTO user) { + Button toggleButton = splitLayoutPanel.getAssociatedSplitter(markPassingsPanel).getToggleButton(); + if (user != null + && (user.hasRole(DefaultRoles.ADMIN.getRolename()) || user.hasRole(Roles.eventmanager.getRolename()))) { + toggleButton.setVisible(true); + forceLayout(); + } else { + markPassingsPanel.setVisible(false); + toggleButton.setVisible(false); + forceLayout(); + } + } } diff --git a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java index 4f99c2bfe41..25ee0d0ac5c 100755 --- a/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java +++ b/java/com.sap.sailing.gwt.ui/src/main/java/com/sap/sailing/gwt/ui/server/SailingServiceImpl.java @@ -17,6 +17,7 @@ import java.net.URLEncoder; import java.net.UnknownHostException; import java.text.ParseException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; @@ -29,6 +30,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.NavigableSet; import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; @@ -58,12 +60,16 @@ import org.osgi.util.tracker.ServiceTracker; import com.sap.sailing.domain.abstractlog.AbstractLog; import com.sap.sailing.domain.abstractlog.MultiLogAnalyzer; import com.sap.sailing.domain.abstractlog.Revokable; +import com.sap.sailing.domain.abstractlog.race.FixedMarkPassingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLog; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEventFactory; import com.sap.sailing.domain.abstractlog.race.RaceLogFlagEvent; +import com.sap.sailing.domain.abstractlog.race.SuppressedMarkPassingsEvent; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.AbortingFlagFinder; import com.sap.sailing.domain.abstractlog.race.analyzing.impl.LastPublishedCourseDesignFinder; +import com.sap.sailing.domain.abstractlog.race.analyzing.impl.MarkPassingDataFinder; +import com.sap.sailing.domain.abstractlog.race.impl.FixedMarkPassingEventImpl; import com.sap.sailing.domain.abstractlog.race.state.ReadonlyRaceState; import com.sap.sailing.domain.abstractlog.race.state.impl.ReadonlyRaceStateImpl; import com.sap.sailing.domain.abstractlog.race.state.racingprocedure.FlagPoleState; @@ -1000,14 +1006,17 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } @Override - public void trackWithTracTrac(RegattaIdentifier regattaToAddTo, Iterable rrs, String liveURI, String storedURI, - String courseDesignUpdateURI, boolean trackWind, final boolean correctWindByDeclination, final boolean simulateWithStartTimeNow, - String tracTracUsername, String tracTracPassword) throws Exception { - logger.info("tracWithTracTrac for regatta "+regattaToAddTo+" for race records "+rrs+" with liveURI "+liveURI+" and storedURI "+storedURI); + public void trackWithTracTrac(RegattaIdentifier regattaToAddTo, Iterable rrs, String liveURI, String storedURI, + String courseDesignUpdateURI, boolean trackWind, final boolean correctWindByDeclination, + final boolean simulateWithStartTimeNow, final boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword) + throws Exception { + logger.info("tracWithTracTrac for regatta " + regattaToAddTo + " for race records " + rrs + " with liveURI " + liveURI + + " and storedURI " + storedURI); for (TracTracRaceRecordDTO rr : rrs) { // reload JSON and load clientparams.php RaceRecord record = getTracTracAdapter().getSingleTracTracRaceRecord(new URL(rr.jsonURL), rr.id, /*loadClientParams*/true); - logger.info("Loaded race " + record.getName() + " in " + record.getEventName() + " start:" + record.getRaceStartTime() + " trackingStart:" + record.getTrackingStartTime() + " trackingEnd:" + record.getTrackingEndTime()); + logger.info("Loaded race " + record.getName() + " in " + record.getEventName() + " start:" + record.getRaceStartTime() + + " trackingStart:" + record.getTrackingStartTime() + " trackingEnd:" + record.getTrackingEndTime()); // note that the live URI may be null for races that were put into replay mode final String effectiveLiveURI; if (!record.getRaceStatus().equals(TracTracConnectionConstants.REPLAY_STATUS)) { @@ -1026,14 +1035,12 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S effectiveStoredURI = storedURI; } final RaceHandle raceHandle = getTracTracAdapter().addTracTracRace(getService(), regattaToAddTo, - record.getParamURL(), effectiveLiveURI == null ? null : new URI(effectiveLiveURI), - new URI(effectiveStoredURI), new URI(courseDesignUpdateURI), - new MillisecondsTimePoint(record.getTrackingStartTime().asMillis()), + record.getParamURL(), effectiveLiveURI == null ? null : new URI(effectiveLiveURI), new URI(effectiveStoredURI), + new URI(courseDesignUpdateURI), new MillisecondsTimePoint(record.getTrackingStartTime().asMillis()), new MillisecondsTimePoint(record.getTrackingEndTime().asMillis()), getRaceLogStore(), getRegattaLogStore(), - RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, simulateWithStartTimeNow, - tracTracUsername, tracTracPassword, - record.getRaceStatus(), record.getRaceVisibility()); + RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, simulateWithStartTimeNow, + useInternalMarkPassingAlgorithm, tracTracUsername, tracTracPassword, record.getRaceStatus(), record.getRaceVisibility()); if (trackWind) { new Thread("Wind tracking starter for race " + record.getEventName() + "/" + record.getName()) { public void run() { @@ -2550,7 +2557,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public void trackWithSwissTiming(RegattaIdentifier regattaToAddTo, Iterable rrs, String hostname, int port, - boolean trackWind, final boolean correctWindByDeclination) throws Exception { + boolean trackWind, final boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) throws Exception { logger.info("tracWithSwissTiming for regatta " + regattaToAddTo + " for race records " + rrs + " with hostname " + hostname + " and port " + port); Map cachedRegattaEntriesLists = new HashMap(); @@ -2561,21 +2568,21 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S raceDescription += raceDescription.length() > 0 ? "/" + rr.getName() : rr.getName(); // try to find a cached entry list for the regatta RegattaResults regattaResults = cachedRegattaEntriesLists.get(rr.xrrEntriesUrl); - if(regattaResults == null) { + if (regattaResults == null) { regattaResults = getSwissTimingAdapter().readRegattaEntryListFromXrrUrl(rr.xrrEntriesUrl); - if(regattaResults != null) { + if (regattaResults != null) { cachedRegattaEntriesLists.put(rr.xrrEntriesUrl, regattaResults); } } StartList startList = null; - if(regattaResults != null) { + if (regattaResults != null) { startList = getSwissTimingAdapter().readStartListForRace(rr.raceId, regattaResults); } // now read the entry list for the race from the result final RaceHandle raceHandle = getSwissTimingAdapter().addSwissTimingRace(getService(), regattaToAddTo, rr.raceId, rr.getName(), raceDescription, boatClass, hostname, port, startList, getRaceLogStore(), getRegattaLogStore(), - RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS); + RaceTracker.TIMEOUT_FOR_RECEIVING_RACE_DEFINITION_IN_MILLISECONDS, useInternalMarkPassingAlgorithm); if (trackWind) { new Thread("Wind tracking starter for race " + rr.raceId + "/" + rr.getName()) { public void run() { @@ -2607,7 +2614,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S @Override public void replaySwissTimingRace(RegattaIdentifier regattaIdentifier, Iterable replayRaceDTOs, - boolean trackWind, boolean correctWindByDeclination, boolean simulateWithStartTimeNow) { + boolean trackWind, boolean correctWindByDeclination, boolean useInternalMarkPassingAlgorithm) { logger.info("replaySwissTimingRace for regatta "+regattaIdentifier+" for races "+replayRaceDTOs); Regatta regatta; for (SwissTimingReplayRaceDTO replayRaceDTO : replayRaceDTOs) { @@ -2636,7 +2643,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } else { regatta = getService().getRegatta(regattaIdentifier); } - getSwissTimingReplayService().loadRaceData(replayRaceDTO.link, regatta, getService()); + getSwissTimingReplayService().loadRaceData(replayRaceDTO.link, regatta, getService(), useInternalMarkPassingAlgorithm); } catch (Exception e) { logger.log(Level.SEVERE, "Error trying to load SwissTimingReplay race " + replayRaceDTO, e); } @@ -5295,7 +5302,7 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S } return null; } - + @Override public ArrayList getEventsForLeaderboard(String leaderboardName) { Leaderboard leaderboard = getService().getLeaderboardByName(leaderboardName); @@ -5312,6 +5319,118 @@ public class SailingServiceImpl extends ProxiedRemoteServiceServlet implements S return new ArrayList<>(events.values()); } + @Override + public Map getCompetitorRaceLogMarkPassingData(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet, + CompetitorDTO competitor) { + Map result = new HashMap<>(); + RaceLog raceLog = getService().getRaceLog(leaderboardName, raceColumnDTO.getName(), fleet.getName()); + for (Triple fixedEvent : new MarkPassingDataFinder(raceLog).analyze()) { + if (fixedEvent.getA().getName().equals(competitor.getName())) { + final Date date; + if (fixedEvent.getC() != null) { + date = new Date(fixedEvent.getC().asMillis()); + } else { + date = null; + } + result.put(fixedEvent.getB(), date); + } + } + return result; + } + + @Override + public void updateFixedMarkPassing(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet, Integer indexOfWaypoint, + Date dateOfMarkPassing, CompetitorDTO competitorDTO) { + RaceLog raceLog = getService().getRaceLog(leaderboardName, raceColumnDTO.getName(), fleet.getName()); + Competitor competitor = getCompetitor(competitorDTO); + FixedMarkPassingEvent oldFixedMarkPassingEvent = null; + for (RaceLogEvent event : raceLog.getUnrevokedEvents()) { + if (event instanceof FixedMarkPassingEventImpl && event.getInvolvedBoats().contains(competitor)) { + FixedMarkPassingEvent fixedEvent = (FixedMarkPassingEvent) event; + if (fixedEvent.getZeroBasedIndexOfPassedWaypoint() == indexOfWaypoint) { + oldFixedMarkPassingEvent = fixedEvent; + } + } + } + if (oldFixedMarkPassingEvent != null) { + try { + raceLog.revokeEvent(getService().getServerAuthor(), oldFixedMarkPassingEvent); + } catch (NotRevokableException e) { + e.printStackTrace(); + } + } + if (dateOfMarkPassing != null) { + raceLog.add(RaceLogEventFactory.INSTANCE.createFixedMarkPassingEvent(MillisecondsTimePoint.now(), getService() + .getServerAuthor(), UUID.randomUUID(), Arrays.asList(competitor), raceLog.getCurrentPassId(), + new MillisecondsTimePoint(dateOfMarkPassing), indexOfWaypoint)); + } + } + + @Override + public void updateSuppressedMarkPassings(String leaderboardName, RaceColumnDTO raceColumnDTO, FleetDTO fleet, + Integer newZeroBasedIndexOfSuppressedMarkPassing, CompetitorDTO competitorDTO) { + SuppressedMarkPassingsEvent oldSuppressedMarkPassingEvent = null; + RaceLog raceLog = getService().getRaceLog(leaderboardName, raceColumnDTO.getName(), fleet.getName()); + Competitor competitor = getCompetitor(competitorDTO); + NavigableSet unrevokedEvents = raceLog.getUnrevokedEvents(); + for (RaceLogEvent event : unrevokedEvents) { + if (event instanceof SuppressedMarkPassingsEvent && event.getInvolvedBoats().contains(competitor)) { + oldSuppressedMarkPassingEvent = (SuppressedMarkPassingsEvent) event; + break; + } + } + + final boolean create; + final boolean revoke; + if (oldSuppressedMarkPassingEvent == null) { + if (newZeroBasedIndexOfSuppressedMarkPassing == null) { + create = false; + revoke = false; + } else { + create = true; + revoke = false; + } + } else { + if (newZeroBasedIndexOfSuppressedMarkPassing == null) { + revoke = true; + create = false; + } else { + boolean same = newZeroBasedIndexOfSuppressedMarkPassing == oldSuppressedMarkPassingEvent + .getZeroBasedIndexOfFirstSuppressedWaypoint(); + create = !same; + revoke = !same; + } + } + if (revoke) { + try { + raceLog.revokeEvent(getService().getServerAuthor(), oldSuppressedMarkPassingEvent); + } catch (NotRevokableException e) { + } + } + if (create) { + raceLog.add(RaceLogEventFactory.INSTANCE.createSuppressedMarkPassingsEvent(MillisecondsTimePoint.now(), getService() + .getServerAuthor(), UUID.randomUUID(), Arrays.asList(competitor), raceLog.getCurrentPassId(), + newZeroBasedIndexOfSuppressedMarkPassing)); + } + } + + @Override + public Map getCompetitorMarkPassings(RegattaAndRaceIdentifier race, CompetitorDTO competitorDTO) { + Map result = new HashMap<>(); + final TrackedRace trackedRace = getExistingTrackedRace(race); + if (trackedRace != null) { + Competitor competitor = getCompetitorByIdAsString(trackedRace.getRace().getCompetitors(), competitorDTO.getIdAsString()); + Set competitorMarkPassings = trackedRace.getMarkPassings(competitor); + Iterable waypoints = trackedRace.getRace().getCourse().getWaypoints(); + if (competitorMarkPassings != null) { + for (MarkPassing markPassing : competitorMarkPassings) { + result.put(Util.indexOf(waypoints, markPassing.getWaypoint()), markPassing.getTimePoint().asDate()); + } + } + } + return result; + } + @Override public PolarSheetsXYDiagramData createXYDiagramForBoatClass(String boatClassName) { BoatClass boatClass = getService().getBaseDomainFactory().getOrCreateBoatClass(boatClassName); diff --git a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingWindTracksTest.java b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingWindTracksTest.java index bf8931f5d89..a8cbdf1f056 100755 --- a/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingWindTracksTest.java +++ b/java/com.sap.sailing.mongodb.test/src/com/sap/sailing/mongodb/test/TestStoringAndRetrievingWindTracksTest.java @@ -87,7 +87,7 @@ public class TestStoringAndRetrievingWindTracksTest extends AbstractTracTracLive @Override public void addRaceDefinition(RaceDefinition race, DynamicTrackedRace trackedRace) { } - }, /* trackedRegattaRegistry */ null, /*courseDesignUpdateURI*/ null, /*tracTracUsername*/ null, /*tracTracPassword*/ null, getEventSubscriber(), getRaceSubscriber(), ReceiverType.RACECOURSE); + }, /* trackedRegattaRegistry */ null, /*courseDesignUpdateURI*/ null, /*tracTracUsername*/ null, /*tracTracPassword*/ null, getEventSubscriber(), getRaceSubscriber(), /*ignoreTracTracMarkPassings*/ false, ReceiverType.RACECOURSE); addListenersForStoredDataAndStartController(typeControllers); RaceDefinition race = domainFactory.getAndWaitForRaceDefinition(getTracTracEvent().getRaces().iterator().next().getId()); DynamicTrackedRace trackedRace = trackedRegatta.createTrackedRace(race, Collections. emptyList(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, @@ -95,7 +95,7 @@ public class TestStoringAndRetrievingWindTracksTest extends AbstractTracTracLive @Override public void addRaceDefinition(RaceDefinition race, DynamicTrackedRace trackedRace) { } - }); + }, /*useMarkPassingCalculator*/ false); WindSource windSource = new WindSourceImpl(WindSourceType.WEB); Mongo myFirstMongo = newMongo(); DB firstDatabase = myFirstMongo.getDB(dbConfiguration.getDatabaseName()); diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogEventDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogEventDeserializer.java index c32bc173a27..f379d3e2655 100755 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogEventDeserializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogEventDeserializer.java @@ -29,6 +29,7 @@ import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogDeviceCo import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogDeviceMarkMappingEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogFinishPositioningConfirmedEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogFinishPositioningListChangedEventSerializer; +import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogFixedMarkPassingEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogFlagEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogGateLineOpeningTimeEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogPassChangeEventSerializer; @@ -40,6 +41,7 @@ import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogRevokeEv import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogStartProcedureChangedEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogStartTimeEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogStartTrackingEventSerializer; +import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogSuppressedMarkPassingsEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogWindFixEventSerializer; import com.sap.sailing.server.gateway.serialization.racelog.tracking.impl.SmartphoneUUIDJsonHandler; @@ -82,7 +84,9 @@ public class RaceLogEventDeserializer implements JsonDeserializer new RaceLogRegisterCompetitorEventDeserializer(competitorDeserializer), new RaceLogDefineMarkEventDeserializer(competitorDeserializer, new MarkDeserializer(domainFactory)), new RaceLogCloseOpenEndedDeviceMappingEventDeserializer(competitorDeserializer), - new RaceLogAdditionalScoringInformationEventDeserializer(competitorDeserializer)); + new RaceLogAdditionalScoringInformationEventDeserializer(competitorDeserializer), + new RaceLogFixedMarkPassingEventDeserializer(competitorDeserializer), + new RaceLogSuppressedMarkPassingsEventDeserializer(competitorDeserializer)); } protected final JsonDeserializer flagEventDeserializer; @@ -107,6 +111,8 @@ public class RaceLogEventDeserializer implements JsonDeserializer protected final JsonDeserializer defineMarkEventDeserializer; protected final JsonDeserializer closeOpenEndedDeviceMappingEventDeserializer; protected final JsonDeserializer additionalScoringInformationEventDeserializer; + protected final JsonDeserializer fixedMarkPassingEventDeserializer; + protected final JsonDeserializer suppressedMarkPassingsDeserializer; public RaceLogEventDeserializer(JsonDeserializer flagEventDeserializer, JsonDeserializer startTimeEventDeserializer, @@ -129,7 +135,9 @@ public class RaceLogEventDeserializer implements JsonDeserializer JsonDeserializer registerCompetitorEventDeserializer, JsonDeserializer defineMarkEventDeserializer, JsonDeserializer closeOpenEndedDeviceMappingEventDeserializer, - JsonDeserializer additionalScoringInformationEventDeserializer) { + JsonDeserializer additionalScoringInformationEventDeserializer, + JsonDeserializer fixedMarkPassingEventDeserializer, + JsonDeserializer suppressedMarkPassingsEventDeserializer) { this.flagEventDeserializer = flagEventDeserializer; this.startTimeEventDeserializer = startTimeEventDeserializer; this.raceStatusEventDeserializer = raceStatusEventDeserializer; @@ -152,6 +160,8 @@ public class RaceLogEventDeserializer implements JsonDeserializer this.defineMarkEventDeserializer = defineMarkEventDeserializer; this.closeOpenEndedDeviceMappingEventDeserializer = closeOpenEndedDeviceMappingEventDeserializer; this.additionalScoringInformationEventDeserializer = additionalScoringInformationEventDeserializer; + this.fixedMarkPassingEventDeserializer = fixedMarkPassingEventDeserializer; + this.suppressedMarkPassingsDeserializer = suppressedMarkPassingsEventDeserializer; } protected JsonDeserializer getDeserializer(JSONObject object) throws JsonDeserializationException { @@ -201,6 +211,10 @@ public class RaceLogEventDeserializer implements JsonDeserializer return closeOpenEndedDeviceMappingEventDeserializer; } else if (type.equals(RaceLogAdditionalScoringInformationSerializer.VALUE_CLASS)) { return additionalScoringInformationEventDeserializer; + } else if (type.equals(RaceLogFixedMarkPassingEventSerializer.VALUE_CLASS)) { + return fixedMarkPassingEventDeserializer; + } else if (type.equals(RaceLogSuppressedMarkPassingsEventSerializer.VALUE_CLASS)){ + return suppressedMarkPassingsDeserializer; } throw new JsonDeserializationException(String.format("There is no deserializer defined for event type %s.", diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogFixedMarkPassingEventDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogFixedMarkPassingEventDeserializer.java new file mode 100644 index 00000000000..246b13ebe93 --- /dev/null +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogFixedMarkPassingEventDeserializer.java @@ -0,0 +1,33 @@ +package com.sap.sailing.server.gateway.deserialization.racelog.impl; + +import java.io.Serializable; +import java.util.List; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; +import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogFixedMarkPassingEventSerializer; +import com.sap.sse.common.TimePoint; +import com.sap.sse.common.impl.MillisecondsTimePoint; + +public class RaceLogFixedMarkPassingEventDeserializer extends BaseRaceLogEventDeserializer implements JsonDeserializer { + + public RaceLogFixedMarkPassingEventDeserializer(JsonDeserializer competitorDeserializer) { + super(competitorDeserializer); + } + + @Override + protected RaceLogEvent deserialize(JSONObject object, Serializable id, TimePoint createdAt, AbstractLogEventAuthor author, + TimePoint timePoint, int passId, List competitors) throws JsonDeserializationException { + TimePoint ofPassing = new MillisecondsTimePoint( + (Long) object.get(RaceLogFixedMarkPassingEventSerializer.FIELD_TIMEPOINT_OF_MARKPASSING)); + Integer zeroBasedIndexOfPassedWaypoint = (Integer) object + .get(RaceLogFixedMarkPassingEventSerializer.FIELD_INDEX_OF_PASSED_WAYPOINT); + return factory.createFixedMarkPassingEvent(timePoint, author, id, competitors, passId, ofPassing, + zeroBasedIndexOfPassedWaypoint); + } +} diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogSuppressedMarkPassingsEventDeserializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogSuppressedMarkPassingsEventDeserializer.java new file mode 100644 index 00000000000..0826c463213 --- /dev/null +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/deserialization/racelog/impl/RaceLogSuppressedMarkPassingsEventDeserializer.java @@ -0,0 +1,28 @@ +package com.sap.sailing.server.gateway.deserialization.racelog.impl; + +import java.io.Serializable; +import java.util.List; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.abstractlog.AbstractLogEventAuthor; +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializationException; +import com.sap.sailing.server.gateway.deserialization.JsonDeserializer; +import com.sap.sailing.server.gateway.serialization.racelog.impl.RaceLogSuppressedMarkPassingsEventSerializer; +import com.sap.sse.common.TimePoint; + +public class RaceLogSuppressedMarkPassingsEventDeserializer extends BaseRaceLogEventDeserializer implements JsonDeserializer { + + public RaceLogSuppressedMarkPassingsEventDeserializer(JsonDeserializer competitorDeserializer) { + super(competitorDeserializer); + } + + @Override + protected RaceLogEvent deserialize(JSONObject object, Serializable id, TimePoint createdAt, AbstractLogEventAuthor author, + TimePoint timePoint, int passId, List competitors) throws JsonDeserializationException { + Integer indexOfFirstWaypoint = (Integer) object.get(RaceLogSuppressedMarkPassingsEventSerializer.FIELD_INDEX_OF_FIRST_SUPPRESSED_WAYPOINTS); + return factory.createSuppressedMarkPassingsEvent(timePoint, author, id, competitors, passId, indexOfFirstWaypoint); + } +} diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogEventSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogEventSerializer.java index 2eded9789ae..068f5bd6391 100644 --- a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogEventSerializer.java +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogEventSerializer.java @@ -2,6 +2,7 @@ package com.sap.sailing.server.gateway.serialization.racelog.impl; import org.json.simple.JSONObject; +import com.sap.sailing.domain.abstractlog.race.FixedMarkPassingEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseAreaChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogCourseDesignChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; @@ -18,6 +19,7 @@ import com.sap.sailing.domain.abstractlog.race.RaceLogRevokeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartProcedureChangedEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogStartTimeEvent; import com.sap.sailing.domain.abstractlog.race.RaceLogWindFixEvent; +import com.sap.sailing.domain.abstractlog.race.SuppressedMarkPassingsEvent; import com.sap.sailing.domain.abstractlog.race.scoring.RaceLogAdditionalScoringInformationEvent; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogCloseOpenEndedDeviceMappingEvent; import com.sap.sailing.domain.abstractlog.race.tracking.RaceLogDefineMarkEvent; @@ -77,7 +79,9 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac new RaceLogRegisterCompetitorEventSerializer(competitorSerializer), new RaceLogDefineMarkEventSerializer(competitorSerializer, new MarkJsonSerializer()), new RaceLogCloseOpenEndedDeviceMappingEventSerializer(competitorSerializer), - new RaceLogAdditionalScoringInformationSerializer(competitorSerializer)); + new RaceLogAdditionalScoringInformationSerializer(competitorSerializer), + new RaceLogFixedMarkPassingEventSerializer(competitorSerializer), + new RaceLogSuppressedMarkPassingsEventSerializer(competitorSerializer)); } private final JsonSerializer flagEventSerializer; @@ -102,7 +106,9 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac private final JsonSerializer defineMarkSerializer; private final JsonSerializer closeOpenEndedDeviceMappingEventSerializer; private final JsonSerializer additionalScoringInformationSerializer; - + private final JsonSerializer fixedMarkPassingEventSerializer; + private final JsonSerializer suppressedMarkPassingsEventSerializer; + private JsonSerializer chosenSerializer; public RaceLogEventSerializer( @@ -127,7 +133,9 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac JsonSerializer registerCompetitorSerializer, JsonSerializer defineMarkSerializer, JsonSerializer closeOpenEndedDeviceMappingEventSerializer, - JsonSerializer additionalScoringInformationSerializer) { + JsonSerializer additionalScoringInformationSerializer, + JsonSerializer fixedMarkPassingEventSerializer, + JsonSerializer suppressedMarkPassingsSerializer) { this.flagEventSerializer = flagEventSerializer; this.startTimeSerializer = startTimeSerializer; this.raceStatusSerializer = raceStatusSerializer; @@ -150,6 +158,8 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac this.defineMarkSerializer = defineMarkSerializer; this.closeOpenEndedDeviceMappingEventSerializer = closeOpenEndedDeviceMappingEventSerializer; this.additionalScoringInformationSerializer = additionalScoringInformationSerializer; + this.fixedMarkPassingEventSerializer = fixedMarkPassingEventSerializer; + this.suppressedMarkPassingsEventSerializer = suppressedMarkPassingsSerializer; this.chosenSerializer = null; } @@ -275,5 +285,18 @@ public class RaceLogEventSerializer implements JsonSerializer, Rac @Override public void visit(RaceLogAdditionalScoringInformationEvent additionalScoringInformation) { chosenSerializer = additionalScoringInformationSerializer; + + } + + @Override + public void visit(FixedMarkPassingEvent event) { + chosenSerializer = fixedMarkPassingEventSerializer; + + } + + + @Override + public void visit(SuppressedMarkPassingsEvent event) { + chosenSerializer = suppressedMarkPassingsEventSerializer; } } diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogFixedMarkPassingEventSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogFixedMarkPassingEventSerializer.java new file mode 100644 index 00000000000..d87f7e9a4b3 --- /dev/null +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogFixedMarkPassingEventSerializer.java @@ -0,0 +1,36 @@ +package com.sap.sailing.server.gateway.serialization.racelog.impl; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.abstractlog.race.FixedMarkPassingEvent; +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.server.gateway.serialization.JsonSerializer; + +public class RaceLogFixedMarkPassingEventSerializer extends BaseRaceLogEventSerializer implements JsonSerializer { + + public RaceLogFixedMarkPassingEventSerializer(JsonSerializer competitorSerializer) { + super(competitorSerializer); + } + + public static final String VALUE_CLASS = FixedMarkPassingEvent.class.getSimpleName(); + + public static final String FIELD_TIMEPOINT_OF_MARKPASSING = "timePointOfMarkPassing"; + public static final String FIELD_INDEX_OF_PASSED_WAYPOINT = "indexOfPassedWaypoint"; + + @Override + protected String getClassFieldValue() { + return VALUE_CLASS; + } + + @Override + public JSONObject serialize(RaceLogEvent object) { + FixedMarkPassingEvent event = (FixedMarkPassingEvent) object; + + JSONObject result = super.serialize(event); + result.put(FIELD_INDEX_OF_PASSED_WAYPOINT, event.getZeroBasedIndexOfPassedWaypoint()); + result.put(FIELD_TIMEPOINT_OF_MARKPASSING, event.getTimePointOfFixedPassing().asMillis()); + return result; + } + +} diff --git a/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogSuppressedMarkPassingsEventSerializer.java b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogSuppressedMarkPassingsEventSerializer.java new file mode 100644 index 00000000000..9b8bf62eb7b --- /dev/null +++ b/java/com.sap.sailing.server.gateway.serialization.shared.android/src/com/sap/sailing/server/gateway/serialization/racelog/impl/RaceLogSuppressedMarkPassingsEventSerializer.java @@ -0,0 +1,35 @@ +package com.sap.sailing.server.gateway.serialization.racelog.impl; + +import org.json.simple.JSONObject; + +import com.sap.sailing.domain.abstractlog.race.RaceLogEvent; +import com.sap.sailing.domain.abstractlog.race.SuppressedMarkPassingsEvent; +import com.sap.sailing.domain.base.Competitor; +import com.sap.sailing.server.gateway.serialization.JsonSerializer; + +public class RaceLogSuppressedMarkPassingsEventSerializer extends BaseRaceLogEventSerializer implements JsonSerializer { + + public RaceLogSuppressedMarkPassingsEventSerializer(JsonSerializer competitorSerializer) { + super(competitorSerializer); + } + + public static final String VALUE_CLASS = SuppressedMarkPassingsEvent.class.getSimpleName(); + + public final static String FIELD_INDEX_OF_FIRST_SUPPRESSED_WAYPOINTS = "indexOfFirstSuppressedWaypoints"; + + @Override + protected String getClassFieldValue() { + return VALUE_CLASS; + } + + @Override + public JSONObject serialize(RaceLogEvent object) { + SuppressedMarkPassingsEvent event = (SuppressedMarkPassingsEvent) object; + + JSONObject result = super.serialize(event); + result.put(FIELD_INDEX_OF_FIRST_SUPPRESSED_WAYPOINTS, event.getZeroBasedIndexOfFirstSuppressedWaypoint()); + return result; + } + + +} diff --git a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java index 1d3f145cbdf..6d393109b53 100644 --- a/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java +++ b/java/com.sap.sailing.server.gateway.serialization.test/src/com/sap/sailing/server/gateway/serialization/test/racelog/RaceLogEventSerializerTest.java @@ -46,6 +46,8 @@ public class RaceLogEventSerializerTest { private JsonSerializer defineMarkEventSerializer; private JsonSerializer closeOpenEndedDeviceMappingEventSerializer; private JsonSerializer additionalScoringInformationSerializer; + private JsonSerializer fixedMarkPassingEventSerializer; + private JsonSerializer suppressedMarkPassingsSerializer; private RaceLogEventFactory factory; private AbstractLogEventAuthor author = new LogEventAuthorImpl("Test Author", 1); @@ -75,6 +77,8 @@ public class RaceLogEventSerializerTest { defineMarkEventSerializer = mock(JsonSerializer.class); closeOpenEndedDeviceMappingEventSerializer = mock(JsonSerializer.class); additionalScoringInformationSerializer = mock(JsonSerializer.class); + fixedMarkPassingEventSerializer = mock(JsonSerializer.class); + suppressedMarkPassingsSerializer = mock(JsonSerializer.class); serializer = new RaceLogEventSerializer(flagEventSerializer, startTimeSerializer, raceStatusSerializer, courseAreaChangedEventSerializer, passChangedSerializer, courseDesignChangedEventSerializer, @@ -83,7 +87,7 @@ public class RaceLogEventSerializerTest { startProcedureTypeChangedEventSerializer, protestStartTimeEventSerializer, windFixEventSerializer, deviceCompetitorMappingEventSerializer, deviceMarkMappingEventSerializer, denoteForTrackingEventSerializer, startTrackingEventSerializer, revokeEventSerializer, registerCompetitorEventSerializer, defineMarkEventSerializer, - closeOpenEndedDeviceMappingEventSerializer, additionalScoringInformationSerializer); + closeOpenEndedDeviceMappingEventSerializer, fixedMarkPassingEventSerializer, suppressedMarkPassingsSerializer, additionalScoringInformationSerializer); factory = RaceLogEventFactory.INSTANCE; } @@ -257,5 +261,4 @@ public class RaceLogEventSerializerTest { serializer.serialize(event); verify(closeOpenEndedDeviceMappingEventSerializer).serialize(event); } - } diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java index d7c330e03df..e87f62f388c 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/InitialLoadReplicationObjectIdentityTest.java @@ -132,7 +132,7 @@ public class InitialLoadReplicationObjectIdentityTest extends AbstractServerRepl masterRegatta.addRace(masterRace); DynamicTrackedRace masterTrackedRace = master.createTrackedRace(new RegattaNameAndRaceName(masterRegatta.getName(), masterRace.getName()), master.getWindStore(), master.getGPSFixStore(), /* delayToLiveInMillis */ 3000, - /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 10000); + /* millisecondsOverWhichToAverageWind */ 15000, /* millisecondsOverWhichToAverageSpeed */ 10000, /*ignoreTracTracMarkPassings*/ false); /* Leaderboard */ final String leaderboardName = "Great Leaderboard"; diff --git a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java index 970b2f27d40..1cc657c1968 100755 --- a/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java +++ b/java/com.sap.sailing.server.replication.test/src/com/sap/sailing/server/replication/test/TrackRaceReplicationTest.java @@ -88,7 +88,7 @@ public class TrackRaceReplicationTest extends AbstractServerReplicationTest { trackingParams = com.sap.sailing.domain.tractracadapter.DomainFactory.INSTANCE .createTrackingConnectivityParameters(paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking, endOfTracking, /* delayToLiveInMillis */ - 0l, /* simulateWithStartTimeNow */false, EmptyRaceLogStore.INSTANCE, + 0l, /* simulateWithStartTimeNow */false, /*ignoreTracTracMarkPassings*/ false, EmptyRaceLogStore.INSTANCE, EmptyRegattaLogStore.INSTANCE, tracTracUsername, tracTracPassword, "", ""); } diff --git a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/RaceTrackerStartStopTest.java b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/RaceTrackerStartStopTest.java index 7ffa5354f62..6d857720fb3 100644 --- a/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/RaceTrackerStartStopTest.java +++ b/java/com.sap.sailing.server.test/src/com/sap/sailing/server/test/RaceTrackerStartStopTest.java @@ -78,13 +78,13 @@ public class RaceTrackerStartStopTest { raceDef3 = new RaceDefinitionImpl(RACENAME3, new CourseImpl("Course3", new ArrayList()), boatClass, new ArrayList()); regatta.addRace(raceDef1); trackedRegatta1.createTrackedRace(raceDef1, Collections. emptyList(), /* windStore */ EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 0l, /* millisecondsOverWhichToAverageWind */ 0l, - /* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null); + /* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null, /*useMarkPassingCalculator*/ false); regatta.addRace(raceDef2); trackedRegatta1.createTrackedRace(raceDef2, Collections. emptyList(), /* windStore */ EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 0l, /* millisecondsOverWhichToAverageWind */ 0l, - /* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null); + /* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null, /*useMarkPassingCalculator*/ false); regatta.addRace(raceDef3); trackedRegatta1.createTrackedRace(raceDef3, Collections. emptyList(), /* windStore */ EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, /* delayToLiveInMillis */ 0l, /* millisecondsOverWhichToAverageWind */ 0l, - /* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null); + /* millisecondsOverWhichToAverageSpeed */ 0l, /* raceDefinitionSetToUpdate */ null, /*useMarkPassingCalculator*/ false); Set raceDefinitionSetRace1 = new HashSet(); raceDefinitionSetRace1.add(raceDef1); Set raceDefinitionSetRace2 = new HashSet(); diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java index c669312498c..892a29e9ad1 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/RacingEventService.java @@ -299,7 +299,7 @@ public interface RacingEventService extends TrackedRegattaRegistry, RegattaFetch void updateStoredLeaderboardGroup(LeaderboardGroup leaderboardGroup); DynamicTrackedRace createTrackedRace(RegattaAndRaceIdentifier raceIdentifier, WindStore windStore, GPSFixStore gpsFixStore, - long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed); + long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed, boolean useMarkPassingCalculator); Regatta getOrCreateDefaultRegatta(String name, String boatClassName, Serializable id); 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 026bb05f609..89d291c7c14 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 @@ -1463,12 +1463,12 @@ public class RacingEventServiceImpl implements RacingEventService, ClearStateTes @Override public DynamicTrackedRace createTrackedRace(RegattaAndRaceIdentifier raceIdentifier, WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, - long millisecondsOverWhichToAverageSpeed) { + long millisecondsOverWhichToAverageSpeed, boolean useMarkPassingCalculator) { DynamicTrackedRegatta trackedRegatta = getOrCreateTrackedRegatta(getRegatta(raceIdentifier)); RaceDefinition race = getRace(raceIdentifier); return trackedRegatta.createTrackedRace(race, Collections. emptyList(), windStore, gpsFixStore, delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, - /* raceDefinitionSetToUpdate */null); + /* raceDefinitionSetToUpdate */null, useMarkPassingCalculator); } private void ensureRegattaIsObservedForDefaultLeaderboardAndAutoLeaderboardLinking( diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java index 855a96492d6..9b1f635d2d0 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java @@ -567,4 +567,10 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { return null; } + @Override + public boolean isUsingMarkPassingCalculator() { + // TODO Auto-generated method stub + return false; + } + } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateTrackedRace.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateTrackedRace.java index 5e7bce313dd..375ca505129 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateTrackedRace.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/operationaltransformation/CreateTrackedRace.java @@ -77,7 +77,10 @@ public class CreateTrackedRace extends AbstractRaceOperation public DynamicTrackedRace internalApplyTo(RacingEventService toState) { return toState.createTrackedRace(getRaceIdentifier(), windStore == null ? EmptyWindStore.INSTANCE : windStore, gpsFixStore == null ? EmptyGPSFixStore.INSTANCE : gpsFixStore, - delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed); + delayToLiveInMillis, millisecondsOverWhichToAverageWind, millisecondsOverWhichToAverageSpeed, + /* useMarkPassingCalculator */ false); // no separate mark passing calculations in replica; + // Mark passings are computed on master and are replicated separately. + // See UpdateMarkPassings } } diff --git a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.java b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.java index 793d1f382ca..f278db89f0a 100755 --- a/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.java +++ b/java/com.sap.sse.gwt/src/com/sap/sse/gwt/client/controls/slider/SliderBar.java @@ -1016,8 +1016,8 @@ public class SliderBar extends FocusPanel implements RequiresResize, HasValue - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +