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