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 }