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 }