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
}