diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractDistance.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractDistance.java index b50418bcfc7..990b6ff2bf6 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractDistance.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/AbstractDistance.java @@ -1,6 +1,7 @@ package com.sap.sailing.domain.common; import com.sap.sailing.domain.common.impl.KilometersPerHourSpeedImpl; +import com.sap.sailing.domain.common.impl.NauticalMileDistance; public abstract class AbstractDistance implements Distance { @@ -65,4 +66,9 @@ public abstract class AbstractDistance implements Distance { public String toString() { return getMeters()+"m"; } + + @Override + public Distance add(Distance d) { + return new NauticalMileDistance(getNauticalMiles()+d.getNauticalMiles()); + } } diff --git a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Distance.java b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Distance.java index 6e7531e4e8c..2eae831e06d 100755 --- a/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Distance.java +++ b/java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/Distance.java @@ -64,6 +64,10 @@ public interface Distance extends Comparable, Serializable { return Speed.NULL; } + @Override + public Distance add(Distance d) { + return d; + } }; double getGeographicalMiles(); @@ -88,4 +92,6 @@ public interface Distance extends Comparable, Serializable { * then the resulting speed will be negative. */ Speed inTime(long milliseconds); + + Distance add(Distance d); } diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DistanceCache.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DistanceCache.java new file mode 100755 index 00000000000..8ef7035ca85 --- /dev/null +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/DistanceCache.java @@ -0,0 +1,112 @@ +package com.sap.sailing.domain.tracking.impl; + +import java.util.Comparator; +import java.util.NavigableSet; + +import com.sap.sailing.domain.common.Distance; +import com.sap.sailing.domain.common.TimePoint; +import com.sap.sailing.domain.common.impl.Util.Pair; +import com.sap.sailing.domain.tracking.GPSFix; +import com.sap.sailing.util.impl.ArrayListNavigableSet; + +/** + * Computing {@link #getDistanceTraveled(TimePoint, TimePoint)} is more expensive the longer the track is and the + * higher the tracking frequency. It is called regularly, usually for the same start time. Keeping to re-evaluate + * all distances between all GPS fixes, even if they remain constant, is a waste of resources, particularly when we + * assume that usually GPS fixes are appended to a track and not usually randomly inserted, even though this can + * happen. + *

+ * + * This cache looks "backwards." It contains pairs whose first component represents a to parameter used + * in {@link #getDistanceTraveled(TimePoint, TimePoint)}. It is ordered by this component. The second component is a + * navigable, ordered set of pairs where the first pair component represents a from parameter used in + * {@link #getDistanceTraveled(TimePoint, TimePoint)} and the second pair component represents the result of + * {@link #getDistanceTraveled(TimePoint, TimePoint)} for this parameter combination. Note that the cache does only + * store distances for from time points actually passed to + * {@link #getDistanceTraveled(TimePoint, TimePoint)} and does not cache all interim combinations collected while + * computing a result. As such, it differs from a typical "dynamic programming" algorithm which would store all + * in-between values too. Assuming that from values are sparse (leg start, race start, ...), there + * doesn't seem to be much use in storing all the other in-between values too. + *

+ * + * For implementation efficiency in combination with using a {@link ArrayListNavigableSet} for the values and in + * order to be able to efficiently extend a cache entry for a single to fix, the navigable sets + * containing the from fixes and distances are ordered such that earlier fixes come later in the set. + * This way, extending the cache entry for a to fix to an earlier from fix only requires + * appending to the set. + *

+ * + * Invalidation: When a new fix is added to this track, all distance cache entries for fixes at or later than + * the new fix's time point are removed from this cache. Additionally, the fix insertion may have an impact on the + * {@link #getEarlierFixesWhoseValidityMayBeAffected(GPSFix) previous fix's} validity (track smoothing) and + * therefore on its selection for distance aggregation. Therefore, if fix addition turned the previous fix invalid, + * the cache entries for the time points at or after the previous fix also need to be removed. + *

+ * + * Cache use: When {@link #getDistanceTraveled(TimePoint, TimePoint)} is called, it first looks for a cache + * entry for the to parameter. If one is found, the earliest entry in the navigable set for the + * navigable set of from and distance values that is at or after the requested from time + * point is determined. If such an entry exists, the distance is remembered and the algorithm is repeated + * recursively, using the from value found in the cache as the new to value, and the + * from value originally passed to {@link #getDistanceTraveled(TimePoint, TimePoint)} as + * from again. If no entry is found in the cache entry for to that is at or after the + * requested from time, the distance is computed by iterating the smoothened fixes. + *

+ * + * If a cache entry for to is not found, the latest cache entry before it is looked up. If one is + * found, the distance between the to time point requested and the to time point found in + * the cache is computed by iterating the smoothened fixes for this interval. If none is found, the distance is + * computed by iterating backwards all the way to from. + *

+ * + * Once the {@link #getDistanceTraveled(TimePoint, TimePoint)} has computed its value, it adds the result to the + * cache. + * + * @author Axel Uhl (D043530) + */ +public class DistanceCache { + private final NavigableSet>>> distanceCache; + + public DistanceCache() { + this.distanceCache = new ArrayListNavigableSet>>>( + new Comparator>>>() { + @Override + public int compare(Pair>> o1, + Pair>> o2) { + return o1.getA().compareTo(o2.getA()); + } + }); + } + + /** + * Looks up the entry for to. If not found, null is returned. If found, the earliest + * pair of from/distance that is at or after from will be returned. If there is no entry that is + * at or after from, null is returned. + */ + public Pair getEarliestFromAndDistanceAtOrAfterFrom(TimePoint from, TimePoint to) { + Pair result = null; + NavigableSet> entryForTo = getEntryForTo(to); + if (entryForTo != null) { + result = entryForTo.floor(new Pair(from, null)); + } + return result; + } + + private NavigableSet> getEntryForTo(TimePoint to) { + NavigableSet> result = null; + Pair>> dummyForTo = createDummy(to); + Pair>> entryForTo = distanceCache.floor(dummyForTo); + if (entryForTo.getA().equals(to)) { + result = entryForTo.getB(); + } + return result; + } + + public void cache(TimePoint from, TimePoint to, Distance distance) { + + } + + private Pair>> createDummy(TimePoint to) { + return new Pair>>(to, null); + } +} diff --git a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java index 0948d8a80bc..17d8c088449 100755 --- a/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java +++ b/java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/GPSFixTrackImpl.java @@ -2,7 +2,6 @@ package com.sap.sailing.domain.tracking.impl; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.ConcurrentModificationException; import java.util.HashSet; import java.util.Iterator; @@ -102,7 +101,7 @@ public class GPSFixTrackImpl extends TrackImpl * Once the {@link #getDistanceTraveled(TimePoint, TimePoint)} has computed its value, it adds the result to the * cache. */ - private final NavigableSet>>> distanceCache; + private final DistanceCache distanceCache; public GPSFixTrackImpl(ItemType trackedItem, long millisecondsOverWhichToAverage) { this(trackedItem, millisecondsOverWhichToAverage, DEFAULT_MAX_SPEED_FOR_SMOOTHING); @@ -114,14 +113,7 @@ public class GPSFixTrackImpl extends TrackImpl this.millisecondsOverWhichToAverage = millisecondsOverWhichToAverage; this.maxSpeedForSmoothing = maxSpeedForSmoothening; this.listeners = new HashSet>(); - this.distanceCache = new ArrayListNavigableSet>>>( - new Comparator>>>() { - @Override - public int compare(Pair>> o1, - Pair>> o2) { - return o1.getA().compareTo(o2.getA()); - } - }); + this.distanceCache = new DistanceCache(); } @Override