TrafficLightSensor.java

  1. package org.opentrafficsim.road.network.lane.object.sensor;

  2. import java.awt.Color;
  3. import java.rmi.RemoteException;
  4. import java.util.ArrayList;
  5. import java.util.HashSet;
  6. import java.util.List;
  7. import java.util.Map;
  8. import java.util.Set;

  9. import javax.media.j3d.Bounds;
  10. import javax.naming.NamingException;

  11. import nl.tudelft.simulation.dsol.animation.Locatable;
  12. import nl.tudelft.simulation.event.EventInterface;
  13. import nl.tudelft.simulation.event.EventListenerInterface;
  14. import nl.tudelft.simulation.event.EventProducer;
  15. import nl.tudelft.simulation.event.EventProducerInterface;
  16. import nl.tudelft.simulation.language.Throw;
  17. import nl.tudelft.simulation.language.d3.DirectedPoint;

  18. import org.djunits.value.vdouble.scalar.Length;
  19. import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
  20. import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
  21. import org.opentrafficsim.core.geometry.OTSGeometryException;
  22. import org.opentrafficsim.core.geometry.OTSLine3D;
  23. import org.opentrafficsim.core.geometry.OTSPoint3D;
  24. import org.opentrafficsim.core.gtu.GTUDirectionality;
  25. import org.opentrafficsim.core.gtu.GTUException;
  26. import org.opentrafficsim.core.gtu.GTUType;
  27. import org.opentrafficsim.core.gtu.RelativePosition.TYPE;
  28. import org.opentrafficsim.core.network.NetworkException;
  29. import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
  30. import org.opentrafficsim.road.network.animation.SensorAnimation;
  31. import org.opentrafficsim.road.network.animation.TrafficLightSensorAnimation;
  32. import org.opentrafficsim.road.network.lane.CrossSectionElement;
  33. import org.opentrafficsim.road.network.lane.Lane;

  34. /**
  35.  * This traffic light sensor reports whether it whether any GTUs are within its area. The area is a sub-section of a Lane. This
  36.  * traffic sensor does <b>not</b> report the total number of GTUs within the area; only whether that number is zero or non-zero.
  37.  * <p>
  38.  * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  39.  * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  40.  * <p>
  41.  * @version $Revision$, $LastChangedDate$, by $Author$, initial version Oct 27, 2016 <br>
  42.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  43.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  44.  * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  45.  */
  46. public class TrafficLightSensor extends EventProducer implements EventListenerInterface, NonDirectionalOccupancySensor,
  47.         EventProducerInterface, Locatable, Sensor
  48. {
  49.     /** */
  50.     private static final long serialVersionUID = 20161103L;

  51.     /** Id of this TrafficLightSensor. */
  52.     private final String id;

  53.     /** The sensor that detects when a GTU enters the sensor area at point A. */
  54.     private final FlankSensor entryA;

  55.     /** The sensor that detects when a GTU exits the sensor area at point A. */
  56.     private final FlankSensor exitA;

  57.     /** The sensor that detects when a GTU enters the sensor area at point B. */
  58.     private final FlankSensor entryB;

  59.     /** The sensor that detects when a GTU exits the sensor area at point B. */
  60.     private final FlankSensor exitB;

  61.     /** GTUs detected by the upSensor, but not yet removed by the downSensor. */
  62.     private final Set<LaneBasedGTU> currentGTUs = new HashSet<>();

  63.     /** The lanes that the detector (partly) covers. */
  64.     private final Set<Lane> lanes = new HashSet<>();

  65.     /** Which side of the position of flank sensor A is the TrafficLightSensor. */
  66.     private final GTUDirectionality directionalityA;

  67.     /** Which side of the position of flank sensor B is the TrafficLightSensor. */
  68.     private final GTUDirectionality directionalityB;

  69.     /** Design line of the sensor. */
  70.     private final OTSLine3D path;

  71.     /**
  72.      * Construct a new traffic light sensor.<br>
  73.      * TODO Possibly provide the GTUTypes that trigger the sensor as an argument for the constructor
  74.      * @param id String; id of this sensor
  75.      * @param laneA Lane; the lane of the A detection point of this traffic light sensor
  76.      * @param positionA Length; the position of the A detection point of this traffic light sensor
  77.      * @param laneB Lane; the lane of the B detection point of this traffic light sensor
  78.      * @param positionB Length; the position of the B detection point of this traffic light sensor
  79.      * @param intermediateLanes List&lt;Lane&gt;; list of intermediate lanes
  80.      * @param entryPosition RelativePosition; the position on the GTUs that trigger the entry events
  81.      * @param exitPosition RelativePosition; the position on the GTUs that trigger the exit events
  82.      * @param simulator OTSDEVSSimulatorInterface; the simulator
  83.      * @throws NetworkException when the network is inconsistent.
  84.      */
  85.     @SuppressWarnings("checkstyle:parameternumber")
  86.     public TrafficLightSensor(final String id, final Lane laneA, final Length positionA, final Lane laneB,
  87.             final Length positionB, final List<Lane> intermediateLanes, final TYPE entryPosition, final TYPE exitPosition,
  88.             final OTSDEVSSimulatorInterface simulator) throws NetworkException
  89.     {
  90.         Throw.whenNull(id, "id may not be null");
  91.         this.id = id;
  92.         this.entryA = new FlankSensor(id + ".entryA", laneA, positionA, entryPosition, simulator, this);
  93.         this.exitA = new FlankSensor(id + ".exitA", laneA, positionA, exitPosition, simulator, this);
  94.         this.entryB = new FlankSensor(id + ".entryB", laneB, positionB, entryPosition, simulator, this);
  95.         this.exitB = new FlankSensor(id + ".exitB", laneB, positionB, exitPosition, simulator, this);
  96.         // Set up detection of GTUs that enter or leave the sensor laterally or appear due to a generator or disappear due to a
  97.         // sink
  98.         this.lanes.add(laneA);
  99.         if (null != intermediateLanes)
  100.         {
  101.             this.lanes.addAll(intermediateLanes);
  102.         }
  103.         this.lanes.add(laneB);
  104.         for (Lane lane : this.lanes)
  105.         {
  106.             lane.addListener(this, Lane.GTU_ADD_EVENT);
  107.             lane.addListener(this, Lane.GTU_REMOVE_EVENT);
  108.         }
  109.         if (laneA.equals(laneB))
  110.         {
  111.             this.directionalityA = positionA.le(positionB) ? GTUDirectionality.DIR_PLUS : GTUDirectionality.DIR_MINUS;
  112.             this.directionalityB = this.directionalityA;
  113.         }
  114.         else
  115.         {
  116.             this.directionalityA = findDirectionality(laneA);
  117.             this.directionalityB =
  118.                     GTUDirectionality.DIR_PLUS == findDirectionality(laneB) ? GTUDirectionality.DIR_MINUS
  119.                             : GTUDirectionality.DIR_PLUS;
  120.             // System.out.println("Directionality on B is " + this.directionalityB);
  121.         }
  122.         List<OTSPoint3D> outLine = new ArrayList<>();
  123.         outLine.add(fixElevation(this.entryA.getGeometry().getCentroid()));
  124.         if (null != intermediateLanes && intermediateLanes.size() > 0)
  125.         {
  126.             Lane prevLane = laneA;
  127.             List<Lane> remainingLanes = new ArrayList<>();
  128.             remainingLanes.addAll(intermediateLanes);
  129.             remainingLanes.add(laneB);
  130.             while (remainingLanes.size() > 0)
  131.             {
  132.                 Lane continuingLane = null;
  133.                 for (Lane nextLane : intermediateLanes)
  134.                 {
  135.                     if (prevLane.nextLanes(GTUType.ALL).containsKey(nextLane))
  136.                     {
  137.                         continuingLane = nextLane;
  138.                         outLine.add(fixElevation(prevLane.getCenterLine().getLast()));
  139.                         break;
  140.                     }
  141.                     else if (prevLane.prevLanes(GTUType.ALL).containsKey(nextLane))
  142.                     {
  143.                         continuingLane = nextLane;
  144.                         outLine.add(fixElevation(prevLane.getCenterLine().getFirst()));
  145.                         break;
  146.                     }
  147.                 }
  148.                 if (null == continuingLane)
  149.                 {
  150.                     throw new NetworkException("Cannot find route from laneA to laneB using the provided intermediateLanes");
  151.                 }
  152.                 remainingLanes.remove(continuingLane);
  153.             }
  154.         }
  155.         outLine.add(fixElevation(this.exitB.getGeometry().getCentroid()));
  156.         try
  157.         {
  158.             this.path = OTSLine3D.createAndCleanOTSLine3D(outLine);
  159.         }
  160.         catch (OTSGeometryException exception)
  161.         {
  162.             // This happens if A and B are the same
  163.             throw new NetworkException(exception);
  164.         }
  165.         try
  166.         {
  167.             new TrafficLightSensorAnimation(this, simulator);
  168.         }
  169.         catch (RemoteException | NamingException | OTSGeometryException exception)
  170.         {
  171.             throw new NetworkException(exception);
  172.         }
  173.     }

  174.     /**
  175.      * Increase the elevation of an OTSPoint3D.
  176.      * @param point OTSPoint3D; the point
  177.      * @return OTSPoint3D
  178.      */
  179.     private OTSPoint3D fixElevation(final OTSPoint3D point)
  180.     {
  181.         return new OTSPoint3D(point.x, point.y, point.z + SingleSensor.DEFAULT_SENSOR_ELEVATION.si);
  182.     }

  183.     /**
  184.      * Figure out which part of a lane is covered by the TrafficLightSensor.
  185.      * @param lane Lane; the lane
  186.      * @return GTUDirectionality; DIR_PLUS if the detector covers the section with higher longitudinal position; DIR_MINUS if
  187.      *         the detector covers the section with lower longitudinal position
  188.      * @throws NetworkException if the lane is not connected to any of the lanes in this.lanes
  189.      */
  190.     private GTUDirectionality findDirectionality(final Lane lane) throws NetworkException
  191.     {
  192.         for (Lane nextLane : lane.nextLanes(GTUType.ALL).keySet())
  193.         {
  194.             if (this.lanes.contains(nextLane))
  195.             {
  196.                 return GTUDirectionality.DIR_PLUS;
  197.             }
  198.         }
  199.         for (Lane prevLane : lane.prevLanes(GTUType.ALL).keySet())
  200.         {
  201.             if (this.lanes.contains(prevLane))
  202.             {
  203.                 return GTUDirectionality.DIR_MINUS;
  204.             }
  205.         }
  206.         throw new NetworkException("lane " + lane + " is not connected to any intermediate lane or the other lane");
  207.     }

  208.     /**
  209.      * Add a GTU to the set.
  210.      * @param gtu LaneBasedGTU; the GTU that must be added
  211.      */
  212.     protected final void addGTU(final LaneBasedGTU gtu)
  213.     {
  214.         if (this.currentGTUs.add(gtu) && this.currentGTUs.size() == 1)
  215.         {
  216.             fireTimedEvent(NonDirectionalOccupancySensor.NON_DIRECTIONAL_OCCUPANCY_SENSOR_TRIGGER_ENTRY_EVENT,
  217.                     new Object[] { getId() }, getSimulator().getSimulatorTime());
  218.         }
  219.     }

  220.     /**
  221.      * Remove a GTU from the set.
  222.      * @param gtu LaneBasedGTU; the GTU that must be removed
  223.      */
  224.     protected final void removeGTU(final LaneBasedGTU gtu)
  225.     {
  226.         if (this.currentGTUs.remove(gtu) && this.currentGTUs.size() == 0)
  227.         {
  228.             fireTimedEvent(NonDirectionalOccupancySensor.NON_DIRECTIONAL_OCCUPANCY_SENSOR_TRIGGER_EXIT_EVENT,
  229.                     new Object[] { getId() }, getSimulator().getSimulatorTime());
  230.         }
  231.     }

  232.     /** {@inheritDoc} */
  233.     @Override
  234.     public final void notify(final EventInterface event) throws RemoteException
  235.     {
  236.         // System.out.println("Received notification: " + event);
  237.         LaneBasedGTU gtu = (LaneBasedGTU) ((Object[]) event.getContent())[1];
  238.         if (Lane.GTU_REMOVE_EVENT.equals(event.getType()))
  239.         {
  240.             if (!this.currentGTUs.contains(gtu))
  241.             {
  242.                 return; // GTU is not currently detected; nothing to do
  243.             }
  244.             try
  245.             {
  246.                 Map<Lane, Length> frontPositions = gtu.positions(gtu.getRelativePositions().get(this.entryA.getPositionType()));
  247.                 Set<Lane> remainingLanes = new HashSet<>(frontPositions.keySet());
  248.                 remainingLanes.retainAll(this.lanes);
  249.                 if (remainingLanes.size() == 0)
  250.                 {
  251.                     removeGTU(gtu);
  252.                 }
  253.                 // else: GTU is still in one of our lanes and we will get another GTU_REMOVE_EVENT or the GTU will trigger one
  254.                 // of our exit flank sensors or when the GTU leaves this detector laterally
  255.                 return;
  256.             }
  257.             catch (GTUException exception)
  258.             {
  259.                 System.err.println("Caught GTU exception trying to get the frontPositions");
  260.                 exception.printStackTrace();
  261.             }
  262.         }
  263.         else if (Lane.GTU_ADD_EVENT.equals(event.getType()))
  264.         {
  265.             if (this.currentGTUs.contains(gtu))
  266.             {
  267.                 return; // GTU is already detected; nothing to do
  268.             }
  269.             // Determine whether the GTU is in our range
  270.             try
  271.             {
  272.                 Map<Lane, Length> frontPositions = gtu.positions(gtu.getRelativePositions().get(this.entryA.getPositionType()));
  273.                 Set<Lane> remainingLanes = new HashSet<>(frontPositions.keySet());
  274.                 remainingLanes.retainAll(this.lanes);
  275.                 if (remainingLanes.size() == 0)
  276.                 {
  277.                     System.err.println("GTU is not in any or our lanes - CANNOT HAPPEN");
  278.                 }
  279.                 Map<Lane, Length> rearPositions = gtu.positions(gtu.getRelativePositions().get(this.exitA.getPositionType()));
  280.                 for (Lane remainingLane : remainingLanes)
  281.                 {
  282.                     Length frontPosition = frontPositions.get(remainingLane);
  283.                     Length rearPosition = rearPositions.get(remainingLane);
  284.                     Length laneLength = remainingLane.getLength();
  285.                     // System.out.println("frontPosition " + frontPosition + ", rearPosition " + rearPosition + ", laneLength "
  286.                     // + laneLength + ", directionalityB " + this.directionalityB);
  287.                     if (laneLength.si >= 900)
  288.                     {
  289.                         System.out.println("Let op");
  290.                     }
  291.                     if (frontPosition.lt(Length.ZERO) && rearPosition.lt(Length.ZERO) || frontPosition.gt(laneLength)
  292.                             && rearPosition.gt(laneLength))
  293.                     {
  294.                         continue; // Not detected on this lane
  295.                     }
  296.                     // The active part of the GTU covers some part of this lane
  297.                     if (this.entryA.getLane() != remainingLane && this.entryB.getLane() != remainingLane)
  298.                     {
  299.                         // The active part covers (part of) an intermediate lane; therefore this detector detects the GTU
  300.                         addGTU(gtu);
  301.                         return;
  302.                     }
  303.                     // The GTU is on the A lane and/or the B lane; in this case the driving direction matters
  304.                     GTUDirectionality drivingDirection = gtu.getDirection(remainingLane);
  305.                     if (this.entryA.getLane().equals(this.entryB.getLane()))
  306.                     {
  307.                         // A lane equals B lane; does the active part of the GTU cover the detector?
  308.                         // TODO: not handling backwards driving GTU
  309.                         if (GTUDirectionality.DIR_PLUS == drivingDirection)
  310.                         {
  311.                             // GTU is driving in direction of increasing longitudinal distance
  312.                             if (this.directionalityA == GTUDirectionality.DIR_PLUS)
  313.                             {
  314.                                 if (frontPosition.ge(this.entryA.getLongitudinalPosition())
  315.                                         && rearPosition.lt(this.exitB.getLongitudinalPosition()))
  316.                                 {
  317.                                     addGTU(gtu);
  318.                                     return;
  319.                                 }
  320.                             }
  321.                             else
  322.                             {
  323.                                 if (frontPosition.le(this.entryB.getLongitudinalPosition())
  324.                                         && rearPosition.gt(this.exitA.getLongitudinalPosition()))
  325.                                 {
  326.                                     addGTU(gtu);
  327.                                     return;
  328.                                 }
  329.                             }
  330.                         }
  331.                         else
  332.                         {
  333.                             // GTU is driving in direction of decreasing longitudinal distance
  334.                             if (this.directionalityA == GTUDirectionality.DIR_MINUS)
  335.                             {
  336.                                 if (frontPosition.le(this.entryB.getLongitudinalPosition())
  337.                                         && rearPosition.gt(this.entryA.getLongitudinalPosition()))
  338.                                 {
  339.                                     addGTU(gtu);
  340.                                     return;
  341.                                 }
  342.                             }
  343.                             else
  344.                             {
  345.                                 if (frontPosition.le(this.entryB.getLongitudinalPosition())
  346.                                         && rearPosition.gt(this.exitA.getLongitudinalPosition()))
  347.                                 {
  348.                                     addGTU(gtu);
  349.                                     return;
  350.                                 }
  351.                             }
  352.                         }
  353.                     }
  354.                     else
  355.                     // Lane A is not equal to lane B; the GTU is on of these
  356.                     {
  357.                         Length detectionPosition;
  358.                         GTUDirectionality detectorDirectionality;
  359.                         if (this.entryA.getLane() == remainingLane)
  360.                         {
  361.                             detectionPosition = this.entryA.getLongitudinalPosition();
  362.                             detectorDirectionality = this.directionalityA;
  363.                         }
  364.                         else
  365.                         {
  366.                             detectionPosition = this.entryB.getLongitudinalPosition();
  367.                             detectorDirectionality = this.directionalityB;
  368.                         }
  369.                         if (GTUDirectionality.DIR_PLUS == drivingDirection)
  370.                         {
  371.                             if (GTUDirectionality.DIR_PLUS == detectorDirectionality)
  372.                             {
  373.                                 if (frontPosition.ge(detectionPosition))
  374.                                 {
  375.                                     addGTU(gtu);
  376.                                     return;
  377.                                 }
  378.                             }
  379.                             else
  380.                             {
  381.                                 if (rearPosition.le(detectionPosition))
  382.                                 {
  383.                                     addGTU(gtu);
  384.                                     return;
  385.                                 }
  386.                             }
  387.                         }
  388.                         else
  389.                         {
  390.                             // GTU is driving in direction of decreasing longitudinal distance
  391.                             if (GTUDirectionality.DIR_PLUS == detectorDirectionality)
  392.                             {
  393.                                 if (rearPosition.ge(detectionPosition))
  394.                                 {
  395.                                     addGTU(gtu);
  396.                                     return;
  397.                                 }
  398.                             }
  399.                             else
  400.                             {
  401.                                 if (frontPosition.le(detectionPosition))
  402.                                 {
  403.                                     addGTU(gtu);
  404.                                     return;
  405.                                 }
  406.                             }
  407.                         }
  408.                     }
  409.                 }
  410.                 return;
  411.             }
  412.             catch (GTUException exception)
  413.             {
  414.                 System.err.println("Caught GTU exception tryint to get the frontPositions");
  415.                 exception.printStackTrace();
  416.             }
  417.         }
  418.         else
  419.         {
  420.             System.err.println("Unexpected event: " + event);
  421.         }
  422.     }

  423.     /** {@inheritDoc} */
  424.     @Override
  425.     public final TYPE getPositionTypeEntry()
  426.     {
  427.         return this.entryA.getPositionType();
  428.     }

  429.     /** {@inheritDoc} */
  430.     @Override
  431.     public final TYPE getPositionTypeExit()
  432.     {
  433.         return this.exitA.getPositionType();
  434.     }

  435.     /** {@inheritDoc} */
  436.     @Override
  437.     public final Length getLanePositionA()
  438.     {
  439.         return this.entryA.getLongitudinalPosition();
  440.     }

  441.     /** {@inheritDoc} */
  442.     @Override
  443.     public final Length getLanePositionB()
  444.     {
  445.         return this.entryB.getLongitudinalPosition();
  446.     }

  447.     /**
  448.      * One of our flank sensors has triggered.
  449.      * @param sensor FlankSensor; the sensor that was triggered
  450.      * @param gtu LaneBasedGTU; the gtu that triggered the flank sensor
  451.      */
  452.     final void signalDetection(final FlankSensor sensor, final LaneBasedGTU gtu)
  453.     {
  454.         GTUDirectionality gtuDirection = null;
  455.         try
  456.         {
  457.             gtuDirection = gtu.getDirection(sensor.getLane());
  458.         }
  459.         catch (GTUException exception)
  460.         {
  461.             exception.printStackTrace();
  462.         }
  463.         // String source =
  464.         // this.entryA == sensor ? "entryA" : this.entryB == sensor ? "entryB" : this.exitA == sensor ? "exitA"
  465.         // : this.exitB == sensor ? "exitB" : "???";
  466.         // System.out.println("Time " + sensor.getSimulator().getSimulatorTime().get() + ": " + this.id + " " + source
  467.         // + " triggered on " + gtu + " driving direction is " + gtuDirection);
  468.         if (this.entryA == sensor && gtuDirection == this.directionalityA || this.entryB == sensor
  469.                 && gtuDirection != this.directionalityB)
  470.         {
  471.             addGTU(gtu);
  472.         }
  473.         else if (this.exitA == sensor && gtuDirection != this.directionalityA || this.exitB == sensor
  474.                 && gtuDirection == this.directionalityB)
  475.         // Some exit sensor has triggered
  476.         {
  477.             removeGTU(gtu);
  478.         }
  479.         // else
  480.         // {
  481.         // // System.out.println("Ignoring event (GTU is driving in wrong direction)");
  482.         // }
  483.     }

  484.     /** {@inheritDoc} */
  485.     @Override
  486.     public final String getId()
  487.     {
  488.         return this.id;
  489.     }

  490.     /** {@inheritDoc} */
  491.     @Override
  492.     public final OTSDEVSSimulatorInterface getSimulator()
  493.     {
  494.         return this.entryA.getSimulator();
  495.     }

  496.     /** {@inheritDoc} */
  497.     @Override
  498.     public final DirectedPoint getLocation() throws RemoteException
  499.     {
  500.         return this.path.getLocation();
  501.     }

  502.     /** {@inheritDoc} */
  503.     @Override
  504.     public final Bounds getBounds() throws RemoteException
  505.     {
  506.         return this.path.getBounds();
  507.     }

  508.     /**
  509.      * Return the path of this traffic light sensor.
  510.      * @return OTSLine3D; the path of this traffic light sensor
  511.      */
  512.     public final OTSLine3D getPath()
  513.     {
  514.         return this.path;
  515.     }

  516.     /**
  517.      * Return the state of this traffic light sensor.
  518.      * @return boolean; true if one or more GTUs are currently detected; false of no GTUs are currently detected
  519.      */
  520.     public final boolean getOccupancy()
  521.     {
  522.         return this.currentGTUs.size() > 0;
  523.     }

  524. }

  525. /**
  526.  * The embedded sensors of a TrafficLightSensor.
  527.  */
  528. class FlankSensor extends AbstractSensor
  529. {
  530.     /** */
  531.     private static final long serialVersionUID = 20161104L;

  532.     /** The parent that must be informed of all flanks. */
  533.     private final TrafficLightSensor parent;

  534.     /**
  535.      * Construct a new FlankSensor.
  536.      * @param id String; the name of the new FlankSensor
  537.      * @param lane Lane; the lane of the new FlankSensor
  538.      * @param longitudinalPosition Length; the longitudinal position of the new FlankSensor
  539.      * @param positionType TYPE; the position on the GTUs that triggers the new FlankSensor
  540.      * @param simulator OTSDEVSSimulatorInterface; the simulator engine
  541.      * @param parent TrafficLightSensor; the traffic light sensor that deploys this FlankSensor
  542.      * @throws NetworkException when the network is inconsistent
  543.      */
  544.     FlankSensor(final String id, final Lane lane, final Length longitudinalPosition, final TYPE positionType,
  545.             final OTSDEVSSimulatorInterface simulator, final TrafficLightSensor parent) throws NetworkException
  546.     {
  547.         super(id, lane, longitudinalPosition, positionType, simulator);
  548.         this.parent = parent;
  549.         try
  550.         {
  551.             new SensorAnimation(this, longitudinalPosition, simulator, Color.BLUE);
  552.         }
  553.         catch (RemoteException | NamingException exception)
  554.         {
  555.             throw new NetworkException(exception);
  556.         }
  557.     }

  558.     /** {@inheritDoc} */
  559.     @Override
  560.     protected void triggerResponse(final LaneBasedGTU gtu)
  561.     {
  562.         this.parent.signalDetection(this, gtu);
  563.     }

  564.     /** {@inheritDoc} */
  565.     @Override
  566.     public FlankSensor clone(final CrossSectionElement newCSE, final OTSSimulatorInterface newSimulator, final boolean animation)
  567.             throws NetworkException
  568.     {
  569.         Throw.when(!(newCSE instanceof Lane), NetworkException.class, "sensors can only be cloned for Lanes");
  570.         Throw.when(!(newSimulator instanceof OTSDEVSSimulatorInterface), NetworkException.class,
  571.                 "simulator should be a DEVSSimulator");
  572.         // XXX should the parent of the clone be our parent??? And should the (cloned) parent not construct its own flank
  573.         // sensors?
  574.         return new FlankSensor(getId(), (Lane) newCSE, getLongitudinalPosition(), getPositionType(),
  575.                 (OTSDEVSSimulatorInterface) newSimulator, this.parent);
  576.     }

  577. }