1 package org.opentrafficsim.road.network.speed;
2
3 import java.io.Serializable;
4 import java.util.ArrayList;
5 import java.util.List;
6 import java.util.SortedSet;
7 import java.util.TreeSet;
8
9 import org.djunits.unit.LengthUnit;
10 import org.djunits.value.vdouble.scalar.Acceleration;
11 import org.djunits.value.vdouble.scalar.Duration;
12 import org.djunits.value.vdouble.scalar.Length;
13 import org.djunits.value.vdouble.scalar.Speed;
14
15 import nl.tudelft.simulation.language.Throw;
16
17 /**
18 * Prospect of speed limits ahead, both legal and otherwise (e.g. curve, speed bump).
19 * <p>
20 * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
21 * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
22 * <p>
23 * @version $Revision$, $LastChangedDate$, by $Author$, initial version May 1, 2016 <br>
24 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
25 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
26 */
27
28 public class SpeedLimitProspect implements Serializable
29 {
30
31 /** */
32 private static final long serialVersionUID = 20160501L;
33
34 /** Spatial prospect of speed info. */
35 private final SortedSet<SpeedLimitEntry<?>> prospect = new TreeSet<>();
36
37 /**
38 * Sets the speed info of a speed limit type.
39 * @param distance location to set info for a speed limit type
40 * @param speedLimitType speed limit type to set the info for
41 * @param speedInfo speed info to set
42 * @param <T> class of speed info
43 * @throws IllegalStateException if speed info for a specific speed limit type is set or removed twice at the same distance
44 * @throws IllegalStateException if speed info for a specific speed limit type is set twice with negative distance
45 * @throws NullPointerException if any input is null
46 */
47 public final <T> void addSpeedInfo(final Length distance, final SpeedLimitType<T> speedLimitType, final T speedInfo)
48 {
49 Throw.whenNull(distance, "Distance may not be null.");
50 Throw.whenNull(speedLimitType, "Speed limit type may not be null.");
51 Throw.whenNull(speedInfo, "Speed info may not be null.");
52 checkAndAdd(new SpeedLimitEntry<>(distance, speedLimitType, speedInfo));
53 }
54
55 /**
56 * Removes the speed info of a speed limit type.
57 * @param distance distance to remove speed info of a speed limit type
58 * @param speedLimitType speed limit type to remove speed info of
59 * @throws IllegalStateException if speed info for a specific speed limit type is set or removed twice at the same distance
60 * @throws IllegalArgumentException if the speed limit type is {@code MAX_VEHICLE_SPEED}
61 * @throws IllegalArgumentException if the distance is negative
62 * @throws NullPointerException if any input is null
63 */
64 @SuppressWarnings({ "unchecked", "rawtypes" })
65 public final void removeSpeedInfo(final Length distance, final SpeedLimitType<?> speedLimitType)
66 {
67 Throw.whenNull(distance, "Distance may not be null.");
68 Throw.when(distance.si < 0, IllegalArgumentException.class,
69 "Removing speed info in the past is not allowed. " + "Only add still active speed info.");
70 Throw.whenNull(speedLimitType, "Speed limit type may not be null.");
71 Throw.when(speedLimitType.equals(SpeedLimitTypes.MAX_VEHICLE_SPEED), IllegalArgumentException.class,
72 "May not remove the maximum vehicle speed.");
73 // null value does not comply to being a T for SpeedLimitType<T> but is separately treated
74 checkAndAdd(new SpeedLimitEntry(distance, speedLimitType, null));
75 }
76
77 /**
78 * Checks the speed limit entry before adding to the prospect.
79 * @param speedLimitEntry speed limit entry to add
80 * @throws IllegalStateException if the speed entry forms an undefined set with any existing entry
81 */
82 private void checkAndAdd(final SpeedLimitEntry<?> speedLimitEntry)
83 {
84 for (SpeedLimitEntry<?> s : this.prospect)
85 {
86 if (s.getSpeedLimitType().equals(speedLimitEntry.getSpeedLimitType()))
87 {
88 /*
89 * For entries at the same distance, the speed limit type may not be the same, this leaves us with an undefined
90 * state as it cannot be derived which remains valid further on.
91 */
92 Throw.when(s.getDistance().equals(speedLimitEntry.getDistance()), IllegalStateException.class,
93 "Info " + "of speed limit type '%s' is set twice at the same location (%s). This is undefined. "
94 + "Either remove speed info, or overwrite with new speed info.",
95 s.getSpeedLimitType(), s.getDistance());
96 }
97 }
98 this.prospect.add(speedLimitEntry);
99 }
100
101 /**
102 * Returns the distances at which a change in the prospect is present in order (upstream first). If multiple changes are
103 * present at the same distance, only one distance is returned in the list.
104 * @return distances at which a change in the prospect is present in order (upstream first)
105 */
106 public final List<Length> getDistances()
107 {
108 List<Length> list = new ArrayList<>();
109 for (SpeedLimitEntry<?> speedLimitEntry : this.prospect)
110 {
111 list.add(speedLimitEntry.getDistance());
112 }
113 return list;
114 }
115
116 /**
117 * Returns the distances at which a change of the given speed limit type in the prospect is present in order (most upstream
118 * first). If multiple changes are present at the same distance, only one distance is returned in the list.
119 * @param speedLimitType speed limit type to get the distances of
120 * @return distances at which a change of the given speed limit type in the prospect is present in order
121 */
122 public final List<Length> getDistances(final SpeedLimitType<?> speedLimitType)
123 {
124 return getDistancesInRange(speedLimitType, null, null);
125 }
126
127 /**
128 * Returns the upstream distances at which a change of the given speed limit type in the prospect is present in order (most
129 * upstream first). If multiple changes are present at the same distance, only one distance is returned in the list.
130 * @param speedLimitType speed limit type to get the distances of
131 * @return distances at which a change of the given speed limit type in the prospect is present in order
132 */
133 public final List<Length> getUpstreamDistances(final SpeedLimitType<?> speedLimitType)
134 {
135 return getDistancesInRange(speedLimitType, null, Length.ZERO);
136 }
137
138 /**
139 * Returns the downstream distances at which a change of the given speed limit type in the prospect is present in order
140 * (most upstream first). If multiple changes are present at the same distance, only one distance is returned in the list.
141 * @param speedLimitType speed limit type to get the distances of
142 * @return distances at which a change of the given speed limit type in the prospect is present in order
143 */
144 public final List<Length> getDownstreamDistances(final SpeedLimitType<?> speedLimitType)
145 {
146 return getDistancesInRange(speedLimitType, Length.ZERO, null);
147 }
148
149 /**
150 * Returns the distances between limits at which a change of the given speed limit type in the prospect is present in order
151 * (most upstream first). If multiple changes are present at the same distance, only one distance is returned in the list.
152 * @param speedLimitType speed limit type to get the distances of
153 * @param min minimum distance, may be {@code null} for no minimum limit
154 * @param max maximum distance, may be {@code null} for no maximum limit
155 * @return distances at which a change of the given speed limit type in the prospect is present in order
156 */
157 private List<Length> getDistancesInRange(final SpeedLimitType<?> speedLimitType, final Length min, final Length max)
158 {
159 List<Length> list = new ArrayList<>();
160 for (SpeedLimitEntry<?> speedLimitEntry : this.prospect)
161 {
162 if (speedLimitEntry.getSpeedLimitType().equals(speedLimitType)
163 && (min == null || speedLimitEntry.getDistance().gt(min))
164 && (max == null || speedLimitEntry.getDistance().le(max)))
165 {
166 list.add(speedLimitEntry.getDistance());
167 }
168 }
169 return list;
170 }
171
172 /**
173 * Returns whether the given speed limit type is changed at the given distance.
174 * @param distance distance to check
175 * @param speedLimitType speed limit type to check
176 * @return whether the given speed limit type is changed at the given distance
177 * @throws NullPointerException if distance is null
178 */
179 public final boolean speedInfoChanged(final Length distance, final SpeedLimitType<?> speedLimitType)
180 {
181 Throw.whenNull(distance, "Distance may not be null.");
182 for (SpeedLimitEntry<?> sle : this.prospect)
183 {
184 if (sle.getDistance().eq(distance) && sle.getSpeedLimitType().equals(speedLimitType))
185 {
186 return true;
187 }
188 }
189 return false;
190 }
191
192 /**
193 * Returns the speed info of given speed limit type where it changed. If the change was removing the speed limit type (e.g.
194 * end of corner), then {@code null} is returned.
195 * @param distance distance where the info changed
196 * @param speedLimitType speed limit type
197 * @return speed info of given speed limit type where it changed, {@code null} if speed limit type is removed
198 * @throws IllegalArgumentException if the speed info did not change at the given distance for the speed limit type
199 * @param <T> class of the speed limit type info
200 */
201 public final <T> T getSpeedInfoChange(final Length distance, final SpeedLimitType<T> speedLimitType)
202 {
203 for (SpeedLimitEntry<?> sle : this.prospect)
204 {
205 if (sle.getDistance().eq(distance) && sle.getSpeedLimitType().equals(speedLimitType))
206 {
207 @SuppressWarnings("unchecked")
208 T info = (T) sle.getSpeedInfo();
209 return info;
210 }
211 }
212 throw new IllegalArgumentException("Speed info of speed limit type '" + speedLimitType.getId()
213 + "' is requested at a distance '" + distance + "' where the info is not changed.");
214 }
215
216 /**
217 * Returns the speed info at a given location.
218 * @param distance where to get the speed info
219 * @return speed info at a given distance
220 * @throws NullPointerException if distance is null
221 */
222 public final SpeedLimitInfo getSpeedLimitInfo(final Length distance)
223 {
224 Throw.whenNull(distance, "Distance may not be null.");
225 SpeedLimitInfo speedLimitInfo = new SpeedLimitInfo();
226 for (SpeedLimitEntry<?> speedLimitEntry : this.prospect)
227 {
228 // use compareTo as this also determines order in this.prospect
229 if (speedLimitEntry.getDistance().compareTo(distance) > 0)
230 {
231 // remaining entries are further ahead
232 return speedLimitInfo;
233 }
234 // make appropriate change to speedLimitInfo
235 if (speedLimitEntry.getSpeedInfo() == null)
236 {
237 speedLimitInfo.removeSpeedInfo(speedLimitEntry.getSpeedLimitType());
238 }
239 else
240 {
241 // method addSpeedInfo guarantees that speedInfo in speedLimitEntry is T
242 // for speedLimitType in speedLimitEntry is SpeedLimitType<T>, null is checked above
243 setAsType(speedLimitInfo, speedLimitEntry);
244 }
245 }
246 return speedLimitInfo;
247 }
248
249 /**
250 * Returns the speed info at a location following an acceleration over some duration.
251 * @param speed current speed
252 * @param acceleration acceleration to apply
253 * @param time duration of acceleration
254 * @return speed info at a given distance
255 * @throws NullPointerException if any input is null
256 */
257 public final SpeedLimitInfo getSpeedLimitInfo(final Speed speed, final Acceleration acceleration, final Duration time)
258 {
259 Throw.whenNull(speed, "Speed may not be null.");
260 Throw.whenNull(acceleration, "Acceleration may not be null.");
261 Throw.whenNull(time, "Time may not be null.");
262 return getSpeedLimitInfo(new Length(speed.si * time.si + .5 * acceleration.si * time.si * time.si, LengthUnit.SI));
263 }
264
265 /**
266 * Sets speed info for a speed limit type in speed limit info by explicitly casting the types. From the context it should be
267 * certain that the speed info inside the speed limit entry matches the declared info type of the speed limit type inside
268 * the entry, i.e. {@code speedLimitEntry.getSpeedLimitType() = SpeedLimitType<T>} and
269 * {@code speedLimitEntry.getSpeedInfo() = T}.
270 * @param speedLimitInfo speed limit info to put speed info in
271 * @param speedLimitEntry entry with speed limit type and speed info to set
272 * @param <T> underlying speed info class depending on speed limit type
273 */
274 @SuppressWarnings("unchecked")
275 private <T> void setAsType(final SpeedLimitInfo speedLimitInfo, final SpeedLimitEntry<?> speedLimitEntry)
276 {
277 SpeedLimitType<T> speedLimitType = (SpeedLimitType<T>) speedLimitEntry.getSpeedLimitType();
278 T speedInfoOfType = (T) speedLimitEntry.getSpeedInfo();
279 speedLimitInfo.addSpeedInfo(speedLimitType, speedInfoOfType);
280 }
281
282 /**
283 * Builds speed limit info with only MAX_VEHICLE_SPEED and the given speed limit type, where the speed info is obtained at
284 * the given distance.
285 * @param distance distance to get the speed info at
286 * @param speedLimitType speed limit type of which to include the info
287 * @param <T> class of speed info of given speed limit type
288 * @return speed limit info with only MAX_VEHICLE_SPEED and the given speed limit type
289 */
290 public final <T> SpeedLimitInfo buildSpeedLimitInfo(final Length distance, final SpeedLimitType<T> speedLimitType)
291 {
292 SpeedLimitInfo out = new SpeedLimitInfo();
293 out.addSpeedInfo(speedLimitType, getSpeedInfoChange(distance, speedLimitType));
294 for (SpeedLimitEntry<?> speedLimitEntry : this.prospect)
295 {
296 if (speedLimitEntry.getDistance().gt(distance))
297 {
298 break;
299 }
300 if (speedLimitEntry.getSpeedLimitType().equals(SpeedLimitTypes.MAX_VEHICLE_SPEED))
301 {
302 out.addSpeedInfo(SpeedLimitTypes.MAX_VEHICLE_SPEED,
303 SpeedLimitTypes.MAX_VEHICLE_SPEED.getInfoClass().cast(speedLimitEntry.getSpeedInfo()));
304 }
305 }
306 return out;
307 }
308
309 /** {@inheritDoc} */
310 @Override
311 public final String toString()
312 {
313 StringBuilder stringBuilder = new StringBuilder("SpeedLimitProspect [");
314 String sep = "";
315 for (SpeedLimitEntry<?> sle : this.prospect)
316 {
317 stringBuilder.append(sep).append(sle.getDistance()).append(": ");
318 if (sle.getSpeedInfo() == null)
319 {
320 stringBuilder.append(sle.getSpeedLimitType().getId()).append("=END");
321 }
322 else
323 {
324 stringBuilder.append(sle.getSpeedLimitType().getId()).append("=");
325 stringBuilder.append(sle.getSpeedInfo());
326 }
327 sep = ", ";
328 }
329 stringBuilder.append("]");
330 return stringBuilder.toString();
331 }
332
333 /**
334 * Stores speed limit type and it's speed info with a location.
335 * <p>
336 * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
337 * <br>
338 * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
339 * <p>
340 * @version $Revision$, $LastChangedDate$, by $Author$, initial version May 1, 2016 <br>
341 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
342 * @param <T> class of speed info
343 */
344 private static class SpeedLimitEntry<T> implements Comparable<SpeedLimitEntry<?>>, Serializable
345 {
346
347 /** */
348 private static final long serialVersionUID = 20160501L;
349
350 /** Location of the speed info. */
351 private final Length distance;
352
353 /** Speed limit type. */
354 private final SpeedLimitType<T> speedLimitType;
355
356 /** Speed info. */
357 private final T speedInfo;
358
359 /**
360 * Constructor.
361 * @param distance location of the speed info
362 * @param speedLimitType speed limit type
363 * @param speedInfo speed info
364 */
365 SpeedLimitEntry(final Length distance, final SpeedLimitType<T> speedLimitType, final T speedInfo)
366 {
367 this.distance = distance;
368 this.speedLimitType = speedLimitType;
369 this.speedInfo = speedInfo;
370 }
371
372 /**
373 * Returns the location of the speed info.
374 * @return location of the speed info
375 */
376 public final Length getDistance()
377 {
378 return this.distance;
379 }
380
381 /**
382 * Returns the speed limit type.
383 * @return speed limit type
384 */
385 public final SpeedLimitType<T> getSpeedLimitType()
386 {
387 return this.speedLimitType;
388 }
389
390 /**
391 * Returns the speed info.
392 * @return the speed info
393 */
394 public final T getSpeedInfo()
395 {
396 return this.speedInfo;
397 }
398
399 /** {@inheritDoc} */
400 @Override
401 public final int hashCode()
402 {
403 final int prime = 31;
404 int result = 1;
405 result = prime * result + this.distance.hashCode();
406 result = prime * result + this.speedInfo.hashCode();
407 result = prime * result + this.speedLimitType.hashCode();
408 return result;
409 }
410
411 /** {@inheritDoc} */
412 @Override
413 public final boolean equals(final Object obj)
414 {
415 if (this == obj)
416 {
417 return true;
418 }
419 if (obj == null)
420 {
421 return false;
422 }
423 if (getClass() != obj.getClass())
424 {
425 return false;
426 }
427 SpeedLimitEntry<?> other = (SpeedLimitEntry<?>) obj;
428 if (!this.distance.equals(other.distance))
429 {
430 return false;
431 }
432 if (!this.speedLimitType.equals(other.speedLimitType))
433 {
434 return false;
435 }
436 if (this.speedInfo == null)
437 {
438 if (other.speedInfo != null)
439 {
440 return false;
441 }
442 }
443 else if (!this.speedInfo.equals(other.speedInfo))
444 {
445 return false;
446 }
447 return true;
448 }
449
450 /** {@inheritDoc} */
451 @Override
452 public final int compareTo(final SpeedLimitEntry<?> speedLimitEntry)
453 {
454 if (this.equals(speedLimitEntry))
455 {
456 return 0;
457 }
458 // order by distance
459 int comp = this.distance.compareTo(speedLimitEntry.distance);
460 if (comp != 0)
461 {
462 return comp;
463 }
464 // order by speed limit type
465 comp = this.speedLimitType.getId().compareTo(speedLimitEntry.speedLimitType.getId());
466 if (comp != 0)
467 {
468 return comp;
469 }
470 // equal distance and speed limit type is not allowed, so below code is not used
471 // if this requirement changes, compareTo should still work
472 if (this.speedInfo == null)
473 {
474 if (speedLimitEntry.speedInfo == null)
475 {
476 return 0; // both null
477 }
478 return -1; // null under non-null
479 }
480 else if (speedLimitEntry.speedInfo == null)
481 {
482 return 1; // non-null over null
483 }
484 return this.speedInfo.hashCode() < speedLimitEntry.speedInfo.hashCode() ? -1 : 1;
485 }
486
487 /** {@inheritDoc} */
488 @Override
489 public final String toString()
490 {
491 return "SpeedLimitEntry [distance=" + this.distance + ", speedLimitType=" + this.speedLimitType + ", speedInfo="
492 + this.speedInfo + "]";
493 }
494
495 }
496
497 }