1 package org.opentrafficsim.trafficcontrol;
2
3 import java.rmi.RemoteException;
4 import java.util.ArrayList;
5 import java.util.LinkedHashSet;
6 import java.util.List;
7 import java.util.Set;
8
9 import nl.tudelft.simulation.dsol.SimRuntimeException;
10 import nl.tudelft.simulation.event.EventInterface;
11
12 import org.djunits.value.vdouble.scalar.Duration;
13 import org.djunits.value.vdouble.scalar.Time;
14 import org.djutils.exceptions.Throw;
15 import org.djutils.immutablecollections.Immutable;
16 import org.djutils.immutablecollections.ImmutableArrayList;
17 import org.djutils.immutablecollections.ImmutableCollections;
18 import org.djutils.immutablecollections.ImmutableHashSet;
19 import org.djutils.immutablecollections.ImmutableList;
20 import org.djutils.immutablecollections.ImmutableMap;
21 import org.djutils.immutablecollections.ImmutableSet;
22 import org.opentrafficsim.base.Identifiable;
23 import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
24 import org.opentrafficsim.core.network.Network;
25 import org.opentrafficsim.core.network.NetworkException;
26 import org.opentrafficsim.core.object.InvisibleObjectInterface;
27 import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight;
28 import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLightColor;
29
30
31
32
33
34
35
36
37
38
39
40
41 public class FixedTimeController extends AbstractTrafficController
42 {
43
44
45 private static final long serialVersionUID = 20190221L;
46
47
48 private final Duration cycleTime;
49
50
51 private final Duration offset;
52
53
54 private final Set<SignalGroup> signalGroups;
55
56
57
58
59
60
61
62
63
64
65
66 @SuppressWarnings({ "synthetic-access" })
67 public FixedTimeController(final String id, final OTSSimulatorInterface simulator, final Network network,
68 final Duration cycleTime, final Duration offset, final Set<SignalGroup> signalGroups) throws SimRuntimeException
69 {
70 super(id, simulator);
71 Throw.whenNull(simulator, "Simulator may not be null.");
72 Throw.whenNull(network, "Network may not be null.");
73 Throw.whenNull(cycleTime, "Cycle time may not be null.");
74 Throw.whenNull(offset, "Offset may not be null.");
75 Throw.whenNull(signalGroups, "Signal groups may not be null.");
76 Throw.when(cycleTime.le0(), IllegalArgumentException.class, "Cycle time must be positive.");
77 Throw.when(signalGroups.isEmpty(), IllegalArgumentException.class, "Signal groups may not be empty.");
78 for (SignalGroup signalGroup1 : signalGroups)
79 {
80 for (SignalGroup signalGroup2 : signalGroups)
81 {
82 if (!signalGroup1.equals(signalGroup2))
83 {
84 Throw.when(!ImmutableCollections.disjoint(signalGroup1.trafficLightIds, signalGroup2.trafficLightIds),
85 IllegalArgumentException.class,
86 "A traffic light is in both signal group %s and signal group %s.", signalGroup1.getId(),
87 signalGroup2.getId());
88 }
89 }
90 }
91 this.cycleTime = cycleTime;
92 this.offset = offset;
93 this.signalGroups = signalGroups;
94 simulator.scheduleEventAbs(Time.ZERO, this, this, "setup", new Object[] { simulator, network });
95 }
96
97
98
99
100
101
102
103 @SuppressWarnings("unused")
104 private void setup(final OTSSimulatorInterface simulator, final Network network) throws SimRuntimeException
105 {
106 for (SignalGroup signalGroup : this.signalGroups)
107 {
108 signalGroup.startup(this.offset, this.cycleTime, simulator, network);
109 }
110 }
111
112
113 @Override
114 public void notify(final EventInterface event) throws RemoteException
115 {
116
117 }
118
119
120 @Override
121 public InvisibleObjectInterface clone(final OTSSimulatorInterface newSimulator, final Network newNetwork)
122 throws NetworkException
123 {
124 Set<SignalGroup> signalGroupsCloned = new LinkedHashSet<>();
125 for (SignalGroup signalGroup : this.signalGroups)
126 {
127 signalGroupsCloned.add(signalGroup.clone());
128 }
129 try
130 {
131 return new FixedTimeController(getId(), newSimulator, newNetwork, this.cycleTime, this.offset,
132 signalGroupsCloned);
133 }
134 catch (SimRuntimeException exception)
135 {
136 throw new RuntimeException("Cloning using a simulator that is not at time 0.");
137 }
138 }
139
140
141 @Override
142 public String getFullId()
143 {
144 return getId();
145 }
146
147
148 @Override
149 public String toString()
150 {
151 return "FixedTimeController [cycleTime=" + this.cycleTime + ", offset=" + this.offset + ", signalGroups="
152 + this.signalGroups + ", full id=" + this.getFullId() + "]";
153 }
154
155
156
157
158
159
160
161
162
163
164
165 public static class SignalGroup implements Identifiable
166 {
167
168
169 private final String id;
170
171
172 private final ImmutableSet<String> trafficLightIds;
173
174
175 private final Duration offset;
176
177
178 private final Duration preGreen;
179
180
181 private final Duration green;
182
183
184 private final Duration yellow;
185
186
187
188
189 private List<TrafficLight> trafficLights;
190
191
192 private OTSSimulatorInterface simulator;
193
194
195 private Duration red;
196
197
198
199
200
201
202
203
204
205 public SignalGroup(final String id, final Set<String> trafficLightIds, final Duration offset, final Duration green,
206 final Duration yellow)
207 {
208 this(id, trafficLightIds, offset, Duration.ZERO, green, yellow);
209 }
210
211
212
213
214
215
216
217
218
219
220 public SignalGroup(final String id, final Set<String> trafficLightIds, final Duration offset,
221 final Duration preGreen, final Duration green, final Duration yellow)
222 {
223 Throw.whenNull(id, "Id may not be null.");
224 Throw.whenNull(trafficLightIds, "Traffic light ids may not be null.");
225 Throw.whenNull(offset, "Offset may not be null.");
226 Throw.whenNull(preGreen, "Pre-green may not be null.");
227 Throw.when(preGreen.lt(Duration.ZERO), IllegalArgumentException.class, "Pre green duration may not be negative");
228 Throw.whenNull(green, "Green may not be null.");
229 Throw.when(green.lt(Duration.ZERO), IllegalArgumentException.class, "Green duration may not be negative");
230 Throw.whenNull(yellow, "Yellow may not be null.");
231 Throw.when(yellow.lt(Duration.ZERO), IllegalArgumentException.class, "Yellow duration may not be negative");
232 Throw.when(trafficLightIds.isEmpty(), IllegalArgumentException.class, "Traffic light ids may not be empty.");
233 this.id = id;
234 this.trafficLightIds = new ImmutableHashSet<>(trafficLightIds, Immutable.COPY);
235 this.offset = offset;
236 this.preGreen = preGreen;
237 this.green = green;
238 this.yellow = yellow;
239 }
240
241
242
243
244 @Override
245 public String getId()
246 {
247 return this.id;
248 }
249
250
251
252
253
254
255
256
257 public void startup(final Duration controllerOffset, final Duration cycleTime, final OTSSimulatorInterface sim,
258 final Network network) throws SimRuntimeException
259 {
260 this.simulator = sim;
261 double totalOffsetSI = this.offset.si + controllerOffset.si;
262 while (totalOffsetSI < 0.0)
263 {
264 totalOffsetSI += cycleTime.si;
265 }
266 Duration totalOffset = Duration.createSI(totalOffsetSI % cycleTime.si);
267 this.red = cycleTime.minus(this.preGreen).minus(this.green).minus(this.yellow);
268 Throw.when(this.red.lt0(), IllegalArgumentException.class, "Cycle time shorter than sum of non-red times.");
269
270 this.trafficLights = new ArrayList<>();
271 ImmutableMap<String, TrafficLight> trafficLightObjects = network.getObjectMap(TrafficLight.class);
272 for (String trafficLightId : this.trafficLightIds)
273 {
274 TrafficLight trafficLight = trafficLightObjects.get(trafficLightId);
275 if (null == trafficLight)
276 {
277 trafficLight = trafficLightObjects.get(network.getId() + "." + trafficLightId);
278 }
279 Throw.when(trafficLight == null, SimRuntimeException.class, "Traffic light \"" + trafficLightId
280 + "\" in fixed time controller could not be found in network " + network.getId() + ".");
281 this.trafficLights.add(trafficLight);
282 }
283
284 Duration inCycleTime = Duration.createSI(totalOffset.si - cycleTime.si);
285 while (inCycleTime.si < 0)
286 {
287 inCycleTime = inCycleTime.plus(cycleTime);
288 }
289 Duration duration;
290 if (inCycleTime.si < this.preGreen.si)
291 {
292 setTrafficLights(TrafficLightColor.PREGREEN);
293 duration = this.preGreen.minus(inCycleTime);
294 }
295 else if (inCycleTime.si < this.preGreen.si + this.green.si)
296 {
297 setTrafficLights(TrafficLightColor.GREEN);
298 duration = this.preGreen.plus(this.green).minus(inCycleTime);
299 }
300 else if (inCycleTime.si < this.preGreen.si + this.green.si + this.yellow.si)
301 {
302 setTrafficLights(TrafficLightColor.YELLOW);
303 duration = this.preGreen.plus(this.green).plus(this.yellow).minus(inCycleTime);
304 }
305 else
306 {
307 setTrafficLights(TrafficLightColor.RED);
308 duration = cycleTime.minus(inCycleTime);
309 }
310 this.simulator.scheduleEventRel(duration, this, this, "updateColors", null);
311 }
312
313
314
315
316 @SuppressWarnings("unused")
317 private void updateColors()
318 {
319 Duration duration = Duration.ZERO;
320 TrafficLightColor color = this.trafficLights.get(0).getTrafficLightColor();
321 while (duration.le0())
322 {
323 switch (color)
324 {
325 case PREGREEN:
326 color = TrafficLightColor.GREEN;
327 duration = this.green;
328 break;
329 case GREEN:
330 color = TrafficLightColor.YELLOW;
331 duration = this.yellow;
332 break;
333 case YELLOW:
334 color = TrafficLightColor.RED;
335 duration = this.red;
336 break;
337 case RED:
338 color = TrafficLightColor.PREGREEN;
339 duration = this.preGreen;
340 break;
341 default:
342 throw new RuntimeException("Cannot happen.");
343 }
344 }
345 setTrafficLights(color);
346 try
347 {
348 this.simulator.scheduleEventRel(duration, this, this, "updateColors", null);
349 }
350 catch (SimRuntimeException exception)
351 {
352
353 throw new RuntimeException(exception);
354 }
355 }
356
357
358
359
360
361 private void setTrafficLights(final TrafficLightColor trafficLightColor)
362 {
363 for (TrafficLight trafficLight : this.trafficLights)
364 {
365 trafficLight.setTrafficLightColor(trafficLightColor);
366 }
367 }
368
369
370
371
372 @Override
373 public SignalGroup clone()
374 {
375 return new SignalGroup(getId(), this.trafficLightIds.toSet(), this.offset, this.preGreen, this.green,
376 this.yellow);
377 }
378
379
380 @Override
381 public int hashCode()
382 {
383 final int prime = 31;
384 int result = 1;
385 result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
386 return result;
387 }
388
389
390 @Override
391 public boolean equals(Object obj)
392 {
393 if (this == obj)
394 {
395 return true;
396 }
397 if (obj == null)
398 {
399 return false;
400 }
401 if (getClass() != obj.getClass())
402 {
403 return false;
404 }
405 SignalGroup other = (SignalGroup) obj;
406 if (this.id == null)
407 {
408 if (other.id != null)
409 {
410 return false;
411 }
412 }
413 else if (!this.id.equals(other.id))
414 {
415 return false;
416 }
417 return true;
418 }
419
420
421
422
423 public final ImmutableList<TrafficLight> getTrafficLights()
424 {
425 return new ImmutableArrayList<>(this.trafficLights);
426 }
427
428
429
430
431 public final Duration getRed()
432 {
433 return this.red;
434 }
435
436
437
438
439 public final ImmutableSet<String> getTrafficLightIds()
440 {
441 return this.trafficLightIds;
442 }
443
444
445
446
447 public final Duration getOffset()
448 {
449 return this.offset;
450 }
451
452
453
454
455 public final Duration getPreGreen()
456 {
457 return this.preGreen;
458 }
459
460
461
462
463 public final Duration getGreen()
464 {
465 return this.green;
466 }
467
468
469
470
471 public final Duration getYellow()
472 {
473 return this.yellow;
474 }
475
476
477 @Override
478 public String toString()
479 {
480 return "SignalGroup [id=" + this.id + ", trafficLightIds=" + this.trafficLightIds + ", offset=" + this.offset
481 + ", preGreen=" + this.preGreen + ", green=" + this.green + ", yellow=" + this.yellow + "]";
482 }
483
484 }
485
486
487
488
489 public final Duration getCycleTime()
490 {
491 return this.cycleTime;
492 }
493
494
495
496
497 public final Duration getOffset()
498 {
499 return this.offset;
500 }
501
502
503
504
505 public final Set<SignalGroup> getSignalGroups()
506 {
507 return this.signalGroups;
508 }
509
510 }