View Javadoc
1   package org.opentrafficsim.road.network.speed;
2   
3   import java.util.LinkedHashMap;
4   import java.util.Map;
5   import java.util.Map.Entry;
6   import java.util.NavigableMap;
7   import java.util.Optional;
8   import java.util.TreeMap;
9   
10  import org.djunits.value.vdouble.scalar.Duration;
11  import org.djunits.value.vdouble.scalar.Speed;
12  import org.djutils.exceptions.Throw;
13  import org.opentrafficsim.core.gtu.GtuType;
14  
15  /**
16   * Speed limits for the context of a single lane.
17   * <p>
18   * Copyright (c) 2026-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
19   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
20   * </p>
21   * @author Wouter Schakel
22   */
23  public class LaneSpeedLimits
24  {
25  
26      /** Speed limits that are applicable to GTU types. */
27      private final Map<GtuType, Speed> gtuTypeSpeedLimits;
28  
29      /** Speed limit information for time windows. */
30      private NavigableMap<Duration, LocalSpeedLimit> speedLimits = new TreeMap<>();
31  
32      /**
33       * Constructor with no speed limit.
34       */
35      public LaneSpeedLimits()
36      {
37          this.gtuTypeSpeedLimits = new LinkedHashMap<>();
38      }
39  
40      /**
41       * Constructor with only a fixed speed limit.
42       * @param speedLimit overall speed limit
43       */
44      public LaneSpeedLimits(final Speed speedLimit)
45      {
46          this.gtuTypeSpeedLimits = new LinkedHashMap<>();
47          addSpeedLimit(speedLimit);
48      }
49  
50      /**
51       * Constructor with only GTU type speed limits.
52       * @param gtuTypeSpeedLimits GTU type speed limits
53       */
54      public LaneSpeedLimits(final Map<GtuType, Speed> gtuTypeSpeedLimits)
55      {
56          Throw.whenNull(gtuTypeSpeedLimits, "gtuTypeSpeedLimits");
57          this.gtuTypeSpeedLimits = new LinkedHashMap<>(gtuTypeSpeedLimits);
58      }
59  
60      /**
61       * Constructor with overall speed limit and GTU type speed limits.
62       * @param speedLimit overall speed limit
63       * @param gtuTypeSpeedLimits GTU type speed limits
64       */
65      public LaneSpeedLimits(final Speed speedLimit, final Map<GtuType, Speed> gtuTypeSpeedLimits)
66      {
67          Throw.whenNull(gtuTypeSpeedLimits, "gtuTypeSpeedLimits");
68          this.gtuTypeSpeedLimits = new LinkedHashMap<>(gtuTypeSpeedLimits);
69          addSpeedLimit(speedLimit);
70      }
71  
72      /**
73       * Add non-enforced constant speed limit.
74       * @param speed speed limit
75       * @return this object for method chaining
76       */
77      public LaneSpeedLimits addSpeedLimit(final Speed speed)
78      {
79          Throw.whenNull(speed, "speed");
80          return addSpeedLimit(Duration.ZERO, speed, false, false);
81      }
82  
83      /**
84       * Add constant speed limit.
85       * <p>
86       * If enforcement is aware of the GTU type, any speed limit applicable to the GTU type (e.g. 80km/h for trucks) becomes an
87       * enforced speed limit.
88       * @param speed speed limit
89       * @param enforced whether the speed limit is enforced
90       * @param enforcedGtuTypeAware whether the speed limit enforcement is aware of GTU types
91       * @return this object for method chaining
92       */
93      public LaneSpeedLimits addSpeedLimit(final Speed speed, final boolean enforced, final boolean enforcedGtuTypeAware)
94      {
95          Throw.whenNull(speed, "speed");
96          return addSpeedLimit(Duration.ZERO, speed, enforced, enforcedGtuTypeAware);
97      }
98  
99      /**
100      * Add non-enforced speed limit. Time of day will by cycled through. For example, if a speed limit of 100km/h is added at 6h
101      * and a speed limit of 130km/h is added at 19h, 100km/h applies from 06:00 till 19:00, while 130km/h applies for all other
102      * hours, including between 00:00 and 06:00.
103      * @param timeOfDay time-of-day when the speed limit becomes active
104      * @param speed speed of the speed limit
105      * @return this object for method chaining
106      * @throws IllegalArgumentException when time-of-day is not in the range [0 24) hours
107      */
108     public LaneSpeedLimits addSpeedLimit(final Duration timeOfDay, final Speed speed)
109     {
110         Throw.whenNull(timeOfDay, "timeOfDay");
111         Throw.whenNull(speed, "speed");
112         return addSpeedLimit(timeOfDay, speed, false, false);
113     }
114 
115     /**
116      * Add speed limit. Time of day will by cycled through. For example, if a speed limit of 100km/h is added at 6h and a speed
117      * limit of 130km/h is added at 19h, 100km/h applies from 06:00 till 19:00, while 130km/h applies for all other hours,
118      * including between 00:00 and 06:00.
119      * <p>
120      * If enforcement is aware of the GTU type, any speed limit applicable to the GTU type (e.g. 80km/h for trucks) becomes an
121      * enforced speed limit.
122      * @param timeOfDay time-of-day when the speed limit becomes active
123      * @param speed speed of the speed limit
124      * @param enforced whether the speed limit is enforced
125      * @param enforcedGtuTypeAware whether the speed limit enforcement is aware of GTU types
126      * @return this object for method chaining
127      * @throws IllegalArgumentException when time-of-day is not in the range [0 24) hours
128      */
129     public LaneSpeedLimits addSpeedLimit(final Duration timeOfDay, final Speed speed, final boolean enforced,
130             final boolean enforcedGtuTypeAware)
131     {
132         Throw.whenNull(timeOfDay, "timeOfDay");
133         Throw.whenNull(speed, "speed");
134         Throw.when(timeOfDay.si < 0.0 || timeOfDay.si >= 86400.0, IllegalArgumentException.class,
135                 "Time of day value must be between 0 (inclusive) and 24 (exclusive) hours.");
136         this.speedLimits.put(timeOfDay, new LocalSpeedLimit(new SpeedLimit(speed, enforced), enforcedGtuTypeAware));
137         return this;
138     }
139 
140     /**
141      * Returns the speed limit.
142      * @param timeOfDay time-of-day
143      * @return speed limit, empty if no speed limit given
144      */
145     public Optional<SpeedLimit> getSpeedLimit(final Duration timeOfDay)
146     {
147         LocalSpeedLimit localSpeedLimit = getLocalSpeedLimit(timeOfDay);
148         return Optional.ofNullable(localSpeedLimit == null ? null : localSpeedLimit.speedLimit());
149     }
150 
151     /**
152      * Returns the applicable speed limits for the given GTU type and time-of-day.
153      * @param gtuType GTU type
154      * @param timeOfDay time-of-day
155      * @return applicable speed limits for the given GTU type and time-of-day
156      */
157     public SpeedLimits getSpeedLimits(final GtuType gtuType, final Duration timeOfDay)
158     {
159         LocalSpeedLimit localSpeedLimit = getLocalSpeedLimit(timeOfDay);
160         Speed gtuTypeSpeed = getGtuTypeSpeedLimit(gtuType);
161         if (gtuTypeSpeed != null)
162         {
163             return new SpeedLimits(localSpeedLimit == null ? null : localSpeedLimit.speedLimit(),
164                     new SpeedLimit(gtuTypeSpeed, localSpeedLimit != null && localSpeedLimit.enforcedGtuTypeAware()
165                             && localSpeedLimit.speedLimit().enforced()));
166         }
167         return new SpeedLimits(localSpeedLimit == null ? null : localSpeedLimit.speedLimit(), null);
168     }
169 
170     /**
171      * Returns the local speed limit at the given time-of-day.
172      * @param timeOfDay time-of-day
173      * @return local speed limit at the given time-of-day
174      */
175     private LocalSpeedLimit getLocalSpeedLimit(final Duration timeOfDay)
176     {
177         Entry<Duration, LocalSpeedLimit> todEntry = this.speedLimits.floorEntry(timeOfDay);
178         // if there is information but not before the requested time-of-day, return the last information (cycles through day)
179         if (todEntry == null)
180         {
181             if (!this.speedLimits.isEmpty())
182             {
183                 return this.speedLimits.lastEntry().getValue();
184             }
185             else
186             {
187                 return null;
188             }
189         }
190         else
191         {
192             return todEntry.getValue();
193         }
194     }
195 
196     /**
197      * Returns the speed limit for the GTU type, or any of its parents.
198      * @param gtuType GTU type
199      * @return speed limit for the GTU type, or any of its parents
200      */
201     private Speed getGtuTypeSpeedLimit(final GtuType gtuType)
202     {
203         Speed speed = this.gtuTypeSpeedLimits.get(gtuType);
204         if (speed != null)
205         {
206             return speed;
207         }
208         Optional<GtuType> parent = gtuType.getParent();
209         if (!parent.isPresent())
210         {
211             this.gtuTypeSpeedLimits.put(gtuType, null);
212             return null;
213         }
214         return getGtuTypeSpeedLimit(parent.get());
215     }
216 
217     /**
218      * Record of speed limit information.
219      * @param speedLimit speed limit
220      * @param enforcedGtuTypeAware whether the enforcement is GTU type aware
221      */
222     private record LocalSpeedLimit(SpeedLimit speedLimit, boolean enforcedGtuTypeAware)
223     {
224     }
225 
226 }