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