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