View Javadoc
1   package org.opentrafficsim.trafficcontrol.trafcod;
2   
3   import java.awt.BorderLayout;
4   import java.awt.Color;
5   import java.awt.Component;
6   import java.awt.Dimension;
7   import java.awt.Graphics2D;
8   import java.awt.event.ActionEvent;
9   import java.awt.event.ActionListener;
10  import java.awt.image.BufferedImage;
11  import java.util.ArrayList;
12  import java.util.Comparator;
13  import java.util.LinkedHashMap;
14  import java.util.LinkedHashSet;
15  import java.util.List;
16  import java.util.Map;
17  import java.util.Set;
18  
19  import javax.swing.BoxLayout;
20  import javax.swing.ImageIcon;
21  import javax.swing.JCheckBox;
22  import javax.swing.JFrame;
23  import javax.swing.JLabel;
24  import javax.swing.JPanel;
25  import javax.swing.JScrollPane;
26  import javax.swing.SwingUtilities;
27  
28  import org.djutils.exceptions.Throw;
29  import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLightException;
30  import org.opentrafficsim.trafficcontrol.TrafficController;
31  
32  /**
33   * Functions that can draw a schematic diagram of an intersection given the list of traffic streams. The traffic stream numbers
34   * must follow the Dutch conventions.
35   * <p>
36   * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
37   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
38   * <p>
39   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Dec 1, 2016 <br>
40   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
41   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
42   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
43   */
44  public class Diagram
45  {
46      /** Numbering of the lateral objects/positions from the median to the shoulder. */
47      /** Central divider. */
48      static final int DIVIDER_1 = 0;
49  
50      /** Left turn area on roundabout. */
51      static final int CAR_ROUNDABOUT_LEFT = 1;
52  
53      /** Public transit between divider and left turn lane. */
54      static final int PT_DIV_L = 3;
55  
56      /** Divider between center public transit and left turn lane. */
57      static final int DIVIDER_2 = 4;
58  
59      /** Left turn lane(s). */
60      static final int CAR_LEFT = 5;
61  
62      /** No turn (center) lane(s). */
63      static final int CAR_CENTER = 7;
64  
65      /** Right turn lane(s). */
66      static final int CAR_RIGHT = 9;
67  
68      /** Divider between right turn lane and bicycle lane. */
69      static final int DIVIDER_3 = 10;
70  
71      /** Public transit between right turn lane and bicycle lane. */
72      static final int PT_RIGHT_BICYCLE = 11;
73  
74      /** Divider. */
75      static final int DIVIDER_4 = 12;
76  
77      /** Bicycle lane. */
78      static final int BICYCLE = 13;
79  
80      /** Divider. */
81      static final int DIVIDER_5 = 14;
82  
83      /** Public transit between bicycle lane and right sidewalk. */
84      static final int PT_BICYCLE_SIDEWALK = 15;
85  
86      /** Divider. */
87      static final int DIVIDER_6 = 16;
88  
89      /** Sidewalk. */
90      static final int SIDEWALK = 17;
91  
92      /** Divider. */
93      static final int DIVIDER_7 = 18;
94  
95      /** Public transit right of right sidewalk. */
96      static final int PT_SIDEWALK_SHOULDER = 19;
97  
98      /** Shoulder right of right sidewalk. */
99      static final int SHOULDER = 20;
100 
101     /** Boundary of schematic intersection. */
102     static final int BOUNDARY = 21;
103 
104     /** The streams crossing the intersection. */
105     private final List<Short> streams;
106 
107     /** The routes through the intersection. */
108     private final Map<Short, XYPair[]> routes = new LinkedHashMap<>();
109 
110     /**
111      * Construct a new diagram.
112      * @param streams Set&lt;Short&gt;; the streams (numbered according to the Dutch standard) that cross the intersection.
113      * @throws TrafficLightException when a route is invalid
114      */
115     public Diagram(final Set<Short> streams) throws TrafficLightException
116     {
117         this.streams = new ArrayList<Short>(streams); // make a deep copy and sort by stream number
118         this.streams.sort(new Comparator<Short>()
119         {
120 
121             @Override
122             public int compare(final Short o1, final Short o2)
123             {
124                 return o1 - o2;
125             }
126         });
127         // System.out.println("streams:");
128         // for (short stream : this.streams)
129         // {
130         // System.out.print(String.format(" %02d", stream));
131         // }
132         // System.out.println("");
133 
134         // Primary car streams
135         //@formatter:off
136         for (short stream = 1; stream <= 12; stream += 3)
137         {
138             int quadrant = (stream - 1) / 3;
139             this.routes.put(stream, rotateRoute(quadrant, assembleRoute(
140                     new RouteStep(-BOUNDARY, CAR_RIGHT), 
141                     new RouteStep(-SHOULDER, CAR_RIGHT, Command.STOP_LINE_AND_ICON), 
142                     new RouteStep(-CAR_CENTER, CAR_RIGHT), 
143                     new RouteStep(-CAR_CENTER, BOUNDARY))));
144             this.routes.put((short) (stream + 1), rotateRoute(quadrant, assembleRoute(
145                     new RouteStep(-BOUNDARY, CAR_CENTER), 
146                     new RouteStep(-SHOULDER, CAR_CENTER, Command.STOP_LINE_AND_ICON), 
147                     new RouteStep(Command.IF, stream + 1 + 60), 
148                         new RouteStep(-CAR_ROUNDABOUT_LEFT, CAR_CENTER), 
149                     new RouteStep(Command.ELSE), 
150                         new RouteStep(BOUNDARY, CAR_CENTER), 
151                     new RouteStep(Command.END_IF))));
152             this.routes.put((short) (stream + 2), rotateRoute(quadrant, assembleRoute(
153                     new RouteStep(-BOUNDARY, CAR_LEFT), 
154                     new RouteStep(-SHOULDER, CAR_LEFT, Command.STOP_LINE_AND_ICON), 
155                     new RouteStep(Command.IF, stream + 2 + 60), 
156                         new RouteStep(-CAR_ROUNDABOUT_LEFT, CAR_LEFT), 
157                     new RouteStep(Command.ELSE_IF, (stream + 10) % 12 + 60),
158                         new RouteStep(CAR_CENTER, CAR_LEFT), 
159                         new RouteStep(CAR_CENTER, CAR_ROUNDABOUT_LEFT),
160                     new RouteStep(Command.ELSE), 
161                         new RouteStep(-CAR_LEFT, CAR_LEFT), 
162                         new RouteStep(-CAR_LEFT, PT_DIV_L), 
163                         new RouteStep(-CAR_ROUNDABOUT_LEFT, PT_DIV_L), 
164                         new RouteStep(-CAR_ROUNDABOUT_LEFT, -CAR_LEFT), 
165                         new RouteStep(PT_DIV_L, -CAR_LEFT),
166                         new RouteStep(PT_DIV_L, -CAR_CENTER), 
167                         new RouteStep(CAR_CENTER, -CAR_CENTER),
168                         new RouteStep(CAR_CENTER, -BOUNDARY),
169                     new RouteStep(Command.END_IF))));
170         }
171         // Bicycle streams
172         for (short stream = 21; stream <= 28; stream += 2)
173         {
174             int quadrant = (stream - 19) / 2 % 4;
175             this.routes.put(stream, rotateRoute(quadrant, assembleRoute(
176                     new RouteStep(DIVIDER_1, BICYCLE, Command.ICON), 
177                     new RouteStep(SHOULDER, BICYCLE),
178                     new RouteStep(BOUNDARY, BICYCLE, Command.ICON))));
179             this.routes.put((short) (stream + 1), rotateRoute(quadrant, assembleRoute(
180                     new RouteStep(-BOUNDARY, BICYCLE), 
181                     new RouteStep(-DIVIDER_3, BICYCLE, Command.ICON),
182                     new RouteStep(Command.IF, stream), 
183                     new RouteStep(-DIVIDER_1, BICYCLE, Command.ICON),
184                     new RouteStep(Command.ELSE), 
185                         new RouteStep(SHOULDER, BICYCLE), 
186                         new RouteStep(BOUNDARY, BICYCLE, Command.ICON), 
187                     new RouteStep(Command.END_IF))));
188         }
189         // Pedestrian streams
190         for (short stream = 31; stream <= 38; stream += 2)
191         {
192             int quadrant = (stream - 29) / 2 % 4;
193             this.routes.put(stream, rotateRoute(quadrant, assembleRoute(
194                     new RouteStep(DIVIDER_1, SIDEWALK), 
195                     new RouteStep(BOUNDARY, SIDEWALK))));
196             this.routes.put((short) (stream + 1), rotateRoute(quadrant, assembleRoute(
197                     new RouteStep(-BOUNDARY, SIDEWALK), 
198                     new RouteStep(Command.IF, stream), 
199                         new RouteStep(-DIVIDER_1, SIDEWALK), 
200                     new RouteStep(Command.ELSE), 
201                         new RouteStep(BOUNDARY, SIDEWALK),
202                     new RouteStep(Command.END_IF))));
203         }
204         // Public transit streams
205         for (short stream = 41; stream <= 52; stream += 3)
206         {
207             int quadrant = (stream - 41) / 3;
208             this.routes.put(stream, rotateRoute(quadrant, assembleRoute(
209                     new RouteStep(-BOUNDARY, PT_DIV_L), 
210                     new RouteStep(-SHOULDER, PT_DIV_L, Command.STOP_LINE), 
211                     new RouteStep(-PT_SIDEWALK_SHOULDER, PT_DIV_L, Command.ICON),
212                     new RouteStep(-CAR_RIGHT, PT_DIV_L), 
213                     new RouteStep(-CAR_RIGHT, CAR_LEFT), 
214                     new RouteStep(-PT_DIV_L, CAR_LEFT), 
215                     new RouteStep(-PT_DIV_L, SHOULDER), 
216                     new RouteStep(-PT_DIV_L, BOUNDARY, Command.ICON))));
217             this.routes.put((short) (stream + 1), rotateRoute(quadrant, assembleRoute(
218                     new RouteStep(-BOUNDARY, PT_DIV_L), 
219                     new RouteStep(-SHOULDER, PT_DIV_L, Command.STOP_LINE), 
220                     new RouteStep(-PT_SIDEWALK_SHOULDER, PT_DIV_L, Command.ICON),
221                     new RouteStep(SHOULDER, PT_DIV_L), 
222                     new RouteStep(BOUNDARY, PT_DIV_L))));
223             this.routes.put((short) (stream + 2), rotateRoute(quadrant, assembleRoute(
224                     new RouteStep(-BOUNDARY, PT_DIV_L), 
225                     new RouteStep(-SHOULDER, PT_DIV_L, Command.STOP_LINE), 
226                     new RouteStep(-PT_SIDEWALK_SHOULDER, PT_DIV_L, Command.ICON),
227                     new RouteStep(-CAR_RIGHT, PT_DIV_L), 
228                     new RouteStep(-CAR_RIGHT, CAR_ROUNDABOUT_LEFT), 
229                     new RouteStep(-PT_DIV_L, CAR_ROUNDABOUT_LEFT), 
230                     new RouteStep(Command.IF, (stream + 2 - 40) % 12 + 60), 
231                         new RouteStep(-PT_DIV_L, -PT_DIV_L), 
232                         new RouteStep(PT_DIV_L, -PT_DIV_L), 
233                     new RouteStep(Command.ELSE), 
234                         new RouteStep(-PT_DIV_L, -CAR_CENTER), 
235                         new RouteStep(CAR_ROUNDABOUT_LEFT, -CAR_CENTER), 
236                         new RouteStep(CAR_ROUNDABOUT_LEFT, -CAR_RIGHT), 
237                         new RouteStep(PT_DIV_L, -CAR_RIGHT), 
238                     new RouteStep(Command.END_IF),
239                     new RouteStep(PT_DIV_L, -SHOULDER), 
240                     new RouteStep(PT_DIV_L, -BOUNDARY, Command.ICON))));
241         }
242         // Secondary car streams
243         for (short stream = 62; stream <= 72; stream += 3)
244         {
245             int quadrant = (stream - 61) / 3;
246             this.routes.put(stream, rotateRoute(quadrant, assembleRoute(
247                     new RouteStep(-CAR_ROUNDABOUT_LEFT, CAR_CENTER), 
248                     new RouteStep(CAR_ROUNDABOUT_LEFT, CAR_CENTER, Command.STOP_LINE_AND_ICON), 
249                     new RouteStep(BOUNDARY, CAR_CENTER))));
250             this.routes.put((short) (stream + 1), rotateRoute(quadrant, assembleRoute(
251                     new RouteStep(-CAR_ROUNDABOUT_LEFT, CAR_LEFT), 
252                     new RouteStep(CAR_ROUNDABOUT_LEFT, CAR_LEFT, Command.STOP_LINE_AND_ICON), 
253                     new RouteStep(CAR_CENTER, CAR_LEFT), 
254                     new RouteStep(Command.IF, ((stream - 61) + 11) % 12 + 60),
255                     new RouteStep(CAR_CENTER, CAR_ROUNDABOUT_LEFT), 
256                     new RouteStep(Command.ELSE), 
257                     new RouteStep(CAR_CENTER, -BOUNDARY), 
258                     new RouteStep(Command.END_IF))));
259         }
260        // @formatter:on
261     }
262 
263     /**
264      * Check that a particular stream exists. Beware that the keys in this.streams are Short.
265      * @param stream short; the number of the stream to check
266      * @return boolean; true if the stream exists; false if it does not exist
267      */
268     private boolean streamExists(final short stream)
269     {
270         return this.streams.contains(stream);
271     }
272 
273     /**
274      * Report if object is inaccessible to all traffic.
275      * @param i int; the number of the object
276      * @return boolean; true if the object is inaccessible to all traffic
277      */
278     public static final boolean isGrass(final int i)
279     {
280         return i == DIVIDER_1 || i == DIVIDER_2 || i == DIVIDER_3 || i == DIVIDER_4 || i == DIVIDER_5 || i == DIVIDER_6
281                 || i == DIVIDER_7 || i == SHOULDER;
282     }
283 
284     /**
285      * Return the LaneType for a stream number.
286      * @param streamNumber int; the standard Dutch traffic stream number
287      * @return LaneType; the lane type of the stream; or null if the stream number is reserved or invalid
288      */
289     final LaneType laneType(final int streamNumber)
290     {
291         if (streamNumber < 20 || streamNumber > 60 && streamNumber <= 80)
292         {
293             return LaneType.CAR_LANE;
294         }
295         if (streamNumber >= 20 && streamNumber < 30)
296         {
297             return LaneType.BICYCLE_LANE;
298         }
299         if (streamNumber >= 30 && streamNumber < 40)
300         {
301             return LaneType.PEDESTRIAN_LANE;
302         }
303         if (streamNumber > 40 && streamNumber <= 52 || streamNumber >= 81 && streamNumber <= 92)
304         {
305             return LaneType.PUBLIC_TRANSIT_LANE;
306         }
307         return null;
308     }
309 
310     /**
311      * Types of lanes.
312      */
313     enum LaneType
314     {
315         /** Car. */
316         CAR_LANE,
317         /** BICYCLE. */
318         BICYCLE_LANE,
319         /** Public transit. */
320         PUBLIC_TRANSIT_LANE,
321         /** Pedestrian. */
322         PEDESTRIAN_LANE,
323     }
324 
325     /**
326      * Return the rotated x value.
327      * @param xyPair XYPair; the XYPair
328      * @param rotation int; rotation in multiples of 90 degrees
329      * @return int; the x component of the rotated coordinates
330      */
331     final int rotatedX(final XYPair xyPair, final int rotation)
332     {
333         switch (rotation % 4)
334         {
335             case 0:
336                 return xyPair.getX();
337             case 1:
338                 return -xyPair.getY();
339             case 2:
340                 return -xyPair.getX();
341             case 3:
342                 return xyPair.getY();
343             default:
344                 break; // cannot happen
345         }
346         return 0; // cannot happen
347     }
348 
349     /**
350      * Return the rotated y value.
351      * @param xyPair XYPair; the XYPair
352      * @param rotation int; rotation in multiples of 90 degrees
353      * @return int; the y component of the rotated coordinates
354      */
355     final int rotatedY(final XYPair xyPair, final int rotation)
356     {
357         switch (rotation % 4)
358         {
359             case 0:
360                 return xyPair.getY();
361             case 1:
362                 return xyPair.getX();
363             case 2:
364                 return -xyPair.getY();
365             case 3:
366                 return -xyPair.getX();
367             default:
368                 break; // cannot happen
369         }
370         return 0; // cannot happen
371     }
372 
373     /**
374      * Commands used in RouteStep.
375      */
376     enum Command
377     {
378         /** No operation. */
379         NO_OP,
380         /** If. */
381         IF,
382         /** Else. */
383         ELSE,
384         /** Else if. */
385         ELSE_IF,
386         /** End if. */
387         END_IF,
388         /** Stop line. */
389         STOP_LINE,
390         /** Icon (bus, bicycle symbol). */
391         ICON,
392         /** Stop line AND icon. */
393         STOP_LINE_AND_ICON,
394     }
395 
396     /**
397      * Step in a schematic route through the intersection.
398      */
399     class RouteStep
400     {
401         /** X object. */
402         private final int x;
403 
404         /** Y object. */
405         private final int y;
406 
407         /** Command of this step. */
408         private final Command command;
409 
410         /** Condition for IF and ELSE_IF commands. */
411         private final int streamCondition;
412 
413         /**
414          * Construct a RouteStep that has a NO_OP command.
415          * @param x int; the X object at the end of this route step
416          * @param y int; the Y object at the end of this route step
417          */
418         RouteStep(final int x, final int y)
419         {
420             this.x = x;
421             this.y = y;
422             this.command = Command.NO_OP;
423             this.streamCondition = TrafficController.NO_STREAM;
424         }
425 
426         /**
427          * Construct a RouteStep with a command condition.
428          * @param x int; the X object at the end of this route step
429          * @param y int; the Y object at the end of this route step
430          * @param command Command; a STOP_LINE or NO_OP command
431          * @throws TrafficLightException when an IF or ELSE_IF has an invalid streamCondition, or when an ELSE or END_IF has a
432          *             valid streamCOndition
433          */
434         RouteStep(final int x, final int y, final Command command) throws TrafficLightException
435         {
436             Throw.when(
437                     Command.STOP_LINE != command && Command.NO_OP != command && Command.ICON != command
438                             && Command.STOP_LINE_AND_ICON != command,
439                     TrafficLightException.class,
440                     "X and Y should only be provided with a NO_OP, STOP_LINE, ICON, or STOP_LINE_AND_ICON command; not with "
441                             + command);
442             this.x = x;
443             this.y = y;
444             this.command = command;
445             this.streamCondition = TrafficController.NO_STREAM;
446         }
447 
448         /**
449          * Construct a RouteStep with a command condition.
450          * @param command Command; an IF, ELSE, ENDIF, or ELSE_IF command
451          * @param streamCondition int; the stream that must exist for the condition to be true
452          * @throws TrafficLightException when an IF or ELSE_IF has an invalid streamCondition, or when an ELSE or END_IF has a
453          *             valid streamCOndition
454          */
455         RouteStep(final Command command, final int streamCondition) throws TrafficLightException
456         {
457             Throw.when(Command.IF != command && Command.ELSE_IF != command, TrafficLightException.class,
458                     "RouteStep constructor with stream condition must use command IF or ELSE_IF");
459             this.x = TrafficController.NO_STREAM;
460             this.y = TrafficController.NO_STREAM;
461             this.command = command;
462             Throw.when(streamCondition == TrafficController.NO_STREAM, TrafficLightException.class,
463                     "IF or ELSE_IF need a valid traffic stream number");
464             this.streamCondition = streamCondition;
465         }
466 
467         /**
468          * Construct a RouteStep for ELSE or END_IF command.
469          * @param command Command; either <code>Command.ELSE</code> or <code>Command.END_IF</code>
470          * @throws TrafficLightException when the Command is not ELSE or END_IF
471          */
472         RouteStep(final Command command) throws TrafficLightException
473         {
474             Throw.when(Command.ELSE != command && Command.END_IF != command, TrafficLightException.class,
475                     "RouteStep constructor with single command parameter requires ELSE or END_IF command");
476             this.x = TrafficController.NO_STREAM;
477             this.y = TrafficController.NO_STREAM;
478             this.command = command;
479             this.streamCondition = TrafficController.NO_STREAM;
480         }
481 
482         /**
483          * Retrieve the X object.
484          * @return int; the X object
485          */
486         public int getX()
487         {
488             return this.x;
489         }
490 
491         /**
492          * Retrieve the Y object.
493          * @return int; the Y object
494          */
495         public int getY()
496         {
497             return this.y;
498         }
499 
500         /**
501          * Retrieve the command.
502          * @return Command
503          */
504         public Command getCommand()
505         {
506             return this.command;
507         }
508 
509         /**
510          * Retrieve the stream condition.
511          * @return int; the streamCondition
512          */
513         public int getStreamCondition()
514         {
515             return this.streamCondition;
516         }
517 
518         /** {@inheritDoc} */
519         @Override
520         public String toString()
521         {
522             return "RouteStep [x=" + this.x + ", y=" + this.y + ", command=" + this.command + ", streamCondition="
523                     + this.streamCondition + "]";
524         }
525 
526     }
527 
528     /**
529      * Pack two integer coordinates in one object.
530      */
531     class XYPair
532     {
533         /** X. */
534         private final int x;
535 
536         /** Y. */
537         private final int y;
538 
539         /**
540          * Construct a new XY pair.
541          * @param x int; the X value
542          * @param y int; the Y value
543          */
544         XYPair(final int x, final int y)
545         {
546             this.x = x;
547             this.y = y;
548         }
549 
550         /**
551          * Construct a new XY pair from a route step.
552          * @param routeStep RouteStep; the route step
553          */
554         XYPair(final RouteStep routeStep)
555         {
556             this.x = routeStep.getX();
557             this.y = routeStep.getY();
558         }
559 
560         /**
561          * Construct a rotated version of an XYPair.
562          * @param in XYPair; the initial version
563          * @param quadrant int; the quadrant
564          */
565         XYPair(final XYPair in, final int quadrant)
566         {
567             this.x = rotatedX(in, quadrant);
568             this.y = rotatedY(in, quadrant);
569         }
570 
571         /**
572          * Retrieve the X value.
573          * @return int; the X value
574          */
575         public int getX()
576         {
577             return this.x;
578         }
579 
580         /**
581          * Retrieve the Y value.
582          * @return int; the Y value
583          */
584         public int getY()
585         {
586             return this.y;
587         }
588 
589         /** {@inheritDoc} */
590         @Override
591         public String toString()
592         {
593             return "XYPair [x=" + this.x + ", y=" + this.y + "]";
594         }
595 
596     }
597 
598     /**
599      * Construct a route.
600      * @param quadrant int; the quadrant to assemble the route for
601      * @param steps RouteStep...; an array, or series of arguments of type RouteStep
602      * @return XYPair[]; an array of XY pairs describing the route through the intersection
603      * @throws TrafficLightException when the route contains commands other than NO_OP and STOP_LINE
604      */
605     private XYPair[] rotateRoute(final int quadrant, final RouteStep... steps) throws TrafficLightException
606     {
607         List<XYPair> route = new ArrayList<>();
608         boolean on = true;
609 
610         for (RouteStep step : steps)
611         {
612             switch (step.getCommand())
613             {
614                 case NO_OP:
615                 case STOP_LINE:
616                 case ICON:
617                 case STOP_LINE_AND_ICON:
618                     if (on)
619                     {
620                         route.add(new XYPair(new XYPair(step), quadrant));
621                     }
622                     break;
623 
624                 default:
625                     throw new TrafficLightException("Bad command in rotateRoute: " + step.getCommand());
626 
627             }
628         }
629         return route.toArray(new XYPair[route.size()]);
630     }
631 
632     /**
633      * Construct a route through the intersection.
634      * @param steps RouteStep...; the steps of the route description
635      * @return RouteStep[]; the route through the intersection
636      * @throws TrafficLightException when something is very wrong
637      */
638     private RouteStep[] assembleRoute(final RouteStep... steps) throws TrafficLightException
639     {
640         List<RouteStep> result = new ArrayList<>();
641         RouteStep step;
642         for (int pointNo = 0; null != (step = routePoint(pointNo, steps)); pointNo++)
643         {
644             result.add(step);
645         }
646         return result.toArray(new RouteStep[result.size()]);
647     }
648 
649     /**
650      * Return the Nth step in a route.
651      * @param pointNo int; the rank of the requested step
652      * @param steps RouteStep...; RouteStep... the steps
653      * @return RouteStep; the Nth step in the route or null if the route does not have <code>pointNo</code> steps
654      * @throws TrafficLightException when the command in a routestep is not recognized
655      */
656     private RouteStep routePoint(final int pointNo, final RouteStep... steps) throws TrafficLightException
657     {
658         boolean active = true;
659         boolean beenActive = false;
660         int index = 0;
661 
662         for (RouteStep routeStep : steps)
663         {
664             switch (routeStep.getCommand())
665             {
666                 case NO_OP:
667                 case STOP_LINE:
668                 case ICON:
669                 case STOP_LINE_AND_ICON:
670                     if (active)
671                     {
672                         if (index++ == pointNo)
673                         {
674                             return routeStep;
675                         }
676                     }
677                     break;
678 
679                 case IF:
680                     active = streamExists((short) routeStep.getStreamCondition());
681                     beenActive = active;
682                     break;
683 
684                 case ELSE_IF:
685                     if (active)
686                     {
687                         active = false;
688                     }
689                     else if (!beenActive)
690                     {
691                         active = this.streams.contains(routeStep.getStreamCondition());
692                     }
693                     if (active)
694                     {
695                         beenActive = true;
696                     }
697                     break;
698 
699                 case ELSE:
700                     active = !beenActive;
701                     break;
702 
703                 case END_IF:
704                     active = true;
705                     break;
706 
707                 default:
708                     throw new TrafficLightException("Bad switch: " + routeStep);
709 
710             }
711         }
712         return null;
713     }
714 
715     /**
716      * Create a BufferedImage and render the schematic on it.
717      * @return BufferedImage
718      */
719     public BufferedImage render()
720     {
721         int range = 2 * BOUNDARY + 1;
722         int cellSize = 10;
723         BufferedImage result = new BufferedImage(range * cellSize, range * cellSize, BufferedImage.TYPE_INT_RGB);
724         Graphics2D graphics = (Graphics2D) result.getGraphics();
725         graphics.setColor(Color.GREEN);
726         graphics.fillRect(0, 0, result.getWidth(), result.getHeight());
727         for (Short stream : this.streams)
728         {
729             switch (laneType(stream))
730             {
731                 case BICYCLE_LANE:
732                     graphics.setColor(Color.RED);
733                     break;
734 
735                 case CAR_LANE:
736                     graphics.setColor(Color.BLACK);
737                     break;
738 
739                 case PEDESTRIAN_LANE:
740                     graphics.setColor(Color.BLUE);
741                     break;
742 
743                 case PUBLIC_TRANSIT_LANE:
744                     graphics.setColor(Color.BLACK);
745                     break;
746 
747                 default:
748                     graphics.setColor(Color.WHITE);
749                     break;
750 
751             }
752             XYPair[] path = this.routes.get(stream);
753             if (null == path)
754             {
755                 System.err.println("Cannot find path for stream " + stream);
756                 continue;
757             }
758             XYPair prevPair = null;
759             for (XYPair xyPair : path)
760             {
761                 if (null != prevPair)
762                 {
763                     int dx = (int) Math.signum(xyPair.getX() - prevPair.getX());
764                     int dy = (int) Math.signum(xyPair.getY() - prevPair.getY());
765                     int x = prevPair.getX() + dx;
766                     int y = prevPair.getY() + dy;
767                     while (x != xyPair.getX() || y != xyPair.getY())
768                     {
769                         fillXYPair(graphics, new XYPair(x, y));
770                         if (x != xyPair.getX())
771                         {
772                             x += dx;
773                         }
774                         if (y != xyPair.getY())
775                         {
776                             y += dy;
777                         }
778                     }
779 
780                 }
781                 fillXYPair(graphics, xyPair);
782                 prevPair = xyPair;
783             }
784         }
785         return result;
786     }
787 
788     /**
789      * Fill one box taking care to rotate to display conventions.
790      * @param graphics Graphics2D; the graphics environment
791      * @param xyPair XYPair; the box to fill
792      */
793     private void fillXYPair(final Graphics2D graphics, final XYPair xyPair)
794     {
795         int cellSize = 10;
796         graphics.fillRect(cellSize * (BOUNDARY - xyPair.getX()), cellSize * (BOUNDARY - xyPair.getY()), cellSize, cellSize);
797     }
798 
799     /**
800      * Test the Diagram code.
801      * @param args String[]; the command line arguments (not used)
802      */
803     public static void main(final String[] args)
804     {
805         SwingUtilities.invokeLater(new Runnable()
806         {
807             @Override
808             public void run()
809             {
810                 JFrame frame = new JFrame("Diagram test");
811                 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
812                 frame.setMinimumSize(new Dimension(1000, 1000));
813                 JPanel mainPanel = new JPanel(new BorderLayout());
814                 frame.add(mainPanel);
815                 checkBoxPanel = new JPanel();
816                 checkBoxPanel.setLayout(new BoxLayout(checkBoxPanel, BoxLayout.Y_AXIS));
817                 JScrollPane scrollPane = new JScrollPane(checkBoxPanel);
818                 scrollPane.setPreferredSize(new Dimension(150, 1000));
819                 mainPanel.add(scrollPane, BorderLayout.LINE_START);
820                 for (int stream = 1; stream <= 12; stream++)
821                 {
822                     checkBoxPanel.add(makeCheckBox(stream, stream % 3 == 2));
823                 }
824                 for (int stream = 21; stream <= 28; stream++)
825                 {
826                     checkBoxPanel.add(makeCheckBox(stream, false));
827                 }
828                 for (int stream = 31; stream <= 38; stream++)
829                 {
830                     checkBoxPanel.add(makeCheckBox(stream, false));
831                 }
832                 for (int stream = 41; stream <= 52; stream++)
833                 {
834                     checkBoxPanel.add(makeCheckBox(stream, false));
835                 }
836                 for (int stream = 61; stream <= 72; stream++)
837                 {
838                     if (stream % 3 == 1)
839                     {
840                         continue;
841                     }
842                     checkBoxPanel.add(makeCheckBox(stream, false));
843                 }
844                 testPanel = new JPanel();
845                 rebuildTestPanel();
846                 mainPanel.add(testPanel, BorderLayout.CENTER);
847                 frame.setVisible(true);
848             }
849         });
850 
851     }
852 
853     /**
854      * Make a check box to switch a particular stream number on or off.
855      * @param stream int; the stream number
856      * @param initialState boolean; if true; the check box will be checked
857      * @return JCheckBox
858      */
859     public static JCheckBox makeCheckBox(final int stream, final boolean initialState)
860     {
861         JCheckBox result = new JCheckBox(String.format("Stream %02d", stream));
862         result.setSelected(initialState);
863         result.addActionListener(new ActionListener()
864         {
865 
866             @Override
867             public void actionPerformed(final ActionEvent e)
868             {
869                 rebuildTestPanel();
870             }
871         });
872         return result;
873     }
874 
875     /** JPanel used to render the intersection for testing. */
876     private static JPanel testPanel = null;
877 
878     /** JPanel that holds all the check boxes. */
879     private static JPanel checkBoxPanel = null;
880 
881     /**
882      * Render the intersection.
883      */
884     static void rebuildTestPanel()
885     {
886         testPanel.removeAll();
887         Set<Short> streamList = new LinkedHashSet<>();
888         for (Component c : checkBoxPanel.getComponents())
889         {
890             if (c instanceof JCheckBox)
891             {
892                 JCheckBox checkBox = (JCheckBox) c;
893                 if (checkBox.isSelected())
894                 {
895                     String caption = checkBox.getText();
896                     String streamText = caption.substring(caption.length() - 2);
897                     Short stream = Short.parseShort(streamText);
898                     streamList.add(stream);
899                 }
900             }
901         }
902         try
903         {
904             Diagramrol/trafcod/Diagram.html#Diagram">Diagram diagram = new Diagram(streamList);
905             testPanel.add(new JLabel(new ImageIcon(diagram.render())));
906         }
907         catch (TrafficLightException exception)
908         {
909             exception.printStackTrace();
910         }
911         testPanel.repaint();
912         testPanel.revalidate();
913     }
914 
915 }