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