diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java index f1de4300373..be7078afd44 100644 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRace.java @@ -63,6 +63,7 @@ import com.sap.sailing.domain.tracking.GPSFixTrack; 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.MarkPositionAtTimePointCache; import com.sap.sailing.domain.tracking.RaceAbortedListener; import com.sap.sailing.domain.tracking.RaceChangeListener; import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider; @@ -612,7 +613,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) { + public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) { return null; } diff --git a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java index 25e7cbdc60d..fefad810f2b 100755 --- a/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java +++ b/java/com.sap.sailing.domain.test/src/com/sap/sailing/domain/test/mock/MockedTrackedRaceWithStartTimeAndRanks.java @@ -42,6 +42,7 @@ import com.sap.sailing.domain.tracking.GPSFixTrack; 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.MarkPositionAtTimePointCache; import com.sap.sailing.domain.tracking.RaceAbortedListener; import com.sap.sailing.domain.tracking.RaceChangeListener; import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider; @@ -218,7 +219,7 @@ public class MockedTrackedRaceWithStartTimeAndRanks implements TrackedRace { } @Override - public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) { + public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) { return null; } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java index b82874e0663..78ed4a0c4f7 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/markpassingcalculation/impl/CandidateFinderImpl.java @@ -39,6 +39,8 @@ 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.GPSFixTrack; +import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache; +import com.sap.sailing.domain.tracking.impl.MarkPositionAtTimePointCacheImpl; import com.sap.sailing.domain.tracking.impl.TimedComparator; import com.sap.sse.common.Duration; import com.sap.sse.common.TimePoint; @@ -611,15 +613,16 @@ public class CandidateFinderImpl implements CandidateFinder { if (Math.abs(dis.getMeters()) < Math.abs(disBefore.getMeters()) && Math.abs(dis.getMeters()) < Math.abs(disAfter.getMeters())) { t = fix.getTimePoint(); + final MarkPositionAtTimePointCache markPositionCache = new MarkPositionAtTimePointCacheImpl(race, t); p = fix.getPosition(); - Double newProbability = getDistanceBasedProbability(w, t, dis); + Double newProbability = getDistanceBasedProbability(w, t, dis, markPositionCache); if (newProbability != null) { // FIXME why not generate the candidate here where we have all information at hand? - final double newOnCorrectSideOfWaypointPenalty = getSidePenalty(w, p, t, portMark); + final double newOnCorrectSideOfWaypointPenalty = getSidePenalty(w, p, t, portMark, markPositionCache); newProbability *= newOnCorrectSideOfWaypointPenalty * PENALTY_FOR_DISTANCE_CANDIDATES; final Double newStartProbabilityBasedOnOtherCompetitors; if (isStartWaypoint(w)) { - newStartProbabilityBasedOnOtherCompetitors = getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(c, t); + newStartProbabilityBasedOnOtherCompetitors = getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(c, t, markPositionCache); if (newStartProbabilityBasedOnOtherCompetitors != null) { newProbability *= newStartProbabilityBasedOnOtherCompetitors; } @@ -670,6 +673,7 @@ public class CandidateFinderImpl implements CandidateFinder { private Map> getDistances(Competitor c, GPSFix fix) { // TODO Possibly for specific waypoints Map> result = distanceCache.get(c).get(fix); + final MarkPositionAtTimePointCache markPositionCache = new MarkPositionAtTimePointCacheImpl(race, fix.getTimePoint()); if (result == null) { // Else calculate distances and put them into the cache result = new LinkedHashMap<>(); @@ -677,7 +681,7 @@ public class CandidateFinderImpl implements CandidateFinder { course.lockForRead(); try { for (Waypoint w : course.getWaypoints()) { - List distances = calculateDistance(fix.getPosition(), w, fix.getTimePoint()); + List distances = calculateDistance(fix.getPosition(), w, fix.getTimePoint(), markPositionCache); result.put(w, distances); } } finally { @@ -818,13 +822,14 @@ public class CandidateFinderImpl implements CandidateFinder { result = new HashMap<>(); Position p = fix.getPosition(); TimePoint t = fix.getTimePoint(); + final MarkPositionAtTimePointCache markPositionCache = new MarkPositionAtTimePointCacheImpl(race, t); Course course = race.getRace().getCourse(); course.lockForRead(); try { for (Waypoint w : course.getWaypoints()) { List distances = new ArrayList<>(); result.put(w, distances); - for (Util.Pair crossingInfo : getCrossingInformation(w, t)) { + for (Util.Pair crossingInfo : getCrossingInformation(w, t, markPositionCache)) { if (crossingInfo.getA() != null && crossingInfo.getB() != null) { distances.add(p.crossTrackError(crossingInfo.getA(), crossingInfo.getB())); } @@ -838,20 +843,6 @@ public class CandidateFinderImpl implements CandidateFinder { return result; } - /** - * Calculates the cross-track error of each fix to the position and crossing bearing of each waypoint. Gates have - * two of these and lines always go from the port mark to the starboard mark. - * - * - * /* private void calculateCrossTrackErrors(Competitor c, Iterable fixes, Iterable - * waypointsToCalculate) { for (GPSFix fix : fixes) { Position fixPos = fix.getPosition(); TimePoint t = - * fix.getTimePoint(); Map> waypointXTE = new HashMap>(); - * crossTrackErrors.get(c).put(fix, waypointXTE); for (Waypoint w : race.getRace().getCourse().getWaypoints()) { - * List xte = new ArrayList<>(); waypointXTE.put(w, xte); for (Util.Pair crossingInfo : - * getCrossingInformation(w, t)) { xte.add(fixPos.crossTrackError(crossingInfo.getA(), - * crossingInfo.getB()).getMeters()); } } } } - */ - /** * {@code xte1} and {@code xte2} are expected to have different signums or both be 0. The method * interpolates between the time points {@code t1} and {@code t2} according to where the XTE is @@ -863,10 +854,11 @@ public class CandidateFinderImpl implements CandidateFinder { final double ratio = (Math.abs(xte1) / (Math.abs(xte1) + Math.abs(xte2))); final TimePoint t = t1.plus((long) (differenceInMillis * ratio)); final Position p = race.getTrack(c).getEstimatedPosition(t, false); - final List distances = calculateDistance(p, w, t); + final List distances = calculateDistance(p, w, t, new MarkPositionAtTimePointCacheImpl(race, t)); final Distance d = portMark ? distances.get(0) : distances.get(1); - final double sidePenalty = getSidePenalty(w, p, t, portMark); - double probability = getDistanceBasedProbability(w, t, d) * sidePenalty; + final MarkPositionAtTimePointCache markPositionCache = new MarkPositionAtTimePointCacheImpl(race, t); + final double sidePenalty = getSidePenalty(w, p, t, portMark, markPositionCache); + double probability = getDistanceBasedProbability(w, t, d, markPositionCache) * sidePenalty; final Double passesInTheRightDirectionProbability = passesInTheRightDirection(w, xte1, xte2, portMark); // null would mean "unknown"; no penalty for those cases probability = passesInTheRightDirectionProbability == null ? probability : probability * passesInTheRightDirectionProbability; @@ -876,7 +868,7 @@ public class CandidateFinderImpl implements CandidateFinder { // at time point t the other competitors are largely not even close to the start waypoint; // this will make start candidates much more likely if many other competitors are also very close to the // start, and it will help ruling out those candidates where someone is practicing a start just for themselves - startProbabilityBasedOnOtherCompetitors = getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(c, t); + startProbabilityBasedOnOtherCompetitors = getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(c, t, markPositionCache); if (startProbabilityBasedOnOtherCompetitors != null) { probability *= startProbabilityBasedOnOtherCompetitors; } @@ -939,9 +931,9 @@ public class CandidateFinderImpl implements CandidateFinder { * A low variance with a positive average (meaning on course side) indicates that everyone else may have started around the * same time, making this a probable late starter's candidate. Therefore, low variance with positive average shall increase * a probability that was low because of a large weighted average distance. - * * @param t * the time point at which to consider the other competitors behavior relative to the start waypoint + * * @return {@code null} if the {@link #race} has a gate start or the start time is known; a probability between 0..1 * (inclusive) where 0 means that based on all but {@code c}'s relation to the start line it seems * completely unlikely that a candidate for {@code c} at time {@code t} could have been a start candidate; 1 @@ -949,11 +941,13 @@ public class CandidateFinderImpl implements CandidateFinder { * a fact that this must have been the start. * */ - private Double getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(Competitor c, TimePoint t) { + private Double getProbabilityForStartCandidateBasedOnOtherCompetitorsBehavior(Competitor c, TimePoint t, MarkPositionAtTimePointCache markPositionCache) { + assert t.equals(markPositionCache.getTimePoint()); + assert race == markPositionCache.getTrackedRace(); final Double result; if (race.getStartOfRace(/* inferred */ false) == null && race.isGateStart() != Boolean.TRUE) { final Waypoint start = race.getRace().getCourse().getFirstWaypoint(); - final Iterable> crossingInformationForStart = getCrossingInformation(start, t); + final Iterable> crossingInformationForStart = getCrossingInformation(start, t, markPositionCache); if (start == null) { result = 1.0; } else { @@ -967,7 +961,7 @@ public class CandidateFinderImpl implements CandidateFinder { final Position estimatedPositionAtT = track.getEstimatedPosition(t, /* extrapolate */ true); // consider that a position may not be available for that other competitor, although a track exists if (estimatedPositionAtT != null) { - final Distance otherCompetitorsDistanceToStartAtT = getMinDistanceOrNull(calculateDistance(estimatedPositionAtT, start, t)); + final Distance otherCompetitorsDistanceToStartAtT = getMinDistanceOrNull(calculateDistance(estimatedPositionAtT, start, t, markPositionCache)); if (otherCompetitorsDistanceToStartAtT != null) { final Distance crossTrackError; if (startIsLine) { @@ -1102,14 +1096,16 @@ public class CandidateFinderImpl implements CandidateFinder { * to be negative. If the passing instructions are {@link PassingInstruction#Line}, it checks whether the boat * passed between the two marks. */ - private double getSidePenalty(Waypoint w, Position p, TimePoint t, boolean portMark) { + private double getSidePenalty(Waypoint w, Position p, TimePoint t, boolean portMark, MarkPositionAtTimePointCache markPositionCache) { + assert t.equals(markPositionCache.getTimePoint()); + assert race == markPositionCache.getTrackedRace(); boolean isOnRightSide = true; Distance onWrongSide = new MeterDistance(0); PassingInstruction instruction = getPassingInstructions(w); if (instruction == PassingInstruction.Line) { List pos = new ArrayList<>(); for (Mark m : w.getMarks()) { - Position po = race.getOrCreateTrack(m).getEstimatedPosition(t, false); + Position po = markPositionCache.getEstimatedPosition(m); if (po == null) { isOnRightSide = true; break; @@ -1137,11 +1133,11 @@ public class CandidateFinderImpl implements CandidateFinder { || instruction == PassingInstruction.FixedBearing || instruction == PassingInstruction.Offset) { m = w.getMarks().iterator().next(); } else if (instruction == PassingInstruction.Gate) { - Util.Pair pair = getPortAndStarboardMarks(t, w); + Util.Pair pair = getPortAndStarboardMarks(t, w, markPositionCache); m = portMark ? pair.getA() : pair.getB(); } if (m != null) { - Util.Pair crossingInfo = Util.get(getCrossingInformation(w, t), portMark ? 0 : 1); + Util.Pair crossingInfo = Util.get(getCrossingInformation(w, t, markPositionCache), portMark ? 0 : 1); isOnRightSide = p.crossTrackError(crossingInfo.getA(), crossingInfo.getB().add(new DegreeBearingImpl(90))).getMeters() < 0; if (isOnRightSide == false) { onWrongSide = p.getDistance(crossingInfo.getA()); @@ -1195,9 +1191,11 @@ public class CandidateFinderImpl implements CandidateFinder { /** * @return a probability based on the distance to w and the average leg lengths before and after. */ - private Double getDistanceBasedProbability(Waypoint w, TimePoint t, Distance distance) { + private Double getDistanceBasedProbability(Waypoint w, TimePoint t, Distance distance, MarkPositionAtTimePointCache markPositionCache) { + assert t.equals(markPositionCache.getTimePoint()); + assert race == markPositionCache.getTrackedRace(); final Double result; - Distance legLength = getAverageLengthOfAdjacentLegs(t, w); + Distance legLength = getAverageLengthOfAdjacentLegs(t, w, markPositionCache); if (legLength != null) { result = 1 / (STRICTNESS_OF_DISTANCE_BASED_PROBABILITY/* Raising this will make it stricter */ // reduce distance by 2x the typical HDOP, accounting for the possibility that some distance from the mark @@ -1212,18 +1210,20 @@ public class CandidateFinderImpl implements CandidateFinder { /** * @return an average of the estimated legs before and after w. */ - private Distance getAverageLengthOfAdjacentLegs(TimePoint t, Waypoint w) { + private Distance getAverageLengthOfAdjacentLegs(TimePoint t, Waypoint w, MarkPositionAtTimePointCache markPositionCache) { + assert t.equals(markPositionCache.getTimePoint()); + assert race == markPositionCache.getTrackedRace(); final Distance result; final Course course = race.getRace().getCourse(); if (course.getNumberOfWaypoints() < 2) { result = null; } else if (w == course.getFirstWaypoint()) { - result = race.getTrackedLegStartingAt(w).getGreatCircleDistance(t); + result = race.getTrackedLegStartingAt(w).getGreatCircleDistance(t, markPositionCache); } else if (w == course.getLastWaypoint()) { - result = race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t); + result = race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t, markPositionCache); } else { - Distance before = race.getTrackedLegStartingAt(w).getGreatCircleDistance(t); - Distance after = race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t); + Distance before = race.getTrackedLegStartingAt(w).getGreatCircleDistance(t, markPositionCache); + Distance after = race.getTrackedLegFinishingAt(w).getGreatCircleDistance(t, markPositionCache); if (after != null && before != null) { result = new MeterDistance(before.add(after).getMeters() / 2); } else { @@ -1235,8 +1235,13 @@ public class CandidateFinderImpl implements CandidateFinder { /** * Calculates the distance from p to each mark of w at the timepoint t. + * + * @param markPositionCache + * a mark position cache for time point {@code t} for this finder's {@link #race} */ - private List calculateDistance(Position p, Waypoint w, TimePoint t) { + private List calculateDistance(Position p, Waypoint w, TimePoint t, MarkPositionAtTimePointCache markPositionCache) { + assert t.equals(markPositionCache.getTimePoint()); + assert race == markPositionCache.getTrackedRace(); final List distances = new ArrayList<>(); PassingInstruction instruction = getPassingInstructions(w); boolean singleMark = false; @@ -1248,25 +1253,25 @@ public class CandidateFinderImpl implements CandidateFinder { singleMark = true; break; case Gate: - Util.Pair posGate = getPortAndStarboardMarks(t, w); + Util.Pair posGate = getPortAndStarboardMarks(t, w, markPositionCache); if (posGate.getA() != null) { - Position portGatePosition = race.getOrCreateTrack(posGate.getA()).getEstimatedPosition(t, false); + Position portGatePosition = markPositionCache.getEstimatedPosition(posGate.getA()); 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); + Position starboardGatePosition = markPositionCache.getEstimatedPosition(posGate.getB()); distances.add(starboardGatePosition != null ? p.getDistance(starboardGatePosition) : null); } else { distances.add(null); } break; case Line: - Util.Pair posLine = getPortAndStarboardMarks(t, w); + Util.Pair posLine = getPortAndStarboardMarks(t, w, markPositionCache); 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); + Position portLinePosition = markPositionCache.getEstimatedPosition(posLine.getA()); + Position starboardLinePosition = markPositionCache.getEstimatedPosition(posLine.getB()); distances.add((portLinePosition != null && starboardLinePosition != null) ? p.getDistanceToLine( portLinePosition, starboardLinePosition).abs() : null); } @@ -1282,8 +1287,7 @@ public class CandidateFinderImpl implements CandidateFinder { break; } if (singleMark) { - Position markPosition = race.getOrCreateTrack(w.getMarks().iterator().next()) - .getEstimatedPosition(t, false); + Position markPosition = markPositionCache.getEstimatedPosition(w.getMarks().iterator().next()); distances.add(markPosition != null ? p.getDistance(markPosition) : null); } return distances; @@ -1304,44 +1308,46 @@ public class CandidateFinderImpl implements CandidateFinder { * @return all possible ways to pass a waypoint, described as a position and a bearing. The line out of those two * must be crossed. */ - private Iterable> getCrossingInformation(Waypoint w, TimePoint t) { + private Iterable> getCrossingInformation(Waypoint w, TimePoint t, MarkPositionAtTimePointCache markPositionCache) { + assert t.equals(markPositionCache.getTimePoint()); + assert race == markPositionCache.getTrackedRace(); List> result = new ArrayList<>(); PassingInstruction instruction = getPassingInstructions(w); if (instruction == PassingInstruction.Line) { - Util.Pair marks = getPortAndStarboardMarks(t, w); + Util.Pair marks = getPortAndStarboardMarks(t, w, markPositionCache); 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); + portPosition = markPositionCache.getEstimatedPosition(portMark); + Position starboardPosition = markPositionCache.getEstimatedPosition(starBoardMark); if (portPosition != null && starboardPosition != null) { b = portPosition.getBearingGreatCircle(starboardPosition); result.add(new Util.Pair(portPosition, b)); } } } else if (instruction == PassingInstruction.Gate) { - Position before = race.getApproximatePosition(race.getTrackedLegFinishingAt(w).getLeg().getFrom(), t); - Position after = race.getApproximatePosition(race.getTrackedLegStartingAt(w).getLeg().getTo(), t); - Util.Pair pos = getPortAndStarboardMarks(t, w); + Position before = markPositionCache.getApproximatePosition(race.getTrackedLegFinishingAt(w).getLeg().getFrom()); + Position after = markPositionCache.getApproximatePosition(race.getTrackedLegStartingAt(w).getLeg().getTo()); + Util.Pair pos = getPortAndStarboardMarks(t, w, markPositionCache); Mark portMark = pos.getA(); if (portMark != null) { - Position portPosition = race.getOrCreateTrack(portMark).getEstimatedPosition(t, false); + Position portPosition = markPositionCache.getEstimatedPosition(portMark); Bearing crossingPort = before.getBearingGreatCircle(portPosition).middle( after.getBearingGreatCircle(portPosition)); result.add(new Util.Pair(portPosition, crossingPort)); } Mark starboardMark = pos.getB(); if (starboardMark != null) { - Position starboardPosition = race.getOrCreateTrack(starboardMark).getEstimatedPosition(t, false); + Position starboardPosition = markPositionCache.getEstimatedPosition(starboardMark); Bearing crossingStarboard = before.getBearingGreatCircle(starboardPosition).middle( after.getBearingGreatCircle(starboardPosition)); result.add(new Util.Pair(starboardPosition, crossingStarboard)); } } else { // single mark Bearing b = null; - Position p = race.getOrCreateTrack(w.getMarks().iterator().next()).getEstimatedPosition(t, false); + Position p = markPositionCache.getEstimatedPosition(w.getMarks().iterator().next()); if (instruction == PassingInstruction.FixedBearing) { b = w.getFixedBearing(); } else { @@ -1351,23 +1357,23 @@ public class CandidateFinderImpl implements CandidateFinder { // orthogonally to the adjacent leg: if (w == race.getRace().getCourse().getFirstWaypoint()) { if (instruction == PassingInstruction.None || instruction == PassingInstruction.Single_Unknown) { - b = race.getTrackedLegStartingAt(w).getLegBearing(t).add(new DegreeBearingImpl(90)); + b = markPositionCache.getLegBearing(race.getTrackedLegStartingAt(w)).add(new DegreeBearingImpl(90)); result.add(new Pair<>(p, b)); - b = race.getTrackedLegStartingAt(w).getLegBearing(t).add(new DegreeBearingImpl(270)); + b = markPositionCache.getLegBearing(race.getTrackedLegStartingAt(w)).add(new DegreeBearingImpl(270)); } else { - b = race.getTrackedLegStartingAt(w).getLegBearing(t).add(new DegreeBearingImpl(instruction == PassingInstruction.Port ? 90 : 270)); + b = markPositionCache.getLegBearing(race.getTrackedLegStartingAt(w)).add(new DegreeBearingImpl(instruction == PassingInstruction.Port ? 90 : 270)); } } else if (w == race.getRace().getCourse().getLastWaypoint()) { if (instruction == PassingInstruction.None || instruction == PassingInstruction.Single_Unknown) { - b = race.getTrackedLegFinishingAt(w).getLegBearing(t).add(new DegreeBearingImpl(90)); + b = markPositionCache.getLegBearing(race.getTrackedLegFinishingAt(w)).add(new DegreeBearingImpl(90)); result.add(new Pair<>(p, b)); - b = race.getTrackedLegFinishingAt(w).getLegBearing(t).add(new DegreeBearingImpl(270)); + b = markPositionCache.getLegBearing(race.getTrackedLegFinishingAt(w)).add(new DegreeBearingImpl(270)); } else { - b = race.getTrackedLegFinishingAt(w).getLegBearing(t).add(new DegreeBearingImpl(instruction == PassingInstruction.Port ? 90 : 270)); + b = markPositionCache.getLegBearing(race.getTrackedLegFinishingAt(w)).add(new DegreeBearingImpl(instruction == PassingInstruction.Port ? 90 : 270)); } } else { - Bearing before = race.getTrackedLegFinishingAt(w).getLegBearing(t); - Bearing after = race.getTrackedLegStartingAt(w).getLegBearing(t); + Bearing before = markPositionCache.getLegBearing(race.getTrackedLegFinishingAt(w)); + Bearing after = markPositionCache.getLegBearing(race.getTrackedLegStartingAt(w)); if (before != null && after != null) { b = before.middle(after.reverse()); } @@ -1379,14 +1385,17 @@ public class CandidateFinderImpl implements CandidateFinder { } /** + * @param markPositionCache + * a mark position cache for this finder's {@link #race} for time point {@code t} * @return the marks of a waypoint with two marks in the order port, starboard (when approaching the waypoint from * the direction of the waypoint beforehand. */ - private Util.Pair getPortAndStarboardMarks(TimePoint t, Waypoint w) { + private Util.Pair getPortAndStarboardMarks(TimePoint t, Waypoint w, MarkPositionAtTimePointCache markPositionCache) { + assert t.equals(markPositionCache.getTimePoint()); + assert race == markPositionCache.getTrackedRace(); List markPositions = new ArrayList(); for (Mark mark : w.getMarks()) { - final Position estimatedMarkPosition = race.getOrCreateTrack(mark).getEstimatedPosition(t, /* extrapolate */ - false); + final Position estimatedMarkPosition = markPositionCache.getEstimatedPosition(mark); if (estimatedMarkPosition == null) { return new Util.Pair(null, null); } @@ -1401,8 +1410,8 @@ public class CandidateFinderImpl implements CandidateFinder { return new Util.Pair(null, null); } final boolean isStartLine = indexOfWaypoint == 0; - final Bearing legDeterminingDirectionBearing = race.getTrackedLeg( - legs.get(isStartLine ? 0 : indexOfWaypoint - 1)).getLegBearing(t); + final Bearing legDeterminingDirectionBearing = markPositionCache.getLegBearing(race.getTrackedLeg( + legs.get(isStartLine ? 0 : indexOfWaypoint - 1))); if (legDeterminingDirectionBearing == null) { return new Util.Pair(null, null); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/MarkPositionAtTimePointCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/MarkPositionAtTimePointCache.java new file mode 100644 index 00000000000..cd00e07b30c --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/MarkPositionAtTimePointCache.java @@ -0,0 +1,58 @@ +package com.sap.sailing.domain.tracking; + +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.Position; +import com.sap.sse.common.TimePoint; + +/** + * Some calculations that span several method calls require access to the same {@link Mark} positions for the same + * {@link TimePoint} within the scope of the same {@link TrackedRace} repeatedly. Usually, for such a compound + * calculation it doesn't matter whether a new fix has arrived in between, making it valid to cache the mark + * positions---once calculated---for the remainder of the compound operation. Based on these positions, approximate + * waypoint positions and leg bearings can also be calculated and cached. + *

+ * + * Implementations of this interface may cache the results of computing a mark's position for the duration of this + * object. + * + * @author Axel Uhl (D043530) + * + */ +public interface MarkPositionAtTimePointCache { + /** + * Gets the non-extrapolated mark position in this cache's tracked race at the time point + * specified. + * + * @see TrackedRace#getOrCreateTrack(Mark) + * @see GPSFixTrack#getEstimatedPosition(TimePoint, boolean) + */ + Position getEstimatedPosition(Mark mark); + + /** + * Gets the approximate position of a waypoint in the scope of this cache's tracked race at + * the time point specified. + * + * @see TrackedRace#getApproximatePosition(Waypoint, TimePoint) + */ + Position getApproximatePosition(Waypoint waypoint); + + /** + * Gets the bearing of the tracked leg at the time point specified, optionally based on mark position + * information already stored in this cache. + * + * @see TrackedLeg#getLegBearing(TimePoint) + */ + Bearing getLegBearing(TrackedLeg trackedLeg); + + /** + * @return the time point for which this cache holds values + */ + TimePoint getTimePoint(); + + /** + * @return the tracked race for which this cache holds values + */ + TrackedRace getTrackedRace(); +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLeg.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLeg.java index a5ece6b1bfa..a68449ac7da 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLeg.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedLeg.java @@ -56,6 +56,16 @@ public interface TrackedLeg extends Serializable { Bearing getLegBearing(TimePoint at); + /** + * Same as {@link #getLegBearing(TimePoint)}, but giving the caller the possibility to provide a cache for + * mark positions and related position and bearing data + * + * @param markPositionCache + * a cache for this tracked leg's {@link MarkPositionAtTimePointCache#getTrackedRace() race} and the + * {@link MarkPositionAtTimePointCache#getTimePoint() timePoint} passed + */ + Bearing getLegBearing(TimePoint at, MarkPositionAtTimePointCache markPositionCache); + /** * Returns the positive (absolute) distance of p to this leg's course middle line at timePoint, * based on the position of the waypoints delimiting this leg at that time. @@ -80,6 +90,16 @@ public interface TrackedLeg extends Serializable { */ Distance getGreatCircleDistance(TimePoint timePoint); + /** + * Same as {@link #getGreatCircleDistance(TimePoint)}, but giving the caller the possibility to provide a cache for + * mark positions and related position and bearing data + * + * @param markPositionCache + * a cache for this tracked leg's {@link MarkPositionAtTimePointCache#getTrackedRace() race} and the + * {@link MarkPositionAtTimePointCache#getTimePoint() timePoint} passed + */ + Distance getGreatCircleDistance(TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache); + /** * If the current {@link #getLeg() leg} is +/- {@link TrackedLegImpl#UPWIND_DOWNWIND_TOLERANCE_IN_DEG} degrees * collinear with the wind's bearing, pos1 is projected onto the line crossing pos2 in the diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java index 94544b60334..b9f36f9e1aa 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/TrackedRace.java @@ -49,6 +49,7 @@ import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException; import com.sap.sailing.domain.polars.PolarDataService; import com.sap.sailing.domain.ranking.RankingMetric; import com.sap.sailing.domain.ranking.RankingMetric.RankingInfo; +import com.sap.sailing.domain.tracking.impl.NonCachingMarkPositionAtTimePointCache; import com.sap.sailing.domain.tracking.impl.TrackedRaceImpl; import com.sap.sse.common.Duration; import com.sap.sse.common.IsManagedByCache; @@ -316,8 +317,22 @@ public interface TrackedRace extends Serializable, IsManagedByCachewaypoint only has one {@link #getMarks() mark}, its position at time timePoint * is returned. Otherwise, the center of gravity between the mark positions is computed and returned. */ - Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint); + default Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) { + return getApproximatePosition(waypoint, timePoint, new NonCachingMarkPositionAtTimePointCache(this, timePoint)); + } + /** + * Same as {@link #getApproximatePosition(Waypoint, TimePoint)}, but giving the caller the possibility to pass a + * cache of mark positions and related information that can help speed up compound operations requiring frequent + * access to the same marks in the same race for the same time point. + * + * @param markPositionCache + * a cache for this {@link MarkPositionAtTimePointCache#getTrackedRace() race} and the + * {@link MarkPositionAtTimePointCache#getTimePoint() timePoint} passed + */ + Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, + MarkPositionAtTimePointCache markPositionCache); + /** * Checks whether the {@link Wind#getTimePoint()} is in range of start and end {@link TimePoint}s plus extra time * for wind recording. If, based on a {@link RaceExecutionOrderProvider}, there is no previous race that takes the @@ -946,4 +961,5 @@ public interface TrackedRace extends Serializable, IsManagedByCache getAttachedRaceLogs(); + } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MarkPositionAtTimePointCacheImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MarkPositionAtTimePointCacheImpl.java new file mode 100644 index 00000000000..a3187c5d202 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/MarkPositionAtTimePointCacheImpl.java @@ -0,0 +1,62 @@ +package com.sap.sailing.domain.tracking.impl; + +import java.util.HashMap; +import java.util.Map; + +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.Position; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.TimePoint; + +public class MarkPositionAtTimePointCacheImpl extends NonCachingMarkPositionAtTimePointCache { + private final Map estimatedMarkPositions; + private final Map approximateWaypointPositions; + private final Map legBearings; + + public MarkPositionAtTimePointCacheImpl(TrackedRace trackedRace, TimePoint timePoint) { + super(trackedRace, timePoint); + estimatedMarkPositions = new HashMap<>(); + approximateWaypointPositions = new HashMap<>(); + legBearings = new HashMap<>(); + } + + @Override + public Position getEstimatedPosition(Mark mark) { + final Position result; + if (estimatedMarkPositions.containsKey(mark)) { + result = estimatedMarkPositions.get(mark); + } else { + result = super.getEstimatedPosition(mark); + estimatedMarkPositions.put(mark, result); + } + return result; + } + + @Override + public Position getApproximatePosition(Waypoint waypoint) { + final Position result; + if (approximateWaypointPositions.containsKey(waypoint)) { + result = approximateWaypointPositions.get(waypoint); + } else { + result = super.getApproximatePosition(waypoint); + approximateWaypointPositions.put(waypoint, result); + } + return result; + } + + @Override + public Bearing getLegBearing(TrackedLeg trackedLeg) { + final Bearing result; + if (legBearings.containsKey(trackedLeg)) { + result = legBearings.get(trackedLeg); + } else { + result = super.getLegBearing(trackedLeg); + legBearings.put(trackedLeg, result); + } + return result; + } + +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/NonCachingMarkPositionAtTimePointCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/NonCachingMarkPositionAtTimePointCache.java new file mode 100644 index 00000000000..600a2beb411 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/NonCachingMarkPositionAtTimePointCache.java @@ -0,0 +1,46 @@ +package com.sap.sailing.domain.tracking.impl; + +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.Position; +import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache; +import com.sap.sailing.domain.tracking.TrackedLeg; +import com.sap.sailing.domain.tracking.TrackedRace; +import com.sap.sse.common.TimePoint; + +public class NonCachingMarkPositionAtTimePointCache implements MarkPositionAtTimePointCache { + private final TrackedRace trackedRace; + private final TimePoint timePoint; + + public NonCachingMarkPositionAtTimePointCache(TrackedRace trackedRace, TimePoint timePoint) { + super(); + this.trackedRace = trackedRace; + this.timePoint = timePoint; + } + + @Override + public Position getEstimatedPosition(Mark mark) { + return trackedRace.getOrCreateTrack(mark).getEstimatedPosition(timePoint, /* extrapolate */ false); + } + + @Override + public Position getApproximatePosition(Waypoint waypoint) { + return trackedRace.getApproximatePosition(waypoint, timePoint, this); + } + + @Override + public Bearing getLegBearing(TrackedLeg trackedLeg) { + return trackedLeg.getLegBearing(timePoint, this); + } + + @Override + public TrackedRace getTrackedRace() { + return trackedRace; + } + + @Override + public TimePoint getTimePoint() { + return timePoint; + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java index d5902aeaac8..4993f55b9d7 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedLegImpl.java @@ -39,6 +39,7 @@ import com.sap.sailing.domain.leaderboard.caching.LeaderboardDTOCalculationReuse import com.sap.sailing.domain.polars.NotEnoughDataHasBeenAddedException; import com.sap.sailing.domain.polars.PolarDataService; import com.sap.sailing.domain.tracking.MarkPassing; +import com.sap.sailing.domain.tracking.MarkPositionAtTimePointCache; import com.sap.sailing.domain.tracking.TrackedLeg; import com.sap.sailing.domain.tracking.TrackedLegOfCompetitor; import com.sap.sailing.domain.tracking.TrackedRace; @@ -204,8 +205,15 @@ public class TrackedLegImpl implements TrackedLeg { @Override public Bearing getLegBearing(TimePoint at) { - Position startMarkPos = getTrackedRace().getApproximatePosition(getLeg().getFrom(), at); - Position endMarkPos = getTrackedRace().getApproximatePosition(getLeg().getTo(), at); + return getLegBearing(at, new MarkPositionAtTimePointCacheImpl(getTrackedRace(), at)); + } + + @Override + public Bearing getLegBearing(TimePoint at, MarkPositionAtTimePointCache markPositionCache) { + assert markPositionCache.getTimePoint().equals(at); + assert markPositionCache.getTrackedRace() == getTrackedRace(); + Position startMarkPos = markPositionCache.getApproximatePosition(getLeg().getFrom()); + Position endMarkPos = markPositionCache.getApproximatePosition(getLeg().getTo()); Bearing legBearing = (startMarkPos != null && endMarkPos != null) ? startMarkPos.getBearingGreatCircle(endMarkPos) : null; return legBearing; } @@ -337,10 +345,12 @@ public class TrackedLegImpl implements TrackedLeg { } @Override - public Distance getGreatCircleDistance(TimePoint timePoint) { + public Distance getGreatCircleDistance(TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) { + assert markPositionCache.getTimePoint().equals(timePoint); + assert markPositionCache.getTrackedRace() == getTrackedRace(); final Distance result; - final Position approximatePositionOfFrom = getTrackedRace().getApproximatePosition(getLeg().getFrom(), timePoint); - final Position approximatePositionOfTo = getTrackedRace().getApproximatePosition(getLeg().getTo(), timePoint); + final Position approximatePositionOfFrom = markPositionCache.getApproximatePosition(getLeg().getFrom()); + final Position approximatePositionOfTo = markPositionCache.getApproximatePosition(getLeg().getTo()); if (approximatePositionOfFrom != null && approximatePositionOfTo != null) { result = approximatePositionOfFrom.getDistance(approximatePositionOfTo); } else { @@ -349,6 +359,11 @@ public class TrackedLegImpl implements TrackedLeg { return result; } + @Override + public Distance getGreatCircleDistance(TimePoint timePoint) { + return getGreatCircleDistance(timePoint, new NonCachingMarkPositionAtTimePointCache(getTrackedRace(), timePoint)); + } + @Override public Distance getAbsoluteWindwardDistance(Position pos1, Position pos2, TimePoint at, WindPositionMode windPositionMode) { return getAbsoluteWindwardDistance(pos1, pos2, at, windPositionMode, new LeaderboardDTOCalculationReuseCache(at)); diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java index 9973e347fb1..7fc6c791f28 100644 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/TrackedRaceImpl.java @@ -127,6 +127,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.MarkPositionAtTimePointCache; import com.sap.sailing.domain.tracking.RaceChangeListener; import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider; import com.sap.sailing.domain.tracking.RaceListener; @@ -1671,10 +1672,12 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl } @Override - public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) { + public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) { + assert timePoint.equals(markPositionCache.getTimePoint()); + assert this == markPositionCache.getTrackedRace(); Position result = null; for (Mark mark : waypoint.getMarks()) { - Position nextPos = getOrCreateTrack(mark).getEstimatedPosition(timePoint, /* extrapolate */false); + Position nextPos = markPositionCache.getEstimatedPosition(mark); if (result == null) { result = nextPos; } else if (nextPos != null) { diff --git a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java index f3eb526e5f2..b58478a3a82 100644 --- a/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java +++ b/java/com.sap.sailing.gwt.ui.test/src/com/sap/sailing/gwt/ui/test/MockedTrackedRace.java @@ -50,6 +50,7 @@ import com.sap.sailing.domain.tracking.GPSFixTrack; 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.MarkPositionAtTimePointCache; import com.sap.sailing.domain.tracking.RaceAbortedListener; import com.sap.sailing.domain.tracking.RaceChangeListener; import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider; @@ -352,7 +353,7 @@ public class MockedTrackedRace implements DynamicTrackedRace { } @Override - public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) { + public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) { return null; } diff --git a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java index 72616d0e514..c5be8205189 100755 --- a/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java +++ b/java/com.sap.sailing.server/src/com/sap/sailing/server/masterdata/DummyTrackedRace.java @@ -41,6 +41,7 @@ import com.sap.sailing.domain.tracking.GPSFixTrack; 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.MarkPositionAtTimePointCache; import com.sap.sailing.domain.tracking.RaceAbortedListener; import com.sap.sailing.domain.tracking.RaceChangeListener; import com.sap.sailing.domain.tracking.RaceExecutionOrderProvider; @@ -213,7 +214,7 @@ public class DummyTrackedRace extends TrackedRaceWithWindEssentials { } @Override - public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint) { + public Position getApproximatePosition(Waypoint waypoint, TimePoint timePoint, MarkPositionAtTimePointCache markPositionCache) { return null; }