80401410884c7673f73e49fc284aadfbcb21c671
java/com.sap.sailing.domain/src/com/sap/sailing/domain/tracking/impl/CourseChangeBasedTrackApproximation.java
| ... | ... | @@ -6,12 +6,12 @@ import java.io.Serializable; |
| 6 | 6 | import java.util.ArrayList; |
| 7 | 7 | import java.util.Collections; |
| 8 | 8 | import java.util.Comparator; |
| 9 | -import java.util.Deque; |
|
| 10 | 9 | import java.util.LinkedList; |
| 11 | 10 | import java.util.List; |
| 12 | 11 | import java.util.ListIterator; |
| 13 | 12 | import java.util.NavigableSet; |
| 14 | 13 | import java.util.TreeSet; |
| 14 | +import java.util.logging.Logger; |
|
| 15 | 15 | |
| 16 | 16 | import com.sap.sailing.domain.base.BoatClass; |
| 17 | 17 | import com.sap.sailing.domain.base.Competitor; |
| ... | ... | @@ -80,6 +80,8 @@ import com.sap.sse.common.scalablevalue.ScalableValueWithDistance; |
| 80 | 80 | */ |
| 81 | 81 | public class CourseChangeBasedTrackApproximation implements Serializable, GPSTrackListener<Competitor, GPSFixMoving> { |
| 82 | 82 | private static final long serialVersionUID = -258129016229111573L; |
| 83 | + private static final Logger logger = Logger.getLogger(CourseChangeBasedTrackApproximation.class.getName()); |
|
| 84 | + |
|
| 83 | 85 | private final GPSFixTrack<Competitor, GPSFixMoving> track; |
| 84 | 86 | private final BoatClass boatClass; |
| 85 | 87 | private final FixWindow fixWindow; |
| ... | ... | @@ -114,15 +116,13 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra |
| 114 | 116 | * |
| 115 | 117 | * See also bug 6209. |
| 116 | 118 | */ |
| 117 | - private final Deque<GPSFixMoving> queueOfNewFixes; |
|
| 119 | + private final LinkedList<GPSFixMoving> queueOfNewFixes; |
|
| 118 | 120 | |
| 119 | 121 | /** |
| 120 | 122 | * We need to remember the speed / bearing as we saw them when we inserted the fixes into the {@link #window} |
| 121 | 123 | * collection. Based on more fixes getting added to the track, things may change. In particular, fixes that may have |
| 122 | 124 | * had a valid speed when inserted may later have their cached speed/bearing invalidated, and computing it again |
| 123 | 125 | * from the track may then yield {@code null}.<p> |
| 124 | - * |
|
| 125 | - * TODO bug6209: the above observation regarding changes when later fixes get added is exactly what is causing the bug6209 issues! |
|
| 126 | 126 | */ |
| 127 | 127 | private final LinkedList<SpeedWithBearing> speedForFixesInWindow; |
| 128 | 128 | |
| ... | ... | @@ -161,29 +161,83 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra |
| 161 | 161 | } |
| 162 | 162 | |
| 163 | 163 | /** |
| 164 | - * Adds a fix to this fix window, sorted by time point. If this produces an interesting candidate within this window, |
|
| 165 | - * the candidate is returned, and the window is reset in such a way that the same candidate will not be returned |
|
| 166 | - * a second time. |
|
| 164 | + * Adds a fix to this fix window, sorted by time point. If this produces an interesting candidate within this |
|
| 165 | + * window, the candidate is returned, and the window is reset in such a way that the same candidate will not be |
|
| 166 | + * returned a second time. |
|
| 167 | 167 | * <p> |
| 168 | 168 | * |
| 169 | 169 | * The window will have established its invariants when this method returns. |
| 170 | + * <p> |
|
| 170 | 171 | * |
| 171 | - * @param next a fix that, in case this window is not empty, is not before the first fix in this window |
|
| 172 | + * This method queues the new fix and only adds it to the window when it is "old enough" to not be influenced |
|
| 173 | + * anymore by newer fixes that may still arrive. The influence is measured by half the |
|
| 174 | + * {@link GPSFixTrack#getMillisecondsOverWhichToAverageSpeed()} interval. This way, approximation results |
|
| 175 | + * are more stable but have a delay with regards to the newest fixes known by the track.<p> |
|
| 176 | + * |
|
| 177 | + * Fixes may arrive out of order. However, the method assumes that most fixes will have to get added to the |
|
| 178 | + * end of the queue. Adding out of order fixes may therefore be a bit slower than simply adding to the end. |
|
| 179 | + * |
|
| 180 | + * @param next |
|
| 181 | + * a fix that, in case this window is not empty, is not before the first fix in this window |
|
| 172 | 182 | * @return a maneuver candidate from the {@link #window} if one became available by adding the {@code next} fix, |
| 173 | 183 | * or {@code null} if no maneuver candidate became available |
| 174 | 184 | */ |
| 175 | 185 | GPSFixMoving add(GPSFixMoving next) { |
| 176 | - final GPSFixMoving result; |
|
| 177 | - queueOfNewFixes.add(next); // FIXME bug6209: the queueOfNewFixes needs to remain ordered by fix TimePoint! |
|
| 186 | + insertIntoQueueSortedByTime(next); |
|
| 178 | 187 | final GPSFixMoving first = queueOfNewFixes.getFirst(); |
| 179 | - if (first.getTimePoint().until(next.getTimePoint()).asMillis() > track.getMillisecondsOverWhichToAverageSpeed()/2) { |
|
| 180 | - result = addOldEnoughFix(queueOfNewFixes.removeFirst()); |
|
| 188 | + final GPSFixMoving result = first.getTimePoint().until(next.getTimePoint()).asMillis() > track.getMillisecondsOverWhichToAverageSpeed()/2 |
|
| 189 | + ? addOldEnoughFix(queueOfNewFixes.removeFirst()) |
|
| 190 | + : null; |
|
| 191 | + return result; |
|
| 192 | + } |
|
| 193 | + |
|
| 194 | + /** |
|
| 195 | + * Inserts {@code fix} into {@link #queueOfNewFixes}, maintaining sorted order by time point. |
|
| 196 | + * If a fix with an equal time point already exists, it is replaced. |
|
| 197 | + */ |
|
| 198 | + private void insertIntoQueueSortedByTime(GPSFixMoving fix) { |
|
| 199 | + if (queueOfNewFixes.isEmpty() || queueOfNewFixes.getLast().getTimePoint().before(fix.getTimePoint())) { |
|
| 200 | + queueOfNewFixes.add(fix); |
|
| 181 | 201 | } else { |
| 182 | - result = null; |
|
| 202 | + final ListIterator<GPSFixMoving> iter = queueOfNewFixes.listIterator(queueOfNewFixes.size()); |
|
| 203 | + boolean added = false; |
|
| 204 | + while (!added && iter.hasPrevious()) { |
|
| 205 | + final GPSFixMoving previous = iter.previous(); |
|
| 206 | + if (previous.getTimePoint().equals(fix.getTimePoint())) { |
|
| 207 | + logger.fine(()->{ |
|
| 208 | + return |
|
| 209 | + "Replacing fix " + previous |
|
| 210 | + + " in queue of new fixes; previous fix was " + previous |
|
| 211 | + + ", new fix is " + fix; |
|
| 212 | + }); |
|
| 213 | + iter.set(fix); // replace existing fix |
|
| 214 | + added = true; |
|
| 215 | + } else if (previous.getTimePoint().before(fix.getTimePoint())) { |
|
| 216 | + iter.next(); // move back to the position after previous |
|
| 217 | + iter.add(fix); |
|
| 218 | + added = true; |
|
| 219 | + } |
|
| 220 | + } |
|
| 221 | + if (!added) { |
|
| 222 | + queueOfNewFixes.addFirst(fix); |
|
| 223 | + } |
|
| 183 | 224 | } |
| 184 | - return result; |
|
| 225 | + assert inIncreasingTimePointOrder(queueOfNewFixes); |
|
| 185 | 226 | } |
| 186 | 227 | |
| 228 | + private boolean inIncreasingTimePointOrder(LinkedList<GPSFixMoving> fixes) { |
|
| 229 | + boolean result = true; |
|
| 230 | + TimePoint previousTimePoint = null; |
|
| 231 | + for (final GPSFixMoving fix : fixes) { |
|
| 232 | + if (previousTimePoint != null && !fix.getTimePoint().after(previousTimePoint)) { |
|
| 233 | + result = false; |
|
| 234 | + break; |
|
| 235 | + } |
|
| 236 | + previousTimePoint = fix.getTimePoint(); |
|
| 237 | + } |
|
| 238 | + return result; |
|
| 239 | + } |
|
| 240 | + |
|
| 187 | 241 | private GPSFixMoving addOldEnoughFix(GPSFixMoving next) { |
| 188 | 242 | assert window.isEmpty() || !next.getTimePoint().before(window.peekFirst().getTimePoint()); |
| 189 | 243 | final GPSFixMoving result; |
| ... | ... | @@ -487,5 +541,4 @@ public class CourseChangeBasedTrackApproximation implements Serializable, GPSTra |
| 487 | 541 | } |
| 488 | 542 | return result; |
| 489 | 543 | } |
| 490 | - |
|
| 491 | 544 | } |