refactored calculation of race start time

This commit is contained in:
Axel Uhl
2012-03-23 11:58:35 +01:00
parent 470b4b0f1d
commit c65eb44a70
4 changed files with 86 additions and 74 deletions
@@ -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
@@ -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;
@@ -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,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() {}