TrafficLightDetector.java

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

  2. import java.rmi.RemoteException;
  3. import java.util.ArrayList;
  4. import java.util.LinkedHashSet;
  5. import java.util.List;
  6. import java.util.Set;
  7. import java.util.UUID;

  8. import org.djunits.value.vdouble.scalar.Length;
  9. import org.djutils.draw.bounds.Bounds2d;
  10. import org.djutils.draw.line.PolyLine2d;
  11. import org.djutils.draw.line.Polygon2d;
  12. import org.djutils.draw.line.Ray2d;
  13. import org.djutils.draw.point.OrientedPoint2d;
  14. import org.djutils.draw.point.Point2d;
  15. import org.djutils.event.Event;
  16. import org.djutils.event.EventListener;
  17. import org.djutils.event.EventType;
  18. import org.djutils.event.LocalEventProducer;
  19. import org.djutils.exceptions.Throw;
  20. import org.djutils.metadata.MetaData;
  21. import org.djutils.metadata.ObjectDescriptor;
  22. import org.opentrafficsim.base.geometry.OtsLine2d;
  23. import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
  24. import org.opentrafficsim.core.gtu.GtuException;
  25. import org.opentrafficsim.core.gtu.RelativePosition;
  26. import org.opentrafficsim.core.gtu.RelativePosition.Type;
  27. import org.opentrafficsim.core.network.Network;
  28. import org.opentrafficsim.core.network.NetworkException;
  29. import org.opentrafficsim.core.object.Detector;
  30. import org.opentrafficsim.core.object.DetectorType;
  31. import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
  32. import org.opentrafficsim.road.network.lane.Lane;

  33. /**
  34.  * This traffic light reports whether any GTUs are within its area. The area is two sub-sections on one or two lanes. This
  35.  * traffic does <b>not</b> report the total number of GTUs within the area; only whether that number is zero or non-zero. This
  36.  * class does not derive from {@code Detector} as it concerns an area, not a cross-section. All sides of the 2 areas are managed
  37.  * by 4 {@code Detector}s to capture GTU longitudinal movement, and by listening to events to capture lane changes, vehicle
  38.  * generation, and vehicle destruction.
  39.  * <p>
  40.  * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  41.  * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  42.  * </p>
  43.  * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  44.  * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
  45.  * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  46.  */
  47. public class TrafficLightDetector extends LocalEventProducer implements EventListener, Detector
  48. {
  49.     /** */
  50.     private static final long serialVersionUID = 20161103L;

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

  53.     /** Unique id for network. */
  54.     private final String uniqueId;

  55.     /** The detector that detects when a GTU enters the detector area at point A. */
  56.     private final StartEndDetector entryA;

  57.     /** The detector that detects when a GTU exits the detector area at point B. */
  58.     private final StartEndDetector exitB;

  59.     /** GTUs detected by the entrance detectors, but not yet removed by the exit detectors. */
  60.     private final Set<LaneBasedGtu> currentGTUs = new LinkedHashSet<>();

  61.     /** The lanes that the detector (partly) covers. */
  62.     private final Set<Lane> lanes = new LinkedHashSet<>();

  63.     /** The OTS network. */
  64.     private final Network network;

  65.     /** Type. */
  66.     private final DetectorType type;

  67.     /** Center location. */
  68.     private final OrientedPoint2d location;

  69.     /** Geometry of the detector. */
  70.     private final Polygon2d contour;

  71.     /**
  72.      * The <b>timed</b> event type for pub/sub indicating the triggering of the entry of a NonDirectionalOccupancyDetector. <br>
  73.      * Payload: Object[] {String detectorId}
  74.      */
  75.     public static final EventType TRAFFIC_LIGHT_DETECTOR_TRIGGER_ENTRY_EVENT =
  76.             new EventType("TRAFFICLIGHTDETECTOR.TRIGGER.ENTRY",
  77.                     new MetaData("Traffic light detector entty", "Traffic light detector was entered",
  78.                             new ObjectDescriptor("Detector id", "Traffic light detector id", String.class)));

  79.     /**
  80.      * The <b>timed</b> event type for pub/sub indicating the triggering of the exit of an NonDirectionalOccupancyDetector. <br>
  81.      * Payload: Object[] {String detectorId}
  82.      */
  83.     public static final EventType TRAFFIC_LIGHT_DETECTOR_TRIGGER_EXIT_EVENT = new EventType("TRAFFICLIGHTDETECTOR.TRIGGER.EXIT",
  84.             new MetaData("Traffic light detector exit", "Traffic light detector was exited",
  85.                     new ObjectDescriptor("Detector id", "Traffic light detector id", String.class)));

  86.     /**
  87.      * Construct a new traffic light detector.<br>
  88.      * TODO Possibly provide the GtuTypes that trigger the detector as an argument for the constructor
  89.      * @param id id of this detector
  90.      * @param laneA the lane of the A detection point of this traffic light detector
  91.      * @param positionA the position of the A detection point of this traffic light detector
  92.      * @param laneB the lane of the B detection point of this traffic light detector
  93.      * @param positionB the position of the B detection point of this traffic light detector
  94.      * @param intermediateLanes list of intermediate lanes
  95.      * @param entryPosition the position on the GTUs that trigger the entry events
  96.      * @param exitPosition the position on the GTUs that trigger the exit events
  97.      * @param detectorType detector type.
  98.      * @throws NetworkException when the network is inconsistent.
  99.      */
  100.     @SuppressWarnings("checkstyle:parameternumber")
  101.     public TrafficLightDetector(final String id, final Lane laneA, final Length positionA, final Lane laneB,
  102.             final Length positionB, final List<Lane> intermediateLanes, final Type entryPosition, final Type exitPosition,
  103.             final DetectorType detectorType) throws NetworkException
  104.     {
  105.         Throw.whenNull(id, "id may not be null");
  106.         this.id = id;
  107.         this.uniqueId = UUID.randomUUID().toString() + "_" + id;
  108.         this.type = detectorType;
  109.         this.entryA = new StartEndDetector(id + ".entryA", laneA, positionA, entryPosition, detectorType);
  110.         this.exitB = new StartEndDetector(id + ".exitB", laneB, positionB, exitPosition, detectorType);
  111.         // Set up detection of GTUs that enter or leave the detector laterally or appear due to a generator or disappear due to
  112.         // a sink
  113.         this.lanes.add(laneA);
  114.         this.network = laneA.getLink().getNetwork();
  115.         if (null != intermediateLanes)
  116.         {
  117.             this.lanes.addAll(intermediateLanes);
  118.         }
  119.         this.lanes.add(laneB);
  120.         for (Lane lane : this.lanes)
  121.         {
  122.             lane.addListener(this, Lane.GTU_ADD_EVENT);
  123.             lane.addListener(this, Lane.GTU_REMOVE_EVENT);
  124.         }
  125.         try
  126.         {
  127.             OtsLine2d path;
  128.             if (this.lanes.size() == 1)
  129.             {
  130.                 path = laneA.getCenterLine().extract(positionA, positionB);
  131.             }
  132.             else
  133.             {
  134.                 List<Point2d> pathPoints = new ArrayList<>();
  135.                 pathPoints.addAll(laneA.getCenterLine().extract(positionA, laneA.getLength()).getPointList());
  136.                 for (Lane intermediateLane : intermediateLanes)
  137.                 {
  138.                     pathPoints.addAll(intermediateLane.getCenterLine().getPointList());
  139.                 }
  140.                 pathPoints.addAll(laneB.getCenterLine().extract(Length.ZERO, positionB).getPointList());
  141.                 path = new OtsLine2d(new PolyLine2d(true, pathPoints));
  142.             }
  143.             OtsLine2d left = path.offsetLine(0.5);
  144.             OtsLine2d right = path.offsetLine(-0.5);
  145.             Ray2d ray = path.getLocationFraction(0.5);
  146.             double dx = ray.x;
  147.             double dy = ray.y;
  148.             this.location = new OrientedPoint2d(dx, dy);
  149.             List<Point2d> geometryPoints = new ArrayList<>();
  150.             geometryPoints.add(new Point2d(right.get(0).x - dx, right.get(0).y - dy));
  151.             for (Point2d p : left.getPointList())
  152.             {
  153.                 geometryPoints.add(new Point2d(p.x - dx, p.y - dy));
  154.             }
  155.             for (Point2d p : right.reverse().getPointList())
  156.             {
  157.                 geometryPoints.add(new Point2d(p.x - dx, p.y - dy));
  158.             }
  159.             this.contour = new Polygon2d(geometryPoints);
  160.         }
  161.         catch (IndexOutOfBoundsException exception)
  162.         {
  163.             throw new NetworkException("Points A and B may be the same.", exception);
  164.         }
  165.         this.network.addObject(this);
  166.     }

  167.     /**
  168.      * Add a GTU to the set.
  169.      * @param gtu the GTU that must be added
  170.      */
  171.     protected final void addGtu(final LaneBasedGtu gtu)
  172.     {
  173.         if (this.currentGTUs.add(gtu) && this.currentGTUs.size() == 1)
  174.         {
  175.             fireTimedEvent(TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_ENTRY_EVENT, new Object[] {getId()},
  176.                     getSimulator().getSimulatorTime());
  177.         }
  178.     }

  179.     /**
  180.      * Remove a GTU from the set.
  181.      * @param gtu the GTU that must be removed
  182.      */
  183.     protected final void removeGtu(final LaneBasedGtu gtu)
  184.     {
  185.         if (this.currentGTUs.remove(gtu) && this.currentGTUs.size() == 0)
  186.         {
  187.             fireTimedEvent(TrafficLightDetector.TRAFFIC_LIGHT_DETECTOR_TRIGGER_EXIT_EVENT, new Object[] {getId()},
  188.                     getSimulator().getSimulatorTime());
  189.         }
  190.     }

  191.     @Override
  192.     public final void notify(final Event event) throws RemoteException
  193.     {
  194.         String gtuId = (String) ((Object[]) event.getContent())[0];
  195.         LaneBasedGtu gtu = (LaneBasedGtu) this.network.getGTU(gtuId);
  196.         if (Lane.GTU_REMOVE_EVENT.equals(event.getType()))
  197.         {
  198.             if (!this.currentGTUs.contains(gtu))
  199.             {
  200.                 return; // GTU is not currently detected; nothing to do
  201.             }
  202.             try
  203.             {
  204.                 // If the detector covers only (part of) one lane, this must have triggered this event, GTU on longer on det.
  205.                 if (this.lanes.size() == 1)
  206.                 {
  207.                     removeGtu(gtu);
  208.                     return;
  209.                 }

  210.                 Lane lane = null;
  211.                 String laneId = (String) ((Object[]) event.getContent())[4];
  212.                 String linkId = (String) ((Object[]) event.getContent())[5];
  213.                 for (Lane detectorLane : this.lanes)
  214.                 {
  215.                     if (detectorLane.getId().equals(laneId) && detectorLane.getLink().getId().equals(linkId))
  216.                     {
  217.                         lane = detectorLane;
  218.                         break;
  219.                     }
  220.                 }

  221.                 Set<Lane> remainingLanes = gtu.positions(gtu.getRelativePositions().get(RelativePosition.CENTER)).keySet();
  222.                 remainingLanes.retainAll(this.lanes);
  223.                 remainingLanes.remove(lane); // still in positions during this event
  224.                 if (remainingLanes.isEmpty())
  225.                 {
  226.                     removeGtu(gtu);
  227.                 }
  228.                 // else: GTU is still in one of our lanes and we will get another GTU_REMOVE_EVENT or the GTU will trigger one
  229.                 // of our exit flank detectors or when the GTU leaves this detector laterally
  230.                 return;
  231.             }
  232.             catch (GtuException exception)
  233.             {
  234.                 System.err.println("Caught GTU exception trying to get the a position");
  235.                 exception.printStackTrace();
  236.             }
  237.         }
  238.         else if (Lane.GTU_ADD_EVENT.equals(event.getType()))
  239.         {
  240.             if (this.currentGTUs.contains(gtu))
  241.             {
  242.                 return; // GTU is already detected; nothing to do
  243.             }
  244.             // Determine whether the GTU is in our range
  245.             try
  246.             {
  247.                 // If the detector covers only (part of) one lane, this must have triggered this event, check position on it
  248.                 if (this.lanes.size() == 1)
  249.                 {
  250.                     Lane lane = this.lanes.iterator().next();
  251.                     Length frontPos = gtu.position(lane, gtu.getRelativePositions().get(this.entryA.getPositionType()));
  252.                     Length rearPos = gtu.position(lane, gtu.getRelativePositions().get(this.exitB.getPositionType()));
  253.                     if (frontPos.gt(this.entryA.getLongitudinalPosition()) && rearPos.lt(this.exitB.getLongitudinalPosition()))
  254.                     {
  255.                         addGtu(gtu);
  256.                     }
  257.                     return;
  258.                 }

  259.                 Lane lane = null;
  260.                 String laneId = (String) ((Object[]) event.getContent())[2];
  261.                 String linkId = (String) ((Object[]) event.getContent())[3];
  262.                 for (Lane detectorLane : this.lanes)
  263.                 {
  264.                     if (detectorLane.getId().equals(laneId) && detectorLane.getLink().getId().equals(linkId))
  265.                     {
  266.                         lane = detectorLane;
  267.                     }
  268.                 }

  269.                 // If the triggering lane neither contains A nor B, it is an intermediate lane, so the GTU is on the detector
  270.                 if (!this.entryA.getLane().equals(lane) && !this.exitB.getLane().equals(lane))
  271.                 {
  272.                     addGtu(gtu);
  273.                     return;
  274.                 }

  275.                 // If triggering lane contains A, detector is triggered if front is beyond A (remainder of lane is all detector)
  276.                 if (this.entryA.getLane().equals(lane))
  277.                 {
  278.                     Length frontPos =
  279.                             gtu.position(this.entryA.getLane(), gtu.getRelativePositions().get(this.entryA.getPositionType()));
  280.                     if (frontPos.gt(this.entryA.getLongitudinalPosition()))
  281.                     {
  282.                         addGtu(gtu);
  283.                     }
  284.                     return;
  285.                 }

  286.                 // If triggering lane contains B, detector is triggered if the rear is before B (before on lane is all detector)
  287.                 if (this.exitB.getLane().equals(lane))
  288.                 {
  289.                     Length rearPos =
  290.                             gtu.position(this.exitB.getLane(), gtu.getRelativePositions().get(this.exitB.getPositionType()));
  291.                     if (rearPos.lt(this.exitB.getLongitudinalPosition()))
  292.                     {
  293.                         addGtu(gtu);
  294.                     }
  295.                     return;
  296.                 }

  297.                 throw new RuntimeException("Traffic light detector was notified that "
  298.                         + "a GTU was added to a lane, but could not figure out what to do with it.");
  299.             }
  300.             catch (GtuException exception)
  301.             {
  302.                 System.err.println("Caught GTU exception trying to get a position");
  303.                 exception.printStackTrace();
  304.             }
  305.         }
  306.         else
  307.         {
  308.             System.err.println("Unexpected event: " + event);
  309.         }
  310.     }

  311.     /** @return the relative position type of the vehicle (e.g., FRONT, BACK) that triggers the detector. */
  312.     public final Type getPositionTypeEntry()
  313.     {
  314.         return this.entryA.getPositionType();
  315.     }

  316.     /** @return the relative position type of the vehicle (e.g., FRONT, BACK) that triggers the detector. */
  317.     public final Type getPositionTypeExit()
  318.     {
  319.         return this.exitB.getPositionType();
  320.     }

  321.     /**
  322.      * Return the A position of this NonDirectionalOccupancyDetector.
  323.      * @return the lane and position on the lane where GTU entry is detected
  324.      */
  325.     public final Length getLanePositionA()
  326.     {
  327.         return this.entryA.getLongitudinalPosition();
  328.     }

  329.     /**
  330.      * Return the B position of this NonDirectionalOccupancyDetector.
  331.      * @return the lane and position on the lane where GTU exit is detected
  332.      */
  333.     public final Length getLanePositionB()
  334.     {
  335.         return this.exitB.getLongitudinalPosition();
  336.     }

  337.     /**
  338.      * One of our start/end detectors has triggered.
  339.      * @param detector the detector that was triggered
  340.      * @param gtu the gtu that triggered the flank detector
  341.      */
  342.     public final void signalDetection(final StartEndDetector detector, final LaneBasedGtu gtu)
  343.     {
  344.         if (this.entryA.equals(detector))// || this.entryB == detector)
  345.         {
  346.             addGtu(gtu);
  347.         }
  348.         else if (this.exitB.equals(detector))// || this.exitA == detector)
  349.         {
  350.             removeGtu(gtu);
  351.         }
  352.     }

  353.     /**
  354.      * Returns the id.
  355.      * @return The id of the detector.
  356.      */
  357.     @Override
  358.     public final String getId()
  359.     {
  360.         return this.id;
  361.     }

  362.     /**
  363.      * Returns the simulator.
  364.      * @return The simulator.
  365.      */
  366.     public final OtsSimulatorInterface getSimulator()
  367.     {
  368.         return this.entryA.getSimulator();
  369.     }

  370.     @Override
  371.     public final OrientedPoint2d getLocation()
  372.     {
  373.         return this.location;
  374.     }

  375.     @Override
  376.     public final Bounds2d getBounds()
  377.     {
  378.         return this.contour.getBounds();
  379.     }

  380.     /**
  381.      * Return the state of this traffic light detector.
  382.      * @return true if one or more GTUs are currently detected; false of no GTUs are currently detected
  383.      */
  384.     public final boolean getOccupancy()
  385.     {
  386.         return this.currentGTUs.size() > 0;
  387.     }

  388.     @Override
  389.     public Polygon2d getContour()
  390.     {
  391.         return this.contour;
  392.     }

  393.     @Override
  394.     public Length getHeight()
  395.     {
  396.         return Length.ZERO;
  397.     }

  398.     @Override
  399.     public String getFullId()
  400.     {
  401.         return this.uniqueId;
  402.     }

  403.     @Override
  404.     public DetectorType getType()
  405.     {
  406.         return this.type;
  407.     }

  408.     @Override
  409.     public final String toString()
  410.     {
  411.         return "TrafficLightDetector [id=" + this.id + ", entryA=" + this.entryA + ", exitB=" + this.exitB + ", currentGTUs="
  412.                 + this.currentGTUs + ", lanes=" + this.lanes + ", geometry=" + this.contour + "]";
  413.     }

  414.     /**
  415.      * Embedded detectors used by a TrafficLightDetector.
  416.      * <p>
  417.      * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  418.      * <br>
  419.      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  420.      * </p>
  421.      * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
  422.      * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
  423.      * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
  424.      */
  425.     public class StartEndDetector extends LaneDetector
  426.     {
  427.         /** */
  428.         private static final long serialVersionUID = 20161104L;

  429.         /**
  430.          * Construct a new StartEndDetector.
  431.          * @param id the name of the new StartEndDetector
  432.          * @param lane the lane of the new StartEndDetector
  433.          * @param longitudinalPosition the longitudinal position of the new StartEndDetector
  434.          * @param positionType the position on the GTUs that triggers the new StartEndDetector
  435.          * @param detectorType detector type.
  436.          * @throws NetworkException when the network is inconsistent
  437.          */
  438.         public StartEndDetector(final String id, final Lane lane, final Length longitudinalPosition, final Type positionType,
  439.                 final DetectorType detectorType) throws NetworkException
  440.         {
  441.             super(id, lane, longitudinalPosition, positionType, detectorType);
  442.         }

  443.         @Override
  444.         protected final void triggerResponse(final LaneBasedGtu gtu)
  445.         {
  446.             TrafficLightDetector.this.signalDetection(this, gtu);
  447.         }

  448.         @Override
  449.         public final String toString()
  450.         {
  451.             return "StartEndDetector [parent=" + TrafficLightDetector.this.getId() + "]";
  452.         }

  453.         /**
  454.          * Returns the parent TrafficLightDetector.
  455.          * @return parent.
  456.          */
  457.         public TrafficLightDetector getParent()
  458.         {
  459.             return TrafficLightDetector.this;
  460.         }

  461.     }

  462. }