1 package org.opentrafficsim.trafficcontrol;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.LinkedHashMap;
6 import java.util.LinkedHashSet;
7 import java.util.List;
8 import java.util.Map;
9 import java.util.Set;
10
11 import org.djunits.value.vdouble.scalar.Duration;
12 import org.djutils.base.Identifiable;
13 import org.djutils.event.Event;
14 import org.djutils.exceptions.Throw;
15 import org.djutils.immutablecollections.Immutable;
16 import org.djutils.immutablecollections.ImmutableArrayList;
17 import org.djutils.immutablecollections.ImmutableHashSet;
18 import org.djutils.immutablecollections.ImmutableList;
19 import org.djutils.immutablecollections.ImmutableMap;
20 import org.djutils.immutablecollections.ImmutableSet;
21 import org.opentrafficsim.base.OtsRuntimeException;
22 import org.opentrafficsim.base.logger.Logger;
23 import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
24 import org.opentrafficsim.core.network.Network;
25 import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight;
26 import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLightColor;
27
28 import nl.tudelft.simulation.dsol.SimRuntimeException;
29
30
31
32
33
34
35
36
37
38
39
40 public class FixedTimeController extends AbstractTrafficController
41 {
42
43
44 private final Duration cycleTime;
45
46
47 private final Duration offset;
48
49
50 private final Set<SignalGroup> signalGroups;
51
52
53
54
55
56
57
58
59
60
61
62 public FixedTimeController(final String id, final OtsSimulatorInterface simulator, final Network network,
63 final Duration cycleTime, final Duration offset, final Set<SignalGroup> signalGroups) throws SimRuntimeException
64 {
65 super(id, simulator);
66 Throw.whenNull(simulator, "Simulator may not be null.");
67 Throw.whenNull(network, "Network may not be null.");
68 Throw.whenNull(cycleTime, "Cycle time may not be null.");
69 Throw.whenNull(offset, "Offset may not be null.");
70 Throw.whenNull(signalGroups, "Signal groups may not be null.");
71 Throw.when(cycleTime.le0(), IllegalArgumentException.class, "Cycle time must be positive.");
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87 this.cycleTime = cycleTime;
88 this.offset = offset;
89 this.signalGroups = new LinkedHashSet<>(signalGroups);
90 mergeGreenPhasesInNewSignalGroups();
91
92 simulator.scheduleEventAbs(Duration.ZERO, () -> setup(simulator, network));
93 }
94
95
96
97
98
99
100 private void mergeGreenPhasesInNewSignalGroups()
101 {
102
103 Map<String, List<SignalGroup>> signalGroupsOfTrafficLight = new LinkedHashMap<>();
104 for (SignalGroup sg : this.signalGroups)
105 {
106 for (String trafficLightId : sg.getTrafficLightIds())
107 {
108 List<SignalGroup> sgList = signalGroupsOfTrafficLight.get(trafficLightId);
109 if (null == sgList)
110 {
111 sgList = new ArrayList<>();
112 signalGroupsOfTrafficLight.put(trafficLightId, sgList);
113 }
114 sgList.add(sg);
115 }
116 }
117
118 int nextNumber = 0;
119 for (String trafficLightId : signalGroupsOfTrafficLight.keySet())
120 {
121 List<SignalGroup> sgList = signalGroupsOfTrafficLight.get(trafficLightId);
122 if (sgList.size() > 1)
123 {
124
125 List<Flank> flanks = new ArrayList<>();
126 for (SignalGroup sg : sgList)
127 {
128 double sgOffset = sg.getOffset().si;
129 double preGreenDuration = sg.getPreGreen().si;
130 if (preGreenDuration > 0)
131 {
132 flanks.add(new Flank(sgOffset % this.cycleTime.si, TrafficLightColor.PREGREEN));
133 sgOffset += preGreenDuration;
134 }
135 flanks.add(new Flank(sgOffset % this.cycleTime.si, TrafficLightColor.GREEN));
136 sgOffset += sg.getGreen().si;
137 double yellowDuration = sg.getYellow().si;
138 if (yellowDuration > 0)
139 {
140 flanks.add(new Flank(sgOffset % this.cycleTime.si, TrafficLightColor.YELLOW));
141 sgOffset += yellowDuration;
142 }
143 flanks.add(new Flank(sgOffset % this.cycleTime.si, TrafficLightColor.RED));
144 }
145 Collections.sort(flanks);
146 boolean combined = false;
147 int greenCount = 0;
148 for (int index = 0; index < flanks.size(); index++)
149 {
150 Flank flank = flanks.get(index);
151 TrafficLightColor nextColor = flank.getTrafficLightColor();
152 if (TrafficLightColor.GREEN == nextColor)
153 {
154 greenCount++;
155 if (greenCount > 1)
156 {
157 flanks.remove(index);
158 index--;
159 combined = true;
160 continue;
161 }
162 }
163 else if (TrafficLightColor.YELLOW == nextColor)
164 {
165 if (greenCount > 1)
166 {
167 flanks.remove(index);
168 index--;
169 continue;
170 }
171 }
172 else if (TrafficLightColor.RED == nextColor)
173 {
174 greenCount--;
175 if (greenCount > 0)
176 {
177 flanks.remove(index);
178 index--;
179 continue;
180 }
181 }
182 }
183 if (combined)
184 {
185
186 String newSignalGroupName = "CombinedSignalGroups_";
187
188 for (SignalGroup sg : sgList)
189 {
190 Logger.ots().trace("Reducing {}", sg);
191 newSignalGroupName = newSignalGroupName + "_" + sg.getId();
192 Set<String> trafficLightIds = new LinkedHashSet<>();
193 for (String tlId : sg.getTrafficLightIds())
194 {
195 if (!tlId.equals(trafficLightId))
196 {
197 trafficLightIds.add(tlId);
198 }
199 }
200 this.signalGroups.remove(sg);
201 if (trafficLightIds.size() > 0)
202 {
203 SignalGroup newSignalGroup = new SignalGroup(sg.getId(), trafficLightIds, sg.getOffset(),
204 sg.getPreGreen(), sg.getGreen(), sg.getYellow());
205 this.signalGroups.add(newSignalGroup);
206 }
207 }
208
209 Duration sgOffset = null;
210 Duration preGreen = Duration.ZERO;
211 Duration green = null;
212 Duration yellow = Duration.ZERO;
213 double cumulativeOffset = 0;
214 for (int index = 0; index < flanks.size(); index++)
215 {
216 Flank flank = flanks.get(index);
217 if (null == sgOffset)
218 {
219 sgOffset = Duration.ofSI(flank.getOffset());
220 }
221 if (TrafficLightColor.GREEN == flank.getTrafficLightColor())
222 {
223 preGreen = Duration.ofSI(flank.getOffset() - sgOffset.si);
224 }
225 if (TrafficLightColor.YELLOW == flank.getTrafficLightColor())
226 {
227 green = Duration.ofSI(flank.getOffset() - cumulativeOffset);
228 }
229 if (TrafficLightColor.RED == flank.getTrafficLightColor())
230 {
231 nextNumber++;
232 yellow = Duration.ofSI(flank.getOffset() - cumulativeOffset);
233 Set<String> trafficLightIds = new LinkedHashSet<>(1);
234 trafficLightIds.add(trafficLightId);
235 SignalGroup newSignalGroup = new SignalGroup(newSignalGroupName + "_" + nextNumber, trafficLightIds,
236 sgOffset, preGreen, green, yellow);
237 this.signalGroups.add(newSignalGroup);
238 }
239 cumulativeOffset = flank.getOffset();
240 }
241 }
242 }
243 }
244 }
245
246
247
248
249
250
251
252 @SuppressWarnings("unused")
253 private void setup(final OtsSimulatorInterface simulator, final Network network) throws SimRuntimeException
254 {
255 for (SignalGroup signalGroup : this.signalGroups)
256 {
257 signalGroup.startup(this.offset, this.cycleTime, simulator, network);
258 }
259 }
260
261 @Override
262 public void notify(final Event event)
263 {
264
265 }
266
267 @Override
268 public String getFullId()
269 {
270 return getId();
271 }
272
273
274
275
276
277 public final Duration getCycleTime()
278 {
279 return this.cycleTime;
280 }
281
282
283
284
285
286 public final Duration getOffset()
287 {
288 return this.offset;
289 }
290
291
292
293
294
295 public final Set<SignalGroup> getSignalGroups()
296 {
297 return this.signalGroups;
298 }
299
300 @Override
301 public String toString()
302 {
303 return "FixedTimeController [cycleTime=" + this.cycleTime + ", offset=" + this.offset + ", signalGroups="
304 + this.signalGroups + ", full id=" + this.getFullId() + "]";
305 }
306
307
308
309
310
311
312
313
314
315
316
317
318 public static class SignalGroup implements Identifiable
319 {
320
321
322 private final String id;
323
324
325 private final ImmutableSet<String> trafficLightIds;
326
327
328 private final Duration offset;
329
330
331 private final Duration preGreen;
332
333
334 private final Duration green;
335
336
337 private final Duration yellow;
338
339
340 private TrafficLightColor currentColor = TrafficLightColor.RED;
341
342
343
344
345 private List<TrafficLight> trafficLights;
346
347
348 private OtsSimulatorInterface simulator;
349
350
351 private Duration red;
352
353
354
355
356
357
358
359
360
361 public SignalGroup(final String id, final Set<String> trafficLightIds, final Duration offset, final Duration green,
362 final Duration yellow)
363 {
364 this(id, trafficLightIds, offset, Duration.ZERO, green, yellow);
365 }
366
367
368
369
370
371
372
373
374
375
376 public SignalGroup(final String id, final Set<String> trafficLightIds, final Duration offset, final Duration preGreen,
377 final Duration green, final Duration yellow)
378 {
379 Throw.whenNull(id, "Id may not be null.");
380 Throw.whenNull(trafficLightIds, "Traffic light ids may not be null.");
381 Throw.whenNull(offset, "Offset may not be null.");
382 Throw.whenNull(preGreen, "Pre-green may not be null.");
383 Throw.when(preGreen.lt(Duration.ZERO), IllegalArgumentException.class, "Pre green duration may not be negative");
384 Throw.whenNull(green, "Green may not be null.");
385 Throw.when(green.lt(Duration.ZERO), IllegalArgumentException.class, "Green duration may not be negative");
386 Throw.whenNull(yellow, "Yellow may not be null.");
387 Throw.when(yellow.lt(Duration.ZERO), IllegalArgumentException.class, "Yellow duration may not be negative");
388 Throw.when(trafficLightIds.isEmpty(), IllegalArgumentException.class, "Traffic light ids may not be empty.");
389 this.id = id;
390 this.trafficLightIds = new ImmutableHashSet<>(trafficLightIds, Immutable.COPY);
391 this.offset = offset;
392 this.preGreen = preGreen;
393 this.green = green;
394 this.yellow = yellow;
395 }
396
397
398
399
400
401 @Override
402 public String getId()
403 {
404 return this.id;
405 }
406
407
408
409
410
411
412
413
414
415
416 public void startup(final Duration controllerOffset, final Duration cycleTime, final OtsSimulatorInterface theSimulator,
417 final Network network) throws SimRuntimeException
418 {
419 this.simulator = theSimulator;
420 double totalOffsetSI = this.offset.si + controllerOffset.si;
421 while (totalOffsetSI < 0.0)
422 {
423 totalOffsetSI += cycleTime.si;
424 }
425 Duration totalOffset = Duration.ofSI(totalOffsetSI % cycleTime.si);
426 this.red = cycleTime.minus(this.preGreen).minus(this.green).minus(this.yellow);
427 Throw.when(this.red.lt0(), IllegalArgumentException.class, "Cycle time shorter than sum of non-red times.");
428
429 this.trafficLights = new ArrayList<>();
430 ImmutableMap<String, TrafficLight> trafficLightObjects = network.getObjectMap(TrafficLight.class);
431 for (String trafficLightId : this.trafficLightIds)
432 {
433 TrafficLight trafficLight = trafficLightObjects.get(trafficLightId);
434 if (null == trafficLight)
435 {
436
437 trafficLight = trafficLightObjects.get(network.getId() + "." + trafficLightId);
438 }
439 Throw.when(trafficLight == null, SimRuntimeException.class, "Traffic light \"" + trafficLightId
440 + "\" in fixed time controller could not be found in network " + network.getId() + ".");
441 this.trafficLights.add(trafficLight);
442 }
443 Duration inCycleTime = Duration.ZERO.minus(totalOffset);
444 while (inCycleTime.si < 0)
445 {
446 inCycleTime = inCycleTime.plus(cycleTime);
447 }
448 Duration duration = null;
449 if (inCycleTime.ge(this.preGreen.plus(this.green).plus(this.yellow)))
450 {
451 this.currentColor = TrafficLightColor.RED;
452 duration = cycleTime.minus(inCycleTime);
453 }
454 else if (inCycleTime.lt(this.preGreen))
455 {
456 this.currentColor = TrafficLightColor.PREGREEN;
457 duration = this.preGreen.minus(inCycleTime);
458 }
459 else if (inCycleTime.lt(this.preGreen.plus(this.green)))
460 {
461 this.currentColor = TrafficLightColor.GREEN;
462 duration = this.preGreen.plus(this.green).minus(inCycleTime);
463 }
464 else if (inCycleTime.lt(this.preGreen.plus(this.green).plus(this.yellow)))
465 {
466 this.currentColor = TrafficLightColor.YELLOW;
467 duration = this.preGreen.plus(this.green).plus(this.yellow).minus(inCycleTime);
468 }
469 else
470 {
471 throw new SimRuntimeException("Cannot determine initial state of signal group " + this);
472 }
473 setTrafficLights(this.currentColor);
474 this.simulator.scheduleEventRel(duration, () -> updateColors());
475 }
476
477
478
479
480 @SuppressWarnings("unused")
481 private void updateColors()
482 {
483 try
484 {
485 Duration duration = Duration.ZERO;
486 TrafficLightColor color = this.currentColor;
487 while (duration.le0())
488 {
489 switch (color)
490 {
491 case PREGREEN:
492 color = TrafficLightColor.GREEN;
493 duration = this.green;
494 break;
495 case GREEN:
496 color = TrafficLightColor.YELLOW;
497 duration = this.yellow;
498 break;
499 case YELLOW:
500 color = TrafficLightColor.RED;
501 duration = this.red;
502 break;
503 case RED:
504 color = TrafficLightColor.PREGREEN;
505 duration = this.preGreen;
506 break;
507 default:
508 throw new OtsRuntimeException("Cannot happen.");
509 }
510 }
511 setTrafficLights(color);
512 this.simulator.scheduleEventRel(duration, () -> updateColors());
513 }
514 catch (SimRuntimeException exception)
515 {
516
517 throw new OtsRuntimeException(exception);
518 }
519 }
520
521
522
523
524
525 private void setTrafficLights(final TrafficLightColor trafficLightColor)
526 {
527 this.currentColor = trafficLightColor;
528 for (TrafficLight trafficLight : this.trafficLights)
529 {
530 trafficLight.setTrafficLightColor(trafficLightColor);
531 }
532 }
533
534 @Override
535 public int hashCode()
536 {
537 final int prime = 31;
538 int result = 1;
539 result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
540 return result;
541 }
542
543 @Override
544 public boolean equals(final Object obj)
545 {
546 if (this == obj)
547 {
548 return true;
549 }
550 if (obj == null)
551 {
552 return false;
553 }
554 if (getClass() != obj.getClass())
555 {
556 return false;
557 }
558 SignalGroup other = (SignalGroup) obj;
559 if (this.id == null)
560 {
561 if (other.id != null)
562 {
563 return false;
564 }
565 }
566 else if (!this.id.equals(other.id))
567 {
568 return false;
569 }
570 return true;
571 }
572
573
574
575
576
577 public final ImmutableList<TrafficLight> getTrafficLights()
578 {
579 return new ImmutableArrayList<>(this.trafficLights);
580 }
581
582
583
584
585
586 public final Duration getRed()
587 {
588 return this.red;
589 }
590
591
592
593
594
595 public final ImmutableSet<String> getTrafficLightIds()
596 {
597 return this.trafficLightIds;
598 }
599
600
601
602
603
604 public final Duration getOffset()
605 {
606 return this.offset;
607 }
608
609
610
611
612
613 public final Duration getPreGreen()
614 {
615 return this.preGreen;
616 }
617
618
619
620
621
622 public final Duration getGreen()
623 {
624 return this.green;
625 }
626
627
628
629
630
631 public final Duration getYellow()
632 {
633 return this.yellow;
634 }
635
636
637
638
639
640 public TrafficLightColor getCurrentColor()
641 {
642 return this.currentColor;
643 }
644
645 @Override
646 public String toString()
647 {
648 return "SignalGroup [id=" + this.id + ", trafficLightIds=" + this.trafficLightIds + ", offset=" + this.offset
649 + ", preGreen=" + this.preGreen + ", green=" + this.green + ", yellow=" + this.yellow + "currentColor="
650 + this.currentColor + "]";
651 }
652
653 }
654
655
656
657
658
659 class Flank implements Comparable<Flank>
660 {
661
662 private final double offset;
663
664
665 private final TrafficLightColor newColor;
666
667
668
669
670
671
672 Flank(final double offset, final TrafficLightColor newColor)
673 {
674 this.offset = offset;
675 this.newColor = newColor;
676 }
677
678
679
680
681
682 public double getOffset()
683 {
684 return this.offset;
685 }
686
687
688
689
690
691 public TrafficLightColor getTrafficLightColor()
692 {
693 return this.newColor;
694 }
695
696 @Override
697 public String toString()
698 {
699 return "Flank [offset=" + this.offset + ", newColor=" + this.newColor + "]";
700 }
701
702
703 private static final double COMPARE_MARGIN = 0.01;
704
705 @Override
706 public int compareTo(final Flank o)
707 {
708 double deltaOffset = this.offset - o.offset;
709 if (Math.abs(deltaOffset) < COMPARE_MARGIN)
710 {
711 deltaOffset = 0;
712 }
713 if (deltaOffset > 0)
714 {
715 return 1;
716 }
717 if (deltaOffset < 0)
718 {
719 return -1;
720 }
721 if (TrafficLightColor.GREEN == this.newColor && TrafficLightColor.GREEN != o.newColor)
722 {
723 return -1;
724 }
725 if (TrafficLightColor.GREEN == o.newColor && TrafficLightColor.GREEN != this.newColor)
726 {
727 return 1;
728 }
729 return 0;
730 }
731
732 }
733
734 }