mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-12 07:10:55 +00:00
Merge remote-tracking branch 'origin/master' into raceboard-simulation
This commit is contained in:
commit
ada387d273
151 files changed
+8256
-4347
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
|
||||
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
|
||||
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
|
||||
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
|
||||
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
|
||||
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
|
||||
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.server/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sailing.dashboards.gwt"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-servlet.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-user.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -war "${project_loc:com.sap.sailing.dashboards.gwt}" -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort 9998 com.sap.sailing.dashboards.gwt.RibDashboard"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-XstartOnFirstThread -Xmx2048m"/>
|
||||
</launchConfiguration>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<launchConfiguration type="com.google.gdt.eclipse.suite.webapp">
|
||||
<booleanAttribute key="com.google.gdt.eclipse.core.RUN_SERVER" value="false"/>
|
||||
<stringAttribute key="com.google.gdt.eclipse.suiteMainTypeProcessor.PREVIOUSLY_SET_MAIN_TYPE_NAME" value="com.google.gwt.dev.GWTShell"/>
|
||||
<booleanAttribute key="com.google.gdt.eclipse.suiteWarArgumentProcessor.IS_WAR_FROM_PROJECT_PROPERTIES" value="false"/>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.CODE_SERVER_PORT" value="9998"/>
|
||||
<listAttribute key="com.google.gwt.eclipse.core.ENTRY_POINT_MODULES">
|
||||
<listEntry value="com.sap.sailing.dashboards.gwt.RibDashboard"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="com.google.gwt.eclipse.core.SUPERDEVMODE_ENABLED" value="true"/>
|
||||
<stringAttribute key="com.google.gwt.eclipse.core.URL" value="/dashboards/RibDashboard.html"/>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
|
||||
<listEntry value="/com.sap.sailing.dashboards.gwt"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
|
||||
<listEntry value="4"/>
|
||||
</listAttribute>
|
||||
<listAttribute key="org.eclipse.debug.ui.favoriteGroups">
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.debug"/>
|
||||
<listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
|
||||
</listAttribute>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
|
||||
<listAttribute key="org.eclipse.jdt.launching.CLASSPATH">
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry containerPath="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" path="1" type="4"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.gwt.ui/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/java" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.dashboards.gwt/src/main/resources" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.server/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry id="org.eclipse.jdt.launching.classpathentry.defaultClasspath"> <memento exportedEntriesOnly="false" project="com.sap.sailing.dashboards.gwt"/> </runtimeClasspathEntry> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-servlet.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.google.gwt.osgi/lib/gwt-user.jar" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sailing.domain.common/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/org.moxieapps.gwt.highcharts/src" path="3" type="2"/> "/>
|
||||
<listEntry value="<?xml version="1.0" encoding="UTF-8" standalone="no"?> <runtimeClasspathEntry internalArchive="/com.sap.sse.gwt.adminconsole/src" path="3" type="2"/> "/>
|
||||
</listAttribute>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.CLASSPATH_PROVIDER" value="com.google.gwt.eclipse.core.moduleClasspathProvider"/>
|
||||
<booleanAttribute key="org.eclipse.jdt.launching.DEFAULT_CLASSPATH" value="false"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.JRE_CONTAINER" value="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="com.google.gwt.dev.DevMode"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="-superDevMode -incremental -war "${project_loc:com.sap.sailing.dashboards.gwt}" -startupUrl /dashboards/RibDashboard.html -noserver -remoteUI "${gwt_remote_ui_server_port}:${unique_id}" -logLevel INFO -codeServerPort 9998 com.sap.sailing.dashboards.gwt.RibDashboard"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="com.sap.sailing.dashboards.gwt"/>
|
||||
<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Xmx2048m"/>
|
||||
</launchConfiguration>
|
||||
+2
-1
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+19
@@ -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<Competitor> 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<Competitor> 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) {
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+224
-191
@@ -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<DeviceIdentifierMongoHandler> deviceIdentifierServiceFinder;
|
||||
private final TypeBasedServiceFinder<DeviceIdentifierMongoHandler> deviceIdentifierServiceFinder;
|
||||
|
||||
/**
|
||||
* Uses <code>null</code> 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 <code>null</code> 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<DeviceIdentifierMongoHandler> deviceIdentifierServiceFinder, DeviceIdentifier device)
|
||||
throws TransformationException, NoCorrespondingServiceRegisteredException {
|
||||
String type = device.getIdentifierType();
|
||||
DeviceIdentifierMongoHandler handler = deviceIdentifierServiceFinder.findService(type);
|
||||
com.sap.sse.common.Util.Pair<String, ? extends Object> 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<DeviceIdentifierMongoHandler> deviceIdentifierServiceFinder, DeviceIdentifier device)
|
||||
throws TransformationException, NoCorrespondingServiceRegisteredException {
|
||||
String type = device.getIdentifierType();
|
||||
DeviceIdentifierMongoHandler handler = deviceIdentifierServiceFinder.findService(type);
|
||||
com.sap.sse.common.Util.Pair<String, ? extends Object> 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);
|
||||
}
|
||||
|
||||
+14
@@ -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);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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.<Sideline> emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0);
|
||||
Collections.<Sideline> 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.<Sideline> emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0);
|
||||
Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE, store, 0, 0, 0, /*useMarkPassingCalculator*/ false);
|
||||
|
||||
trackedRace.addListener(new AbstractRaceChangeListener() {
|
||||
@Override
|
||||
|
||||
+1
-5
@@ -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) {
|
||||
|
||||
+12
@@ -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();
|
||||
|
||||
}
|
||||
+6
@@ -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<Competitor> 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<Competitor> competitors, Integer passId, Integer zeroBasedIndexOfFirstSuppressedWaypoint);
|
||||
}
|
||||
+5
@@ -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);
|
||||
}
|
||||
+9
@@ -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();
|
||||
|
||||
}
|
||||
+35
@@ -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<Set<Triple<Competitor, Integer, TimePoint>>> {
|
||||
|
||||
public MarkPassingDataFinder(RaceLog raceLog) {
|
||||
super(raceLog);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<Triple<Competitor, Integer, TimePoint>> performAnalysis() {
|
||||
Set<Triple<Competitor, Integer, TimePoint>> result = new HashSet<Triple<Competitor, Integer, TimePoint>>();
|
||||
for (RaceLogEvent event : getLog().getUnrevokedEvents()) {
|
||||
if (event instanceof FixedMarkPassingEvent) {
|
||||
FixedMarkPassingEvent castedEvent = (FixedMarkPassingEvent) event;
|
||||
result.add(new Triple<Competitor, Integer, TimePoint>(castedEvent.getInvolvedBoats().get(0), castedEvent
|
||||
.getZeroBasedIndexOfPassedWaypoint(), castedEvent.getTimePointOfFixedPassing()));
|
||||
} else if (event instanceof SuppressedMarkPassingsEvent){
|
||||
SuppressedMarkPassingsEvent castedEvent = (SuppressedMarkPassingsEvent) event;
|
||||
result.add(new Triple<Competitor, Integer, TimePoint>(castedEvent.getInvolvedBoats().get(0), castedEvent.getZeroBasedIndexOfFirstSuppressedWaypoint(), null));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
+12
@@ -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);
|
||||
}
|
||||
}
|
||||
+12
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
+47
@@ -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<Competitor> 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();
|
||||
}
|
||||
}
|
||||
+16
-2
@@ -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.<Competitor>emptyList(), currentPassId,
|
||||
informationType);
|
||||
return new RaceLogAdditionalScoringInformationEventImpl(MillisecondsTimePoint.now(), author, timePoint, id,
|
||||
Collections.<Competitor> emptyList(), currentPassId, informationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RaceLogEvent createFixedMarkPassingEvent(TimePoint logicalTimePoint, AbstractLogEventAuthor author,
|
||||
Serializable id, List<Competitor> 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<Competitor> competitors, Integer passId, Integer zeroBasedIndexOfFirstSuppressedWaypoint) {
|
||||
return new SuppressedMarkPassingsEventImpl(MillisecondsTimePoint.now(), author, logicalTimePoint, id, competitors, passId,
|
||||
zeroBasedIndexOfFirstSuppressedWaypoint);
|
||||
}
|
||||
}
|
||||
+39
@@ -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<Competitor> 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();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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);
|
||||
|
||||
Regular → Executable
+171
-147
@@ -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 <code>ItemType</code>. 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.<p>
|
||||
*
|
||||
* 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<FixType extends Timed> 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 <code>finally</code>
|
||||
* 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
|
||||
* <code>finally</code> 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<FixType> 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<FixType> 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<FixType> 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 <code>null</code> 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 <code>null</code> 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 <code>startingAt</code> (or "at or after" in case
|
||||
* <code>inclusive</code> is <code>true</code>). 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<FixType> getFixesIterator(TimePoint startingAt, boolean inclusive);
|
||||
|
||||
/**
|
||||
* Returns an iterator starting at the first raw fix after <code>startingAt</code> (or "at or after" in case
|
||||
* <code>inclusive</code> is <code>true</code>). 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<FixType> getRawFixesIterator(TimePoint startingAt, boolean inclusive);
|
||||
|
||||
/**
|
||||
* Returns a descending iterator starting at the first fix before <code>startingAt</code> (or "at or before" in case
|
||||
* <code>inclusive</code> is <code>true</code>). 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<FixType> getFixesDescendingIterator(TimePoint startingAt, boolean inclusive);
|
||||
|
||||
/**
|
||||
* Returns a descending iterator starting at the first raw fix before <code>startingAt</code> (or "at or before" in case
|
||||
* <code>inclusive</code> is <code>true</code>). 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<FixType> getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive);
|
||||
|
||||
/**
|
||||
* @return the average duration between two fixes (outliers removed) in this track or <code>null</code> if there is not
|
||||
* more than one fix in the track
|
||||
*/
|
||||
Duration getAverageIntervalBetweenFixes();
|
||||
|
||||
/**
|
||||
* @return the average duration between two fixes (outliers <em>not</em> removed) in this track or <code>null</code> 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 <code>ItemType</code>. 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.<p>
|
||||
*
|
||||
* 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<FixType extends Timed> 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 <code>finally</code>
|
||||
* 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
|
||||
* <code>finally</code> 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<FixType> 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<FixType> 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<FixType> getRawFixes();
|
||||
|
||||
/**
|
||||
* Returns <code>null</code> if no such fix exists.
|
||||
*/
|
||||
FixType getLastFixAtOrBefore(TimePoint timePoint);
|
||||
|
||||
/**
|
||||
* Returns <code>null</code> if no such fix exists.
|
||||
*/
|
||||
FixType getLastFixBefore(TimePoint timePoint);
|
||||
|
||||
/**
|
||||
* Returns <code>null</code> if no such fix exists.
|
||||
*/
|
||||
FixType getLastRawFixAtOrBefore(TimePoint timePoint);
|
||||
|
||||
/**
|
||||
* Returns <code>null</code> if no such fix exists.
|
||||
*/
|
||||
FixType getFirstFixAtOrAfter(TimePoint timePoint);
|
||||
|
||||
/**
|
||||
* Returns <code>null</code> if no such fix exists.
|
||||
*/
|
||||
FixType getFirstRawFixAtOrAfter(TimePoint timePoint);
|
||||
|
||||
/**
|
||||
* Returns <code>null</code> if no such fix exists.
|
||||
*/
|
||||
FixType getLastRawFixBefore(TimePoint timePoint);
|
||||
|
||||
/**
|
||||
* Returns <code>null</code> if no such fix exists.
|
||||
*/
|
||||
FixType getFirstRawFixAfter(TimePoint timePoint);
|
||||
|
||||
/**
|
||||
* Returns <code>null</code> if no such fix exists.
|
||||
*/
|
||||
FixType getFirstFixAfter(TimePoint timePoint);
|
||||
|
||||
/**
|
||||
* The first fix in this track or <code>null</code> 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 <code>null</code> 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 <code>startingAt</code> (or "at or after" in case
|
||||
* <code>inclusive</code> is <code>true</code>). 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<FixType> getFixesIterator(TimePoint startingAt, boolean inclusive);
|
||||
|
||||
/**
|
||||
* Returns an iterator starting at the first raw fix after <code>startingAt</code> (or "at or after" in case
|
||||
* <code>inclusive</code> is <code>true</code>). 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<FixType> getRawFixesIterator(TimePoint startingAt, boolean inclusive);
|
||||
|
||||
/**
|
||||
* Returns a descending iterator starting at the first fix before <code>startingAt</code> (or "at or before" in case
|
||||
* <code>inclusive</code> is <code>true</code>). 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<FixType> getFixesDescendingIterator(TimePoint startingAt, boolean inclusive);
|
||||
|
||||
/**
|
||||
* Returns a descending iterator starting at the first raw fix before <code>startingAt</code> (or "at or before" in case
|
||||
* <code>inclusive</code> is <code>true</code>). 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<FixType> getRawFixesDescendingIterator(TimePoint startingAt, boolean inclusive);
|
||||
|
||||
/**
|
||||
* @return the average duration between two fixes (outliers removed) in this track or <code>null</code> if there is not
|
||||
* more than one fix in the track
|
||||
*/
|
||||
Duration getAverageIntervalBetweenFixes();
|
||||
|
||||
/**
|
||||
* @return the average duration between two fixes (outliers <em>not</em> removed) in this track or <code>null</code> if there is not
|
||||
* more than one raw fix in the track
|
||||
*/
|
||||
Duration getAverageIntervalBetweenRawFixes();
|
||||
}
|
||||
+1
-1
@@ -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<String> devices);
|
||||
|
||||
|
||||
+2
-1
@@ -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);
|
||||
|
||||
+10
-7
@@ -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);
|
||||
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+3
-2
@@ -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
|
||||
|
||||
+6
-6
@@ -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
|
||||
|
||||
+65
-60
@@ -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 <code>null</code> 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<String, Competitor>();
|
||||
this.useInternalMarkPassingAlgorithm = useInternalMarkPassingAlgorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -328,69 +330,72 @@ public class SwissTimingRaceTrackerImpl extends AbstractRaceTrackerImpl implemen
|
||||
public void receivedTimingData(String raceID, String boatID,
|
||||
List<com.sap.sse.common.Util.Triple<Integer, Integer, Long>> 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<Integer, MarkPassing> markPassingsByMarkIndex = new TreeMap<Integer, MarkPassing>();
|
||||
// now fill with the already existing mark passings for the competitor identified by boatID...
|
||||
NavigableSet<MarkPassing> 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<Integer, Integer, Long> 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<Integer, MarkPassing> markPassingsByMarkIndex = new TreeMap<Integer, MarkPassing>();
|
||||
// now fill with the already existing mark passings for the competitor identified by boatID...
|
||||
NavigableSet<MarkPassing> 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<Integer, Integer, Long> 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());
|
||||
}
|
||||
|
||||
|
||||
+11
-7
@@ -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
|
||||
|
||||
+2
-2
@@ -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<? extends TrackedRace> 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<? extends TrackedRace> trackedRaces = replayListener.getTrackedRaces();
|
||||
TrackedRace trackedRace = trackedRaces.iterator().next();
|
||||
|
||||
+2
-1
@@ -39,7 +39,8 @@ public interface SwissTimingReplayService {
|
||||
* @param regatta
|
||||
* the regatta to which the race shall be added; if <code>null</code>, 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);
|
||||
|
||||
}
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
+9
-4
@@ -133,13 +133,17 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
|
||||
private final Map<Competitor, Short> lastNextMark;
|
||||
|
||||
private RaceStatus lastRaceStatus;
|
||||
|
||||
private final boolean useInternalMarkPassingAlgorithm;
|
||||
|
||||
/**
|
||||
* @param regatta
|
||||
* the regatta to associate the race(s) received by the listener with, or <code>null</code> 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<DynamicTrackedRace> getTrackedRaces() {
|
||||
@@ -260,7 +265,7 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter {
|
||||
short trueWindDirectionInDegrees) {
|
||||
final List<String> 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.<Sideline> 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) {
|
||||
|
||||
BIN
Binary file not shown.
+685
@@ -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 ------------------
|
||||
BIN
Binary file not shown.
+685
@@ -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 ------------------
|
||||
@@ -303,7 +303,7 @@ public class CourseTest {
|
||||
new RaceDefinitionImpl("Test Race", course, new BoatClassImpl("49er", /* upwind start */true), hasso),
|
||||
Collections.<Sideline> 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.<Sideline> 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);
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ public class CourseUpdateDuringNonAtomicSerializationTest implements Serializabl
|
||||
trackedRace = new DynamicTrackedRaceImpl(trackedRegatta, race, Collections.<Sideline> emptySet(),
|
||||
EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE,
|
||||
/* delayToLiveInMillis */10000, /* millisecondsOverWhichToAverageWind */30000, /* millisecondsOverWhichToAverageSpeed */
|
||||
7000) {
|
||||
7000, /*useMarkPassingCalculator*/ false) {
|
||||
private static final long serialVersionUID = 9114777576548711763L;
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -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<IControlRoute, Long, Void> event) {
|
||||
super.handleEvent(event);
|
||||
|
||||
+1
-1
@@ -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<Sideline>(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, 5000, 20000, 20000));
|
||||
new HashSet<Sideline>(), EmptyWindStore.INSTANCE, EmptyGPSFixStore.INSTANCE, 5000, 20000, 20000, /*useMarkPassingCalculator*/ false));
|
||||
|
||||
return spyedTrackedRace;
|
||||
}
|
||||
|
||||
+4
-4
@@ -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,
|
||||
|
||||
+1
-1
@@ -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() {
|
||||
|
||||
+378
-378
@@ -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<Competitor> 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<Competitor>();
|
||||
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<MarkPassing> hungersMarkPassings = createMarkPassings(hunger, start);
|
||||
getTrackedRace().updateMarkPassings(hunger, hungersMarkPassings);
|
||||
List<MarkPassing> plattnersMarkPassings = createMarkPassings(plattner, start);
|
||||
getTrackedRace().updateMarkPassings(plattner, plattnersMarkPassings);
|
||||
}
|
||||
|
||||
protected DynamicTrackedRaceImpl createTrackedRace(String regattaName, String raceName, String boatClassName,
|
||||
Iterable<Competitor> 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<Waypoint> waypoints = new ArrayList<Waypoint>();
|
||||
// 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.<Sideline> 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<TrackedLeg> 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<Competitor> 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<Competitor>();
|
||||
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<MarkPassing> hungersMarkPassings = createMarkPassings(hunger, start);
|
||||
getTrackedRace().updateMarkPassings(hunger, hungersMarkPassings);
|
||||
List<MarkPassing> plattnersMarkPassings = createMarkPassings(plattner, start);
|
||||
getTrackedRace().updateMarkPassings(plattner, plattnersMarkPassings);
|
||||
}
|
||||
|
||||
protected DynamicTrackedRaceImpl createTrackedRace(String regattaName, String raceName, String boatClassName,
|
||||
Iterable<Competitor> 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<Waypoint> waypoints = new ArrayList<Waypoint>();
|
||||
// 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.<Sideline> 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<TrackedLeg> 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);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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);
|
||||
}
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ public class StarbordSideOfStartLineRecognitionTest {
|
||||
private static final long serialVersionUID = -8007932232555073829L;
|
||||
|
||||
public MockedTrackedRaceImpl() {
|
||||
super(null, null, Collections.<Sideline> emptyList(), null, null, 0, 0, 0, 0);
|
||||
super(null, null, Collections.<Sideline> emptyList(), null, null, 0, 0, 0, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -117,7 +117,7 @@ public abstract class TrackBasedTest {
|
||||
DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(trackedRegatta, race, Collections.<Sideline> 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
|
||||
|
||||
+2
-1
@@ -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());
|
||||
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ public class UpdateMarkPassingTest {
|
||||
/* trackedRegatta */new DynamicTrackedRegattaImpl(new RegattaImpl("test", null, null, null, new HashSet<Series>(), false, null,
|
||||
"test", null)), race, Collections.<Sideline> 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) }));
|
||||
|
||||
@@ -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<Wind> expectedWind = new HashSet<Wind>();
|
||||
expectedWind.add(wind1);
|
||||
expectedWind.add(wind2);
|
||||
Set<Wind> actualWind = new HashSet<Wind>();
|
||||
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 <code>null</code> 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.<Sideline> 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<Pair<Position, TimePoint>> timeWeigherThatPretendsToAlsoWeighPositions = new PositionAndTimePointWeigher(
|
||||
/* halfConfidenceAfterMilliseconds */10000l, new MeterDistance(1000));
|
||||
ConfidenceBasedWindAverager<Pair<Position, TimePoint>> averager = ConfidenceFactory.INSTANCE
|
||||
.createWindAverager(timeWeigherThatPretendsToAlsoWeighPositions);
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
final DegreePosition p1 = new DegreePosition(0, 0);
|
||||
WindWithConfidence<Pair<Position, TimePoint>> w1 = new WindWithConfidenceImpl<>(
|
||||
new WindImpl(p1, now, new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(90))),
|
||||
/* confidence */ 1.0, new Pair<Position, TimePoint>(p1, now), /* useSpeed */ true);
|
||||
final Position p2 = p1.translateGreatCircle(new DegreeBearingImpl(0), new MeterDistance(1000));
|
||||
WindWithConfidence<Pair<Position, TimePoint>> w2 = new WindWithConfidenceImpl<>(
|
||||
new WindImpl(p2, now, new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))),
|
||||
/* confidence */ 1.0, new Pair<Position, TimePoint>(p2, now), /* useSpeed */ true);
|
||||
List<WindWithConfidence<Pair<Position, TimePoint>>> fixes = new ArrayList<>();
|
||||
fixes.add(w1);
|
||||
fixes.add(w2);
|
||||
WindWithConfidence<Pair<Position, TimePoint>> averageAtP1 = averager.getAverage(fixes, new Pair<Position, TimePoint>(p1, now));
|
||||
WindWithConfidence<Pair<Position, TimePoint>> averageAtP2 = averager.getAverage(fixes, new Pair<Position, TimePoint>(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<Pair<Position, TimePoint>> averageAtMiddleBetweenP1AndP2 = averager.getAverage(fixes,
|
||||
new Pair<Position, TimePoint>(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<Pair<Position, TimePoint>> w3 = new WindWithConfidenceImpl<>(
|
||||
new WindImpl(p3, now, new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))),
|
||||
/* confidence */ 1.0, new Pair<Position, TimePoint>(p3, now), /* useSpeed */ true);
|
||||
List<WindWithConfidence<Pair<Position, TimePoint>>> fixesFurtherApart = new ArrayList<>();
|
||||
fixesFurtherApart.add(w1);
|
||||
fixesFurtherApart.add(w3);
|
||||
WindWithConfidence<Pair<Position, TimePoint>> averageFurtherApartAtP1 = averager.getAverage(fixesFurtherApart, new Pair<Position, TimePoint>(p1, now));
|
||||
WindWithConfidence<Pair<Position, TimePoint>> averageAtP3 = averager.getAverage(fixesFurtherApart, new Pair<Position, TimePoint>(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<Wind> expectedWind = new HashSet<Wind>();
|
||||
expectedWind.add(wind1);
|
||||
expectedWind.add(wind2);
|
||||
Set<Wind> actualWind = new HashSet<Wind>();
|
||||
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 <code>null</code> 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.<Sideline> 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<Pair<Position, TimePoint>> timeWeigherThatPretendsToAlsoWeighPositions = new PositionAndTimePointWeigher(
|
||||
/* halfConfidenceAfterMilliseconds */10000l, new MeterDistance(1000));
|
||||
ConfidenceBasedWindAverager<Pair<Position, TimePoint>> averager = ConfidenceFactory.INSTANCE
|
||||
.createWindAverager(timeWeigherThatPretendsToAlsoWeighPositions);
|
||||
TimePoint now = MillisecondsTimePoint.now();
|
||||
final DegreePosition p1 = new DegreePosition(0, 0);
|
||||
WindWithConfidence<Pair<Position, TimePoint>> w1 = new WindWithConfidenceImpl<>(
|
||||
new WindImpl(p1, now, new KnotSpeedWithBearingImpl(10, new DegreeBearingImpl(90))),
|
||||
/* confidence */ 1.0, new Pair<Position, TimePoint>(p1, now), /* useSpeed */ true);
|
||||
final Position p2 = p1.translateGreatCircle(new DegreeBearingImpl(0), new MeterDistance(1000));
|
||||
WindWithConfidence<Pair<Position, TimePoint>> w2 = new WindWithConfidenceImpl<>(
|
||||
new WindImpl(p2, now, new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))),
|
||||
/* confidence */ 1.0, new Pair<Position, TimePoint>(p2, now), /* useSpeed */ true);
|
||||
List<WindWithConfidence<Pair<Position, TimePoint>>> fixes = new ArrayList<>();
|
||||
fixes.add(w1);
|
||||
fixes.add(w2);
|
||||
WindWithConfidence<Pair<Position, TimePoint>> averageAtP1 = averager.getAverage(fixes, new Pair<Position, TimePoint>(p1, now));
|
||||
WindWithConfidence<Pair<Position, TimePoint>> averageAtP2 = averager.getAverage(fixes, new Pair<Position, TimePoint>(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<Pair<Position, TimePoint>> averageAtMiddleBetweenP1AndP2 = averager.getAverage(fixes,
|
||||
new Pair<Position, TimePoint>(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<Pair<Position, TimePoint>> w3 = new WindWithConfidenceImpl<>(
|
||||
new WindImpl(p3, now, new KnotSpeedWithBearingImpl(20, new DegreeBearingImpl(0))),
|
||||
/* confidence */ 1.0, new Pair<Position, TimePoint>(p3, now), /* useSpeed */ true);
|
||||
List<WindWithConfidence<Pair<Position, TimePoint>>> fixesFurtherApart = new ArrayList<>();
|
||||
fixesFurtherApart.add(w1);
|
||||
fixesFurtherApart.add(w3);
|
||||
WindWithConfidence<Pair<Position, TimePoint>> averageFurtherApartAtP1 = averager.getAverage(fixesFurtherApart, new Pair<Position, TimePoint>(p1, now));
|
||||
WindWithConfidence<Pair<Position, TimePoint>> averageAtP3 = averager.getAverage(fixesFurtherApart, new Pair<Position, TimePoint>(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()));
|
||||
}
|
||||
}
|
||||
+70
-59
@@ -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<Competitor, LinkedHashMap<Waypoint, MarkPassing>> givenPasses = new LinkedHashMap<>();
|
||||
private ArrayList<Waypoint> waypoints = new ArrayList<>();
|
||||
private Map<Competitor, Map<Waypoint, MarkPassing>> givenPasses = new HashMap<>();
|
||||
private List<Waypoint> 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<Waypoint, MarkPassing> givenMarkPasses = new LinkedHashMap<Waypoint, MarkPassing>();
|
||||
Map<Waypoint, MarkPassing> givenMarkPasses = new HashMap<Waypoint, MarkPassing>();
|
||||
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<Competitor, LinkedHashMap<Waypoint, MarkPassing>> computedPasses = new LinkedHashMap<>();
|
||||
Map<Competitor, Map<Waypoint, MarkPassing>> 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<Waypoint, MarkPassing>());
|
||||
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<GPSFix> fixes = new ArrayList<GPSFix>();
|
||||
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<GPSFix> fixes = new ArrayList<GPSFix>();
|
||||
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<Candidate>, Iterable<Candidate>> 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<Candidate>, Iterable<Candidate>> 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
|
||||
|
||||
+79
-95
@@ -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<Waypoint> 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<String>(), 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<Sideline>(), new EmptyWindStore(), EmptyGPSFixStore.INSTANCE, 0, 10000, 10000);
|
||||
trackedRace.setStartTimeReceived(new MillisecondsTimePoint(System.currentTimeMillis() - 120000));
|
||||
List<MillisecondsTimePoint> tps = Arrays.asList(new MillisecondsTimePoint(System.currentTimeMillis()), new MillisecondsTimePoint(System.currentTimeMillis() - 30000),
|
||||
new MillisecondsTimePoint(System.currentTimeMillis() + 30000));
|
||||
List<Util.Pair<Mark, Position>> pos = Arrays.asList(new Util.Pair<Mark, Position>(m, new DegreePosition(0, 0)), new Util.Pair<Mark, Position>(m2, new DegreePosition(-0.001, -0.00005)),
|
||||
new Util.Pair<Mark, Position>(m3, new DegreePosition(-0.001, 0.00005)));
|
||||
for (Util.Pair<Mark, Position> 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<Waypoint> 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<String>(), 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<Sideline>(), new EmptyWindStore(), EmptyGPSFixStore.INSTANCE, 0, 10000, 10000, /*useMarkPassingCalculator*/ false);
|
||||
race.setStartTimeReceived(new MillisecondsTimePoint(10000));
|
||||
TimePoint t = new MillisecondsTimePoint(30000);
|
||||
List<Util.Pair<Mark, Position>> pos = Arrays.asList(new Util.Pair<Mark, Position>(m, new DegreePosition(0, 0)),
|
||||
new Util.Pair<Mark, Position>(gate1, new DegreePosition(-0.001, -0.00005)), new Util.Pair<Mark, Position>(gate2, new DegreePosition(-0.001, 0.00005)), new Util.Pair<Mark, Position>(reaching, new DegreePosition(-0.0005, -0.0005)));
|
||||
for (Util.Pair<Mark, Position> pair : pos) {
|
||||
race.recordFix(pair.getA(), new GPSFixImpl(pair.getB(), t));
|
||||
}
|
||||
}
|
||||
}
|
||||
-54
@@ -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<GPSFix> fixes = new ArrayList<GPSFix>();
|
||||
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
|
||||
}
|
||||
}
|
||||
+97
-53
@@ -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
|
||||
*
|
||||
|
||||
+13
-10
@@ -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() {
|
||||
|
||||
+2
@@ -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");
|
||||
|
||||
+4
-3
@@ -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_";
|
||||
|
||||
+28
-12
@@ -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<GPSFix> 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<Candidate> newCandidates = new ArrayList<>();
|
||||
for (int k = 0; k < numberToAddEachTime; k++) {
|
||||
newCandidates.add(randomCan());
|
||||
}
|
||||
time = System.currentTimeMillis();
|
||||
c.calculateMarkPassDeltas(bob, new Util.Pair<Iterable<Candidate>, Iterable<Candidate>>(newCandidates, new ArrayList<Candidate>()));
|
||||
c.calculateMarkPassDeltas(ron, newCandidates, new ArrayList<Candidate>());
|
||||
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);
|
||||
}
|
||||
}
|
||||
+305
@@ -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<GPSFix> fixes = new ArrayList<GPSFix>();
|
||||
fixes.add(fix1);
|
||||
fixes.add(fix2);
|
||||
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> 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<MarkPassing> markPassings = race.getMarkPassings(ron);
|
||||
assertEquals(1, markPassings.size());
|
||||
chooser.suppressMarkPassings(ron, 1);
|
||||
NavigableSet<MarkPassing> 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<GPSFix> fixes = new ArrayList<>();
|
||||
fixes.add(fix1);
|
||||
fixes.add(fix3);
|
||||
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> 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<GPSFix> fixes = new ArrayList<>();
|
||||
fixes.addAll(Arrays.asList(fix1, fix2));
|
||||
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> 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<Candidate>());
|
||||
NavigableSet<MarkPassing> 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<GPSFix> fixes = new ArrayList<>();
|
||||
fixes.addAll(Arrays.asList(fix1, fix2, fix3));
|
||||
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> 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<Waypoint> 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<Candidate>());
|
||||
|
||||
NavigableSet<MarkPassing> markPassingsBeforeFixing = race.getMarkPassings(tom);
|
||||
MarkPassing markPassingBeforeAdding = Util.get(markPassingsBeforeFixing, 1);
|
||||
assertTrue(markPassingBeforeAdding.getTimePoint().asMillis() == 60000);
|
||||
|
||||
chooser.setFixedPassing(tom, 1, new MillisecondsTimePoint(58000));
|
||||
|
||||
NavigableSet<MarkPassing> markPassingsAfterFixing = race.getMarkPassings(tom);
|
||||
MarkPassing markPassingAfterAdding = Util.get(markPassingsAfterFixing, 1);
|
||||
assertTrue(markPassingAfterAdding.getTimePoint().asMillis() == 58000);
|
||||
|
||||
chooser.setFixedPassing(tom, 1, new MillisecondsTimePoint(59000));
|
||||
|
||||
NavigableSet<MarkPassing> markPassingsAfterSecondFixing = race.getMarkPassings(tom);
|
||||
MarkPassing markPassingAfterSecondAdding = Util.get(markPassingsAfterSecondFixing, 1);
|
||||
assertTrue(markPassingAfterSecondAdding.getTimePoint().asMillis() == 59000);
|
||||
|
||||
chooser.removeFixedPassing(tom, 1);
|
||||
|
||||
NavigableSet<MarkPassing> 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<Candidate>, Iterable<Candidate>> 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<Candidate>, List<Candidate>> addedCans = finder.updateWaypoints(Arrays.asList(newWaypoint), new ArrayList<Waypoint>(), 4).get(ron);
|
||||
assertEquals(Util.size(addedCans.getA()), 1);
|
||||
for (Candidate c : addedCans.getA()) {
|
||||
assertEquals(c.getWaypoint(), newWaypoint);
|
||||
}
|
||||
|
||||
// Before removing
|
||||
Util.Pair<Iterable<Candidate>, Iterable<Candidate>> 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<Candidate>, List<Candidate>> removedCans = finder.updateWaypoints(new ArrayList<Waypoint>(), 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<Candidate>, Iterable<Candidate>> allCansAfterRemoving = finder.getAllCandidates(ron);
|
||||
assertEquals(Util.size(allCansAfterRemoving.getB()), 0);
|
||||
assertEquals(Util.size(allCansAfterRemoving.getA()), 0);
|
||||
}
|
||||
|
||||
}
|
||||
+57
-32
@@ -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() {
|
||||
|
||||
+41
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
+7
-13
@@ -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<Sideline> 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<Mark, Mark> 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) {
|
||||
}
|
||||
|
||||
+6
@@ -592,4 +592,10 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUsingMarkPassingCalculator() {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+7
-6
@@ -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<Receiver> 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<com.sap.sailing.domain.base.Competitor> competitors, BoatClass boatClass,
|
||||
Course course, Iterable<Sideline> 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<Receiver> 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 <code>tractracRace</code> which includes removing it from the race cache, from the
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
-12
@@ -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();
|
||||
}
|
||||
}
|
||||
+16
-19
@@ -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<Receiver> result = new ArrayList<Receiver>();
|
||||
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<Competitor> competitors, BoatClass boatClass, Course course,
|
||||
Iterable<Sideline> 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<Sideline> 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);
|
||||
}
|
||||
|
||||
|
||||
+10
-9
@@ -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...
|
||||
* <pre>
|
||||
* 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
|
||||
* </pre>
|
||||
*/
|
||||
@Override
|
||||
@@ -93,16 +95,15 @@ public class MetadataParserImpl implements MetadataParser {
|
||||
Map<Integer, PassingInstruction> result = new HashMap<Integer, PassingInstruction>();
|
||||
if (routeMetadataString != null) {
|
||||
Map<String, String> 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;
|
||||
|
||||
+6
-8
@@ -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<IControlRoute,
|
||||
private final String tracTracPassword;
|
||||
private final IRace tractracRace;
|
||||
private final IControlRouteChangeListener listener;
|
||||
private final boolean useInternalMarkPassingAlgorithm;
|
||||
|
||||
public RaceCourseReceiver(DomainFactory domainFactory, DynamicTrackedRegatta trackedRegatta, IEvent tractracEvent,
|
||||
IRace tractracRace, WindStore windStore, DynamicRaceDefinitionSet raceDefinitionSetToUpdate,
|
||||
long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, Simulator simulator,
|
||||
URI courseDesignUpdateURI, String tracTracUsername, String tracTracPassword,
|
||||
IEventSubscriber eventSubscriber, IRaceSubscriber raceSubscriber) {
|
||||
IEventSubscriber eventSubscriber, IRaceSubscriber raceSubscriber, boolean useInternalMarkPassingAlgorithm) {
|
||||
super(domainFactory, tractracEvent, trackedRegatta, simulator, eventSubscriber, raceSubscriber);
|
||||
this.tractracRace = tractracRace;
|
||||
this.millisecondsOverWhichToAverageWind = millisecondsOverWhichToAverageWind;
|
||||
@@ -79,6 +79,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<IControlRoute,
|
||||
this.tracTracUpdateURI = courseDesignUpdateURI;
|
||||
this.tracTracUsername = tracTracUsername;
|
||||
this.tracTracPassword = tracTracPassword;
|
||||
this.useInternalMarkPassingAlgorithm = useInternalMarkPassingAlgorithm;
|
||||
listener = new IControlRouteChangeListener() {
|
||||
@Override
|
||||
public void gotRouteChange(IControlRoute controlRoute, long timeStamp) {
|
||||
@@ -113,7 +114,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<IControlRoute,
|
||||
}
|
||||
Map<Integer, PassingInstruction> courseWaypointPassingInstructions = getDomainFactory().getMetadataParser().parsePassingInstructionData(routeMetadataString, routeControlPoints);
|
||||
List<com.sap.sse.common.Util.Pair<TracTracControlPoint, PassingInstruction>> 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<TracTracControlPoint, PassingInstruction>(ttControlPointsForAllOriginalEventControlPoints.get(cp), passingInstructions));
|
||||
@@ -154,7 +155,7 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<IControlRoute,
|
||||
getTrackedRegatta(), tractracRace.getId(), tractracRace.getName(), competitorsAndDominantBoatClass.getA(),
|
||||
competitorsAndDominantBoatClass.getB(), course, sidelines, windStore, delayToLiveInMillis,
|
||||
millisecondsOverWhichToAverageWind, raceDefinitionSetToUpdate, tracTracUpdateURI,
|
||||
getTracTracEvent().getId(), tracTracUsername, tracTracPassword);
|
||||
getTracTracEvent().getId(), tracTracUsername, tracTracPassword, useInternalMarkPassingAlgorithm);
|
||||
needToUpdateRaceTimes = true;
|
||||
if (getSimulator() != null) {
|
||||
getSimulator().setTrackedRace(trackedRace);
|
||||
@@ -219,11 +220,8 @@ public class RaceCourseReceiver extends AbstractReceiverWithQueue<IControlRoute,
|
||||
DynamicTrackedRace trackedRace = getTrackedRegatta().createTrackedRace(race, sidelines,
|
||||
windStore, EmptyGPSFixStore.INSTANCE, delayToLiveInMillis, millisecondsOverWhichToAverageWind,
|
||||
/* time over which to average speed: */ race.getBoatClass().getApproximateManeuverDurationInMilliseconds(),
|
||||
raceDefinitionSetToUpdate);
|
||||
raceDefinitionSetToUpdate, useInternalMarkPassingAlgorithm);
|
||||
getDomainFactory().addTracTracUpdateHandlers(tracTracUpdateURI, getTracTracEvent().getId(), tracTracUsername, tracTracPassword, race, trackedRace);
|
||||
if (!Activator.getInstance().isUseTracTracMarkPassings()) {
|
||||
new MarkPassingCalculator(trackedRace, true);
|
||||
}
|
||||
return trackedRace;
|
||||
}
|
||||
|
||||
|
||||
+5
-3
@@ -35,10 +35,11 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
|
||||
private final String tracTracPassword;
|
||||
private final String raceStatus;
|
||||
private final String raceVisibility;
|
||||
private final boolean useInternalMarkPassingAlgorithm;
|
||||
|
||||
public RaceTrackingConnectivityParametersImpl(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,
|
||||
DomainFactory domainFactory, String tracTracUsername, String tracTracPassword, String raceStatus,
|
||||
String raceVisibility) {
|
||||
super();
|
||||
@@ -57,6 +58,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
|
||||
this.tracTracPassword = tracTracPassword;
|
||||
this.raceStatus = raceStatus;
|
||||
this.raceVisibility = raceVisibility;
|
||||
this.useInternalMarkPassingAlgorithm = useInternalMarkPassingAlgorithm;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,7 +66,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
|
||||
GPSFixStore gpsFixStore) throws MalformedURLException, FileNotFoundException, URISyntaxException,
|
||||
CreateModelException, SubscriberInitializationException {
|
||||
RaceTracker tracker = domainFactory.createRaceTracker(paramURL, liveURI, storedURI, courseDesignUpdateURI,
|
||||
startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore,
|
||||
startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore,
|
||||
regattaLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus,
|
||||
raceVisibility, trackedRegattaRegistry);
|
||||
return tracker;
|
||||
@@ -74,7 +76,7 @@ public class RaceTrackingConnectivityParametersImpl implements RaceTrackingConne
|
||||
public RaceTracker createRaceTracker(Regatta regatta, TrackedRegattaRegistry trackedRegattaRegistry,
|
||||
WindStore windStore, GPSFixStore gpsFixStore) throws Exception {
|
||||
RaceTracker tracker = domainFactory.createRaceTracker(regatta, paramURL, liveURI, storedURI,
|
||||
courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow,
|
||||
courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm,
|
||||
raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername, tracTracPassword, raceStatus,
|
||||
raceVisibility, trackedRegattaRegistry);
|
||||
return tracker;
|
||||
|
||||
+3
-3
@@ -56,7 +56,7 @@ public class TracTracAdapterImpl implements TracTracAdapter {
|
||||
getTracTracDomainFactory().createTrackingConnectivityParameters(paramURL, liveURI, storedURI,
|
||||
courseDesignUpdateURI,
|
||||
/* startOfTracking */null,
|
||||
/* endOfTracking */null, delayToLiveInMillis, /* simulateWithStartTimeNow */false,
|
||||
/* endOfTracking */null, delayToLiveInMillis, /* simulateWithStartTimeNow */false, /* ignoreTracTracMarkPassings */ false,
|
||||
raceLogStore, regattaLogStore, tracTracUsername, tracTracPassword, raceStatus, raceVisibility),
|
||||
timeoutInMilliseconds);
|
||||
}
|
||||
@@ -65,13 +65,13 @@ public class TracTracAdapterImpl implements TracTracAdapter {
|
||||
public RaceHandle addTracTracRace(TrackerManager trackerManager, RegattaIdentifier regattaToAddTo, URL paramURL,
|
||||
URI liveURI, URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking,
|
||||
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, long timeoutInMilliseconds,
|
||||
boolean simulateWithStartTimeNow, String tracTracUsername, String tracTracPassword, String raceStatus,
|
||||
boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm, String tracTracUsername, String tracTracPassword, String raceStatus,
|
||||
String raceVisibility) throws Exception {
|
||||
return trackerManager.addRace(
|
||||
regattaToAddTo,
|
||||
getTracTracDomainFactory().createTrackingConnectivityParameters(paramURL, liveURI, storedURI,
|
||||
courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis,
|
||||
simulateWithStartTimeNow, raceLogStore, regattaLogStore, tracTracUsername, tracTracPassword,
|
||||
simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, tracTracUsername, tracTracPassword,
|
||||
raceStatus, raceVisibility), timeoutInMilliseconds);
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -248,26 +248,26 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
|
||||
*/
|
||||
protected TracTracRaceTrackerImpl(DomainFactory domainFactory, 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 URISyntaxException, MalformedURLException, FileNotFoundException, CreateModelException,
|
||||
SubscriberInitializationException {
|
||||
this(ModelLocator.getEventFactory().createRace(new URI(paramURL.toString())), domainFactory, paramURL, liveURI,
|
||||
storedURI, courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis,
|
||||
simulateWithStartTimeNow, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
|
||||
simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
|
||||
tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry);
|
||||
}
|
||||
|
||||
private TracTracRaceTrackerImpl(IRace tractracRace, DomainFactory domainFactory, 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, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername,
|
||||
String tracTracPassword, String raceStatus, String raceVisibility,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry) throws URISyntaxException, MalformedURLException,
|
||||
FileNotFoundException, SubscriberInitializationException {
|
||||
this(tractracRace, null, domainFactory, paramURL, liveURI, storedURI, courseDesignUpdateURI, startOfTracking,
|
||||
endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, raceLogStore, regattaLogStore, windStore,
|
||||
endOfTracking, delayToLiveInMillis, simulateWithStartTimeNow, useInternalMarkPassingAlgorithm, raceLogStore, regattaLogStore, windStore,
|
||||
gpsFixStore, tracTracUsername, tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry);
|
||||
}
|
||||
|
||||
@@ -279,14 +279,14 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
|
||||
*/
|
||||
protected TracTracRaceTrackerImpl(Regatta regatta, DomainFactory domainFactory, URL paramURL, URI liveURI,
|
||||
URI storedURI, URI courseDesignUpdateURI, TimePoint startOfTracking, TimePoint endOfTracking,
|
||||
long delayToLiveInMillis, boolean simulateWithStartTimeNow, RaceLogStore raceLogStore,
|
||||
long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean ignoreTracTracMarkPassings, RaceLogStore raceLogStore,
|
||||
RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore, String tracTracUsername,
|
||||
String tracTracPassword, String raceStatus, String raceVisibility,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry) throws URISyntaxException,
|
||||
MalformedURLException, FileNotFoundException, CreateModelException, SubscriberInitializationException {
|
||||
this(ModelLocator.getEventFactory().createRace(new URI(paramURL.toString())), regatta, domainFactory, paramURL,
|
||||
liveURI, storedURI, courseDesignUpdateURI, startOfTracking, endOfTracking, delayToLiveInMillis,
|
||||
simulateWithStartTimeNow, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
|
||||
simulateWithStartTimeNow, ignoreTracTracMarkPassings, raceLogStore, regattaLogStore, windStore, gpsFixStore, tracTracUsername,
|
||||
tracTracPassword, raceStatus, raceVisibility, trackedRegattaRegistry);
|
||||
}
|
||||
|
||||
@@ -303,7 +303,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
|
||||
*/
|
||||
private TracTracRaceTrackerImpl(IRace tractracRace, final Regatta regatta, DomainFactory domainFactory,
|
||||
URL paramURL, URI liveURI, URI storedURI, URI tracTracUpdateURI, TimePoint startOfTracking,
|
||||
TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow,
|
||||
TimePoint endOfTracking, long delayToLiveInMillis, boolean simulateWithStartTimeNow, boolean useInternalMarkPassingAlgorithm,
|
||||
RaceLogStore raceLogStore, RegattaLogStore regattaLogStore, WindStore windStore, GPSFixStore gpsFixStore,
|
||||
String tracTracUsername, String tracTracPassword, String raceStatus, String raceVisibility,
|
||||
TrackedRegattaRegistry trackedRegattaRegistry) throws URISyntaxException, MalformedURLException,
|
||||
@@ -398,7 +398,7 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl implements
|
||||
receivers = new HashSet<Receiver>();
|
||||
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);
|
||||
|
||||
Regular → Executable
+1
@@ -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,
|
||||
|
||||
@@ -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());
|
||||
|
||||
+12
@@ -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);
|
||||
|
||||
-23
@@ -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<Candidate>, Iterable<Candidate>> candidateDeltas);
|
||||
|
||||
public LinkedHashMap<Competitor, LinkedHashMap<Waypoint, MarkPassing>> getAllPasses();
|
||||
}
|
||||
-45
@@ -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<Competitor, List<GPSFix>> calculateFixesAffectedByNewMarkFixes(Mark mark, Iterable<GPSFix> 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<Candidate>, Iterable<Candidate>> getCandidateDeltas(Competitor c, Iterable<GPSFix> 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<Candidate>, Iterable<Candidate>> getAllCandidates(Competitor c);
|
||||
}
|
||||
+15
-44
@@ -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<Candidate> {
|
||||
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<CandidateImpl> {
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
+14
-218
@@ -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, <code>start</code> and <code>end</code>. 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<Candidate> newCans, Iterable<Candidate> oldCans);
|
||||
|
||||
private LinkedHashMap<Competitor, LinkedHashMap<Waypoint, MarkPassing>> currentMarkPasses = new LinkedHashMap<>();
|
||||
private LinkedHashMap<Competitor, Set<Edge>> allEdges = new LinkedHashMap<>();
|
||||
private LinkedHashMap<Competitor, Set<Candidate>> 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<Waypoint> 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<Candidate>());
|
||||
currentMarkPasses.put(c, new LinkedHashMap<Waypoint, MarkPassing>());
|
||||
allEdges.put(c, new TreeSet<Edge>());
|
||||
addCandidates(Arrays.asList(start, end), c);
|
||||
}
|
||||
}
|
||||
void setFixedPassing(Competitor c, Integer zeroBasedIndexOfWaypoint, TimePoint t);
|
||||
|
||||
@Override
|
||||
public LinkedHashMap<Competitor, LinkedHashMap<Waypoint, MarkPassing>> getAllPasses() {
|
||||
return currentMarkPasses;
|
||||
}
|
||||
void removeFixedPassing(Competitor c, Integer zeroBasedIndexOfWaypoint);
|
||||
|
||||
@Override
|
||||
public void calculateMarkPassDeltas(Competitor c, Util.Pair<Iterable<Candidate>, Iterable<Candidate>> 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<Candidate> 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<Edge> all = new ArrayList<>();
|
||||
for (Edge e : allEdges.get(co)) {
|
||||
all.add(e);
|
||||
}
|
||||
LinkedHashMap<Candidate, Util.Pair<Candidate, Double>> candidateWithParent = new LinkedHashMap<>();
|
||||
candidateWithParent.put(start, new Util.Pair<Candidate, Double>(null, 0.0));
|
||||
Util.Pair<Edge, Double> 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<Edge, Double>(e, cost);
|
||||
} else if (cost < currentMostLikelyEdge.getB()) {
|
||||
currentMostLikelyEdge = new Util.Pair<Edge, Double>(e, cost);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!candidateWithParent.containsKey(currentMostLikelyEdge.getA().getEnd())) {
|
||||
candidateWithParent.put(currentMostLikelyEdge.getA().getEnd(), new Util.Pair<Candidate, Double>(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<MarkPassing> 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<Candidate> 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<Candidate> wrongCandidates, Competitor co) {
|
||||
for (Candidate c : wrongCandidates) {
|
||||
candidates.get(co).remove(c);
|
||||
List<Edge> 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<Edge> newEdges = new ArrayList<>(); ArrayList<Edge> 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); }
|
||||
*/
|
||||
+23
-446
@@ -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<Competitor, TreeMap<GPSFix, LinkedHashMap<Waypoint, Util.Pair<Double, Double>>>> crossTrackErrors = new LinkedHashMap<>();
|
||||
private LinkedHashMap<Competitor, TreeMap<GPSFix, LinkedHashMap<Waypoint, Distance>>> distances = new LinkedHashMap<>();
|
||||
private LinkedHashMap<Competitor, LinkedHashMap<Waypoint, LinkedHashMap<List<GPSFix>, Candidate>>> cteCandidates = new LinkedHashMap<>();
|
||||
private LinkedHashMap<Competitor, LinkedHashMap<Waypoint, LinkedHashMap<GPSFix, Candidate>>> distanceCandidates = new LinkedHashMap<>();
|
||||
private final DynamicTrackedRace race;
|
||||
private final double penaltyForSkipping = 1 - Edge.getPenaltyForSkipping();
|
||||
private final LinkedHashMap<Waypoint, PassingInstruction> passingInstructions = new LinkedHashMap<>();
|
||||
private final Comparator<GPSFix> comp = new Comparator<GPSFix>() {
|
||||
@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<Candidate>, Iterable<Candidate>> getCandidateDeltas(Competitor c, Iterable<GPSFix> 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<GPSFix, LinkedHashMap<Waypoint, Util.Pair<Double, Double>>>(comp));
|
||||
distances.put(c, new TreeMap<GPSFix, LinkedHashMap<Waypoint, Distance>>(comp));
|
||||
cteCandidates.put(c, new LinkedHashMap<Waypoint, LinkedHashMap<List<GPSFix>, Candidate>>());
|
||||
distanceCandidates.put(c, new LinkedHashMap<Waypoint, LinkedHashMap<GPSFix, Candidate>>());
|
||||
for (Waypoint w : race.getRace().getCourse().getWaypoints()) {
|
||||
cteCandidates.get(c).put(w, new LinkedHashMap<List<GPSFix>, Candidate>());
|
||||
distanceCandidates.get(c).put(w, new LinkedHashMap<GPSFix, Candidate>());
|
||||
}
|
||||
}
|
||||
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<Mark> 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<Candidate>, Iterable<Candidate>> getAllCandidates(Competitor c) {
|
||||
Set<GPSFix> fixes = new TreeSet<GPSFix>(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<Candidate>, Iterable<Candidate>> getAllCandidates(Competitor c);
|
||||
|
||||
Map<Competitor, Util.Pair<List<Candidate>, List<Candidate>>> updateWaypoints(Iterable<Waypoint> addedWaypoints, Iterable<Waypoint> removedWaypoints, Integer smallestIndex);
|
||||
|
||||
@Override
|
||||
public LinkedHashMap<Competitor,List<GPSFix>> calculateFixesAffectedByNewMarkFixes(Mark mark, Iterable<GPSFix> gps) {
|
||||
TreeSet<GPSFix> fixes = new TreeSet<GPSFix>(comp);
|
||||
for (GPSFix fix : gps) {
|
||||
fixes.add(fix);
|
||||
}
|
||||
LinkedHashMap<Competitor, List<GPSFix>> affectedFixes = new LinkedHashMap<>();
|
||||
for (Competitor c : race.getRace().getCompetitors()) {
|
||||
affectedFixes.put(c, new ArrayList<GPSFix>());
|
||||
}
|
||||
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<Competitor, List<GPSFix>> calculateFixesAffectedByNewMarkFixes(Map<Mark, List<GPSFix>> newMarkFixes);
|
||||
|
||||
@Override
|
||||
public Util.Pair<Iterable<Candidate>, Iterable<Candidate>> getCandidateDeltas(Competitor c, Iterable<GPSFix> fixes) {
|
||||
if (logger.getLevel() == Level.FINEST) {
|
||||
logger.finest("Reevaluating MarkPasses for" + c);
|
||||
}
|
||||
Set<Candidate> newCans = new TreeSet<>();
|
||||
Set<Candidate> wrongCans = new TreeSet<>();
|
||||
LinkedHashMap<Waypoint, Util.Pair<List<Candidate>, List<Candidate>>> distanceCandidates = checkForDistanceCandidateChanges(c, fixes);
|
||||
LinkedHashMap<Waypoint, Util.Pair<List<Candidate>, List<Candidate>>> 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<Candidate>, Iterable<Candidate>>(newCans, wrongCans);
|
||||
}
|
||||
|
||||
private LinkedHashMap<Waypoint, Util.Pair<List<Candidate>, List<Candidate>>> checkForDistanceCandidateChanges(Competitor c, Iterable<GPSFix> fixes) {
|
||||
LinkedHashMap<Waypoint, Util.Pair<List<Candidate>, List<Candidate>>> result = new LinkedHashMap<>();
|
||||
calculatesDistances(c, fixes);
|
||||
for (Waypoint w : passingInstructions.keySet()) {
|
||||
result.put(w, new Util.Pair<List<Candidate>, List<Candidate>>(new ArrayList<Candidate>(), new ArrayList<Candidate>()));
|
||||
}
|
||||
Set<GPSFix> affectedFixes = new TreeSet<GPSFix>(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<GPSFix> fixes) {
|
||||
for (GPSFix fix : fixes) {
|
||||
distances.get(c).put(fix, new LinkedHashMap<Waypoint, Distance>());
|
||||
for (Waypoint w : passingInstructions.keySet()) {
|
||||
Distance distance = calculateDistance(fix.getPosition(), w, fix.getTimePoint());
|
||||
distances.get(c).get(fix).put(w, distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private LinkedHashMap<Waypoint, Util.Pair<List<Candidate>, List<Candidate>>> checkForCTECandidatesChanges(Competitor c, Iterable<GPSFix> fixes) {
|
||||
calculateCrossTrackErrors(c, fixes);
|
||||
LinkedHashMap<Waypoint, Util.Pair<List<Candidate>, List<Candidate>>> result = new LinkedHashMap<>();
|
||||
for (Waypoint w : passingInstructions.keySet()) {
|
||||
result.put(w, new Util.Pair<List<Candidate>, List<Candidate>>(new ArrayList<Candidate>(), new ArrayList<Candidate>()));
|
||||
}
|
||||
for (GPSFix fix : fixes) {
|
||||
GPSFix fixBefore = crossTrackErrors.get(c).lowerKey(fix);
|
||||
GPSFix fixAfter = crossTrackErrors.get(c).higherKey(fix);
|
||||
for (Waypoint w : passingInstructions.keySet()) {
|
||||
List<List<GPSFix>> oldCandidates = new ArrayList<>();
|
||||
Set<List<GPSFix>> newCandidates = new TreeSet<List<GPSFix>>(new Comparator<List<GPSFix>>() {
|
||||
@Override
|
||||
public int compare(List<GPSFix> arg0, List<GPSFix> 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<GPSFix> 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<GPSFix> 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<GPSFix> 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<GPSFix> fixes) {
|
||||
for (GPSFix fix : fixes) {
|
||||
crossTrackErrors.get(c).put(fix, new LinkedHashMap<Waypoint, Util.Pair<Double, Double>>());
|
||||
for (Waypoint w : passingInstructions.keySet()) {
|
||||
Iterator<Mark> it = w.getMarks().iterator();
|
||||
List<Mark> marks = new ArrayList<Mark>();
|
||||
while (it.hasNext()) {
|
||||
marks.add(it.next());
|
||||
}
|
||||
Util.Pair<Double, Double> 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<Double, Double>(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<Mark, Mark> 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;
|
||||
}
|
||||
}
|
||||
-59
@@ -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, <code>start</code> and <code>end</code>.
|
||||
* {@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<Edge> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
+176
-94
@@ -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 <code>listen</code> 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 <code>combinedFixes</code>. If <code>suspended</code> is
|
||||
* false, the new Fixes are handed to the <code>executor</code> 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 <code>end</code> 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 <code>listen</code> 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<Object, GPSFix> end = new Util.Pair<Object, GPSFix>(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<Runnable>(), 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<Candidate>, Iterable<Candidate>> 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 <code>competitorFixes</code> and <code>markFixes</code>. Finally, if <code>suspended</code>
|
||||
* 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<Object, List<GPSFix>> combinedFixes = new LinkedHashMap<>();
|
||||
Map<Competitor, List<GPSFix>> competitorFixes = new HashMap<>();
|
||||
Map<Mark, List<GPSFix>> markFixes = new HashMap<>();
|
||||
List<Waypoint> addedWaypoints = new ArrayList<>();
|
||||
List<Waypoint> removedWaypoints = new ArrayList<>();
|
||||
Integer smallestChangedWaypointIndex = null;
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings = new ArrayList<>();
|
||||
List<Pair<Competitor, Integer>> removedFixedMarkPassings = new ArrayList<>();
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings = new ArrayList<>();
|
||||
List<Competitor> unsuppressedMarkPassings = new ArrayList<>();
|
||||
while (!finished) {
|
||||
List<Util.Pair<Object, GPSFix>> allNewFixes = new ArrayList<Util.Pair<Object, GPSFix>>();
|
||||
logger.finer("MPC is checking the queue");
|
||||
List<StorePositionUpdateStrategy> 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<Object, GPSFix> 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<GPSFix>());
|
||||
}
|
||||
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<Competitor, Util.Pair<List<Candidate>, List<Candidate>>> candidateDeltas = finder
|
||||
.updateWaypoints(addedWaypoints, removedWaypoints, smallestChangedWaypointIndex);
|
||||
chooser.removeWaypoints(removedWaypoints);
|
||||
for (Entry<Competitor, Util.Pair<List<Candidate>, List<Candidate>>> entry : candidateDeltas
|
||||
.entrySet()) {
|
||||
Util.Pair<List<Candidate>, List<Candidate>> 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<Object, List<GPSFix>> combinedFixes) {
|
||||
|
||||
LinkedHashMap<Competitor, Set<GPSFix>> comFixes = new LinkedHashMap<>();
|
||||
Comparator<GPSFix> com = new Comparator<GPSFix>(){
|
||||
@Override
|
||||
public int compare(GPSFix arg0, GPSFix arg1) {
|
||||
return arg0.getTimePoint().compareTo(arg1.getTimePoint());
|
||||
}
|
||||
};
|
||||
List<FutureTask<LinkedHashMap<Competitor, List<GPSFix>>>> markTasks = new ArrayList<>();
|
||||
for (Object o : combinedFixes.keySet()) {
|
||||
if (o instanceof Mark) {
|
||||
FutureTask<LinkedHashMap<Competitor, List<GPSFix>>> 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<GPSFix>(com));
|
||||
}
|
||||
comFixes.get(o).addAll(combinedFixes.get(o));
|
||||
}
|
||||
|
||||
private void updateManuallySetMarkPassings(List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unsuppressedMarkPassings) {
|
||||
logger.finest("Updating manually edited MarkPassings");
|
||||
for (Pair<Competitor, Integer> pair : suppressedMarkPassings) {
|
||||
chooser.suppressMarkPassings(pair.getA(), pair.getB());
|
||||
}
|
||||
for(FutureTask<LinkedHashMap<Competitor, List<GPSFix>>> task : markTasks){
|
||||
LinkedHashMap<Competitor, List<GPSFix>> 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<GPSFix>(com));
|
||||
for (Competitor c : unsuppressedMarkPassings) {
|
||||
chooser.stopSuppressingMarkPassings(c);
|
||||
}
|
||||
|
||||
for (Pair<Competitor, Integer> pair : removedMarkPassings) {
|
||||
chooser.removeFixedPassing(pair.getA(), pair.getB());
|
||||
}
|
||||
for (Triple<Competitor, Integer, TimePoint> 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<Competitor, List<GPSFix>> newCompetitorFixes,
|
||||
Map<Mark, List<GPSFix>> newMarkFixes) {
|
||||
logger.finer("Calculating markpassings with " + newCompetitorFixes.size() + " new competitor Fixes and "
|
||||
+ newMarkFixes.size() + "new mark fixes.");
|
||||
Map<Competitor, Set<GPSFix>> combinedCompetitorFixes = new HashMap<>();
|
||||
|
||||
for (Entry<Competitor, List<GPSFix>> competitorEntry : newCompetitorFixes.entrySet()) {
|
||||
Set<GPSFix> fixesForCompetitor = new HashSet<>();
|
||||
combinedCompetitorFixes.put(competitorEntry.getKey(), fixesForCompetitor);
|
||||
fixesForCompetitor.addAll(competitorEntry.getValue());
|
||||
}
|
||||
if (!newMarkFixes.isEmpty()) {
|
||||
for (Entry<Competitor, List<GPSFix>> fixesAffectedByNewMarkFixes : finder
|
||||
.calculateFixesAffectedByNewMarkFixes(newMarkFixes).entrySet()) {
|
||||
Set<GPSFix> 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<Callable<Object>> tasks = new ArrayList<>();
|
||||
for (Competitor c : comFixes.keySet()) {
|
||||
tasks.add(Executors.callable(new ComputeMarkPassings(c, comFixes.get(c))));
|
||||
for (Entry<Competitor, Set<GPSFix>> 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<GPSFix> fixes;
|
||||
public ComputeMarkPassings(Competitor c, Set<GPSFix> fixes) {
|
||||
Iterable<GPSFix> fixes;
|
||||
|
||||
public ComputeMarkPassings(Competitor c, Iterable<GPSFix> 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<Candidate>, Iterable<Candidate>> 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<LinkedHashMap<Competitor, List<GPSFix>>> {
|
||||
Mark m;
|
||||
Iterable<GPSFix> fixes;
|
||||
public FixesAffectedByNewMarkFixes(Mark m, Iterable<GPSFix> fixes) {
|
||||
this.m = m;
|
||||
this.fixes = fixes;
|
||||
}
|
||||
@Override
|
||||
public LinkedHashMap<Competitor, List<GPSFix>> 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<Competitor, List<GPSFix>> competitorFixes,
|
||||
Map<Mark, List<GPSFix>> markFixes, List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints,
|
||||
Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LinkedHashMap<Competitor, LinkedHashMap<Waypoint, MarkPassing>> 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<Candidate>, Iterable<Candidate>> allCandidates = finder.getAllCandidates(c);
|
||||
chooser.calculateMarkPassDeltas(c, allCandidates.getA(), allCandidates.getB());
|
||||
}
|
||||
}
|
||||
|
||||
public MarkPassingUpdateListener getListener() {
|
||||
return listener;
|
||||
}
|
||||
}
|
||||
+146
-19
@@ -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 <code>end</code> object is passed through to signalise the end of the race.
|
||||
* the <code>end</code> object is passed through to signal the end of the race.
|
||||
*
|
||||
* @author Nicolas Klose
|
||||
*
|
||||
*/
|
||||
public class MarkPassingUpdateListener extends AbstractRaceChangeListener {
|
||||
private final LinkedBlockingQueue<Util.Pair<Object, GPSFix>> queue;
|
||||
private final Util.Pair<Object, GPSFix> end;
|
||||
private static final Logger logger = Logger.getLogger(MarkPassingUpdateListener.class.getName());
|
||||
private LinkedBlockingQueue<StorePositionUpdateStrategy> queue;
|
||||
private final StorePositionUpdateStrategy endMarker = new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
}
|
||||
};
|
||||
|
||||
public MarkPassingUpdateListener(TrackedRace race, Util.Pair<Object, GPSFix> end) {
|
||||
race.addListener(this);
|
||||
/**
|
||||
* Adds itself automatically as a Listener on the <code>race</code> and its course.
|
||||
*/
|
||||
public MarkPassingUpdateListener(DynamicTrackedRace race) {
|
||||
queue = new LinkedBlockingQueue<>();
|
||||
this.end = end;
|
||||
race.addListener(this);
|
||||
race.getRace().getCourse().addCourseListener(this);
|
||||
}
|
||||
|
||||
public LinkedBlockingQueue<Util.Pair<Object, GPSFix>> getQueue() {
|
||||
public BlockingQueue<StorePositionUpdateStrategy> getQueue() {
|
||||
return queue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void competitorPositionChanged(GPSFixMoving fix, Competitor item) {
|
||||
queue.add(new Util.Pair<Object, GPSFix>(item, fix));
|
||||
public void competitorPositionChanged(final GPSFixMoving fix, final Competitor competitor) {
|
||||
queue.add(new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
List<GPSFix> 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<Object, GPSFix>(mark, fix));
|
||||
public void markPositionChanged(final GPSFix fix, final Mark mark, boolean firstInTrack) {
|
||||
queue.add(new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
List<GPSFix> 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<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> 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<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> 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<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
fixedMarkPassings.add(new Triple<Competitor, Integer, TimePoint>(c, zeroBasedIndexOfWaypoint, timePointOfFixedPassing));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void removeFixedPassing(final Competitor c, final Integer zeroBasedIndexOfWaypoint) {
|
||||
queue.add(new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
removedMarkPassings.add(new Pair<Competitor, Integer>(c, zeroBasedIndexOfWaypoint));
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public void addSuppressedPassing(final Competitor c, final Integer zeroBasedIndexOfWaypoint) {
|
||||
queue.add(new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
suppressedMarkPassings.add(new Pair<Competitor, Integer>(c, zeroBasedIndexOfWaypoint));
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void removeSuppressedPassing(final Competitor c) {
|
||||
queue.add(new StorePositionUpdateStrategy() {
|
||||
@Override
|
||||
public void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings) {
|
||||
unSuppressedMarkPassings.add(c);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+27
@@ -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 <code>competitorFixes</code> or <code>markFixes</code>, depending what the fix is for. If
|
||||
* no fixes exist yet for that object, a new entry is created.
|
||||
*/
|
||||
void storePositionUpdate(Map<Competitor, List<GPSFix>> competitorFixes, Map<Mark, List<GPSFix>> markFixes,
|
||||
List<Waypoint> addedWaypoints, List<Waypoint> removedWaypoints, Integer smallestChangedWaypointIndex,
|
||||
List<Triple<Competitor, Integer, TimePoint>> fixedMarkPassings, List<Pair<Competitor, Integer>> removedMarkPassings,
|
||||
List<Pair<Competitor, Integer>> suppressedMarkPassings, List<Competitor> unSuppressedMarkPassings);
|
||||
}
|
||||
+457
@@ -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, <code>start</code> and <code>end</code> 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 <code>allEdges</code>, 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<Competitor, Map<Waypoint, MarkPassing>> currentMarkPasses = new HashMap<>();
|
||||
private Map<Competitor, Map<Candidate, Set<Edge>>> allEdges = new HashMap<>();
|
||||
private Map<Competitor, Set<Candidate>> candidates = new HashMap<>();
|
||||
private Map<Competitor, NavigableSet<Candidate>> fixedPassings = new HashMap<>();
|
||||
private Map<Competitor, Integer> 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 <code>null</code>
|
||||
* 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<Candidate> startAndEnd = Arrays.asList(start, end);
|
||||
for (Competitor c : race.getRace().getCompetitors()) {
|
||||
candidates.put(c, new TreeSet<Candidate>());
|
||||
currentMarkPasses.put(c, new HashMap<Waypoint, MarkPassing>());
|
||||
TreeSet<Candidate> fixedPasses = new TreeSet<Candidate>(new Comparator<Candidate>() {
|
||||
@Override
|
||||
public int compare(Candidate o1, Candidate o2) {
|
||||
return o1.getOneBasedIndexOfWaypoint() - o2.getOneBasedIndexOfWaypoint();
|
||||
}
|
||||
});
|
||||
fixedPassings.put(c, fixedPasses);
|
||||
allEdges.put(c, new HashMap<Candidate, Set<Edge>>());
|
||||
fixedPasses.addAll(startAndEnd);
|
||||
addCandidates(c, startAndEnd);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void calculateMarkPassDeltas(Competitor c, Iterable<Candidate> newCans, Iterable<Candidate> 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<Candidate> 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<Waypoint> 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<Candidate> 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<Candidate> newCandidates) {
|
||||
final Boolean isGateStart = race.isGateStart();
|
||||
Map<Candidate, Set<Edge>> 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<Candidate> 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<Candidate, Set<Edge>> edges, Edge e) {
|
||||
logger.finest("Adding "+ e.toString());
|
||||
Set<Edge> 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}.<p>
|
||||
*
|
||||
* 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<Candidate, Set<Edge>> allCompetitorEdges = allEdges.get(c);
|
||||
SortedSet<Candidate> mostLikelyCandidates = new TreeSet<>();
|
||||
NavigableSet<Candidate> 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<Util.Pair<Edge, Double>> currentEdgesCheapestFirst = new TreeSet<>(new Comparator<Util.Pair<Edge, Double>>() {
|
||||
@Override
|
||||
public int compare(Util.Pair<Edge, Double> o1, Util.Pair<Edge, Double> o2) {
|
||||
int result = o1.getB().compareTo(o2.getB());
|
||||
return result != 0 ? result : o1.getA().compareTo(o2.getA());
|
||||
}
|
||||
});
|
||||
Map<Candidate, Util.Pair<Candidate, Double>> candidateWithParentAndSmallestTotalCost = new HashMap<>();
|
||||
int indexOfEndOfFixedInterval = endOfFixedInterval.getOneBasedIndexOfWaypoint();
|
||||
|
||||
boolean endFound = false;
|
||||
currentEdgesCheapestFirst.add(new Util.Pair<Edge, Double>(new Edge(new CandidateImpl(-1, null, /* estimated distance probability */ 1, null), startOfFixedInterval,
|
||||
0, race.getRace().getCourse()), 0.0));
|
||||
while (!endFound) {
|
||||
Util.Pair<Edge, Double> 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<Candidate, Double>(
|
||||
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<Edge> 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<Edge, Double>(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<Waypoint, MarkPassing> 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<MarkPassing> 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.
|
||||
* <p>
|
||||
*
|
||||
* 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<Candidate> newCandidates) {
|
||||
for (Candidate can : newCandidates) {
|
||||
candidates.get(c).add(can);
|
||||
}
|
||||
createNewEdges(c, newCandidates);
|
||||
}
|
||||
|
||||
private void removeCandidates(Competitor c, Iterable<Candidate> wrongCandidates) {
|
||||
for (Candidate can : wrongCandidates) {
|
||||
logger.finest("Removing all edges containing " + can.toString() + "of "+ c);
|
||||
candidates.get(c).remove(can);
|
||||
Map<Candidate, Set<Edge>> edges = allEdges.get(c);
|
||||
edges.remove(can);
|
||||
for (Set<Edge> set : edges.values()) {
|
||||
for (Iterator<Edge> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+869
@@ -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<Competitor, LinkedHashMap<GPSFix, Map<Waypoint, List<Distance>>>> distanceCache = new LinkedHashMap<>();
|
||||
private Map<Competitor, LinkedHashMap<GPSFix, Map<Waypoint, List<Distance>>>> xteCache = new LinkedHashMap<>();
|
||||
|
||||
private Map<Competitor, Map<Waypoint, Map<List<GPSFix>, Candidate>>> xteCandidates = new HashMap<>();
|
||||
private Map<Competitor, Map<Waypoint, Map<GPSFix, Candidate>>> distanceCandidates = new HashMap<>();
|
||||
private final DynamicTrackedRace race;
|
||||
private final double penaltyForSkipping = 1 - Edge.getPenaltyForSkipping();
|
||||
private final Map<Waypoint, PassingInstruction> passingInstructions = new LinkedHashMap<>();
|
||||
private final Comparator<GPSFix> comp = new Comparator<GPSFix>() {
|
||||
@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<GPSFix, Map<Waypoint, List<Distance>>>(25));
|
||||
distanceCache.put(c, new LimitedLinkedHashMap<GPSFix, Map<Waypoint, List<Distance>>>(25));
|
||||
xteCandidates.put(c, new HashMap<Waypoint, Map<List<GPSFix>, Candidate>>());
|
||||
distanceCandidates.put(c, new HashMap<Waypoint, Map<GPSFix, Candidate>>());
|
||||
}
|
||||
}
|
||||
|
||||
private class LimitedLinkedHashMap<K, V> extends LinkedHashMap<K, V> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private int limit;
|
||||
|
||||
public LimitedLinkedHashMap(int limit) {
|
||||
super();
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
|
||||
return this.size() > limit;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Util.Pair<Iterable<Candidate>, Iterable<Candidate>> getAllCandidates(Competitor c) {
|
||||
Set<GPSFix> 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<Competitor, List<GPSFix>> calculateFixesAffectedByNewMarkFixes(Map<Mark, List<GPSFix>> markFixes) {
|
||||
// TODO Right now creates on time stretch between the 2 outside markfixes
|
||||
Map<Competitor, List<GPSFix>> affectedFixes = new HashMap<>();
|
||||
TimePoint start = null;
|
||||
TimePoint end = null;
|
||||
for (Entry<Mark, List<GPSFix>> fixes : markFixes.entrySet()) {
|
||||
for (GPSFix fix : fixes.getValue()) {
|
||||
Util.Pair<TimePoint, TimePoint> 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<GPSFix> comFixes = new ArrayList<>();
|
||||
DynamicGPSFixTrack<Competitor, GPSFixMoving> 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<Candidate>, Iterable<Candidate>> getCandidateDeltas(Competitor c, Iterable<GPSFix> fixes) {
|
||||
|
||||
List<Candidate> newCans = new ArrayList<>();
|
||||
List<Candidate> wrongCans = new ArrayList<>();
|
||||
Util.Pair<List<Candidate>, List<Candidate>> distanceCandidates = checkForDistanceCandidateChanges(c, fixes,
|
||||
race.getRace().getCourse().getWaypoints());
|
||||
Util.Pair<List<Candidate>, List<Candidate>> 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<Candidate>, Iterable<Candidate>>(newCans, wrongCans);
|
||||
}
|
||||
|
||||
private Map<Competitor, Util.Pair<List<Candidate>, List<Candidate>>> invalidateAfterCourseChange(int indexOfChange) {
|
||||
Map<Competitor, Util.Pair<List<Candidate>, List<Candidate>>> result = new HashMap<>();
|
||||
Course course = race.getRace().getCourse();
|
||||
for (Competitor c : race.getRace().getCompetitors()) {
|
||||
distanceCache.get(c).clear();
|
||||
xteCache.get(c).clear();
|
||||
}
|
||||
List<Waypoint> changedWaypoints = new ArrayList<>();
|
||||
for (Waypoint w : course.getWaypoints()) {
|
||||
if (course.getIndexOfWaypoint(w) > indexOfChange - 2) {
|
||||
changedWaypoints.add(w);
|
||||
}
|
||||
}
|
||||
for (Competitor c : race.getRace().getCompetitors()) {
|
||||
List<Candidate> badCans = new ArrayList<>();
|
||||
List<Candidate> newCans = new ArrayList<>();
|
||||
for (Waypoint w : changedWaypoints) {
|
||||
Map<List<GPSFix>, Candidate> xteCans = getXteCandidates(c, w);
|
||||
badCans.addAll(xteCans.values());
|
||||
xteCans.clear();
|
||||
Map<GPSFix, Candidate> distanceCans = getDistanceCandidates(c, w);
|
||||
badCans.addAll(distanceCans.values());
|
||||
distanceCans.clear();
|
||||
}
|
||||
Set<GPSFix> allFixes = getAllFixes(c);
|
||||
newCans.addAll(checkForDistanceCandidateChanges(c, allFixes, changedWaypoints).getA());
|
||||
newCans.addAll(checkForXTECandidatesChanges(c, allFixes, changedWaypoints).getA());
|
||||
result.put(c, new Util.Pair<List<Candidate>, List<Candidate>>(newCans, badCans));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Competitor, Util.Pair<List<Candidate>, List<Candidate>>> updateWaypoints(
|
||||
Iterable<Waypoint> addedWaypoints, Iterable<Waypoint> removedWaypoints, Integer smallestIndex) {
|
||||
Map<Competitor, List<Candidate>> removedWaypointCandidates = removeWaypoints(removedWaypoints);
|
||||
Map<Competitor, Util.Pair<List<Candidate>, List<Candidate>>> newAndUpdatedCandidates = invalidateAfterCourseChange(smallestIndex);
|
||||
for (Entry<Competitor, List<Candidate>> entry : removedWaypointCandidates.entrySet()) {
|
||||
newAndUpdatedCandidates.get(entry.getKey()).getB().addAll(entry.getValue());
|
||||
}
|
||||
return newAndUpdatedCandidates;
|
||||
}
|
||||
|
||||
private Map<Competitor, List<Candidate>> removeWaypoints(Iterable<Waypoint> waypoints) {
|
||||
// TODO Clear caches?
|
||||
Map<Competitor, List<Candidate>> result = new HashMap<>();
|
||||
for (Competitor c : race.getRace().getCompetitors()) {
|
||||
result.put(c, new ArrayList<Candidate>());
|
||||
}
|
||||
for (Waypoint w : waypoints) {
|
||||
passingInstructions.remove(w);
|
||||
for (Entry<Competitor, List<Candidate>> entry : result.entrySet()) {
|
||||
Competitor c = entry.getKey();
|
||||
List<Candidate> 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<GPSFix, Candidate> getDistanceCandidates(Competitor c, Waypoint w) {
|
||||
synchronized (distanceCandidates) {
|
||||
Map<GPSFix, Candidate> result = distanceCandidates.get(c).get(w);
|
||||
if (result == null) {
|
||||
result = new HashMap<>();
|
||||
distanceCandidates.get(c).put(w, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private Map<List<GPSFix>, Candidate> getXteCandidates(Competitor c, Waypoint w) {
|
||||
synchronized (xteCandidates) {
|
||||
Map<List<GPSFix>, 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<Mark> 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<GPSFix> getAllFixes(Competitor c) {
|
||||
Set<GPSFix> fixes = new TreeSet<GPSFix>(comp);
|
||||
DynamicGPSFixTrack<Competitor, GPSFixMoving> 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<Candidate>, List<Candidate>> checkForDistanceCandidateChanges(Competitor c,
|
||||
Iterable<GPSFix> fixes, Iterable<Waypoint> waypoints) {
|
||||
Util.Pair<List<Candidate>, List<Candidate>> result = new Util.Pair<List<Candidate>, List<Candidate>>(
|
||||
new ArrayList<Candidate>(), new ArrayList<Candidate>());
|
||||
TreeSet<GPSFix> affectedFixes = new TreeSet<GPSFix>(comp);
|
||||
GPSFixTrack<Competitor, GPSFixMoving> 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<Waypoint, List<Distance>> fixDistances = getDistances(c, fix);
|
||||
Map<Waypoint, List<Distance>> fixDistancesBefore = getDistances(c, fixBefore);
|
||||
Map<Waypoint, List<Distance>> fixDistancesAfter = getDistances(c, fixAfter);
|
||||
for (Waypoint w : waypoints) {
|
||||
Boolean wasCan = false;
|
||||
Boolean isCan = false;
|
||||
Candidate oldCan = null;
|
||||
Double probability = null;
|
||||
List<Distance> waypointDistances = fixDistances.get(w);
|
||||
List<Distance> waypointDistancesBefore = fixDistancesBefore.get(w);
|
||||
List<Distance> 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<Distance> disIter = waypointDistances.iterator();
|
||||
Iterator<Distance> disBeforeIter = waypointDistancesBefore.iterator();
|
||||
Iterator<Distance> 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<Waypoint, List<Distance>> getDistances(Competitor c, GPSFix fix) {
|
||||
// TODO Possibly for specific waypoints
|
||||
Map<Waypoint, List<Distance>> 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<Distance> 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<Candidate>, List<Candidate>> checkForXTECandidatesChanges(Competitor c,
|
||||
Iterable<GPSFix> fixes, Iterable<Waypoint> waypoints) {
|
||||
Util.Pair<List<Candidate>, List<Candidate>> result = new Util.Pair<List<Candidate>, List<Candidate>>(
|
||||
new ArrayList<Candidate>(), new ArrayList<Candidate>());
|
||||
DynamicGPSFixTrack<Competitor, GPSFixMoving> 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<Waypoint, List<Distance>> xtesBefore = null;
|
||||
Map<Waypoint, List<Distance>> 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<Waypoint, List<Distance>> xtes = getXTE(c, fix);
|
||||
for (Waypoint w : waypoints) {
|
||||
List<List<GPSFix>> oldCandidates = new ArrayList<>();
|
||||
Map<List<GPSFix>, Candidate> newCandidates = new HashMap<List<GPSFix>, Candidate>();
|
||||
Map<List<GPSFix>, Candidate> waypointCandidates = getXteCandidates(c, w);
|
||||
for (List<GPSFix> fixPair : waypointCandidates.keySet()) {
|
||||
if (fixPair.contains(fix)) {
|
||||
oldCandidates.add(fixPair);
|
||||
}
|
||||
}
|
||||
List<Distance> 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<List<GPSFix>, Candidate> candidateWithFixes : newCandidates.entrySet()) {
|
||||
Candidate newCan = candidateWithFixes.getValue();
|
||||
List<GPSFix> 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<GPSFix> badCanFixes : oldCandidates) {
|
||||
result.getB().add(waypointCandidates.get(badCanFixes));
|
||||
waypointCandidates.remove(badCanFixes);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Map<Waypoint, List<Distance>> getXTE(Competitor c, GPSFix fix) {
|
||||
Map<Waypoint, List<Distance>> 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<Distance> distances = new ArrayList<>();
|
||||
result.put(w, distances);
|
||||
for (Util.Pair<Position, Bearing> 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<Waypoint> 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<GPSFix> fixes, Iterable<Waypoint>
|
||||
* waypointsToCalculate) { for (GPSFix fix : fixes) { Position fixPos = fix.getPosition(); TimePoint t =
|
||||
* fix.getTimePoint(); Map<Waypoint, List<Double>> waypointXTE = new HashMap<Waypoint, List<Double>>();
|
||||
* crossTrackErrors.get(c).put(fix, waypointXTE); for (Waypoint w : race.getRace().getCourse().getWaypoints()) {
|
||||
* List<Double> xte = new ArrayList<>(); waypointXTE.put(w, xte); for (Util.Pair<Position, Bearing> 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<Distance> 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 <code>p</code> 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<Position> 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<Mark, Mark> pair = getPortAndStarboardMarks(t, w);
|
||||
m = portMark ? pair.getA() : pair.getB();
|
||||
}
|
||||
if (m != null) {
|
||||
Util.Pair<Position, Bearing> 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 <code>w</code> 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 <code>w</code>.
|
||||
*/
|
||||
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 <code>m</code> is contained in <code>lineMarks</code>, the distance to that line is calculated, else simply
|
||||
* the distance to the m.
|
||||
*/
|
||||
private List<Distance> calculateDistance(Position p, Waypoint w, TimePoint t) {
|
||||
List<Distance> 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<Mark, Mark> 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<Mark, Mark> 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<Util.Pair<Position, Bearing>> getCrossingInformation(Waypoint w, TimePoint t) {
|
||||
List<Util.Pair<Position, Bearing>> result = new ArrayList<>();
|
||||
PassingInstruction instruction = getPassingInstructions(w);
|
||||
if (instruction == PassingInstruction.Line) {
|
||||
Util.Pair<Mark, Mark> 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<Position, Bearing>(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<Mark, Mark> 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<Position, Bearing>(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<Position, Bearing>(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<Position, Bearing>(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<Mark, Mark> getPortAndStarboardMarks(TimePoint t, Waypoint w) {
|
||||
List<Position> markPositions = new ArrayList<Position>();
|
||||
for (Mark mark : w.getMarks()) {
|
||||
final Position estimatedMarkPosition = race.getOrCreateTrack(mark).getEstimatedPosition(t, /* extrapolate */ false);
|
||||
if (estimatedMarkPosition == null) {
|
||||
return new Util.Pair<Mark, Mark>(null, null);
|
||||
}
|
||||
markPositions.add(estimatedMarkPosition);
|
||||
}
|
||||
final List<Leg> legs = race.getRace().getCourse().getLegs();
|
||||
final int indexOfWaypoint = race.getRace().getCourse().getIndexOfWaypoint(w);
|
||||
if (indexOfWaypoint < 0) {
|
||||
return new Util.Pair<Mark, Mark>(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<Mark, Mark>(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<Mark, Mark>(portMarkWhileApproachingLine, starboardMarkWhileApproachingLine);
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -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());
|
||||
}
|
||||
}
|
||||
Executable
+70
@@ -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, <code>start</code> and <code>end</code>. {@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<Edge> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
-7
@@ -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<Mark, Mark> getPortAndStarboardMarks(TimePoint t, Waypoint w);
|
||||
}
|
||||
-12
@@ -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 <code>race</code>. See also {@link #getTrackedRace(RaceDefinition)} for a blocking variant.
|
||||
*/
|
||||
DynamicTrackedRace getExistingTrackedRace(RaceDefinition race);
|
||||
|
||||
/**
|
||||
* @param raceDefinitionSetToUpdate
|
||||
* may be <code>null</code> which means that no update will be fired to any
|
||||
* {@link DynamicRaceDefinitionSet}.
|
||||
*/
|
||||
DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable<Sideline> sidelines,
|
||||
WindStore windStore, GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind,
|
||||
long millisecondsOverWhichToAverageSpeed, DynamicRaceDefinitionSet raceDefinitionSetToUpdate);
|
||||
|
||||
}
|
||||
@@ -751,4 +751,6 @@ public interface TrackedRace extends Serializable {
|
||||
*/
|
||||
Distance getAdditionalGateStartDistance(Competitor competitor, TimePoint timePoint);
|
||||
|
||||
boolean isUsingMarkPassingCalculator();
|
||||
|
||||
}
|
||||
@@ -44,7 +44,7 @@ public interface TrackedRegatta extends Serializable {
|
||||
*/
|
||||
DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable<Sideline> sidelines, WindStore windStore,
|
||||
GPSFixStore gpsFixStore, long delayToLiveInMillis, long millisecondsOverWhichToAverageWind, long millisecondsOverWhichToAverageSpeed,
|
||||
DynamicRaceDefinitionSet raceDefinitionSetToUpdate);
|
||||
DynamicRaceDefinitionSet raceDefinitionSetToUpdate, boolean useInternalMarkPassingAlgorithm);
|
||||
|
||||
/**
|
||||
* Obtains the tracked race for <code>race</code>. Blocks until the tracked race has been created
|
||||
|
||||
+814
-883
File diff suppressed because it is too large.
Load diff
Regular → Executable
+107
-11
@@ -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<RaceLog> 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<Competitor, Integer, TimePoint> 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<Competitor, Integer, TimePoint> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -35,8 +35,8 @@ public class DynamicTrackedRegattaImpl extends TrackedRegattaImpl implements Dyn
|
||||
@Override
|
||||
public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable<Sideline> 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);
|
||||
}
|
||||
}
|
||||
+41
-3
@@ -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<TimePoint, Future<Wind>> directionFromStartToNextMarkCache;
|
||||
|
||||
protected final MarkPassingCalculator markPassingCalculator;
|
||||
|
||||
private final ConcurrentHashMap<Mark, GPSFixTrack<Mark, GPSFix>> markTracks;
|
||||
|
||||
private final Map<String, Sideline> 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<Sideline> sidelines, final WindStore windStore, final GPSFixStore gpsFixStore,
|
||||
public TrackedRaceImpl(final TrackedRegatta trackedRegatta, RaceDefinition race,
|
||||
final Iterable<Sideline> 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) {
|
||||
|
||||
+3
-2
@@ -171,10 +171,11 @@ public class TrackedRegattaImpl implements TrackedRegatta {
|
||||
@Override
|
||||
public DynamicTrackedRace createTrackedRace(RaceDefinition raceDefinition, Iterable<Sideline> 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) {
|
||||
|
||||
+1
-1
@@ -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<Void>() {
|
||||
false, false, /* simulateWithStartTimeNow */ false, /* ignoreTracTracMarkPassings */ false, TRACTRAC_USERNAME, TRACTRAC_PASSWORD, new AsyncCallback<Void>() {
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
|
||||
+10
-13
@@ -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<Sideline> 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<Mark, Mark> 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 {
|
||||
}
|
||||
|
||||
Loaded 100 of 151 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user