View Javadoc
1   package org.opentrafficsim.trafficcontrol;
2   
3   import java.rmi.RemoteException;
4   import java.util.ArrayList;
5   import java.util.LinkedHashSet;
6   import java.util.List;
7   import java.util.Set;
8   
9   import nl.tudelft.simulation.dsol.SimRuntimeException;
10  import nl.tudelft.simulation.event.EventInterface;
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.ImmutableCollections;
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  /**
31   * Fixed time traffic light control.
32   * <p>
33   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
34   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
35   * <p>
36   * @version $Revision$, $LastChangedDate$, by $Author$, initial version 21 feb. 2019 <br>
37   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
38   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
39   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
40   */
41  public class FixedTimeController extends AbstractTrafficController
42  {
43  
44      /** */
45      private static final long serialVersionUID = 20190221L;
46  
47      /** Cycle time. */
48      private final Duration cycleTime;
49  
50      /** Offset. */
51      private final Duration offset;
52  
53      /** Signal groups, for cloning. */
54      private final Set<SignalGroup> signalGroups;
55  
56      /**
57       * Constructor for fixed time traffic controller.
58       * @param id String; id
59       * @param simulator OTSSimulatorInterface; simulator
60       * @param network Network; network
61       * @param offset Duration; off set from simulation start time
62       * @param cycleTime Duration; cycle time
63       * @param signalGroups Set&lt;SignalGroup&gt;; signal groups
64       * @throws SimRuntimeException simulator is past zero time
65       */
66      @SuppressWarnings({ "synthetic-access" })
67      public FixedTimeController(final String id, final OTSSimulatorInterface simulator, final Network network,
68              final Duration cycleTime, final Duration offset, final Set<SignalGroup> signalGroups) throws SimRuntimeException
69      {
70          super(id, simulator);
71          Throw.whenNull(simulator, "Simulator may not be null.");
72          Throw.whenNull(network, "Network may not be null.");
73          Throw.whenNull(cycleTime, "Cycle time may not be null.");
74          Throw.whenNull(offset, "Offset may not be null.");
75          Throw.whenNull(signalGroups, "Signal groups may not be null.");
76          Throw.when(cycleTime.le0(), IllegalArgumentException.class, "Cycle time must be positive.");
77          Throw.when(signalGroups.isEmpty(), IllegalArgumentException.class, "Signal groups may not be empty.");
78          for (SignalGroup signalGroup1 : signalGroups)
79          {
80              for (SignalGroup signalGroup2 : signalGroups)
81              {
82                  if (!signalGroup1.equals(signalGroup2))
83                  {
84                      Throw.when(!ImmutableCollections.disjoint(signalGroup1.trafficLightIds, signalGroup2.trafficLightIds),
85                              IllegalArgumentException.class,
86                              "A traffic light is in both signal group %s and signal group %s.", signalGroup1.getId(),
87                              signalGroup2.getId());
88                  }
89              }
90          }
91          this.cycleTime = cycleTime;
92          this.offset = offset;
93          this.signalGroups = signalGroups;
94          simulator.scheduleEventAbs(Time.ZERO, this, this, "setup", new Object[] { simulator, network });
95      }
96  
97      /**
98       * Initiates all traffic control events.
99       * @param simulator OTSSimulatorInterface; simulator
100      * @param network network
101      * @throws SimRuntimeException when traffic light does not exist in the network
102      */
103     @SuppressWarnings("unused")
104     private void setup(final OTSSimulatorInterface simulator, final Network network) throws SimRuntimeException
105     {
106         for (SignalGroup signalGroup : this.signalGroups)
107         {
108             signalGroup.startup(this.offset, this.cycleTime, simulator, network);
109         }
110     }
111 
112     /** {@inheritDoc} */
113     @Override
114     public void notify(final EventInterface event) throws RemoteException
115     {
116         // nothing
117     }
118 
119     /** {@inheritDoc} */
120     @Override
121     public InvisibleObjectInterface clone(final OTSSimulatorInterface newSimulator, final Network newNetwork)
122             throws NetworkException
123     {
124         Set<SignalGroup> signalGroupsCloned = new LinkedHashSet<>();
125         for (SignalGroup signalGroup : this.signalGroups)
126         {
127             signalGroupsCloned.add(signalGroup.clone());
128         }
129         try
130         {
131             return new FixedTimeController(getId(), newSimulator, newNetwork, this.cycleTime, this.offset,
132                     signalGroupsCloned);
133         }
134         catch (SimRuntimeException exception)
135         {
136             throw new RuntimeException("Cloning using a simulator that is not at time 0.");
137         }
138     }
139 
140     /** {@inheritDoc} */
141     @Override
142     public String getFullId()
143     {
144         return getId();
145     }
146 
147     /** {@inheritDoc} */
148     @Override
149     public String toString()
150     {
151         return "FixedTimeController [cycleTime=" + this.cycleTime + ", offset=" + this.offset + ", signalGroups="
152                 + this.signalGroups + ", full id=" + this.getFullId() + "]";
153     }
154 
155     /**
156      * <p>
157      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
158      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
159      * <p>
160      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 21 feb. 2019 <br>
161      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
162      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
163      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
164      */
165     public static class SignalGroup implements Identifiable
166     {
167 
168         /** Id. */
169         private final String id;
170 
171         /** Traffic light ids. */
172         private final ImmutableSet<String> trafficLightIds;
173 
174         /** Offset from start of cycle. */
175         private final Duration offset;
176 
177         /** Pre-green duration. */
178         private final Duration preGreen;
179 
180         /** Green duration. */
181         private final Duration green;
182 
183         /** Yellow duration. */
184         private final Duration yellow;
185 
186         // The following properties are remembered for the updates after the startup
187 
188         /** Traffic light objects. */
189         private List<TrafficLight> trafficLights;
190 
191         /** Simulator. */
192         private OTSSimulatorInterface simulator;
193 
194         /** Red time. */
195         private Duration red;
196 
197         /**
198          * Constructor without pre-green duration.
199          * @param id String; id
200          * @param trafficLightIds List&lt;String&gt;; traffic light ids
201          * @param offset Duration; offset from start of cycle
202          * @param green Duration; green duration
203          * @param yellow Duration; yellow duration
204          */
205         public SignalGroup(final String id, final Set<String> trafficLightIds, final Duration offset, final Duration green,
206                 final Duration yellow)
207         {
208             this(id, trafficLightIds, offset, Duration.ZERO, green, yellow);
209         }
210 
211         /**
212          * Constructor with pre-green duration.
213          * @param id String; id
214          * @param trafficLightIds List&lt;String&gt;; traffic light ids
215          * @param offset Duration; offset from start of cycle
216          * @param preGreen Duration; pre-green duration
217          * @param green Duration; green duration
218          * @param yellow Duration; yellow duration
219          */
220         public SignalGroup(final String id, final Set<String> trafficLightIds, final Duration offset,
221                 final Duration preGreen, final Duration green, final Duration yellow)
222         {
223             Throw.whenNull(id, "Id may not be null.");
224             Throw.whenNull(trafficLightIds, "Traffic light ids may not be null.");
225             Throw.whenNull(offset, "Offset may not be null.");
226             Throw.whenNull(preGreen, "Pre-green may not be null.");
227             Throw.when(preGreen.lt(Duration.ZERO), IllegalArgumentException.class, "Pre green duration may not be negative");
228             Throw.whenNull(green, "Green may not be null.");
229             Throw.when(green.lt(Duration.ZERO), IllegalArgumentException.class, "Green duration may not be negative");
230             Throw.whenNull(yellow, "Yellow may not be null.");
231             Throw.when(yellow.lt(Duration.ZERO), IllegalArgumentException.class, "Yellow duration may not be negative");
232             Throw.when(trafficLightIds.isEmpty(), IllegalArgumentException.class, "Traffic light ids may not be empty.");
233             this.id = id;
234             this.trafficLightIds = new ImmutableHashSet<>(trafficLightIds, Immutable.COPY);
235             this.offset = offset;
236             this.preGreen = preGreen;
237             this.green = green;
238             this.yellow = yellow;
239         }
240 
241         /**
242          * @return id.
243          */
244         @Override
245         public String getId()
246         {
247             return this.id;
248         }
249 
250         /**
251          * @param controllerOffset
252          * @param cycleTime
253          * @param sim
254          * @param network
255          * @throws SimRuntimeException when traffic light does not exist in the network
256          */
257         public void startup(final Duration controllerOffset, final Duration cycleTime, final OTSSimulatorInterface sim,
258                 final Network network) throws SimRuntimeException
259         {
260             this.simulator = sim;
261             double totalOffsetSI = this.offset.si + controllerOffset.si;
262             while (totalOffsetSI < 0.0)
263             {
264                 totalOffsetSI += cycleTime.si;
265             }
266             Duration totalOffset = Duration.createSI(totalOffsetSI % cycleTime.si);
267             this.red = cycleTime.minus(this.preGreen).minus(this.green).minus(this.yellow);
268             Throw.when(this.red.lt0(), IllegalArgumentException.class, "Cycle time shorter than sum of non-red times.");
269 
270             this.trafficLights = new ArrayList<>();
271             ImmutableMap<String, TrafficLight> trafficLightObjects = network.getObjectMap(TrafficLight.class);
272             for (String trafficLightId : this.trafficLightIds)
273             {
274                 TrafficLight trafficLight = trafficLightObjects.get(trafficLightId);
275                 if (null == trafficLight) // Traffic light not found using id; try to find it by full id
276                 {
277                     trafficLight = trafficLightObjects.get(network.getId() + "." + trafficLightId);
278                 }
279                 Throw.when(trafficLight == null, SimRuntimeException.class, "Traffic light \"" + trafficLightId
280                         + "\" in fixed time controller could not be found in network " + network.getId() + ".");
281                 this.trafficLights.add(trafficLight);
282             }
283 
284             Duration inCycleTime = Duration.createSI(totalOffset.si - cycleTime.si);
285             while (inCycleTime.si < 0)
286             {
287                 inCycleTime = inCycleTime.plus(cycleTime);
288             }
289             Duration duration;
290             if (inCycleTime.si < this.preGreen.si)
291             {
292                 setTrafficLights(TrafficLightColor.PREGREEN);
293                 duration = this.preGreen.minus(inCycleTime);
294             }
295             else if (inCycleTime.si < this.preGreen.si + this.green.si)
296             {
297                 setTrafficLights(TrafficLightColor.GREEN);
298                 duration = this.preGreen.plus(this.green).minus(inCycleTime);
299             }
300             else if (inCycleTime.si < this.preGreen.si + this.green.si + this.yellow.si)
301             {
302                 setTrafficLights(TrafficLightColor.YELLOW);
303                 duration = this.preGreen.plus(this.green).plus(this.yellow).minus(inCycleTime);
304             }
305             else
306             {
307                 setTrafficLights(TrafficLightColor.RED);
308                 duration = cycleTime.minus(inCycleTime);
309             }
310             this.simulator.scheduleEventRel(duration, this, this, "updateColors", null);
311         }
312 
313         /**
314          * Updates the color of the traffic lights.
315          */
316         @SuppressWarnings("unused")
317         private void updateColors()
318         {
319             Duration duration = Duration.ZERO;
320             TrafficLightColor color = this.trafficLights.get(0).getTrafficLightColor();
321             while (duration.le0())
322             {
323                 switch (color)
324                 {
325                     case PREGREEN:
326                         color = TrafficLightColor.GREEN;
327                         duration = this.green;
328                         break;
329                     case GREEN:
330                         color = TrafficLightColor.YELLOW;
331                         duration = this.yellow;
332                         break;
333                     case YELLOW:
334                         color = TrafficLightColor.RED;
335                         duration = this.red;
336                         break;
337                     case RED:
338                         color = TrafficLightColor.PREGREEN;
339                         duration = this.preGreen;
340                         break;
341                     default:
342                         throw new RuntimeException("Cannot happen.");
343                 }
344             }
345             setTrafficLights(color);
346             try
347             {
348                 this.simulator.scheduleEventRel(duration, this, this, "updateColors", null);
349             }
350             catch (SimRuntimeException exception)
351             {
352                 // cannot happen; we check all durations for consistency
353                 throw new RuntimeException(exception);
354             }
355         }
356 
357         /**
358          * Change the color of our traffic lights.
359          * @param trafficLightColor the new traffic light color
360          */
361         private void setTrafficLights(final TrafficLightColor trafficLightColor)
362         {
363             for (TrafficLight trafficLight : this.trafficLights)
364             {
365                 trafficLight.setTrafficLightColor(trafficLightColor);
366             }
367         }
368 
369         /**
370          * Clones the object for a cloned simulation.
371          */
372         @Override
373         public SignalGroup clone()
374         {
375             return new SignalGroup(getId(), this.trafficLightIds.toSet(), this.offset, this.preGreen, this.green,
376                     this.yellow);
377         }
378 
379         /** {@inheritDoc} */
380         @Override
381         public int hashCode()
382         {
383             final int prime = 31;
384             int result = 1;
385             result = prime * result + ((this.id == null) ? 0 : this.id.hashCode());
386             return result;
387         }
388 
389         /** {@inheritDoc} */
390         @Override
391         public boolean equals(Object obj)
392         {
393             if (this == obj)
394             {
395                 return true;
396             }
397             if (obj == null)
398             {
399                 return false;
400             }
401             if (getClass() != obj.getClass())
402             {
403                 return false;
404             }
405             SignalGroup other = (SignalGroup) obj;
406             if (this.id == null)
407             {
408                 if (other.id != null)
409                 {
410                     return false;
411                 }
412             }
413             else if (!this.id.equals(other.id))
414             {
415                 return false;
416             }
417             return true;
418         }
419 
420         /**
421          * @return trafficLights.
422          */
423         public final ImmutableList<TrafficLight> getTrafficLights()
424         {
425             return new ImmutableArrayList<>(this.trafficLights);
426         }
427 
428         /**
429          * @return red.
430          */
431         public final Duration getRed()
432         {
433             return this.red;
434         }
435 
436         /**
437          * @return trafficLightIds.
438          */
439         public final ImmutableSet<String> getTrafficLightIds()
440         {
441             return this.trafficLightIds;
442         }
443 
444         /**
445          * @return offset.
446          */
447         public final Duration getOffset()
448         {
449             return this.offset;
450         }
451 
452         /**
453          * @return preGreen.
454          */
455         public final Duration getPreGreen()
456         {
457             return this.preGreen;
458         }
459 
460         /**
461          * @return green.
462          */
463         public final Duration getGreen()
464         {
465             return this.green;
466         }
467 
468         /**
469          * @return yellow.
470          */
471         public final Duration getYellow()
472         {
473             return this.yellow;
474         }
475 
476         /** {@inheritDoc} */
477         @Override
478         public String toString()
479         {
480             return "SignalGroup [id=" + this.id + ", trafficLightIds=" + this.trafficLightIds + ", offset=" + this.offset
481                     + ", preGreen=" + this.preGreen + ", green=" + this.green + ", yellow=" + this.yellow + "]";
482         }
483 
484     }
485 
486     /**
487      * @return cycleTime.
488      */
489     public final Duration getCycleTime()
490     {
491         return this.cycleTime;
492     }
493 
494     /**
495      * @return offset.
496      */
497     public final Duration getOffset()
498     {
499         return this.offset;
500     }
501 
502     /**
503      * @return signalGroups.
504      */
505     public final Set<SignalGroup> getSignalGroups()
506     {
507         return this.signalGroups;
508     }
509 
510 }