mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-28 08:26:44 +00:00
refactored calculation of race start time
This commit is contained in:
@@ -53,8 +53,8 @@ public interface TrackedRace {
|
||||
|
||||
/**
|
||||
* Computes the estimated start time for this race (not to be confused with the {@link #getStartOfTracking()} time point
|
||||
* which is expected to be before the race start time). When there are no {@link MarkPassing}s for the first mark, the
|
||||
* start of tracking is used, assuming that we missed the start. This is also used if there are mark passings for
|
||||
* which is expected to be before the race start time). When there are no {@link MarkPassing}s for the first mark, <code>null</code>
|
||||
* is returned. If there are mark passings for
|
||||
* the first mark and the start time is less than
|
||||
* {@link #MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS} before the first mark passing for the
|
||||
* first mark. Otherwise, the first mark passing for the first mark minus
|
||||
|
||||
+73
-16
@@ -242,7 +242,7 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<MarkPassing> getMarkPassingsInOrder(Waypoint waypoint) {
|
||||
public NavigableSet<MarkPassing> getMarkPassingsInOrder(Waypoint waypoint) {
|
||||
return markPassingsForWaypoint.get(waypoint);
|
||||
}
|
||||
|
||||
@@ -253,18 +253,13 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
|
||||
@Override
|
||||
public TimePoint getStart() {
|
||||
TimePoint result;
|
||||
Iterable<MarkPassing> markPassingsInOrder = getMarkPassingsInOrder(getRace().getCourse().getFirstWaypoint());
|
||||
MarkPassing firstMarkPassingFirstMark = null;
|
||||
synchronized (markPassingsInOrder) {
|
||||
Iterator<MarkPassing> markPassingsFirstMarkIter = markPassingsInOrder.iterator();
|
||||
if (markPassingsFirstMarkIter.hasNext()) {
|
||||
firstMarkPassingFirstMark = markPassingsFirstMarkIter.next();
|
||||
}
|
||||
}
|
||||
if (firstMarkPassingFirstMark != null) {
|
||||
TimePoint timeOfFirstMarkPassingFirstMark = firstMarkPassingFirstMark.getTimePoint();
|
||||
if (startTimeReceived != null) {
|
||||
TimePoint result = startTimeReceived;
|
||||
// If not null, check if the first mark passing for the start line is too much after the startTimeReceived;
|
||||
// if so, return an adjusted, later start time.
|
||||
// If no official start time was received, try to estimate the start time using the mark passings for the start line.
|
||||
if (startTimeReceived != null) {
|
||||
TimePoint timeOfFirstMarkPassingFirstMark = getFirstStartPassingTime();
|
||||
if (timeOfFirstMarkPassingFirstMark != null) {
|
||||
long startTimeReceived2timeOfFirstMarkPassingFirstMark = timeOfFirstMarkPassingFirstMark.asMillis()
|
||||
- startTimeReceived.asMillis();
|
||||
if (startTimeReceived2timeOfFirstMarkPassingFirstMark > MAX_TIME_BETWEEN_START_AND_FIRST_MARK_PASSING_IN_MILLISECONDS) {
|
||||
@@ -273,15 +268,77 @@ public abstract class TrackedRaceImpl implements TrackedRace, CourseListener {
|
||||
} else {
|
||||
result = startTimeReceived;
|
||||
}
|
||||
} else {
|
||||
result = timeOfFirstMarkPassingFirstMark;
|
||||
}
|
||||
} else {
|
||||
result = startTimeReceived;
|
||||
result = calculateStartOfRaceFromMarkPassings(getMarkPassingsInOrder(getRace().getCourse().getFirstWaypoint()), getRace().getCompetitors());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private TimePoint getFirstStartPassingTime() {
|
||||
Iterable<MarkPassing> markPassingsInOrder = getMarkPassingsInOrder(getRace().getCourse().getFirstWaypoint());
|
||||
MarkPassing firstMarkPassingFirstMark = null;
|
||||
synchronized (markPassingsInOrder) {
|
||||
Iterator<MarkPassing> markPassingsFirstMarkIter = markPassingsInOrder.iterator();
|
||||
if (markPassingsFirstMarkIter.hasNext()) {
|
||||
firstMarkPassingFirstMark = markPassingsFirstMarkIter.next();
|
||||
}
|
||||
}
|
||||
TimePoint timeOfFirstMarkPassingFirstMark = null;
|
||||
if (firstMarkPassingFirstMark != null) {
|
||||
timeOfFirstMarkPassingFirstMark = firstMarkPassingFirstMark.getTimePoint();
|
||||
}
|
||||
return timeOfFirstMarkPassingFirstMark;
|
||||
}
|
||||
|
||||
private TimePoint calculateStartOfRaceFromMarkPassings(NavigableSet<MarkPassing> markPassings, Iterable<Competitor> competitors) {
|
||||
TimePoint startOfRace = null;
|
||||
// Find the first mark passing within the largest cluster crossing the line within one minute.
|
||||
final long ONE_MINUTE_IN_MILLIS = 60 * 1000;
|
||||
synchronized (markPassings) {
|
||||
if (markPassings != null) {
|
||||
int largestStartGroupWithinOneMinuteSize = 0;
|
||||
MarkPassing startOfLargestGroupSoFar = null;
|
||||
int candiateGroupSize = 0;
|
||||
MarkPassing candidateForStartOfLargestGroupSoFar = null;
|
||||
Iterator<MarkPassing> iterator = markPassings.iterator();
|
||||
// sweep over all start mark passings and for each element find the number of competitors that passed the start up to one minute later;
|
||||
// pick the start mark passing of the competitor leading the largest such group
|
||||
while (iterator.hasNext()) {
|
||||
MarkPassing currentMarkPassing = iterator.next();
|
||||
if (candidateForStartOfLargestGroupSoFar == null) {
|
||||
// first start mark passing
|
||||
candidateForStartOfLargestGroupSoFar = currentMarkPassing;
|
||||
candiateGroupSize = 1;
|
||||
startOfLargestGroupSoFar = currentMarkPassing;
|
||||
largestStartGroupWithinOneMinuteSize = 1;
|
||||
} else {
|
||||
if (currentMarkPassing.getTimePoint().asMillis() - candidateForStartOfLargestGroupSoFar.getTimePoint().asMillis() <= ONE_MINUTE_IN_MILLIS) {
|
||||
// currentMarkPassing is within one minute of candidateForStartOfLargestGroupSoFar; extend candidate group...
|
||||
candiateGroupSize++;
|
||||
if (candiateGroupSize > largestStartGroupWithinOneMinuteSize) {
|
||||
// ...and remember as best fit if greater than largest group so far
|
||||
startOfLargestGroupSoFar = candidateForStartOfLargestGroupSoFar;
|
||||
largestStartGroupWithinOneMinuteSize = candiateGroupSize;
|
||||
}
|
||||
} else {
|
||||
// currentMarkPassing is more than a minute after candidateForStartOfLargestGroupSoFar; advance
|
||||
// candidateForStartOfLargestGroupSoFar and reduce group size counter, until candidateForStartOfLargestGroupSoFar
|
||||
// is again within the one-minute interval; may catch up all the way to currentMarkPassing if that was
|
||||
// more than a minute after its predecessor
|
||||
while (currentMarkPassing.getTimePoint().asMillis() - candidateForStartOfLargestGroupSoFar.getTimePoint().asMillis() > ONE_MINUTE_IN_MILLIS) {
|
||||
candidateForStartOfLargestGroupSoFar = markPassings.higher(candidateForStartOfLargestGroupSoFar);
|
||||
candiateGroupSize--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
startOfRace = startOfLargestGroupSoFar.getTimePoint();
|
||||
}
|
||||
}
|
||||
return startOfRace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimePoint getAssumedEnd() {
|
||||
TimePoint result = null;
|
||||
|
||||
+6
-56
@@ -910,7 +910,7 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
|
||||
TrackedRace trackedRace = getExistingTrackedRace(raceIdentifier);
|
||||
if (trackedRace != null) {
|
||||
raceTimesInfo = new RaceTimesInfoDTO(raceIdentifier);
|
||||
raceTimesInfo.startOfRace = trackedRace.getStart() == null ? null : trackedRace.getStart().asDate();
|
||||
raceTimesInfo.setStartOfRace(trackedRace.getStart() == null ? null : trackedRace.getStart().asDate());
|
||||
raceTimesInfo.startOfTracking = trackedRace.getStartOfTracking() == null ? null : trackedRace.getStartOfTracking().asDate();
|
||||
raceTimesInfo.timePointOfLastEvent = trackedRace.getTimePointOfLastEvent() == null ? null : trackedRace.getTimePointOfLastEvent().asDate();
|
||||
raceTimesInfo.timePointOfNewestEvent = trackedRace.getTimePointOfNewestEvent() == null ? null : trackedRace.getTimePointOfNewestEvent().asDate();
|
||||
@@ -922,21 +922,15 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
|
||||
int i = 1;
|
||||
for (TrackedLeg trackedLeg : trackedLegs) {
|
||||
if (i == 1) {
|
||||
// calculate the point in time where all boats passed the start line in a defined time frame
|
||||
Waypoint from = trackedLeg.getLeg().getFrom();
|
||||
Iterable<MarkPassing> markPassings = trackedRace.getMarkPassingsInOrder(from);
|
||||
Iterable<Competitor> competitors = trackedRace.getRace().getCompetitors();
|
||||
|
||||
Date startOfRacePassingDate = calculateStartOfRaceFromMarkPassings(markPassings, competitors);
|
||||
if(startOfRacePassingDate != null) {
|
||||
if (raceTimesInfo.startOfRace != null) {
|
||||
// For the race start, we'd like to show the race start date as provided by TrackedRace.getStart().
|
||||
// Therefore, the use of "firstPassingDate" is not exactly correct for the race start.
|
||||
LegTimesInfoDTO legTimepointDTO = new LegTimesInfoDTO("S");
|
||||
legTimepointDTO.firstPassingDate = startOfRacePassingDate;
|
||||
legTimepointDTO.firstPassingDate = raceTimesInfo.getStartOfRace();
|
||||
legTimes.add(legTimepointDTO);
|
||||
raceTimesInfo.setStartOfRace(startOfRacePassingDate);
|
||||
lastLegPassingTime = startOfRacePassingDate;
|
||||
lastLegPassingTime = raceTimesInfo.getStartOfRace();
|
||||
}
|
||||
}
|
||||
|
||||
Waypoint to = trackedLeg.getLeg().getTo();
|
||||
Iterable<MarkPassing> markPassings = trackedRace.getMarkPassingsInOrder(to);
|
||||
if (markPassings != null) {
|
||||
@@ -976,50 +970,6 @@ public class SailingServiceImpl extends RemoteServiceServlet implements SailingS
|
||||
return raceTimesInfos;
|
||||
}
|
||||
|
||||
private Date calculateStartOfRaceFromMarkPassings(Iterable<MarkPassing> markPassings, Iterable<Competitor> competitors) {
|
||||
Date startOfRace = null;
|
||||
|
||||
// try to calculate the point in time where all boats passed the start line in a small time frame
|
||||
synchronized (markPassings) {
|
||||
int markPassingsCount = Util.size(markPassings);
|
||||
// require more than half the boats registered for the race to have passed the start mark
|
||||
if (markPassings != null && markPassingsCount > Util.size(competitors) / 2) {
|
||||
long maxTimeFrameInMs = 60 * 1000; // = 1 minute
|
||||
Iterator<MarkPassing> iterator = markPassings.iterator();
|
||||
long currentTimeToCheck = 0;
|
||||
int enoughCompetitors = markPassingsCount / 2;
|
||||
int maxCompetitors = 0;
|
||||
long maxCompetitorsFirstPassingTime = 0;
|
||||
int currentCompetitorsInTime = 0;
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
MarkPassing currentMarkPassing = iterator.next();
|
||||
long diff = currentMarkPassing.getTimePoint().asMillis() - currentTimeToCheck;
|
||||
if (diff > maxTimeFrameInMs) {
|
||||
// reset the check, but save the amount of competitors for the fallback solution
|
||||
if(currentCompetitorsInTime > maxCompetitors)
|
||||
{
|
||||
maxCompetitors = currentCompetitorsInTime;
|
||||
maxCompetitorsFirstPassingTime = currentTimeToCheck;
|
||||
}
|
||||
currentCompetitorsInTime = 0;
|
||||
currentTimeToCheck = currentMarkPassing.getTimePoint().asMillis();
|
||||
} else {
|
||||
currentCompetitorsInTime++;
|
||||
if (currentCompetitorsInTime == enoughCompetitors) {
|
||||
startOfRace = currentMarkPassing.getTimePoint().asDate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if we didn't find a start time we use the first passing time of the largest competitor group as fallback
|
||||
if(startOfRace == null)
|
||||
startOfRace = new Date(maxCompetitorsFirstPassingTime);
|
||||
}
|
||||
}
|
||||
return startOfRace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MarkDTO> getMarkPositions(RaceIdentifier raceIdentifier, Date date) {
|
||||
List<MarkDTO> result = new ArrayList<MarkDTO>();
|
||||
|
||||
+5
@@ -5,6 +5,11 @@ import java.util.Date;
|
||||
import com.google.gwt.user.client.rpc.IsSerializable;
|
||||
|
||||
public class LegTimesInfoDTO extends NamedDTO implements IsSerializable {
|
||||
/**
|
||||
* The time point when the leg was first entered by a competitor; for the first leg the semantics
|
||||
* are slightly different: if the official race start time is known it is used instead of the first
|
||||
* passing event.
|
||||
*/
|
||||
public Date firstPassingDate;
|
||||
|
||||
public LegTimesInfoDTO() {}
|
||||
|
||||
Reference in New Issue
Block a user