View Javadoc
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 }