Diagram.java

  1. package org.opentrafficsim.trafficcontrol.trafcod;

  2. import java.awt.BorderLayout;
  3. import java.awt.Color;
  4. import java.awt.Component;
  5. import java.awt.Dimension;
  6. import java.awt.Graphics2D;
  7. import java.awt.event.ActionEvent;
  8. import java.awt.event.ActionListener;
  9. import java.awt.image.BufferedImage;
  10. import java.util.ArrayList;
  11. import java.util.Comparator;
  12. import java.util.LinkedHashMap;
  13. import java.util.LinkedHashSet;
  14. import java.util.List;
  15. import java.util.Map;
  16. import java.util.Set;

  17. import javax.swing.BoxLayout;
  18. import javax.swing.ImageIcon;
  19. import javax.swing.JCheckBox;
  20. import javax.swing.JFrame;
  21. import javax.swing.JLabel;
  22. import javax.swing.JPanel;
  23. import javax.swing.JScrollPane;
  24. import javax.swing.SwingUtilities;

  25. import org.djutils.exceptions.Throw;
  26. import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLightException;
  27. import org.opentrafficsim.trafficcontrol.TrafficController;

  28. /**
  29.  * Functions that can draw a schematic diagram of an intersection given the list of traffic streams. The traffic stream numbers
  30.  * must follow the Dutch conventions.
  31.  * <p>
  32.  * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  33.  * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  34.  * <p>
  35.  * @version $Revision$, $LastChangedDate$, by $Author$, initial version Dec 1, 2016 <br>
  36.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  37.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  38.  * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  39.  */
  40. public class Diagram
  41. {
  42.     /** Numbering of the lateral objects/positions from the median to the shoulder. */
  43.     /** Central divider. */
  44.     static final int DIVIDER_1 = 0;

  45.     /** Left turn area on roundabout. */
  46.     static final int CAR_ROUNDABOUT_LEFT = 1;

  47.     /** Public transit between divider and left turn lane. */
  48.     static final int PT_DIV_L = 3;

  49.     /** Divider between center public transit and left turn lane. */
  50.     static final int DIVIDER_2 = 4;

  51.     /** Left turn lane(s). */
  52.     static final int CAR_LEFT = 5;

  53.     /** No turn (center) lane(s). */
  54.     static final int CAR_CENTER = 7;

  55.     /** Right turn lane(s). */
  56.     static final int CAR_RIGHT = 9;

  57.     /** Divider between right turn lane and bicycle lane. */
  58.     static final int DIVIDER_3 = 10;

  59.     /** Public transit between right turn lane and bicycle lane. */
  60.     static final int PT_RIGHT_BICYCLE = 11;

  61.     /** Divider. */
  62.     static final int DIVIDER_4 = 12;

  63.     /** Bicycle lane. */
  64.     static final int BICYCLE = 13;

  65.     /** Divider. */
  66.     static final int DIVIDER_5 = 14;

  67.     /** Public transit between bicycle lane and right sidewalk. */
  68.     static final int PT_BICYCLE_SIDEWALK = 15;

  69.     /** Divider. */
  70.     static final int DIVIDER_6 = 16;

  71.     /** Sidewalk. */
  72.     static final int SIDEWALK = 17;

  73.     /** Divider. */
  74.     static final int DIVIDER_7 = 18;

  75.     /** Public transit right of right sidewalk. */
  76.     static final int PT_SIDEWALK_SHOULDER = 19;

  77.     /** Shoulder right of right sidewalk. */
  78.     static final int SHOULDER = 20;

  79.     /** Boundary of schematic intersection. */
  80.     static final int BOUNDARY = 21;

  81.     /** The streams crossing the intersection. */
  82.     private final List<Short> streams;

  83.     /** The routes through the intersection. */
  84.     private final Map<Short, XYPair[]> routes = new LinkedHashMap<>();

  85.     /**
  86.      * Construct a new diagram.
  87.      * @param streams Set&lt;Short&gt;; the streams (numbered according to the Dutch standard) that cross the intersection.
  88.      * @throws TrafficLightException when a route is invalid
  89.      */
  90.     public Diagram(final Set<Short> streams) throws TrafficLightException
  91.     {
  92.         this.streams = new ArrayList<Short>(streams); // make a deep copy and sort by stream number
  93.         this.streams.sort(new Comparator<Short>()
  94.         {

  95.             @Override
  96.             public int compare(final Short o1, final Short o2)
  97.             {
  98.                 return o1 - o2;
  99.             }
  100.         });
  101.         // System.out.println("streams:");
  102.         // for (short stream : this.streams)
  103.         // {
  104.         // System.out.print(String.format(" %02d", stream));
  105.         // }
  106.         // System.out.println("");

  107.         // Primary car streams
  108.         //@formatter:off
  109.         for (short stream = 1; stream <= 12; stream += 3)
  110.         {
  111.             int quadrant = (stream - 1) / 3;
  112.             this.routes.put(stream, rotateRoute(quadrant, assembleRoute(
  113.                     new RouteStep(-BOUNDARY, CAR_RIGHT),
  114.                     new RouteStep(-SHOULDER, CAR_RIGHT, Command.STOP_LINE_AND_ICON),
  115.                     new RouteStep(-CAR_CENTER, CAR_RIGHT),
  116.                     new RouteStep(-CAR_CENTER, BOUNDARY))));
  117.             this.routes.put((short) (stream + 1), rotateRoute(quadrant, assembleRoute(
  118.                     new RouteStep(-BOUNDARY, CAR_CENTER),
  119.                     new RouteStep(-SHOULDER, CAR_CENTER, Command.STOP_LINE_AND_ICON),
  120.                     new RouteStep(Command.IF, stream + 1 + 60),
  121.                         new RouteStep(-CAR_ROUNDABOUT_LEFT, CAR_CENTER),
  122.                     new RouteStep(Command.ELSE),
  123.                         new RouteStep(BOUNDARY, CAR_CENTER),
  124.                     new RouteStep(Command.END_IF))));
  125.             this.routes.put((short) (stream + 2), rotateRoute(quadrant, assembleRoute(
  126.                     new RouteStep(-BOUNDARY, CAR_LEFT),
  127.                     new RouteStep(-SHOULDER, CAR_LEFT, Command.STOP_LINE_AND_ICON),
  128.                     new RouteStep(Command.IF, stream + 2 + 60),
  129.                         new RouteStep(-CAR_ROUNDABOUT_LEFT, CAR_LEFT),
  130.                     new RouteStep(Command.ELSE_IF, (stream + 10) % 12 + 60),
  131.                         new RouteStep(CAR_CENTER, CAR_LEFT),
  132.                         new RouteStep(CAR_CENTER, CAR_ROUNDABOUT_LEFT),
  133.                     new RouteStep(Command.ELSE),
  134.                         new RouteStep(-CAR_LEFT, CAR_LEFT),
  135.                         new RouteStep(-CAR_LEFT, PT_DIV_L),
  136.                         new RouteStep(-CAR_ROUNDABOUT_LEFT, PT_DIV_L),
  137.                         new RouteStep(-CAR_ROUNDABOUT_LEFT, -CAR_LEFT),
  138.                         new RouteStep(PT_DIV_L, -CAR_LEFT),
  139.                         new RouteStep(PT_DIV_L, -CAR_CENTER),
  140.                         new RouteStep(CAR_CENTER, -CAR_CENTER),
  141.                         new RouteStep(CAR_CENTER, -BOUNDARY),
  142.                     new RouteStep(Command.END_IF))));
  143.         }
  144.         // Bicycle streams
  145.         for (short stream = 21; stream <= 28; stream += 2)
  146.         {
  147.             int quadrant = (stream - 19) / 2 % 4;
  148.             this.routes.put(stream, rotateRoute(quadrant, assembleRoute(
  149.                     new RouteStep(DIVIDER_1, BICYCLE, Command.ICON),
  150.                     new RouteStep(SHOULDER, BICYCLE),
  151.                     new RouteStep(BOUNDARY, BICYCLE, Command.ICON))));
  152.             this.routes.put((short) (stream + 1), rotateRoute(quadrant, assembleRoute(
  153.                     new RouteStep(-BOUNDARY, BICYCLE),
  154.                     new RouteStep(-DIVIDER_3, BICYCLE, Command.ICON),
  155.                     new RouteStep(Command.IF, stream),
  156.                     new RouteStep(-DIVIDER_1, BICYCLE, Command.ICON),
  157.                     new RouteStep(Command.ELSE),
  158.                         new RouteStep(SHOULDER, BICYCLE),
  159.                         new RouteStep(BOUNDARY, BICYCLE, Command.ICON),
  160.                     new RouteStep(Command.END_IF))));
  161.         }
  162.         // Pedestrian streams
  163.         for (short stream = 31; stream <= 38; stream += 2)
  164.         {
  165.             int quadrant = (stream - 29) / 2 % 4;
  166.             this.routes.put(stream, rotateRoute(quadrant, assembleRoute(
  167.                     new RouteStep(DIVIDER_1, SIDEWALK),
  168.                     new RouteStep(BOUNDARY, SIDEWALK))));
  169.             this.routes.put((short) (stream + 1), rotateRoute(quadrant, assembleRoute(
  170.                     new RouteStep(-BOUNDARY, SIDEWALK),
  171.                     new RouteStep(Command.IF, stream),
  172.                         new RouteStep(-DIVIDER_1, SIDEWALK),
  173.                     new RouteStep(Command.ELSE),
  174.                         new RouteStep(BOUNDARY, SIDEWALK),
  175.                     new RouteStep(Command.END_IF))));
  176.         }
  177.         // Public transit streams
  178.         for (short stream = 41; stream <= 52; stream += 3)
  179.         {
  180.             int quadrant = (stream - 41) / 3;
  181.             this.routes.put(stream, rotateRoute(quadrant, assembleRoute(
  182.                     new RouteStep(-BOUNDARY, PT_DIV_L),
  183.                     new RouteStep(-SHOULDER, PT_DIV_L, Command.STOP_LINE),
  184.                     new RouteStep(-PT_SIDEWALK_SHOULDER, PT_DIV_L, Command.ICON),
  185.                     new RouteStep(-CAR_RIGHT, PT_DIV_L),
  186.                     new RouteStep(-CAR_RIGHT, CAR_LEFT),
  187.                     new RouteStep(-PT_DIV_L, CAR_LEFT),
  188.                     new RouteStep(-PT_DIV_L, SHOULDER),
  189.                     new RouteStep(-PT_DIV_L, BOUNDARY, Command.ICON))));
  190.             this.routes.put((short) (stream + 1), rotateRoute(quadrant, assembleRoute(
  191.                     new RouteStep(-BOUNDARY, PT_DIV_L),
  192.                     new RouteStep(-SHOULDER, PT_DIV_L, Command.STOP_LINE),
  193.                     new RouteStep(-PT_SIDEWALK_SHOULDER, PT_DIV_L, Command.ICON),
  194.                     new RouteStep(SHOULDER, PT_DIV_L),
  195.                     new RouteStep(BOUNDARY, PT_DIV_L))));
  196.             this.routes.put((short) (stream + 2), rotateRoute(quadrant, assembleRoute(
  197.                     new RouteStep(-BOUNDARY, PT_DIV_L),
  198.                     new RouteStep(-SHOULDER, PT_DIV_L, Command.STOP_LINE),
  199.                     new RouteStep(-PT_SIDEWALK_SHOULDER, PT_DIV_L, Command.ICON),
  200.                     new RouteStep(-CAR_RIGHT, PT_DIV_L),
  201.                     new RouteStep(-CAR_RIGHT, CAR_ROUNDABOUT_LEFT),
  202.                     new RouteStep(-PT_DIV_L, CAR_ROUNDABOUT_LEFT),
  203.                     new RouteStep(Command.IF, (stream + 2 - 40) % 12 + 60),
  204.                         new RouteStep(-PT_DIV_L, -PT_DIV_L),
  205.                         new RouteStep(PT_DIV_L, -PT_DIV_L),
  206.                     new RouteStep(Command.ELSE),
  207.                         new RouteStep(-PT_DIV_L, -CAR_CENTER),
  208.                         new RouteStep(CAR_ROUNDABOUT_LEFT, -CAR_CENTER),
  209.                         new RouteStep(CAR_ROUNDABOUT_LEFT, -CAR_RIGHT),
  210.                         new RouteStep(PT_DIV_L, -CAR_RIGHT),
  211.                     new RouteStep(Command.END_IF),
  212.                     new RouteStep(PT_DIV_L, -SHOULDER),
  213.                     new RouteStep(PT_DIV_L, -BOUNDARY, Command.ICON))));
  214.         }
  215.         // Secondary car streams
  216.         for (short stream = 62; stream <= 72; stream += 3)
  217.         {
  218.             int quadrant = (stream - 61) / 3;
  219.             this.routes.put(stream, rotateRoute(quadrant, assembleRoute(
  220.                     new RouteStep(-CAR_ROUNDABOUT_LEFT, CAR_CENTER),
  221.                     new RouteStep(CAR_ROUNDABOUT_LEFT, CAR_CENTER, Command.STOP_LINE_AND_ICON),
  222.                     new RouteStep(BOUNDARY, CAR_CENTER))));
  223.             this.routes.put((short) (stream + 1), rotateRoute(quadrant, assembleRoute(
  224.                     new RouteStep(-CAR_ROUNDABOUT_LEFT, CAR_LEFT),
  225.                     new RouteStep(CAR_ROUNDABOUT_LEFT, CAR_LEFT, Command.STOP_LINE_AND_ICON),
  226.                     new RouteStep(CAR_CENTER, CAR_LEFT),
  227.                     new RouteStep(Command.IF, ((stream - 61) + 11) % 12 + 60),
  228.                     new RouteStep(CAR_CENTER, CAR_ROUNDABOUT_LEFT),
  229.                     new RouteStep(Command.ELSE),
  230.                     new RouteStep(CAR_CENTER, -BOUNDARY),
  231.                     new RouteStep(Command.END_IF))));
  232.         }
  233.        // @formatter:on
  234.     }

  235.     /**
  236.      * Check that a particular stream exists. Beware that the keys in this.streams are Short.
  237.      * @param stream short; the number of the stream to check
  238.      * @return boolean; true if the stream exists; false if it does not exist
  239.      */
  240.     private boolean streamExists(final short stream)
  241.     {
  242.         return this.streams.contains(stream);
  243.     }

  244.     /**
  245.      * Report if object is inaccessible to all traffic.
  246.      * @param i int; the number of the object
  247.      * @return boolean; true if the object is inaccessible to all traffic
  248.      */
  249.     public static final boolean isGrass(final int i)
  250.     {
  251.         return i == DIVIDER_1 || i == DIVIDER_2 || i == DIVIDER_3 || i == DIVIDER_4 || i == DIVIDER_5 || i == DIVIDER_6
  252.                 || i == DIVIDER_7 || i == SHOULDER;
  253.     }

  254.     /**
  255.      * Return the LaneType for a stream number.
  256.      * @param streamNumber int; the standard Dutch traffic stream number
  257.      * @return LaneType; the lane type of the stream; or null if the stream number is reserved or invalid
  258.      */
  259.     final LaneType laneType(final int streamNumber)
  260.     {
  261.         if (streamNumber < 20 || streamNumber > 60 && streamNumber <= 80)
  262.         {
  263.             return LaneType.CAR_LANE;
  264.         }
  265.         if (streamNumber >= 20 && streamNumber < 30)
  266.         {
  267.             return LaneType.BICYCLE_LANE;
  268.         }
  269.         if (streamNumber >= 30 && streamNumber < 40)
  270.         {
  271.             return LaneType.PEDESTRIAN_LANE;
  272.         }
  273.         if (streamNumber > 40 && streamNumber <= 52 || streamNumber >= 81 && streamNumber <= 92)
  274.         {
  275.             return LaneType.PUBLIC_TRANSIT_LANE;
  276.         }
  277.         return null;
  278.     }

  279.     /**
  280.      * Types of lanes.
  281.      */
  282.     enum LaneType
  283.     {
  284.         /** Car. */
  285.         CAR_LANE,
  286.         /** BICYCLE. */
  287.         BICYCLE_LANE,
  288.         /** Public transit. */
  289.         PUBLIC_TRANSIT_LANE,
  290.         /** Pedestrian. */
  291.         PEDESTRIAN_LANE,
  292.     }

  293.     /**
  294.      * Return the rotated x value.
  295.      * @param xyPair XYPair; the XYPair
  296.      * @param rotation int; rotation in multiples of 90 degrees
  297.      * @return int; the x component of the rotated coordinates
  298.      */
  299.     final int rotatedX(final XYPair xyPair, final int rotation)
  300.     {
  301.         switch (rotation % 4)
  302.         {
  303.             case 0:
  304.                 return xyPair.getX();
  305.             case 1:
  306.                 return -xyPair.getY();
  307.             case 2:
  308.                 return -xyPair.getX();
  309.             case 3:
  310.                 return xyPair.getY();
  311.             default:
  312.                 break; // cannot happen
  313.         }
  314.         return 0; // cannot happen
  315.     }

  316.     /**
  317.      * Return the rotated y value.
  318.      * @param xyPair XYPair; the XYPair
  319.      * @param rotation int; rotation in multiples of 90 degrees
  320.      * @return int; the y component of the rotated coordinates
  321.      */
  322.     final int rotatedY(final XYPair xyPair, final int rotation)
  323.     {
  324.         switch (rotation % 4)
  325.         {
  326.             case 0:
  327.                 return xyPair.getY();
  328.             case 1:
  329.                 return xyPair.getX();
  330.             case 2:
  331.                 return -xyPair.getY();
  332.             case 3:
  333.                 return -xyPair.getX();
  334.             default:
  335.                 break; // cannot happen
  336.         }
  337.         return 0; // cannot happen
  338.     }

  339.     /**
  340.      * Commands used in RouteStep.
  341.      */
  342.     enum Command
  343.     {
  344.         /** No operation. */
  345.         NO_OP,
  346.         /** If. */
  347.         IF,
  348.         /** Else. */
  349.         ELSE,
  350.         /** Else if. */
  351.         ELSE_IF,
  352.         /** End if. */
  353.         END_IF,
  354.         /** Stop line. */
  355.         STOP_LINE,
  356.         /** Icon (bus, bicycle symbol). */
  357.         ICON,
  358.         /** Stop line AND icon. */
  359.         STOP_LINE_AND_ICON,
  360.     }

  361.     /**
  362.      * Step in a schematic route through the intersection.
  363.      */
  364.     class RouteStep
  365.     {
  366.         /** X object. */
  367.         private final int x;

  368.         /** Y object. */
  369.         private final int y;

  370.         /** Command of this step. */
  371.         private final Command command;

  372.         /** Condition for IF and ELSE_IF commands. */
  373.         private final int streamCondition;

  374.         /**
  375.          * Construct a RouteStep that has a NO_OP command.
  376.          * @param x int; the X object at the end of this route step
  377.          * @param y int; the Y object at the end of this route step
  378.          */
  379.         RouteStep(final int x, final int y)
  380.         {
  381.             this.x = x;
  382.             this.y = y;
  383.             this.command = Command.NO_OP;
  384.             this.streamCondition = TrafficController.NO_STREAM;
  385.         }

  386.         /**
  387.          * Construct a RouteStep with a command condition.
  388.          * @param x int; the X object at the end of this route step
  389.          * @param y int; the Y object at the end of this route step
  390.          * @param command Command; a STOP_LINE or NO_OP command
  391.          * @throws TrafficLightException when an IF or ELSE_IF has an invalid streamCondition, or when an ELSE or END_IF has a
  392.          *             valid streamCOndition
  393.          */
  394.         RouteStep(final int x, final int y, final Command command) throws TrafficLightException
  395.         {
  396.             Throw.when(
  397.                     Command.STOP_LINE != command && Command.NO_OP != command && Command.ICON != command
  398.                             && Command.STOP_LINE_AND_ICON != command,
  399.                     TrafficLightException.class,
  400.                     "X and Y should only be provided with a NO_OP, STOP_LINE, ICON, or STOP_LINE_AND_ICON command; not with "
  401.                             + command);
  402.             this.x = x;
  403.             this.y = y;
  404.             this.command = command;
  405.             this.streamCondition = TrafficController.NO_STREAM;
  406.         }

  407.         /**
  408.          * Construct a RouteStep with a command condition.
  409.          * @param command Command; an IF, ELSE, ENDIF, or ELSE_IF command
  410.          * @param streamCondition int; the stream that must exist for the condition to be true
  411.          * @throws TrafficLightException when an IF or ELSE_IF has an invalid streamCondition, or when an ELSE or END_IF has a
  412.          *             valid streamCOndition
  413.          */
  414.         RouteStep(final Command command, final int streamCondition) throws TrafficLightException
  415.         {
  416.             Throw.when(Command.IF != command && Command.ELSE_IF != command, TrafficLightException.class,
  417.                     "RouteStep constructor with stream condition must use command IF or ELSE_IF");
  418.             this.x = TrafficController.NO_STREAM;
  419.             this.y = TrafficController.NO_STREAM;
  420.             this.command = command;
  421.             Throw.when(streamCondition == TrafficController.NO_STREAM, TrafficLightException.class,
  422.                     "IF or ELSE_IF need a valid traffic stream number");
  423.             this.streamCondition = streamCondition;
  424.         }

  425.         /**
  426.          * Construct a RouteStep for ELSE or END_IF command.
  427.          * @param command Command; either <code>Command.ELSE</code> or <code>Command.END_IF</code>
  428.          * @throws TrafficLightException when the Command is not ELSE or END_IF
  429.          */
  430.         RouteStep(final Command command) throws TrafficLightException
  431.         {
  432.             Throw.when(Command.ELSE != command && Command.END_IF != command, TrafficLightException.class,
  433.                     "RouteStep constructor with single command parameter requires ELSE or END_IF command");
  434.             this.x = TrafficController.NO_STREAM;
  435.             this.y = TrafficController.NO_STREAM;
  436.             this.command = command;
  437.             this.streamCondition = TrafficController.NO_STREAM;
  438.         }

  439.         /**
  440.          * Retrieve the X object.
  441.          * @return int; the X object
  442.          */
  443.         public int getX()
  444.         {
  445.             return this.x;
  446.         }

  447.         /**
  448.          * Retrieve the Y object.
  449.          * @return int; the Y object
  450.          */
  451.         public int getY()
  452.         {
  453.             return this.y;
  454.         }

  455.         /**
  456.          * Retrieve the command.
  457.          * @return Command
  458.          */
  459.         public Command getCommand()
  460.         {
  461.             return this.command;
  462.         }

  463.         /**
  464.          * Retrieve the stream condition.
  465.          * @return int; the streamCondition
  466.          */
  467.         public int getStreamCondition()
  468.         {
  469.             return this.streamCondition;
  470.         }

  471.         /** {@inheritDoc} */
  472.         @Override
  473.         public String toString()
  474.         {
  475.             return "RouteStep [x=" + this.x + ", y=" + this.y + ", command=" + this.command + ", streamCondition="
  476.                     + this.streamCondition + "]";
  477.         }

  478.     }

  479.     /**
  480.      * Pack two integer coordinates in one object.
  481.      */
  482.     class XYPair
  483.     {
  484.         /** X. */
  485.         private final int x;

  486.         /** Y. */
  487.         private final int y;

  488.         /**
  489.          * Construct a new XY pair.
  490.          * @param x int; the X value
  491.          * @param y int; the Y value
  492.          */
  493.         XYPair(final int x, final int y)
  494.         {
  495.             this.x = x;
  496.             this.y = y;
  497.         }

  498.         /**
  499.          * Construct a new XY pair from a route step.
  500.          * @param routeStep RouteStep; the route step
  501.          */
  502.         XYPair(final RouteStep routeStep)
  503.         {
  504.             this.x = routeStep.getX();
  505.             this.y = routeStep.getY();
  506.         }

  507.         /**
  508.          * Construct a rotated version of an XYPair.
  509.          * @param in XYPair; the initial version
  510.          * @param quadrant int; the quadrant
  511.          */
  512.         XYPair(final XYPair in, final int quadrant)
  513.         {
  514.             this.x = rotatedX(in, quadrant);
  515.             this.y = rotatedY(in, quadrant);
  516.         }

  517.         /**
  518.          * Retrieve the X value.
  519.          * @return int; the X value
  520.          */
  521.         public int getX()
  522.         {
  523.             return this.x;
  524.         }

  525.         /**
  526.          * Retrieve the Y value.
  527.          * @return int; the Y value
  528.          */
  529.         public int getY()
  530.         {
  531.             return this.y;
  532.         }

  533.         /** {@inheritDoc} */
  534.         @Override
  535.         public String toString()
  536.         {
  537.             return "XYPair [x=" + this.x + ", y=" + this.y + "]";
  538.         }

  539.     }

  540.     /**
  541.      * Construct a route.
  542.      * @param quadrant int; the quadrant to assemble the route for
  543.      * @param steps RouteStep...; an array, or series of arguments of type RouteStep
  544.      * @return XYPair[]; an array of XY pairs describing the route through the intersection
  545.      * @throws TrafficLightException when the route contains commands other than NO_OP and STOP_LINE
  546.      */
  547.     private XYPair[] rotateRoute(final int quadrant, final RouteStep... steps) throws TrafficLightException
  548.     {
  549.         List<XYPair> route = new ArrayList<>();
  550.         boolean on = true;

  551.         for (RouteStep step : steps)
  552.         {
  553.             switch (step.getCommand())
  554.             {
  555.                 case NO_OP:
  556.                 case STOP_LINE:
  557.                 case ICON:
  558.                 case STOP_LINE_AND_ICON:
  559.                     if (on)
  560.                     {
  561.                         route.add(new XYPair(new XYPair(step), quadrant));
  562.                     }
  563.                     break;

  564.                 default:
  565.                     throw new TrafficLightException("Bad command in rotateRoute: " + step.getCommand());

  566.             }
  567.         }
  568.         return route.toArray(new XYPair[route.size()]);
  569.     }

  570.     /**
  571.      * Construct a route through the intersection.
  572.      * @param steps RouteStep...; the steps of the route description
  573.      * @return RouteStep[]; the route through the intersection
  574.      * @throws TrafficLightException when something is very wrong
  575.      */
  576.     private RouteStep[] assembleRoute(final RouteStep... steps) throws TrafficLightException
  577.     {
  578.         List<RouteStep> result = new ArrayList<>();
  579.         RouteStep step;
  580.         for (int pointNo = 0; null != (step = routePoint(pointNo, steps)); pointNo++)
  581.         {
  582.             result.add(step);
  583.         }
  584.         return result.toArray(new RouteStep[result.size()]);
  585.     }

  586.     /**
  587.      * Return the Nth step in a route.
  588.      * @param pointNo int; the rank of the requested step
  589.      * @param steps RouteStep...; RouteStep... the steps
  590.      * @return RouteStep; the Nth step in the route or null if the route does not have <code>pointNo</code> steps
  591.      * @throws TrafficLightException when the command in a routestep is not recognized
  592.      */
  593.     private RouteStep routePoint(final int pointNo, final RouteStep... steps) throws TrafficLightException
  594.     {
  595.         boolean active = true;
  596.         boolean beenActive = false;
  597.         int index = 0;

  598.         for (RouteStep routeStep : steps)
  599.         {
  600.             switch (routeStep.getCommand())
  601.             {
  602.                 case NO_OP:
  603.                 case STOP_LINE:
  604.                 case ICON:
  605.                 case STOP_LINE_AND_ICON:
  606.                     if (active)
  607.                     {
  608.                         if (index++ == pointNo)
  609.                         {
  610.                             return routeStep;
  611.                         }
  612.                     }
  613.                     break;

  614.                 case IF:
  615.                     active = streamExists((short) routeStep.getStreamCondition());
  616.                     beenActive = active;
  617.                     break;

  618.                 case ELSE_IF:
  619.                     if (active)
  620.                     {
  621.                         active = false;
  622.                     }
  623.                     else if (!beenActive)
  624.                     {
  625.                         active = this.streams.contains(routeStep.getStreamCondition());
  626.                     }
  627.                     if (active)
  628.                     {
  629.                         beenActive = true;
  630.                     }
  631.                     break;

  632.                 case ELSE:
  633.                     active = !beenActive;
  634.                     break;

  635.                 case END_IF:
  636.                     active = true;
  637.                     break;

  638.                 default:
  639.                     throw new TrafficLightException("Bad switch: " + routeStep);

  640.             }
  641.         }
  642.         return null;
  643.     }

  644.     /**
  645.      * Create a BufferedImage and render the schematic on it.
  646.      * @return BufferedImage
  647.      */
  648.     public BufferedImage render()
  649.     {
  650.         int range = 2 * BOUNDARY + 1;
  651.         int cellSize = 10;
  652.         BufferedImage result = new BufferedImage(range * cellSize, range * cellSize, BufferedImage.TYPE_INT_RGB);
  653.         Graphics2D graphics = (Graphics2D) result.getGraphics();
  654.         graphics.setColor(Color.GREEN);
  655.         graphics.fillRect(0, 0, result.getWidth(), result.getHeight());
  656.         for (Short stream : this.streams)
  657.         {
  658.             switch (laneType(stream))
  659.             {
  660.                 case BICYCLE_LANE:
  661.                     graphics.setColor(Color.RED);
  662.                     break;

  663.                 case CAR_LANE:
  664.                     graphics.setColor(Color.BLACK);
  665.                     break;

  666.                 case PEDESTRIAN_LANE:
  667.                     graphics.setColor(Color.BLUE);
  668.                     break;

  669.                 case PUBLIC_TRANSIT_LANE:
  670.                     graphics.setColor(Color.BLACK);
  671.                     break;

  672.                 default:
  673.                     graphics.setColor(Color.WHITE);
  674.                     break;

  675.             }
  676.             XYPair[] path = this.routes.get(stream);
  677.             if (null == path)
  678.             {
  679.                 System.err.println("Cannot find path for stream " + stream);
  680.                 continue;
  681.             }
  682.             XYPair prevPair = null;
  683.             for (XYPair xyPair : path)
  684.             {
  685.                 if (null != prevPair)
  686.                 {
  687.                     int dx = (int) Math.signum(xyPair.getX() - prevPair.getX());
  688.                     int dy = (int) Math.signum(xyPair.getY() - prevPair.getY());
  689.                     int x = prevPair.getX() + dx;
  690.                     int y = prevPair.getY() + dy;
  691.                     while (x != xyPair.getX() || y != xyPair.getY())
  692.                     {
  693.                         fillXYPair(graphics, new XYPair(x, y));
  694.                         if (x != xyPair.getX())
  695.                         {
  696.                             x += dx;
  697.                         }
  698.                         if (y != xyPair.getY())
  699.                         {
  700.                             y += dy;
  701.                         }
  702.                     }

  703.                 }
  704.                 fillXYPair(graphics, xyPair);
  705.                 prevPair = xyPair;
  706.             }
  707.         }
  708.         return result;
  709.     }

  710.     /**
  711.      * Fill one box taking care to rotate to display conventions.
  712.      * @param graphics Graphics2D; the graphics environment
  713.      * @param xyPair XYPair; the box to fill
  714.      */
  715.     private void fillXYPair(final Graphics2D graphics, final XYPair xyPair)
  716.     {
  717.         int cellSize = 10;
  718.         graphics.fillRect(cellSize * (BOUNDARY - xyPair.getX()), cellSize * (BOUNDARY - xyPair.getY()), cellSize, cellSize);
  719.     }

  720.     /**
  721.      * Test the Diagram code.
  722.      * @param args String[]; the command line arguments (not used)
  723.      */
  724.     public static void main(final String[] args)
  725.     {
  726.         SwingUtilities.invokeLater(new Runnable()
  727.         {
  728.             @Override
  729.             public void run()
  730.             {
  731.                 JFrame frame = new JFrame("Diagram test");
  732.                 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  733.                 frame.setMinimumSize(new Dimension(1000, 1000));
  734.                 JPanel mainPanel = new JPanel(new BorderLayout());
  735.                 frame.add(mainPanel);
  736.                 checkBoxPanel = new JPanel();
  737.                 checkBoxPanel.setLayout(new BoxLayout(checkBoxPanel, BoxLayout.Y_AXIS));
  738.                 JScrollPane scrollPane = new JScrollPane(checkBoxPanel);
  739.                 scrollPane.setPreferredSize(new Dimension(150, 1000));
  740.                 mainPanel.add(scrollPane, BorderLayout.LINE_START);
  741.                 for (int stream = 1; stream <= 12; stream++)
  742.                 {
  743.                     checkBoxPanel.add(makeCheckBox(stream, stream % 3 == 2));
  744.                 }
  745.                 for (int stream = 21; stream <= 28; stream++)
  746.                 {
  747.                     checkBoxPanel.add(makeCheckBox(stream, false));
  748.                 }
  749.                 for (int stream = 31; stream <= 38; stream++)
  750.                 {
  751.                     checkBoxPanel.add(makeCheckBox(stream, false));
  752.                 }
  753.                 for (int stream = 41; stream <= 52; stream++)
  754.                 {
  755.                     checkBoxPanel.add(makeCheckBox(stream, false));
  756.                 }
  757.                 for (int stream = 61; stream <= 72; stream++)
  758.                 {
  759.                     if (stream % 3 == 1)
  760.                     {
  761.                         continue;
  762.                     }
  763.                     checkBoxPanel.add(makeCheckBox(stream, false));
  764.                 }
  765.                 testPanel = new JPanel();
  766.                 rebuildTestPanel();
  767.                 mainPanel.add(testPanel, BorderLayout.CENTER);
  768.                 frame.setVisible(true);
  769.             }
  770.         });

  771.     }

  772.     /**
  773.      * Make a check box to switch a particular stream number on or off.
  774.      * @param stream int; the stream number
  775.      * @param initialState boolean; if true; the check box will be checked
  776.      * @return JCheckBox
  777.      */
  778.     public static JCheckBox makeCheckBox(final int stream, final boolean initialState)
  779.     {
  780.         JCheckBox result = new JCheckBox(String.format("Stream %02d", stream));
  781.         result.setSelected(initialState);
  782.         result.addActionListener(new ActionListener()
  783.         {

  784.             @Override
  785.             public void actionPerformed(final ActionEvent e)
  786.             {
  787.                 rebuildTestPanel();
  788.             }
  789.         });
  790.         return result;
  791.     }

  792.     /** JPanel used to render the intersection for testing. */
  793.     private static JPanel testPanel = null;

  794.     /** JPanel that holds all the check boxes. */
  795.     private static JPanel checkBoxPanel = null;

  796.     /**
  797.      * Render the intersection.
  798.      */
  799.     static void rebuildTestPanel()
  800.     {
  801.         testPanel.removeAll();
  802.         Set<Short> streamList = new LinkedHashSet<>();
  803.         for (Component c : checkBoxPanel.getComponents())
  804.         {
  805.             if (c instanceof JCheckBox)
  806.             {
  807.                 JCheckBox checkBox = (JCheckBox) c;
  808.                 if (checkBox.isSelected())
  809.                 {
  810.                     String caption = checkBox.getText();
  811.                     String streamText = caption.substring(caption.length() - 2);
  812.                     Short stream = Short.parseShort(streamText);
  813.                     streamList.add(stream);
  814.                 }
  815.             }
  816.         }
  817.         try
  818.         {
  819.             Diagram diagram = new Diagram(streamList);
  820.             testPanel.add(new JLabel(new ImageIcon(diagram.render())));
  821.         }
  822.         catch (TrafficLightException exception)
  823.         {
  824.             exception.printStackTrace();
  825.         }
  826.         testPanel.repaint();
  827.         testPanel.revalidate();
  828.     }

  829. }