1 package org.opentrafficsim.road.network.speed;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.HashMap;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.SortedSet;
9 import java.util.TreeSet;
10
11 import org.djunits.unit.LengthUnit;
12 import org.djunits.value.vdouble.scalar.Acceleration;
13 import org.djunits.value.vdouble.scalar.Duration;
14 import org.djunits.value.vdouble.scalar.Length;
15 import org.djunits.value.vdouble.scalar.Speed;
16 import org.djutils.exceptions.Throw;
17
18
19
20
21
22
23
24
25
26
27
28 public class SpeedLimitProspect implements Serializable
29 {
30
31
32 private static final long serialVersionUID = 20160501L;
33
34
35 private final SortedSet<SpeedLimitEntry<?>> prospect = new TreeSet<>();
36
37
38 private final Map<Object, SpeedLimitEntry<?>> addSources = new HashMap<>();
39
40
41 private final Map<Object, SpeedLimitEntry<?>> removeSources = new HashMap<>();
42
43
44 private Length odometer;
45
46
47
48
49
50 public SpeedLimitProspect(final Length odometer)
51 {
52 this.odometer = odometer;
53 }
54
55
56
57
58
59 public void update(final Length newOdometer)
60 {
61 Length dx = newOdometer.minus(this.odometer);
62 for (SpeedLimitEntry<?> entry : this.prospect)
63 {
64 entry.move(dx);
65 }
66 }
67
68
69
70
71
72
73 public final boolean containsAddSource(final Object source)
74 {
75 return this.addSources.containsKey(source);
76 }
77
78
79
80
81
82
83 public final boolean containsRemoveSource(final Object source)
84 {
85 return this.removeSources.containsKey(source);
86 }
87
88
89
90
91
92 public final Length getOdometer()
93 {
94 return this.odometer;
95 }
96
97
98
99
100
101
102
103
104
105
106
107
108 public final <T> void addSpeedInfo(final Length distance, final SpeedLimitType<T> speedLimitType, final T speedInfo,
109 final Object source)
110 {
111 Throw.whenNull(distance, "Distance may not be null.");
112 Throw.whenNull(speedLimitType, "Speed limit type may not be null.");
113 Throw.whenNull(speedInfo, "Speed info may not be null.");
114 checkAndAdd(new SpeedLimitEntry<>(distance, speedLimitType, speedInfo), source, false);
115 }
116
117
118
119
120
121
122
123
124
125
126
127 @SuppressWarnings({ "unchecked", "rawtypes" })
128 public final void removeSpeedInfo(final Length distance, final SpeedLimitType<?> speedLimitType, final Object source)
129 {
130 Throw.whenNull(distance, "Distance may not be null.");
131 Throw.when(distance.si < 0, IllegalArgumentException.class,
132 "Removing speed info in the past is not allowed. " + "Only add still active speed info.");
133 Throw.whenNull(speedLimitType, "Speed limit type may not be null.");
134 Throw.when(speedLimitType.equals(SpeedLimitTypes.MAX_VEHICLE_SPEED), IllegalArgumentException.class,
135 "May not remove the maximum vehicle speed.");
136
137 checkAndAdd(new SpeedLimitEntry(distance, speedLimitType, null), source, true);
138 }
139
140
141
142
143
144
145
146
147 private void checkAndAdd(final SpeedLimitEntry<?> speedLimitEntry, final Object source, final boolean remove)
148 {
149 for (SpeedLimitEntry<?> s : this.prospect)
150 {
151 if (s.getSpeedLimitType().equals(speedLimitEntry.getSpeedLimitType()))
152 {
153
154
155
156
157 Throw.when(s.getDistance().equals(speedLimitEntry.getDistance()), IllegalStateException.class,
158 "Info " + "of speed limit type '%s' is set twice at the same location (%s). This is undefined. "
159 + "Either remove speed info, or overwrite with new speed info.",
160 s.getSpeedLimitType(), s.getDistance());
161 }
162 }
163 if (remove)
164 {
165 SpeedLimitEntry<?> prev = this.removeSources.get(source);
166 if (prev != null)
167 {
168 this.prospect.remove(prev);
169 }
170 this.removeSources.put(source, speedLimitEntry);
171 }
172 else
173 {
174 SpeedLimitEntry<?> prev = this.addSources.get(source);
175 if (prev != null)
176 {
177 this.prospect.remove(prev);
178 }
179 this.addSources.put(source, speedLimitEntry);
180 }
181 this.prospect.add(speedLimitEntry);
182 }
183
184
185
186
187
188
189 public final List<Length> getDistances()
190 {
191 List<Length> list = new ArrayList<>();
192 for (SpeedLimitEntry<?> speedLimitEntry : this.prospect)
193 {
194 list.add(speedLimitEntry.getDistance());
195 }
196 return list;
197 }
198
199
200
201
202
203
204
205 public final List<Length> getDistances(final SpeedLimitType<?> speedLimitType)
206 {
207 return getDistancesInRange(speedLimitType, null, null);
208 }
209
210
211
212
213
214
215
216 public final List<Length> getUpstreamDistances(final SpeedLimitType<?> speedLimitType)
217 {
218 return getDistancesInRange(speedLimitType, null, Length.ZERO);
219 }
220
221
222
223
224
225
226
227 public final List<Length> getDownstreamDistances(final SpeedLimitType<?> speedLimitType)
228 {
229 return getDistancesInRange(speedLimitType, Length.ZERO, null);
230 }
231
232
233
234
235
236
237
238
239
240 private List<Length> getDistancesInRange(final SpeedLimitType<?> speedLimitType, final Length min, final Length max)
241 {
242 List<Length> list = new ArrayList<>();
243 for (SpeedLimitEntry<?> speedLimitEntry : this.prospect)
244 {
245 if (speedLimitEntry.getSpeedLimitType().equals(speedLimitType)
246 && (min == null || speedLimitEntry.getDistance().gt(min))
247 && (max == null || speedLimitEntry.getDistance().le(max)))
248 {
249 list.add(speedLimitEntry.getDistance());
250 }
251 }
252 return list;
253 }
254
255
256
257
258
259
260
261
262 public final boolean speedInfoChanged(final Length distance, final SpeedLimitType<?> speedLimitType)
263 {
264 Throw.whenNull(distance, "Distance may not be null.");
265 for (SpeedLimitEntry<?> sle : this.prospect)
266 {
267 if (sle.getDistance().eq(distance) && sle.getSpeedLimitType().equals(speedLimitType))
268 {
269 return true;
270 }
271 }
272 return false;
273 }
274
275
276
277
278
279
280
281
282
283
284 public final <T> T getSpeedInfoChange(final Length distance, final SpeedLimitType<T> speedLimitType)
285 {
286 for (SpeedLimitEntry<?> sle : this.prospect)
287 {
288 if (sle.getDistance().eq(distance) && sle.getSpeedLimitType().equals(speedLimitType))
289 {
290 @SuppressWarnings("unchecked")
291 T info = (T) sle.getSpeedInfo();
292 return info;
293 }
294 }
295 throw new IllegalArgumentException("Speed info of speed limit type '" + speedLimitType.getId()
296 + "' is requested at a distance '" + distance + "' where the info is not changed.");
297 }
298
299
300
301
302
303
304
305 public final SpeedLimitInfo getSpeedLimitInfo(final Length distance)
306 {
307 Throw.whenNull(distance, "Distance may not be null.");
308 SpeedLimitInfo speedLimitInfo = new SpeedLimitInfo();
309 for (SpeedLimitEntry<?> speedLimitEntry : this.prospect)
310 {
311
312 if (speedLimitEntry.getDistance().compareTo(distance) > 0)
313 {
314
315 return speedLimitInfo;
316 }
317
318 if (speedLimitEntry.getSpeedInfo() == null)
319 {
320 speedLimitInfo.removeSpeedInfo(speedLimitEntry.getSpeedLimitType());
321 }
322 else
323 {
324
325
326 setAsType(speedLimitInfo, speedLimitEntry);
327 }
328 }
329 return speedLimitInfo;
330 }
331
332
333
334
335
336
337
338
339
340 public final SpeedLimitInfo getSpeedLimitInfo(final Speed speed, final Acceleration acceleration, final Duration time)
341 {
342 Throw.whenNull(speed, "Speed may not be null.");
343 Throw.whenNull(acceleration, "Acceleration may not be null.");
344 Throw.whenNull(time, "Time may not be null.");
345 return getSpeedLimitInfo(new Length(speed.si * time.si + .5 * acceleration.si * time.si * time.si, LengthUnit.SI));
346 }
347
348
349
350
351
352
353
354
355
356
357 @SuppressWarnings("unchecked")
358 private <T> void setAsType(final SpeedLimitInfo speedLimitInfo, final SpeedLimitEntry<?> speedLimitEntry)
359 {
360 SpeedLimitType<T> speedLimitType = (SpeedLimitType<T>) speedLimitEntry.getSpeedLimitType();
361 T speedInfoOfType = (T) speedLimitEntry.getSpeedInfo();
362 speedLimitInfo.addSpeedInfo(speedLimitType, speedInfoOfType);
363 }
364
365
366
367
368
369
370
371
372
373 public final <T> SpeedLimitInfo buildSpeedLimitInfo(final Length distance, final SpeedLimitType<T> speedLimitType)
374 {
375 SpeedLimitInfo out = new SpeedLimitInfo();
376 out.addSpeedInfo(speedLimitType, getSpeedInfoChange(distance, speedLimitType));
377 for (SpeedLimitEntry<?> speedLimitEntry : this.prospect)
378 {
379 if (speedLimitEntry.getDistance().gt(distance))
380 {
381 break;
382 }
383 if (speedLimitEntry.getSpeedLimitType().equals(SpeedLimitTypes.MAX_VEHICLE_SPEED))
384 {
385 out.addSpeedInfo(SpeedLimitTypes.MAX_VEHICLE_SPEED,
386 SpeedLimitTypes.MAX_VEHICLE_SPEED.getInfoClass().cast(speedLimitEntry.getSpeedInfo()));
387 }
388 }
389 return out;
390 }
391
392
393 @Override
394 public final String toString()
395 {
396 StringBuilder stringBuilder = new StringBuilder("SpeedLimitProspect [");
397 String sep = "";
398 for (SpeedLimitEntry<?> sle : this.prospect)
399 {
400 stringBuilder.append(sep).append(sle.getDistance()).append(": ");
401 if (sle.getSpeedInfo() == null)
402 {
403 stringBuilder.append(sle.getSpeedLimitType().getId()).append("=END");
404 }
405 else
406 {
407 stringBuilder.append(sle.getSpeedLimitType().getId()).append("=");
408 stringBuilder.append(sle.getSpeedInfo());
409 }
410 sep = ", ";
411 }
412 stringBuilder.append("]");
413 return stringBuilder.toString();
414 }
415
416
417
418
419
420
421
422
423
424
425
426
427 private static class SpeedLimitEntry<T> implements Comparable<SpeedLimitEntry<?>>, Serializable
428 {
429
430
431 private static final long serialVersionUID = 20160501L;
432
433
434 private Length distance;
435
436
437 private final SpeedLimitType<T> speedLimitType;
438
439
440 private final T speedInfo;
441
442
443
444
445
446
447
448 SpeedLimitEntry(final Length distance, final SpeedLimitType<T> speedLimitType, final T speedInfo)
449 {
450 this.distance = distance;
451 this.speedLimitType = speedLimitType;
452 this.speedInfo = speedInfo;
453 }
454
455
456
457
458
459 public final Length getDistance()
460 {
461 return this.distance;
462 }
463
464
465
466
467
468 public final SpeedLimitType<T> getSpeedLimitType()
469 {
470 return this.speedLimitType;
471 }
472
473
474
475
476
477 public final T getSpeedInfo()
478 {
479 return this.speedInfo;
480 }
481
482
483
484
485
486 public final void move(final Length dist)
487 {
488 this.distance = this.distance.minus(dist);
489 }
490
491
492 @Override
493 public final int hashCode()
494 {
495 final int prime = 31;
496 int result = 1;
497 result = prime * result + this.distance.hashCode();
498 result = prime * result + this.speedInfo.hashCode();
499 result = prime * result + this.speedLimitType.hashCode();
500 return result;
501 }
502
503
504 @Override
505 public final boolean equals(final Object obj)
506 {
507 if (this == obj)
508 {
509 return true;
510 }
511 if (obj == null)
512 {
513 return false;
514 }
515 if (getClass() != obj.getClass())
516 {
517 return false;
518 }
519 SpeedLimitEntry<?> other = (SpeedLimitEntry<?>) obj;
520 if (!this.distance.equals(other.distance))
521 {
522 return false;
523 }
524 if (!this.speedLimitType.equals(other.speedLimitType))
525 {
526 return false;
527 }
528 if (this.speedInfo == null)
529 {
530 if (other.speedInfo != null)
531 {
532 return false;
533 }
534 }
535 else if (!this.speedInfo.equals(other.speedInfo))
536 {
537 return false;
538 }
539 return true;
540 }
541
542
543 @Override
544 public final int compareTo(final SpeedLimitEntry<?> speedLimitEntry)
545 {
546 if (this.equals(speedLimitEntry))
547 {
548 return 0;
549 }
550
551 int comp = this.distance.compareTo(speedLimitEntry.distance);
552 if (comp != 0)
553 {
554 return comp;
555 }
556
557 comp = this.speedLimitType.getId().compareTo(speedLimitEntry.speedLimitType.getId());
558 if (comp != 0)
559 {
560 return comp;
561 }
562
563
564 if (this.speedInfo == null)
565 {
566 if (speedLimitEntry.speedInfo == null)
567 {
568 return 0;
569 }
570 return -1;
571 }
572 else if (speedLimitEntry.speedInfo == null)
573 {
574 return 1;
575 }
576 return this.speedInfo.hashCode() < speedLimitEntry.speedInfo.hashCode() ? -1 : 1;
577 }
578
579
580 @Override
581 public final String toString()
582 {
583 return "SpeedLimitEntry [distance=" + this.distance + ", speedLimitType=" + this.speedLimitType + ", speedInfo="
584 + this.speedInfo + "]";
585 }
586
587 }
588
589 }