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