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