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 @Override
265 public void notify(final Event event) throws RemoteException
266 {
267
268 }
269
270 @Override
271 public String getFullId()
272 {
273 return getId();
274 }
275
276
277
278
279 public final Duration getCycleTime()
280 {
281 return this.cycleTime;
282 }
283
284
285
286
287 public final Duration getOffset()
288 {
289 return this.offset;
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.instantiateSI(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, this, "updateColors", null);
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 RuntimeException("Cannot happen.");
509 }
510 }
511 setTrafficLights(color);
512 this.simulator.scheduleEventRel(duration, this, "updateColors", null);
513 }
514 catch (SimRuntimeException exception)
515 {
516
517 throw new RuntimeException(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 public final ImmutableList<TrafficLight> getTrafficLights()
577 {
578 return new ImmutableArrayList<>(this.trafficLights);
579 }
580
581
582
583
584 public final Duration getRed()
585 {
586 return this.red;
587 }
588
589
590
591
592 public final ImmutableSet<String> getTrafficLightIds()
593 {
594 return this.trafficLightIds;
595 }
596
597
598
599
600 public final Duration getOffset()
601 {
602 return this.offset;
603 }
604
605
606
607
608 public final Duration getPreGreen()
609 {
610 return this.preGreen;
611 }
612
613
614
615
616 public final Duration getGreen()
617 {
618 return this.green;
619 }
620
621
622
623
624 public final Duration getYellow()
625 {
626 return this.yellow;
627 }
628
629
630
631
632
633 public TrafficLightColor getCurrentColor()
634 {
635 return this.currentColor;
636 }
637
638 @Override
639 public String toString()
640 {
641 return "SignalGroup [id=" + this.id + ", trafficLightIds=" + this.trafficLightIds + ", offset=" + this.offset
642 + ", preGreen=" + this.preGreen + ", green=" + this.green + ", yellow=" + this.yellow + "currentColor="
643 + this.currentColor + "]";
644 }
645
646 }
647
648
649
650
651
652 class Flank implements Comparable<Flank>
653 {
654
655 private final double offset;
656
657
658 private final TrafficLightColor newColor;
659
660
661
662
663
664
665 Flank(final double offset, final TrafficLightColor newColor)
666 {
667 this.offset = offset;
668 this.newColor = newColor;
669 }
670
671
672
673
674
675 public double getOffset()
676 {
677 return this.offset;
678 }
679
680
681
682
683
684 public TrafficLightColor getTrafficLightColor()
685 {
686 return this.newColor;
687 }
688
689 @Override
690 public String toString()
691 {
692 return "Flank [offset=" + this.offset + ", newColor=" + this.newColor + "]";
693 }
694
695
696 private static final double COMPARE_MARGIN = 0.01;
697
698 @Override
699 public int compareTo(final Flank o)
700 {
701 double deltaOffset = this.offset - o.offset;
702 if (Math.abs(deltaOffset) < COMPARE_MARGIN)
703 {
704 deltaOffset = 0;
705 }
706 if (deltaOffset > 0)
707 {
708 return 1;
709 }
710 if (deltaOffset < 0)
711 {
712 return -1;
713 }
714 if (TrafficLightColor.GREEN == this.newColor && TrafficLightColor.GREEN != o.newColor)
715 {
716 return -1;
717 }
718 if (TrafficLightColor.GREEN == o.newColor && TrafficLightColor.GREEN != this.newColor)
719 {
720 return 1;
721 }
722 return 0;
723 }
724
725 }
726
727 }