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