mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-10-30 15:57:11 +00:00
cache leg info about boat farthest ahead and calculate and cache the leader by calculated time for the leg in RankingInfo
This commit is contained in:
1 parent
f11c534892
commit
c9a43c5781
4 files changed
+138
-103
No files matched your search
+128
-9
@@ -2,7 +2,9 @@ package com.sap.sailing.domain.ranking;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.StreamSupport;
|
||||
@@ -10,9 +12,11 @@ import java.util.stream.StreamSupport;
|
||||
import com.sap.sailing.domain.base.Competitor;
|
||||
import com.sap.sailing.domain.base.Leg;
|
||||
import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.base.impl.CompetitorImpl;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.Speed;
|
||||
import com.sap.sailing.domain.common.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuseCache;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.TrackedLeg;
|
||||
@@ -121,14 +125,77 @@ public abstract class AbstractRankingMetric implements RankingMetric {
|
||||
}
|
||||
}
|
||||
|
||||
public class RankingInfoImpl implements RankingMetric.RankingInfo {
|
||||
private static final long serialVersionUID = -2390284312153324336L;
|
||||
|
||||
/**
|
||||
* Helper instance used to encode <code>null</code> values in {@link ConcurrentHashMap} instances which do not accept
|
||||
* <code>null</code> as key nor value.
|
||||
*/
|
||||
private final static Competitor NULL_COMPETITOR = new CompetitorImpl(null, null, null, null, null, null, null, null, null);
|
||||
|
||||
public abstract class AbstractRankingInfo implements RankingMetric.RankingInfo {
|
||||
private static final long serialVersionUID = 6845168655725234325L;
|
||||
|
||||
/**
|
||||
* The time point for which this ranking information is valid
|
||||
*/
|
||||
private final TimePoint timePoint;
|
||||
|
||||
/**
|
||||
* Caches, on demand, the results of calls to {@link #getCompetitorFarthestAheadInLeg(Leg, TimePoint, WindLegTypeAndLegBearingCache)}.
|
||||
* <code>null</code> values are encoded as the {@link #NULL_COMPETITOR} and must be translated back to <code>null</code> before returning
|
||||
* to a caller outside of this class.
|
||||
*/
|
||||
private final ConcurrentHashMap<Leg, Competitor> competitorFarthestAheadInLeg;
|
||||
|
||||
public AbstractRankingInfo(final TimePoint timePoint) {
|
||||
this.timePoint = timePoint;
|
||||
this.competitorFarthestAheadInLeg = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimePoint getTimePoint() {
|
||||
return timePoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getActualTimeFromRaceStartToReachFarthestAheadInLeg(Competitor competitor, Leg leg, WindLegTypeAndLegBearingCache cache) {
|
||||
final Duration result;
|
||||
final TrackedLegOfCompetitor tloc = getTrackedRace().getTrackedLeg(competitor, getTimePoint());
|
||||
final Duration raceDurationAtTimePoint = getTrackedRace().getStartOfRace().until(getTimePoint());
|
||||
if (tloc.hasStartedLeg(getTimePoint())) {
|
||||
final Competitor competitorFarthestAheadInLeg = getCompetitorFarthestAheadInLeg(leg, getTimePoint(), cache);
|
||||
final TrackedLegOfCompetitor tlocOfCompetitorFarthestAheadInLeg = tloc.getTrackedLeg().getTrackedLeg(competitorFarthestAheadInLeg);
|
||||
final Duration predictedDurationFromTimePointToReachFarthestAheadInLeg = getPredictedDurationToReachWindwardPositionOf(
|
||||
tloc, tlocOfCompetitorFarthestAheadInLeg, getTimePoint(), cache);
|
||||
final Duration cDurationFromRaceStartToReachFarthestInLeg = raceDurationAtTimePoint.plus(predictedDurationFromTimePointToReachFarthestAheadInLeg);
|
||||
result = cDurationFromRaceStartToReachFarthestInLeg;
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Competitor getCompetitorFarthestAheadInLeg(Leg leg, TimePoint timePoint, WindLegTypeAndLegBearingCache cache) {
|
||||
Competitor result = competitorFarthestAheadInLeg.get(leg);
|
||||
if (result == NULL_COMPETITOR) {
|
||||
result = null;
|
||||
} else if (result == null) {
|
||||
result = AbstractRankingMetric.this.getCompetitorFarthestAheadInLeg(getTrackedRace().getTrackedLeg(leg), timePoint, cache);
|
||||
competitorFarthestAheadInLeg.put(leg, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Competitor getLeaderInLegByCalculatedTime(Leg leg, WindLegTypeAndLegBearingCache cache) {
|
||||
final TrackedLeg trackedLeg = getTrackedRace().getTrackedLeg(leg);
|
||||
return trackedLeg.getLeader(getTimePoint(), cache);
|
||||
}
|
||||
}
|
||||
|
||||
public class RankingInfoImpl extends AbstractRankingInfo {
|
||||
private static final long serialVersionUID = -2390284312153324336L;
|
||||
|
||||
/**
|
||||
* The basic information for each competitor, telling about actual and corrected times as well as information
|
||||
* about actual and corrected times needed to reach the position of the competitor farthest ahead at
|
||||
@@ -145,8 +212,8 @@ public abstract class AbstractRankingMetric implements RankingMetric {
|
||||
private final Competitor leaderByCorrectedEstimatedTimeToCompetitorFarthestAhead;
|
||||
|
||||
public RankingInfoImpl(TimePoint timePoint, Map<Competitor, RankingMetric.CompetitorRankingInfo> competitorRankingInfo, Competitor competitorFarthestAhead) {
|
||||
super(timePoint);
|
||||
final Comparator<Duration> durationComparatorNullsLast = Comparator.nullsLast(Comparator.naturalOrder());
|
||||
this.timePoint = timePoint;
|
||||
this.competitorRankingInfo = c->competitorRankingInfo.get(c);
|
||||
this.competitorFarthestAhead = competitorFarthestAhead;
|
||||
leaderByCorrectedEstimatedTimeToCompetitorFarthestAhead = competitorRankingInfo.keySet().stream().sorted(
|
||||
@@ -156,11 +223,6 @@ public abstract class AbstractRankingMetric implements RankingMetric {
|
||||
findFirst().get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimePoint getTimePoint() {
|
||||
return timePoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<Competitor, CompetitorRankingInfo> getCompetitorRankingInfo() {
|
||||
return competitorRankingInfo;
|
||||
@@ -553,4 +615,61 @@ public abstract class AbstractRankingMetric implements RankingMetric {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>null</code> if no competitor has started the leg yet; the first competitor to finish the leg if any
|
||||
* has already finished the leg at <code>timePoint</code>; or the competitor with the greatest windward
|
||||
* distance traveled in the leg at <code>timePoint</code> otherwise
|
||||
*/
|
||||
protected Competitor getCompetitorFarthestAheadInLeg(TrackedLeg trackedLeg, TimePoint timePoint, WindLegTypeAndLegBearingCache cache) {
|
||||
Competitor firstAroundMark = getFirstLegFinisherBefore(trackedLeg, timePoint);
|
||||
final Competitor result;
|
||||
if (firstAroundMark != null) {
|
||||
result = firstAroundMark;
|
||||
} else {
|
||||
Iterable<MarkPassing> markPassingsForLegStart = getTrackedRace().getMarkPassingsInOrder(trackedLeg.getLeg().getFrom());
|
||||
Distance maxWindwardDistanceTraveled = new MeterDistance(Double.MIN_VALUE);
|
||||
Competitor competitorFarthestAlong = null;
|
||||
getTrackedRace().lockForRead(markPassingsForLegStart);
|
||||
try {
|
||||
for (MarkPassing mp : markPassingsForLegStart) {
|
||||
if (mp.getTimePoint().after(timePoint)) {
|
||||
break;
|
||||
}
|
||||
final Distance windwardDistanceTraveled = getWindwardDistanceTraveled(mp.getCompetitor(), mp.getWaypoint(),
|
||||
timePoint, cache);
|
||||
if (windwardDistanceTraveled.compareTo(maxWindwardDistanceTraveled) > 0) {
|
||||
maxWindwardDistanceTraveled = windwardDistanceTraveled;
|
||||
competitorFarthestAlong = mp.getCompetitor();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
getTrackedRace().unlockAfterRead(markPassingsForLegStart);
|
||||
}
|
||||
result = competitorFarthestAlong;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the first competitor finishing the leg identified by <code>trackedLeg</code> at or before <code>timePoint</code>. If
|
||||
* no such competitor exists, <code>null</code> is returned.
|
||||
*/
|
||||
private Competitor getFirstLegFinisherBefore(TrackedLeg trackedLeg, TimePoint timePoint) {
|
||||
Iterable<MarkPassing> markPassingsForLegEnd = getTrackedRace().getMarkPassingsInOrder(trackedLeg.getLeg().getTo());
|
||||
Competitor firstAroundMark = null;
|
||||
getTrackedRace().lockForRead(markPassingsForLegEnd);
|
||||
try {
|
||||
final Iterator<MarkPassing> i = markPassingsForLegEnd.iterator();
|
||||
if (i.hasNext()) {
|
||||
MarkPassing markPassing = i.next();
|
||||
if (!markPassing.getTimePoint().after(timePoint)) {
|
||||
firstAroundMark = markPassing.getCompetitor();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
getTrackedRace().unlockAfterRead(markPassingsForLegEnd);
|
||||
}
|
||||
return firstAroundMark;
|
||||
}
|
||||
}
|
||||
+1
-6
@@ -75,14 +75,9 @@ public class OneDesignRankingMetric extends AbstractRankingMetric {
|
||||
|
||||
@Override
|
||||
public RankingInfo getRankingInfo(final TimePoint timePoint, final WindLegTypeAndLegBearingCache cache) {
|
||||
return new RankingInfo() {
|
||||
return new AbstractRankingInfo(timePoint) {
|
||||
private static final long serialVersionUID = 25689357311324825L;
|
||||
|
||||
@Override
|
||||
public TimePoint getTimePoint() {
|
||||
return timePoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Function<Competitor, CompetitorRankingInfo> getCompetitorRankingInfo() {
|
||||
return competitor -> getCompetitorRankingInfo(competitor);
|
||||
|
||||
@@ -122,7 +122,11 @@ public interface RankingMetric extends Serializable {
|
||||
* is normalized to the {@link TrackedLeg#getWindwardDistance() windward distance of the leg} at the
|
||||
* {@link TrackedLeg#getReferenceTimePoint() reference time point}.
|
||||
*/
|
||||
Competitor getLeaderInLegByCalculatedTime(Leg leg);
|
||||
Competitor getLeaderInLegByCalculatedTime(Leg leg, WindLegTypeAndLegBearingCache cache);
|
||||
|
||||
Competitor getCompetitorFarthestAheadInLeg(Leg leg, TimePoint timePoint, WindLegTypeAndLegBearingCache cache);
|
||||
|
||||
Duration getActualTimeFromRaceStartToReachFarthestAheadInLeg(Competitor competitor, Leg leg, WindLegTypeAndLegBearingCache cache);
|
||||
}
|
||||
|
||||
public interface LegRankingInfo extends Timed, Serializable {
|
||||
|
||||
+4
-87
@@ -2,7 +2,6 @@ package com.sap.sailing.domain.ranking;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@@ -14,7 +13,6 @@ import com.sap.sailing.domain.base.Waypoint;
|
||||
import com.sap.sailing.domain.common.Distance;
|
||||
import com.sap.sailing.domain.common.Mile;
|
||||
import com.sap.sailing.domain.common.Position;
|
||||
import com.sap.sailing.domain.common.impl.MeterDistance;
|
||||
import com.sap.sailing.domain.tracking.MarkPassing;
|
||||
import com.sap.sailing.domain.tracking.TrackedLeg;
|
||||
import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor;
|
||||
@@ -160,63 +158,6 @@ public class TimeOnTimeAndDistanceRankingMetric extends AbstractRankingMetric {
|
||||
compareTo(correctedTimesToReachFastestBoatsPositionAtTimePointOrEndOfLegMeasuredFromStartOfRace.get(tloc2.getCompetitor()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return <code>null</code> if no competitor has started the leg yet; the first competitor to finish the leg if any
|
||||
* has already finished the leg at <code>timePoint</code>; or the competitor with the greatest windward
|
||||
* distance traveled in the leg at <code>timePoint</code> otherwise
|
||||
*/
|
||||
private Competitor getCompetitorFarthestAheadInLeg(TrackedLeg trackedLeg, TimePoint timePoint, WindLegTypeAndLegBearingCache cache) {
|
||||
Competitor firstAroundMark = getFirstLegFinisherBefore(trackedLeg, timePoint);
|
||||
final Competitor result;
|
||||
if (firstAroundMark != null) {
|
||||
result = firstAroundMark;
|
||||
} else {
|
||||
Iterable<MarkPassing> markPassingsForLegStart = getTrackedRace().getMarkPassingsInOrder(trackedLeg.getLeg().getFrom());
|
||||
Distance maxWindwardDistanceTraveled = new MeterDistance(Double.MIN_VALUE);
|
||||
Competitor competitorFarthestAlong = null;
|
||||
getTrackedRace().lockForRead(markPassingsForLegStart);
|
||||
try {
|
||||
for (MarkPassing mp : markPassingsForLegStart) {
|
||||
if (mp.getTimePoint().after(timePoint)) {
|
||||
break;
|
||||
}
|
||||
final Distance windwardDistanceTraveled = getWindwardDistanceTraveled(mp.getCompetitor(), mp.getWaypoint(),
|
||||
timePoint, cache);
|
||||
if (windwardDistanceTraveled.compareTo(maxWindwardDistanceTraveled) > 0) {
|
||||
maxWindwardDistanceTraveled = windwardDistanceTraveled;
|
||||
competitorFarthestAlong = mp.getCompetitor();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
getTrackedRace().unlockAfterRead(markPassingsForLegStart);
|
||||
}
|
||||
result = competitorFarthestAlong;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the first competitor finishing the leg identified by <code>trackedLeg</code> at or before <code>timePoint</code>. If
|
||||
* no such competitor exists, <code>null</code> is returned.
|
||||
*/
|
||||
private Competitor getFirstLegFinisherBefore(TrackedLeg trackedLeg, TimePoint timePoint) {
|
||||
Iterable<MarkPassing> markPassingsForLegEnd = getTrackedRace().getMarkPassingsInOrder(trackedLeg.getLeg().getTo());
|
||||
Competitor firstAroundMark = null;
|
||||
getTrackedRace().lockForRead(markPassingsForLegEnd);
|
||||
try {
|
||||
final Iterator<MarkPassing> i = markPassingsForLegEnd.iterator();
|
||||
if (i.hasNext()) {
|
||||
MarkPassing markPassing = i.next();
|
||||
if (!markPassing.getTimePoint().after(timePoint)) {
|
||||
firstAroundMark = markPassing.getCompetitor();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
getTrackedRace().unlockAfterRead(markPassingsForLegEnd);
|
||||
}
|
||||
return firstAroundMark;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getCorrectedTime(Competitor competitor, TimePoint timePoint, WindLegTypeAndLegBearingCache cache) {
|
||||
final Duration timeActuallySpent = getActualTimeSinceStartOfRace(competitor, timePoint);
|
||||
@@ -379,47 +320,23 @@ public class TimeOnTimeAndDistanceRankingMetric extends AbstractRankingMetric {
|
||||
public Duration getLegGapToLegLeaderInOwnTime(TrackedLegOfCompetitor trackedLegOfCompetitor, TimePoint timePoint,
|
||||
RankingInfo rankingInfo, WindLegTypeAndLegBearingCache cache) {
|
||||
final Duration result;
|
||||
final Leg leg = trackedLegOfCompetitor.getLeg();
|
||||
if (getTrackedRace().getStartOfRace() == null || !trackedLegOfCompetitor.hasStartedLeg(timePoint)) {
|
||||
result = null;
|
||||
} else {
|
||||
final Duration raceDurationAtTimePoint = getTrackedRace().getStartOfRace().until(timePoint);
|
||||
final Competitor farthestAheadOrEarliestLegFinisher = getCompetitorFarthestAheadInLeg(trackedLegOfCompetitor.getTrackedLeg(), timePoint, cache);
|
||||
final TrackedLegOfCompetitor tlocOfFarthestAhead = trackedLegOfCompetitor.getTrackedLeg().getTrackedLeg(farthestAheadOrEarliestLegFinisher);
|
||||
final boolean farthestAheadAlreadyFinishedLeg = tlocOfFarthestAhead.hasFinishedLeg(timePoint);
|
||||
final Distance windwardDistanceFarthestTraveledUntilFinishingLeg = getWindwardDistanceTraveled(farthestAheadOrEarliestLegFinisher,
|
||||
farthestAheadAlreadyFinishedLeg ? tlocOfFarthestAhead.getFinishTime() : timePoint, cache);
|
||||
final Map<Competitor, Duration> correctedTimeToReachFarthestAheadInLeg = new HashMap<>();
|
||||
final Map<Competitor, Duration> actualTimeFromRaceStartToReachFarthestAheadInLeg = new HashMap<>();
|
||||
// Consider all competitors, regardless of whether they have started the leg or not; a competitor in a previous
|
||||
// leg may well be the race leader. However, if a competitor has finished the leg already, use the finishing time
|
||||
// point for that competitor.
|
||||
// FIXME need to calculate the ranking stuff only once for requests for all competitors to this method, or else we'll end up with n^2 effort
|
||||
// FIXME The competitor iteration is only necessary here because we need to know the "leader"; other than that, computing things for the leader and the trackedLegOfCompetitor's competitor is sufficient here
|
||||
// FIXME Grab the leader information from RankingInfo; CAUTION: leg leader is not necessarily identical to race leader! RankingInfo may need another field for each leg leader
|
||||
for (Competitor c : getTrackedRace().getRace().getCompetitors()) {
|
||||
final TrackedLegOfCompetitor tloc = trackedLegOfCompetitor.getTrackedLeg().getTrackedLeg(c);
|
||||
if (tloc.hasStartedLeg(timePoint)) {
|
||||
final Duration predictedDurationFromTimePointToReachFarthestAheadInLeg = getPredictedDurationToReachWindwardPositionOf(tloc, tlocOfFarthestAhead, timePoint, cache);
|
||||
final Duration cDurationFromRaceStartToReachFarthestInLeg = raceDurationAtTimePoint.plus(predictedDurationFromTimePointToReachFarthestAheadInLeg);
|
||||
actualTimeFromRaceStartToReachFarthestAheadInLeg.put(c, cDurationFromRaceStartToReachFarthestInLeg);
|
||||
final Duration correctedTimeWhenReachingFarthestAheadInLeg = getCorrectedTime(c, ()->tloc.getTrackedLeg().getLeg(),
|
||||
()->farthestAheadAlreadyFinishedLeg?getTrackedRace().getApproximatePosition(tlocOfFarthestAhead.getTrackedLeg().getLeg().getTo(), tlocOfFarthestAhead.getFinishTime()):
|
||||
getTrackedRace().getTrack(farthestAheadOrEarliestLegFinisher).getEstimatedPosition(timePoint, /* extrapolate */ true),
|
||||
cDurationFromRaceStartToReachFarthestInLeg, windwardDistanceFarthestTraveledUntilFinishingLeg);
|
||||
correctedTimeToReachFarthestAheadInLeg.put(c, correctedTimeWhenReachingFarthestAheadInLeg);
|
||||
} else {
|
||||
actualTimeFromRaceStartToReachFarthestAheadInLeg.put(c, null);
|
||||
correctedTimeToReachFarthestAheadInLeg.put(c, null);
|
||||
}
|
||||
}
|
||||
// leader in the leg is now the competitor with the least corrected time to reach the competitor farthest ahead in the leg
|
||||
Comparator<Duration> durationComparatorNullsLast = Comparator.nullsLast(Comparator.naturalOrder());
|
||||
final Competitor legLeader = rankingInfo.getLeaderInLegByCalculatedTime(trackedLegOfCompetitor.getTrackedLeg().getLeg());
|
||||
final Competitor legLeader = correctedTimeToReachFarthestAheadInLeg.entrySet().stream()
|
||||
.sorted((e1, e2) -> durationComparatorNullsLast.compare(e1.getValue(), e2.getValue())).findFirst().get().getKey();
|
||||
final Competitor legLeader = rankingInfo.getLeaderInLegByCalculatedTime(trackedLegOfCompetitor.getTrackedLeg().getLeg(), cache);
|
||||
result = getGapToCompetitorInOwnTime(trackedLegOfCompetitor.getCompetitor(), legLeader,
|
||||
actualTimeFromRaceStartToReachFarthestAheadInLeg.get(trackedLegOfCompetitor.getCompetitor()),
|
||||
actualTimeFromRaceStartToReachFarthestAheadInLeg.get(legLeader),
|
||||
rankingInfo.getActualTimeFromRaceStartToReachFarthestAheadInLeg(trackedLegOfCompetitor.getCompetitor(), leg, cache),
|
||||
rankingInfo.getActualTimeFromRaceStartToReachFarthestAheadInLeg(legLeader, leg, cache),
|
||||
windwardDistanceFarthestTraveledUntilFinishingLeg);
|
||||
}
|
||||
return result;
|
||||
|
||||
Reference in new issue
Block a user