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