AbstractLaneBasedGTU.java

  1. package org.opentrafficsim.road.gtu.lane;

  2. import java.util.ArrayList;
  3. import java.util.Collections;
  4. import java.util.Iterator;
  5. import java.util.LinkedHashMap;
  6. import java.util.LinkedHashSet;
  7. import java.util.List;
  8. import java.util.Map;
  9. import java.util.Set;

  10. import javax.media.j3d.Bounds;
  11. import javax.vecmath.Point3d;

  12. import org.djunits.unit.DirectionUnit;
  13. import org.djunits.unit.DurationUnit;
  14. import org.djunits.unit.LengthUnit;
  15. import org.djunits.unit.PositionUnit;
  16. import org.djunits.value.vdouble.scalar.Acceleration;
  17. import org.djunits.value.vdouble.scalar.Duration;
  18. import org.djunits.value.vdouble.scalar.Length;
  19. import org.djunits.value.vdouble.scalar.Speed;
  20. import org.djunits.value.vdouble.scalar.Time;
  21. import org.djutils.exceptions.Throw;
  22. import org.djutils.exceptions.Try;
  23. import org.djutils.immutablecollections.ImmutableMap;
  24. import org.opentrafficsim.base.parameters.ParameterException;
  25. import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
  26. import org.opentrafficsim.core.geometry.OTSGeometryException;
  27. import org.opentrafficsim.core.geometry.OTSLine3D;
  28. import org.opentrafficsim.core.geometry.OTSLine3D.FractionalFallback;
  29. import org.opentrafficsim.core.geometry.OTSPoint3D;
  30. import org.opentrafficsim.core.gtu.AbstractGTU;
  31. import org.opentrafficsim.core.gtu.GTU;
  32. import org.opentrafficsim.core.gtu.GTUDirectionality;
  33. import org.opentrafficsim.core.gtu.GTUException;
  34. import org.opentrafficsim.core.gtu.GTUType;
  35. import org.opentrafficsim.core.gtu.RelativePosition;
  36. import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
  37. import org.opentrafficsim.core.gtu.perception.EgoPerception;
  38. import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
  39. import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanBuilder;
  40. import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
  41. import org.opentrafficsim.core.network.LateralDirectionality;
  42. import org.opentrafficsim.core.network.Link;
  43. import org.opentrafficsim.core.network.NetworkException;
  44. import org.opentrafficsim.core.perception.Historical;
  45. import org.opentrafficsim.core.perception.HistoricalValue;
  46. import org.opentrafficsim.core.perception.HistoryManager;
  47. import org.opentrafficsim.core.perception.collections.HistoricalLinkedHashMap;
  48. import org.opentrafficsim.core.perception.collections.HistoricalMap;
  49. import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
  50. import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
  51. import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
  52. import org.opentrafficsim.road.gtu.lane.perception.categories.DefaultSimplePerception;
  53. import org.opentrafficsim.road.gtu.lane.perception.categories.InfrastructurePerception;
  54. import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.NeighborsPerception;
  55. import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTU;
  56. import org.opentrafficsim.road.gtu.lane.plan.operational.LaneBasedOperationalPlan;
  57. import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
  58. import org.opentrafficsim.road.network.OTSRoadNetwork;
  59. import org.opentrafficsim.road.network.RoadNetwork;
  60. import org.opentrafficsim.road.network.lane.CrossSectionElement;
  61. import org.opentrafficsim.road.network.lane.CrossSectionLink;
  62. import org.opentrafficsim.road.network.lane.DirectedLanePosition;
  63. import org.opentrafficsim.road.network.lane.Lane;
  64. import org.opentrafficsim.road.network.lane.LaneDirection;
  65. import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
  66. import org.opentrafficsim.road.network.speed.SpeedLimitTypes;

  67. import nl.tudelft.simulation.dsol.SimRuntimeException;
  68. import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
  69. import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
  70. import nl.tudelft.simulation.dsol.simtime.SimTimeDoubleUnit;
  71. import nl.tudelft.simulation.language.d3.BoundingBox;
  72. import nl.tudelft.simulation.language.d3.DirectedPoint;

  73. /**
  74.  * This class contains most of the code that is needed to run a lane based GTU. <br>
  75.  * The starting point of a LaneBasedTU is that it can be in <b>multiple lanes</b> at the same time. This can be due to a lane
  76.  * change (lateral), or due to crossing a link (front of the GTU is on another Lane than rear of the GTU). If a Lane is shorter
  77.  * than the length of the GTU (e.g. when we do node expansion on a crossing, this is very well possible), a GTU could occupy
  78.  * dozens of Lanes at the same time.
  79.  * <p>
  80.  * When calculating a headway, the GTU has to look in successive lanes. When Lanes (or underlying CrossSectionLinks) diverge,
  81.  * the headway algorithms have to look at multiple Lanes and return the minimum headway in each of the Lanes. When the Lanes (or
  82.  * underlying CrossSectionLinks) converge, "parallel" traffic is not taken into account in the headway calculation. Instead, gap
  83.  * acceptance algorithms or their equivalent should guide the merging behavior.
  84.  * <p>
  85.  * To decide its movement, an AbstractLaneBasedGTU applies its car following algorithm and lane change algorithm to set the
  86.  * acceleration and any lane change operation to perform. It then schedules the triggers that will add it to subsequent lanes
  87.  * and remove it from current lanes as needed during the time step that is has committed to. Finally, it re-schedules its next
  88.  * movement evaluation with the simulator.
  89.  * <p>
  90.  * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  91.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  92.  * <p>
  93.  * @version $Revision: 1408 $, $LastChangedDate: 2015-09-24 15:17:25 +0200 (Thu, 24 Sep 2015) $, by $Author: pknoppers $,
  94.  *          initial version Oct 22, 2014 <br>
  95.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  96.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  97.  */
  98. public abstract class AbstractLaneBasedGTU extends AbstractGTU implements LaneBasedGTU
  99. {
  100.     /** */
  101.     private static final long serialVersionUID = 20140822L;

  102.     /**
  103.      * Fractional longitudinal positions of the reference point of the GTU on one or more links at the start of the current
  104.      * operational plan. Because the reference point of the GTU might not be on all the links the GTU is registered on, the
  105.      * fractional longitudinal positions can be more than one, or less than zero.
  106.      */
  107.     private HistoricalMap<Link, Double> fractionalLinkPositions;

  108.     /**
  109.      * The lanes the GTU is registered on. Each lane has to have its link registered in the fractionalLinkPositions as well to
  110.      * keep consistency. Each link from the fractionalLinkPositions can have one or more Lanes on which the vehicle is
  111.      * registered. This is a list to improve reproducibility: The 'oldest' lanes on which the vehicle is registered are at the
  112.      * front of the list, the later ones more to the back.
  113.      */
  114.     private final HistoricalMap<Lane, GTUDirectionality> currentLanes;

  115.     /** Maps that we enter when initiating a lane change, but we may not actually enter given a deviative plan. */
  116.     private final Set<Lane> enteredLanes = new LinkedHashSet<>();

  117.     /** Pending leave triggers for each lane. */
  118.     private Map<Lane, List<SimEventInterface<SimTimeDoubleUnit>>> pendingLeaveTriggers = new LinkedHashMap<>();

  119.     /** Pending enter triggers for each lane. */
  120.     private Map<Lane, List<SimEventInterface<SimTimeDoubleUnit>>> pendingEnterTriggers = new LinkedHashMap<>();

  121.     /** Event to finalize lane change. */
  122.     private SimEventInterface<SimTimeDoubleUnit> finalizeLaneChangeEvent = null;

  123.     /** Cached desired speed. */
  124.     private Speed cachedDesiredSpeed;

  125.     /** Time desired speed was cached. */
  126.     private Time desiredSpeedTime;

  127.     /** Cached car-following acceleration. */
  128.     private Acceleration cachedCarFollowingAcceleration;

  129.     /** Time car-following acceleration was cached. */
  130.     private Time carFollowingAccelerationTime;

  131.     /** The object to lock to make the GTU thread safe. */
  132.     private Object lock = new Object();

  133.     /** The threshold distance for differences between initial locations of the GTU on different lanes. */
  134.     @SuppressWarnings("checkstyle:visibilitymodifier")
  135.     public static Length initialLocationThresholdDifference = new Length(1.0, LengthUnit.MILLIMETER);

  136.     /** Turn indicator status. */
  137.     private final Historical<TurnIndicatorStatus> turnIndicatorStatus;

  138.     /** Caching on or off. */
  139.     // TODO: should be indicated with a Parameter
  140.     public static boolean CACHING = true;

  141.     /** cached position count. */
  142.     // TODO: can be removed after testing period
  143.     public static int CACHED_POSITION = 0;

  144.     /** cached position count. */
  145.     // TODO: can be removed after testing period
  146.     public static int NON_CACHED_POSITION = 0;

  147.     /** Vehicle model. */
  148.     private VehicleModel vehicleModel = VehicleModel.MINMAX;

  149.     /**
  150.      * Construct a Lane Based GTU.
  151.      * @param id String; the id of the GTU
  152.      * @param gtuType GTUType; the type of GTU, e.g. TruckType, CarType, BusType
  153.      * @param network OTSRoadNetwork; the network that the GTU is initially registered in
  154.      * @throws GTUException when initial values are not correct
  155.      */
  156.     public AbstractLaneBasedGTU(final String id, final GTUType gtuType, final OTSRoadNetwork network) throws GTUException
  157.     {
  158.         super(id, gtuType, network.getSimulator(), network);
  159.         OTSSimulatorInterface simulator = network.getSimulator();
  160.         HistoryManager historyManager = simulator.getReplication().getHistoryManager(simulator);
  161.         this.fractionalLinkPositions = new HistoricalLinkedHashMap<>(historyManager);
  162.         this.currentLanes = new HistoricalLinkedHashMap<>(historyManager);
  163.         this.turnIndicatorStatus = new HistoricalValue<>(historyManager, TurnIndicatorStatus.NOTPRESENT);
  164.     }

  165.     /**
  166.      * @param strategicalPlanner LaneBasedStrategicalPlanner; the strategical planner (e.g., route determination) to use
  167.      * @param initialLongitudinalPositions Set&lt;DirectedLanePosition&gt;; the initial positions of the car on one or more
  168.      *            lanes with their directions
  169.      * @param initialSpeed Speed; the initial speed of the car on the lane
  170.      * @throws NetworkException when the GTU cannot be placed on the given lane
  171.      * @throws SimRuntimeException when the move method cannot be scheduled
  172.      * @throws GTUException when initial values are not correct
  173.      * @throws OTSGeometryException when the initial path is wrong
  174.      */
  175.     @SuppressWarnings("checkstyle:designforextension")
  176.     public void init(final LaneBasedStrategicalPlanner strategicalPlanner,
  177.             final Set<DirectedLanePosition> initialLongitudinalPositions, final Speed initialSpeed)
  178.             throws NetworkException, SimRuntimeException, GTUException, OTSGeometryException
  179.     {
  180.         Throw.when(null == initialLongitudinalPositions, GTUException.class, "InitialLongitudinalPositions is null");
  181.         Throw.when(0 == initialLongitudinalPositions.size(), GTUException.class, "InitialLongitudinalPositions is empty set");

  182.         DirectedPoint lastPoint = null;
  183.         for (DirectedLanePosition pos : initialLongitudinalPositions)
  184.         {
  185.             // Throw.when(lastPoint != null && pos.getLocation().distance(lastPoint) > initialLocationThresholdDifference.si,
  186.             // GTUException.class, "initial locations for GTU have distance > " + initialLocationThresholdDifference);
  187.             lastPoint = pos.getLocation();
  188.         }
  189.         DirectedPoint initialLocation = lastPoint;

  190.         // Give the GTU a 1 micrometer long operational plan, or a stand-still plan, so the first move and events will work
  191.         Time now = getSimulator().getSimulatorTime();
  192.         try
  193.         {
  194.             if (initialSpeed.si < OperationalPlan.DRIFTING_SPEED_SI)
  195.             {
  196.                 this.operationalPlan
  197.                         .set(new OperationalPlan(this, initialLocation, now, new Duration(1E-6, DurationUnit.SECOND)));
  198.             }
  199.             else
  200.             {
  201.                 OTSPoint3D p2 = new OTSPoint3D(initialLocation.x + 1E-6 * Math.cos(initialLocation.getRotZ()),
  202.                         initialLocation.y + 1E-6 * Math.sin(initialLocation.getRotZ()), initialLocation.z);
  203.                 OTSLine3D path = new OTSLine3D(new OTSPoint3D(initialLocation), p2);
  204.                 this.operationalPlan.set(OperationalPlanBuilder.buildConstantSpeedPlan(this, path, now, initialSpeed));
  205.             }
  206.         }
  207.         catch (OperationalPlanException e)
  208.         {
  209.             throw new RuntimeException("Initial operational plan could not be created.", e);
  210.         }

  211.         // register the GTU on the lanes
  212.         for (DirectedLanePosition directedLanePosition : initialLongitudinalPositions)
  213.         {
  214.             Lane lane = directedLanePosition.getLane();
  215.             addLaneToGtu(lane, directedLanePosition.getPosition(), directedLanePosition.getGtuDirection()); // enter lane part 1
  216.         }

  217.         // init event
  218.         DirectedLanePosition referencePosition = getReferencePosition();
  219.         fireTimedEvent(LaneBasedGTU.LANEBASED_INIT_EVENT,
  220.                 new Object[] { getId(), initialLocation, getLength(), getWidth(), referencePosition.getLane(),
  221.                         referencePosition.getPosition(), referencePosition.getGtuDirection(), getGTUType() },
  222.                 getSimulator().getSimulatorTime());

  223.         // register the GTU on the lanes
  224.         for (DirectedLanePosition directedLanePosition : initialLongitudinalPositions)
  225.         {
  226.             Lane lane = directedLanePosition.getLane();
  227.             lane.addGTU(this, directedLanePosition.getPosition()); // enter lane part 2
  228.         }

  229.         // initiate the actual move
  230.         super.init(strategicalPlanner, initialLocation, initialSpeed);

  231.         this.referencePositionTime = Double.NaN; // remove cache, it may be invalid as the above init results in a lane change

  232.     }

  233.     /**
  234.      * {@inheritDoc} All lanes the GTU is on will be left.
  235.      */
  236.     @Override
  237.     public void setParent(final GTU gtu) throws GTUException
  238.     {
  239.         for (Lane lane : new LinkedHashSet<>(this.currentLanes.keySet())) // copy for concurrency problems
  240.         {
  241.             leaveLane(lane);
  242.         }
  243.         super.setParent(gtu);
  244.     }

  245.     /**
  246.      * Reinitializes the GTU on the network using the existing strategical planner and zero speed.
  247.      * @param initialLongitudinalPositions Set&lt;DirectedLanePosition&gt;; initial position
  248.      * @throws NetworkException when the GTU cannot be placed on the given lane
  249.      * @throws SimRuntimeException when the move method cannot be scheduled
  250.      * @throws GTUException when initial values are not correct
  251.      * @throws OTSGeometryException when the initial path is wrong
  252.      */
  253.     public void reinit(final Set<DirectedLanePosition> initialLongitudinalPositions)
  254.             throws NetworkException, SimRuntimeException, GTUException, OTSGeometryException
  255.     {
  256.         init(getStrategicalPlanner(), initialLongitudinalPositions, Speed.ZERO);
  257.     }

  258.     /**
  259.      * Hack method. TODO remove and solve better
  260.      * @return safe to change
  261.      * @throws GTUException on error
  262.      */
  263.     public final boolean isSafeToChange() throws GTUException
  264.     {
  265.         return this.fractionalLinkPositions.get(getReferencePosition().getLane().getParentLink()) > 0.0;
  266.     }

  267.     /**
  268.      * insert GTU at a certain position. This can happen at setup (first initialization), and after a lane change of the GTU.
  269.      * The relative position that will be registered is the referencePosition (dx, dy, dz) = (0, 0, 0). Front and rear positions
  270.      * are relative towards this position.
  271.      * @param lane Lane; the lane to add to the list of lanes on which the GTU is registered.
  272.      * @param gtuDirection GTUDirectionality; the direction of the GTU on the lane (which can be bidirectional). If the GTU has
  273.      *            a positive speed, it is moving in this direction.
  274.      * @param position Length; the position on the lane.
  275.      * @throws GTUException when positioning the GTU on the lane causes a problem
  276.      */
  277.     @SuppressWarnings("checkstyle:designforextension")
  278.     public void enterLane(final Lane lane, final Length position, final GTUDirectionality gtuDirection) throws GTUException
  279.     {
  280.         if (lane == null || gtuDirection == null || position == null)
  281.         {
  282.             throw new GTUException("enterLane - one of the arguments is null");
  283.         }
  284.         addLaneToGtu(lane, position, gtuDirection);
  285.         addGtuToLane(lane, position);
  286.     }

  287.     /**
  288.      * Registers the lane at the GTU. Only works at the start of a operational plan.
  289.      * @param lane Lane; the lane to add to the list of lanes on which the GTU is registered.
  290.      * @param gtuDirection GTUDirectionality; the direction of the GTU on the lane (which can be bidirectional). If the GTU has
  291.      *            a positive speed, it is moving in this direction.
  292.      * @param position Length; the position on the lane.
  293.      * @throws GTUException when positioning the GTU on the lane causes a problem
  294.      */
  295.     private void addLaneToGtu(final Lane lane, final Length position, final GTUDirectionality gtuDirection) throws GTUException
  296.     {
  297.         if (this.currentLanes.containsKey(lane))
  298.         {
  299.             System.err.println(this + " is already registered on lane: " + lane + " at fractional position "
  300.                     + this.fractionalPosition(lane, RelativePosition.REFERENCE_POSITION) + " intended position is " + position
  301.                     + " length of lane is " + lane.getLength());
  302.             return;
  303.         }
  304.         // if the GTU is already registered on a lane of the same link, do not change its fractional position, as
  305.         // this might lead to a "jump".
  306.         if (!this.fractionalLinkPositions.containsKey(lane.getParentLink()))
  307.         {
  308.             this.fractionalLinkPositions.put(lane.getParentLink(), lane.fraction(position));
  309.         }
  310.         this.currentLanes.put(lane, gtuDirection);
  311.     }

  312.     /**
  313.      * Part of 'enterLane' which registers the GTU with the lane so the lane can report its GTUs.
  314.      * @param lane Lane; lane
  315.      * @param position Length; position
  316.      * @throws GTUException on exception
  317.      */
  318.     protected void addGtuToLane(final Lane lane, final Length position) throws GTUException
  319.     {
  320.         List<SimEventInterface<SimTimeDoubleUnit>> pending = this.pendingEnterTriggers.get(lane);
  321.         if (null != pending)
  322.         {
  323.             for (SimEventInterface<SimTimeDoubleUnit> event : pending)
  324.             {
  325.                 if (event.getAbsoluteExecutionTime().get().ge(getSimulator().getSimulatorTime()))
  326.                 {
  327.                     boolean result = getSimulator().cancelEvent(event);
  328.                     if (!result && event.getAbsoluteExecutionTime().get().ne(getSimulator().getSimulatorTime()))
  329.                     {
  330.                         System.err.println("addLaneToGtu, trying to remove event: NOTHING REMOVED -- result=" + result
  331.                                 + ", simTime=" + getSimulator().getSimulatorTime() + ", eventTime="
  332.                                 + event.getAbsoluteExecutionTime().get());
  333.                     }
  334.                 }
  335.             }
  336.             this.pendingEnterTriggers.remove(lane);
  337.         }
  338.         lane.addGTU(this, position);
  339.     }

  340.     /**
  341.      * Unregister the GTU from a lane.
  342.      * @param lane Lane; the lane to remove from the list of lanes on which the GTU is registered.
  343.      * @throws GTUException when leaveLane should not be called
  344.      */
  345.     @SuppressWarnings("checkstyle:designforextension")
  346.     public void leaveLane(final Lane lane) throws GTUException
  347.     {
  348.         leaveLane(lane, false);
  349.     }

  350.     /**
  351.      * Leave a lane but do not complain about having no lanes left when beingDestroyed is true.
  352.      * @param lane Lane; the lane to leave
  353.      * @param beingDestroyed boolean; if true, no complaints about having no lanes left
  354.      * @throws GTUException in case leaveLane should not be called
  355.      */
  356.     @SuppressWarnings("checkstyle:designforextension")
  357.     public void leaveLane(final Lane lane, final boolean beingDestroyed) throws GTUException
  358.     {
  359.         Length position = position(lane, getReference());
  360.         this.currentLanes.remove(lane);
  361.         removePendingEvents(lane, this.pendingLeaveTriggers);
  362.         removePendingEvents(lane, this.pendingEnterTriggers);
  363.         // check if there are any lanes for this link left. If not, remove the link.
  364.         boolean found = false;
  365.         for (Lane l : this.currentLanes.keySet())
  366.         {
  367.             if (l.getParentLink().equals(lane.getParentLink()))
  368.             {
  369.                 found = true;
  370.             }
  371.         }
  372.         if (!found)
  373.         {
  374.             this.fractionalLinkPositions.remove(lane.getParentLink());
  375.         }
  376.         lane.removeGTU(this, !found, position);
  377.         if (this.currentLanes.size() == 0 && !beingDestroyed)
  378.         {
  379.             System.err.println("leaveLane: lanes.size() = 0 for GTU " + getId());
  380.         }
  381.     }

  382.     /**
  383.      * Removes and cancels events for the given lane.
  384.      * @param lane Lane; lane
  385.      * @param triggers Map&lt;Lane, List&lt;SimEventInterface&lt;SimTimeDoubleUnit&gt;&gt;&gt;; map to use
  386.      */
  387.     private void removePendingEvents(final Lane lane, final Map<Lane, List<SimEventInterface<SimTimeDoubleUnit>>> triggers)
  388.     {
  389.         List<SimEventInterface<SimTimeDoubleUnit>> pending = triggers.get(lane);
  390.         if (null != pending)
  391.         {
  392.             for (SimEventInterface<SimTimeDoubleUnit> event : pending)
  393.             {
  394.                 if (event.getAbsoluteExecutionTime().get().ge(getSimulator().getSimulatorTime()))
  395.                 {
  396.                     boolean result = getSimulator().cancelEvent(event);
  397.                     if (!result && event.getAbsoluteExecutionTime().get().ne(getSimulator().getSimulatorTime()))
  398.                     {
  399.                         System.err.println("leaveLane, trying to remove event: NOTHING REMOVED -- result=" + result
  400.                                 + ", simTime=" + getSimulator().getSimulatorTime() + ", eventTime="
  401.                                 + event.getAbsoluteExecutionTime().get());
  402.                     }
  403.                 }
  404.             }
  405.             triggers.remove(lane);
  406.         }
  407.     }

  408.     /** {@inheritDoc} */
  409.     @Override
  410.     public void changeLaneInstantaneously(final LateralDirectionality laneChangeDirection) throws GTUException
  411.     {

  412.         // from info
  413.         DirectedLanePosition from = getReferencePosition();

  414.         // keep a copy of the lanes and directions (!)
  415.         Set<Lane> lanesToBeRemoved = new LinkedHashSet<>(this.currentLanes.keySet());

  416.         // store the new positions
  417.         // start with current link position, these will be overwritten, except if from a lane no adjacent lane is found, i.e.
  418.         // changing over a continuous line when probably the reference point is past the line
  419.         Map<Link, Double> newLinkPositionsLC = new LinkedHashMap<>(this.fractionalLinkPositions);

  420.         // obtain position on lane adjacent to reference lane and enter lanes upstream/downstream from there
  421.         Set<Lane> adjLanes = from.getLane().accessibleAdjacentLanesPhysical(laneChangeDirection, getGTUType(),
  422.                 this.currentLanes.get(from.getLane()));
  423.         Lane adjLane = adjLanes.iterator().next();
  424.         Length position = adjLane.position(from.getLane().fraction(from.getPosition()));
  425.         GTUDirectionality direction = getDirection(from.getLane());
  426.         Length planLength = Try.assign(() -> getOperationalPlan().getTraveledDistance(getSimulator().getSimulatorTime()),
  427.                 "Exception while determining plan length.");
  428.         enterLaneRecursive(new LaneDirection(adjLane, direction), position, newLinkPositionsLC, planLength, lanesToBeRemoved,
  429.                 0);

  430.         // update the positions on the lanes we are registered on
  431.         this.fractionalLinkPositions.clear();
  432.         this.fractionalLinkPositions.putAll(newLinkPositionsLC);

  433.         // leave the from lanes
  434.         for (Lane lane : lanesToBeRemoved)
  435.         {
  436.             leaveLane(lane);
  437.         }

  438.         // stored positions no longer valid
  439.         this.referencePositionTime = Double.NaN;
  440.         this.cachedPositions.clear();

  441.         // fire event
  442.         this.fireTimedEvent(LaneBasedGTU.LANE_CHANGE_EVENT, new Object[] { getId(), laneChangeDirection, from },
  443.                 getSimulator().getSimulatorTime());

  444.     }

  445.     /**
  446.      * Enters lanes upstream and downstream of the new location after an instantaneous lane change.
  447.      * @param lane LaneDirection; considered lane
  448.      * @param position Length; position to add GTU at
  449.      * @param newLinkPositionsLC Map&lt;Link, Double&gt;; new link fractions to store
  450.      * @param planLength Length; length of plan, to consider fractions at start
  451.      * @param lanesToBeRemoved Set&lt;Lane&gt;; lanes to leave, from which lanes are removed when entered (such that they arent
  452.      *            then left)
  453.      * @param dir int; below 0 for upstream, above 0 for downstream, 0 for both
  454.      * @throws GTUException on exception
  455.      */
  456.     private void enterLaneRecursive(final LaneDirection lane, final Length position, final Map<Link, Double> newLinkPositionsLC,
  457.             final Length planLength, final Set<Lane> lanesToBeRemoved, final int dir) throws GTUException
  458.     {
  459.         enterLane(lane.getLane(), position, lane.getDirection());
  460.         lanesToBeRemoved.remove(lane);
  461.         Length adjusted = lane.getDirection().isPlus() ? position.minus(planLength) : position.plus(planLength);
  462.         newLinkPositionsLC.put(lane.getLane().getParentLink(), adjusted.si / lane.getLength().si);

  463.         // upstream
  464.         if (dir < 1)
  465.         {
  466.             Length rear = lane.getDirection().isPlus() ? position.plus(getRear().getDx()) : position.minus(getRear().getDx());
  467.             Length before = null;
  468.             if (lane.getDirection().isPlus() && rear.si < 0.0)
  469.             {
  470.                 before = rear.neg();
  471.             }
  472.             else if (lane.getDirection().isMinus() && rear.si > lane.getLength().si)
  473.             {
  474.                 before = rear.minus(lane.getLength());
  475.             }
  476.             if (before != null)
  477.             {
  478.                 GTUDirectionality upDir = lane.getDirection();
  479.                 ImmutableMap<Lane, GTUDirectionality> upstream = lane.getLane().upstreamLanes(upDir, getGTUType());
  480.                 if (!upstream.isEmpty())
  481.                 {
  482.                     Lane upLane = null;
  483.                     for (Lane nextUp : upstream.keySet())
  484.                     {
  485.                         if (newLinkPositionsLC.containsKey(nextUp.getParentLink()))
  486.                         {
  487.                             // multiple upstream lanes could belong to the same link, we pick an arbitrary lane
  488.                             // (a conflict should solve this)
  489.                             upLane = nextUp;
  490.                             break;
  491.                         }
  492.                     }
  493.                     if (upLane == null)
  494.                     {
  495.                         // the rear is on an upstream section we weren't before the lane change, due to curvature, we pick an
  496.                         // arbitrary lane (a conflict should solve this)
  497.                         upLane = upstream.keySet().iterator().next();
  498.                     }
  499.                     if (!this.currentLanes.containsKey(upLane))
  500.                     {
  501.                         upDir = upstream.get(upLane);
  502.                         LaneDirection next = new LaneDirection(upLane, upDir);
  503.                         Length nextPos = upDir.isPlus() ? next.getLength().minus(before).minus(getRear().getDx())
  504.                                 : before.plus(getRear().getDx());
  505.                         enterLaneRecursive(next, nextPos, newLinkPositionsLC, planLength, lanesToBeRemoved, -1);
  506.                     }
  507.                 }
  508.             }
  509.         }

  510.         // downstream
  511.         if (dir > -1)
  512.         {
  513.             Length front =
  514.                     lane.getDirection().isPlus() ? position.plus(getFront().getDx()) : position.minus(getFront().getDx());
  515.             Length passed = null;
  516.             if (lane.getDirection().isPlus() && front.si > lane.getLength().si)
  517.             {
  518.                 passed = front.minus(lane.getLength());
  519.             }
  520.             else if (lane.getDirection().isMinus() && front.si < 0.0)
  521.             {
  522.                 passed = front.neg();
  523.             }
  524.             if (passed != null)
  525.             {
  526.                 LaneDirection next = lane.getNextLaneDirection(this);
  527.                 if (!this.currentLanes.containsKey(next.getLane()))
  528.                 {
  529.                     Length nextPos = next.getDirection().isPlus() ? passed.minus(getFront().getDx())
  530.                             : next.getLength().minus(passed).plus(getFront().getDx());
  531.                     enterLaneRecursive(next, nextPos, newLinkPositionsLC, planLength, lanesToBeRemoved, 1);
  532.                 }
  533.             }
  534.         }
  535.     }

  536.     /**
  537.      * Register on lanes in target lane.
  538.      * @param laneChangeDirection LateralDirectionality; direction of lane change
  539.      * @throws GTUException exception
  540.      */
  541.     @Override
  542.     @SuppressWarnings("checkstyle:designforextension")
  543.     public void initLaneChange(final LateralDirectionality laneChangeDirection) throws GTUException
  544.     {
  545.         Map<Lane, GTUDirectionality> lanesCopy = new LinkedHashMap<>(this.currentLanes);
  546.         Map<Lane, Double> fractionalLanePositions = new LinkedHashMap<>();
  547.         for (Lane lane : lanesCopy.keySet())
  548.         {
  549.             fractionalLanePositions.put(lane, fractionalPosition(lane, getReference()));
  550.         }
  551.         int numRegistered = 0;
  552.         for (Lane lane : lanesCopy.keySet())
  553.         {
  554.             Set<Lane> laneSet = lane.accessibleAdjacentLanesLegal(laneChangeDirection, getGTUType(), getDirection(lane));
  555.             if (laneSet.size() > 0)
  556.             {
  557.                 numRegistered++;
  558.                 Lane adjacentLane = laneSet.iterator().next();
  559.                 Length position = adjacentLane.getLength().times(fractionalLanePositions.get(lane));
  560.                 if (lanesCopy.get(lane).isPlus() ? position.lt(lane.getLength().minus(getRear().getDx()))
  561.                         : position.gt(getFront().getDx().neg()))
  562.                 {
  563.                     this.enteredLanes.add(adjacentLane);
  564.                     enterLane(adjacentLane, position, lanesCopy.get(lane));
  565.                 }
  566.                 else
  567.                 {
  568.                     System.out.println("Skipping enterLane for GTU " + getId() + " on lane " + lane.getFullId() + " at "
  569.                             + position + ", lane length = " + lane.getLength() + " rear = " + getRear().getDx() + " front = "
  570.                             + getFront().getDx());
  571.                 }
  572.             }
  573.         }
  574.         Throw.when(numRegistered == 0, GTUException.class, "Gtu %s starting %s lane change, but no adjacent lane found.",
  575.                 getId(), laneChangeDirection);
  576.     }

  577.     /**
  578.      * Performs the finalization of a lane change by leaving the from lanes.
  579.      * @param laneChangeDirection LateralDirectionality; direction of lane change
  580.      */
  581.     @SuppressWarnings("checkstyle:designforextension")
  582.     protected void finalizeLaneChange(final LateralDirectionality laneChangeDirection)
  583.     {
  584.         Map<Lane, GTUDirectionality> lanesCopy = new LinkedHashMap<>(this.currentLanes);
  585.         Set<Lane> lanesToBeRemoved = new LinkedHashSet<>();
  586.         Lane fromLane = null;
  587.         Length fromPosition = null;
  588.         GTUDirectionality fromDirection = null;
  589.         try
  590.         {
  591.             // find lanes to leave as they have an adjacent lane the GTU is also on in the lane change direction
  592.             for (Lane lane : lanesCopy.keySet())
  593.             {
  594.                 Iterator<Lane> iterator =
  595.                         lane.accessibleAdjacentLanesPhysical(laneChangeDirection, getGTUType(), getDirection(lane)).iterator();
  596.                 if (iterator.hasNext() && lanesCopy.keySet().contains(iterator.next()))
  597.                 {
  598.                     lanesToBeRemoved.add(lane);
  599.                 }
  600.             }
  601.             // some lanes registered to the GTU may be downstream of a split and have no adjacent lane, find longitudinally
  602.             boolean added = true;
  603.             while (added)
  604.             {
  605.                 added = false;
  606.                 Set<Lane> lanesToAlsoBeRemoved = new LinkedHashSet<>();
  607.                 for (Lane lane : lanesToBeRemoved)
  608.                 {
  609.                     GTUDirectionality direction = getDirection(lane);
  610.                     for (Lane nextLane : direction.isPlus() ? lane.nextLanes(getGTUType()).keySet()
  611.                             : lane.prevLanes(getGTUType()).keySet())
  612.                     {
  613.                         if (lanesCopy.containsKey(nextLane) && !lanesToBeRemoved.contains(nextLane))
  614.                         {
  615.                             added = true;
  616.                             lanesToAlsoBeRemoved.add(nextLane);
  617.                         }
  618.                     }
  619.                 }
  620.                 lanesToBeRemoved.addAll(lanesToAlsoBeRemoved);
  621.             }
  622.             double nearest = Double.POSITIVE_INFINITY;
  623.             for (Lane lane : lanesToBeRemoved)
  624.             {
  625.                 Length pos = position(lane, RelativePosition.REFERENCE_POSITION);
  626.                 if (0.0 <= pos.si && pos.si <= lane.getLength().si)
  627.                 {
  628.                     fromLane = lane;
  629.                     fromPosition = pos;
  630.                     fromDirection = getDirection(lane);
  631.                 }
  632.                 else if (fromLane == null && (getDirection(lane).isPlus() ? pos.si > lane.getLength().si : pos.le0()))
  633.                 {
  634.                     // if the reference point is in between two lanes, this recognizes the lane upstream of the gap
  635.                     double distance = getDirection(lane).isPlus() ? pos.si - lane.getLength().si : -pos.si;
  636.                     if (distance < nearest)
  637.                     {
  638.                         nearest = distance;
  639.                         fromLane = lane;
  640.                         fromPosition = pos;
  641.                         fromDirection = getDirection(lane);
  642.                     }
  643.                 }
  644.                 leaveLane(lane);
  645.             }
  646.             this.referencePositionTime = Double.NaN;
  647.             this.finalizeLaneChangeEvent = null;
  648.         }
  649.         catch (GTUException exception)
  650.         {
  651.             // should not happen, lane was obtained from GTU
  652.             throw new RuntimeException("position on lane not possible", exception);
  653.         }
  654.         Throw.when(fromLane == null, RuntimeException.class, "No from lane for lane change event.");
  655.         DirectedLanePosition from;
  656.         try
  657.         {
  658.             from = new DirectedLanePosition(fromLane, fromPosition, fromDirection);
  659.         }
  660.         catch (GTUException exception)
  661.         {
  662.             throw new RuntimeException(exception);
  663.         }
  664.         this.fireTimedEvent(LaneBasedGTU.LANE_CHANGE_EVENT, new Object[] { getId(), laneChangeDirection, from },
  665.                 getSimulator().getSimulatorTime());
  666.     }

  667.     /** {@inheritDoc} */
  668.     @Override
  669.     public void setFinalizeLaneChangeEvent(final SimEventInterface<SimTimeDoubleUnit> event)
  670.     {
  671.         this.finalizeLaneChangeEvent = event;
  672.     }

  673.     /** {@inheritDoc} */
  674.     @Override
  675.     public final GTUDirectionality getDirection(final Lane lane) throws GTUException
  676.     {
  677.         Throw.when(!this.currentLanes.containsKey(lane), GTUException.class, "getDirection: Lanes %s does not contain %s",
  678.                 this.currentLanes.keySet(), lane);
  679.         return this.currentLanes.get(lane);
  680.     }

  681.     /** {@inheritDoc} */
  682.     @Override
  683.     @SuppressWarnings("checkstyle:designforextension")
  684.     protected boolean move(final DirectedPoint fromLocation)
  685.             throws SimRuntimeException, GTUException, OperationalPlanException, NetworkException, ParameterException
  686.     {
  687.         // DirectedPoint currentPoint = getLocation(); // used for "jump" detection that is also commented out
  688.         // Only carry out move() if we still have lane(s) to drive on.
  689.         // Note: a (Sink) trigger can have 'destroyed' us between the previous evaluation step and this one.
  690.         if (this.currentLanes.isEmpty())
  691.         {
  692.             destroy();
  693.             return false; // Done; do not re-schedule execution of this move method.
  694.         }

  695.         // remove enter events
  696.         // WS: why?
  697.         // for (Lane lane : this.pendingEnterTriggers.keySet())
  698.         // {
  699.         // System.out.println("GTU " + getId() + " is canceling event on lane " + lane.getFullId());
  700.         // List<SimEventInterface<SimTimeDoubleUnit>> events = this.pendingEnterTriggers.get(lane);
  701.         // for (SimEventInterface<SimTimeDoubleUnit> event : events)
  702.         // {
  703.         // // also unregister from lane
  704.         // this.currentLanes.remove(lane);
  705.         // getSimulator().cancelEvent(event);
  706.         // }
  707.         // }
  708.         // this.pendingEnterTriggers.clear();

  709.         // get distance covered in previous plan, to aid a shift in link fraction (from which a plan moves onwards)
  710.         Length covered;
  711.         if (getOperationalPlan() instanceof LaneBasedOperationalPlan
  712.                 && ((LaneBasedOperationalPlan) getOperationalPlan()).isDeviative())
  713.         {
  714.             // traveled distance as difference between start and current position on reference lane
  715.             // note that for a deviative plan the traveled distance along the path is not valuable here
  716.             LaneBasedOperationalPlan plan = (LaneBasedOperationalPlan) getOperationalPlan();
  717.             DirectedLanePosition ref = getReferencePosition();
  718.             covered = ref.getGtuDirection().isPlus()
  719.                     ? position(ref.getLane(), getReference())
  720.                             .minus(position(ref.getLane(), getReference(), plan.getStartTime()))
  721.                     : position(ref.getLane(), getReference(), plan.getStartTime())
  722.                             .minus(position(ref.getLane(), getReference()));
  723.             // Note that distance is valid as the reference lane can not change (and location of previous plan is start location
  724.             // of current plan). Only instantaneous lane changes can do that, which do not result in deviative plans.
  725.         }
  726.         else
  727.         {
  728.             covered = getOperationalPlan().getTraveledDistance(getSimulator().getSimulatorTime());
  729.         }

  730.         // generate the next operational plan and carry it out
  731.         // in case of an instantaneous lane change, fractionalLinkPositions will be accordingly adjusted to the new lane
  732.         super.move(fromLocation);

  733.         // update the positions on the lanes we are registered on
  734.         // WS: this was previously done using fractions calculated before super.move() based on the GTU position, but an
  735.         // instantaneous lane change while e.g. the nose is on the next lane which is curved, results in a different fraction on
  736.         // the next link (the GTU doesn't stretch or shrink)
  737.         Map<Link, Double> newLinkFractions = new LinkedHashMap<>(this.fractionalLinkPositions);
  738.         Set<Link> done = new LinkedHashSet<>();
  739.         // WS: this used to be on all current lanes, skipping links already processed, but 'covered' regards the reference lane
  740.         updateLinkFraction(getReferencePosition().getLane(), newLinkFractions, done, false, covered, true);
  741.         updateLinkFraction(getReferencePosition().getLane(), newLinkFractions, done, true, covered, true);
  742.         this.fractionalLinkPositions.clear();
  743.         this.fractionalLinkPositions.putAll(newLinkFractions);

  744.         DirectedLanePosition dlp = getReferencePosition();
  745.         fireTimedEvent(
  746.                 LaneBasedGTU.LANEBASED_MOVE_EVENT,
  747.                 new Object[] { getId(), new OTSPoint3D(fromLocation).doubleVector(PositionUnit.METER),
  748.                         OTSPoint3D.direction(fromLocation, DirectionUnit.EAST_RADIAN), getSpeed(), getAcceleration(),
  749.                         getTurnIndicatorStatus(), getOdometer(), dlp.getLane().getParentLink().getId(), dlp.getLane().getId(),
  750.                         dlp.getPosition(), dlp.getGtuDirection().name() },
  751.                 getSimulator().getSimulatorTime());

  752.         if (getOperationalPlan().getAcceleration(Duration.ZERO).si < -10
  753.                 && getOperationalPlan().getSpeed(Duration.ZERO).si > 2.5)
  754.         {
  755.             System.err.println("GTU: " + getId() + " - getOperationalPlan().getAcceleration(Duration.ZERO).si < -10)");
  756.             System.err.println("Lanes in current plan: " + this.currentLanes.keySet());
  757.             if (getTacticalPlanner().getPerception().contains(DefaultSimplePerception.class))
  758.             {
  759.                 DefaultSimplePerception p =
  760.                         getTacticalPlanner().getPerception().getPerceptionCategory(DefaultSimplePerception.class);
  761.                 System.err.println("HeadwayGTU: " + p.getForwardHeadwayGTU());
  762.                 System.err.println("HeadwayObject: " + p.getForwardHeadwayObject());
  763.             }
  764.         }
  765.         // DirectedPoint currentPointAfterMove = getLocation();
  766.         // if (currentPoint.distance(currentPointAfterMove) > 0.1)
  767.         // {
  768.         // System.err.println(this.getId() + " jumped");
  769.         // }
  770.         // schedule triggers and determine when to enter lanes with front and leave lanes with rear
  771.         scheduleEnterLeaveTriggers();
  772.         return false;
  773.     }

  774.     /**
  775.      * Recursive update of link fractions based on a moved distance.
  776.      * @param lane Lane; current lane, start with reference lane
  777.      * @param newLinkFractions Map&lt;Link, Double&gt;; map to put new fractions in
  778.      * @param done Set&lt;Link&gt;; links to skip as link are already done
  779.      * @param prevs boolean; whether to loop to the previous or next lanes, regardless of driving direction
  780.      * @param covered Length; covered distance along the reference lane
  781.      * @param isReferenceLane boolean; whether this lane is the reference lane (to skip in second call)
  782.      */
  783.     private void updateLinkFraction(final Lane lane, final Map<Link, Double> newLinkFractions, final Set<Link> done,
  784.             final boolean prevs, final Length covered, final boolean isReferenceLane)
  785.     {
  786.         if (!prevs || !isReferenceLane)
  787.         {
  788.             if (done.contains(lane.getParentLink()) || !this.currentLanes.containsKey(lane))
  789.             {
  790.                 return;
  791.             }
  792.             double sign;
  793.             try
  794.             {
  795.                 sign = getDirection(lane).isPlus() ? 1.0 : -1.0;
  796.             }
  797.             catch (GTUException exception)
  798.             {
  799.                 // can not happen as we check that the lane is in the currentLanes
  800.                 throw new RuntimeException("Unexpected exception: trying to obtain direction on lane.", exception);
  801.             }
  802.             newLinkFractions.put(lane.getParentLink(),
  803.                     this.fractionalLinkPositions.get(lane.getParentLink()) + sign * covered.si / lane.getLength().si);
  804.             done.add(lane.getParentLink());
  805.         }
  806.         for (Lane nextLane : (prevs ? lane.prevLanes(getGTUType()) : lane.nextLanes(getGTUType())).keySet())
  807.         {
  808.             updateLinkFraction(nextLane, newLinkFractions, done, prevs, covered, false);
  809.         }
  810.     }

  811.     /** {@inheritDoc} */
  812.     @Override
  813.     public final Map<Lane, Length> positions(final RelativePosition relativePosition) throws GTUException
  814.     {
  815.         return positions(relativePosition, getSimulator().getSimulatorTime());
  816.     }

  817.     /** {@inheritDoc} */
  818.     @Override
  819.     public final Map<Lane, Length> positions(final RelativePosition relativePosition, final Time when) throws GTUException
  820.     {
  821.         Map<Lane, Length> positions = new LinkedHashMap<>();
  822.         for (Lane lane : this.currentLanes.keySet())
  823.         {
  824.             positions.put(lane, position(lane, relativePosition, when));
  825.         }
  826.         return positions;
  827.     }

  828.     /** {@inheritDoc} */
  829.     @Override
  830.     public final Length position(final Lane lane, final RelativePosition relativePosition) throws GTUException
  831.     {
  832.         return position(lane, relativePosition, getSimulator().getSimulatorTime());
  833.     }

  834.     /**
  835.      * Return the longitudinal position that the indicated relative position of this GTU would have if it were to change to
  836.      * another Lane with a / the current CrossSectionLink. This point may be before the begin or after the end of the link of
  837.      * the projection lane of the GTU. This preserves the length of the GTU.
  838.      * @param projectionLane Lane; the lane onto which the position of this GTU must be projected
  839.      * @param relativePosition RelativePosition; the point on this GTU that must be projected
  840.      * @param when Time; the time for which to project the position of this GTU
  841.      * @return Length; the position of this GTU in the projectionLane
  842.      * @throws GTUException when projectionLane it not in any of the CrossSectionLink that the GTU is on
  843.      */
  844.     @SuppressWarnings("checkstyle:designforextension")
  845.     public Length translatedPosition(final Lane projectionLane, final RelativePosition relativePosition, final Time when)
  846.             throws GTUException
  847.     {
  848.         CrossSectionLink link = projectionLane.getParentLink();
  849.         for (CrossSectionElement cse : link.getCrossSectionElementList())
  850.         {
  851.             if (cse instanceof Lane)
  852.             {
  853.                 Lane cseLane = (Lane) cse;
  854.                 if (null != this.currentLanes.get(cseLane))
  855.                 {
  856.                     double fractionalPosition = fractionalPosition(cseLane, RelativePosition.REFERENCE_POSITION, when);
  857.                     Length pos = new Length(projectionLane.getLength().getSI() * fractionalPosition, LengthUnit.SI);
  858.                     if (this.currentLanes.get(cseLane).isPlus())
  859.                     {
  860.                         return pos.plus(relativePosition.getDx());
  861.                     }
  862.                     return pos.minus(relativePosition.getDx());
  863.                 }
  864.             }
  865.         }
  866.         throw new GTUException(this + " is not on any lane of Link " + link);
  867.     }

  868.     /**
  869.      * Return the longitudinal position on the projection lane that has the same fractional position on one of the current lanes
  870.      * of the indicated relative position. This preserves the fractional positions of all relative positions of the GTU.
  871.      * @param projectionLane Lane; the lane onto which the position of this GTU must be projected
  872.      * @param relativePosition RelativePosition; the point on this GTU that must be projected
  873.      * @param when Time; the time for which to project the position of this GTU
  874.      * @return Length; the position of this GTU in the projectionLane
  875.      * @throws GTUException when projectionLane it not in any of the CrossSectionLink that the GTU is on
  876.      */
  877.     @SuppressWarnings("checkstyle:designforextension")
  878.     public Length projectedPosition(final Lane projectionLane, final RelativePosition relativePosition, final Time when)
  879.             throws GTUException
  880.     {
  881.         CrossSectionLink link = projectionLane.getParentLink();
  882.         for (CrossSectionElement cse : link.getCrossSectionElementList())
  883.         {
  884.             if (cse instanceof Lane)
  885.             {
  886.                 Lane cseLane = (Lane) cse;
  887.                 if (null != this.currentLanes.get(cseLane))
  888.                 {
  889.                     double fractionalPosition = fractionalPosition(cseLane, relativePosition, when);
  890.                     return new Length(projectionLane.getLength().getSI() * fractionalPosition, LengthUnit.SI);
  891.                 }
  892.             }
  893.         }
  894.         throw new GTUException(this + " is not on any lane of Link " + link);
  895.     }

  896.     /** caching of time field for last stored position(s). */
  897.     private double cachePositionsTime = Double.NaN;

  898.     /** caching of last stored position(s). */
  899.     private Map<Integer, Length> cachedPositions = new LinkedHashMap<>();

  900.     /** {@inheritDoc} */
  901.     @Override
  902.     @SuppressWarnings("checkstyle:designforextension")
  903.     public Length position(final Lane lane, final RelativePosition relativePosition, final Time when) throws GTUException
  904.     {
  905.         int cacheIndex = 0;
  906.         if (CACHING)
  907.         {
  908.             cacheIndex = 17 * lane.hashCode() + relativePosition.hashCode();
  909.             Length l;
  910.             if (when.si == this.cachePositionsTime && (l = this.cachedPositions.get(cacheIndex)) != null)
  911.             {
  912.                 // PK verify the result; uncomment if you don't trust the cache
  913.                 // this.cachedPositions.clear();
  914.                 // Length difficultWay = position(lane, relativePosition, when);
  915.                 // if (Math.abs(l.si - difficultWay.si) > 0.00001)
  916.                 // {
  917.                 // System.err.println("Whoops: cache returns bad value for GTU " + getId());
  918.                 // }
  919.                 CACHED_POSITION++;
  920.                 return l;
  921.             }
  922.             if (when.si != this.cachePositionsTime)
  923.             {
  924.                 this.cachedPositions.clear();
  925.                 this.cachePositionsTime = when.si;
  926.             }
  927.         }
  928.         NON_CACHED_POSITION++;

  929.         synchronized (this.lock)
  930.         {
  931.             double loc = Double.NaN;
  932.             try
  933.             {
  934.                 OperationalPlan plan = getOperationalPlan(when);
  935.                 if (!(plan instanceof LaneBasedOperationalPlan) || !((LaneBasedOperationalPlan) plan).isDeviative())
  936.                 {
  937.                     double longitudinalPosition;
  938.                     try
  939.                     {
  940.                         longitudinalPosition =
  941.                                 lane.positionSI(this.fractionalLinkPositions.get(when).get(lane.getParentLink()));
  942.                     }
  943.                     catch (NullPointerException exception)
  944.                     {
  945.                         throw exception;
  946.                     }
  947.                     if (this.currentLanes.get(when).get(lane).isPlus())
  948.                     {
  949.                         loc = longitudinalPosition + plan.getTraveledDistanceSI(when) + relativePosition.getDx().si;
  950.                     }
  951.                     else
  952.                     {
  953.                         loc = longitudinalPosition - plan.getTraveledDistanceSI(when) - relativePosition.getDx().si;
  954.                     }
  955.                 }
  956.                 else
  957.                 {
  958.                     // deviative LaneBasedOperationalPlan, i.e. the GTU is not on a center line
  959.                     DirectedPoint p = plan.getLocation(when, relativePosition);
  960.                     double f = lane.getCenterLine().projectFractional(null, null, p.x, p.y, FractionalFallback.NaN);
  961.                     if (!Double.isNaN(f))
  962.                     {
  963.                         loc = f * lane.getLength().si;
  964.                     }
  965.                     else
  966.                     {
  967.                         // the point does not project fractionally to this lane, it has to be up- or downstream of the lane

  968.                         // simple heuristic to decide if we first look upstream or downstream
  969.                         boolean upstream = this.fractionalLinkPositions.get(lane.getParentLink()) < 0.0 ? true : false;

  970.                         // use loop up to 2 times (for loop creates 'loc not initialized' warning)
  971.                         int i = 0;
  972.                         while (true)
  973.                         {
  974.                             Set<Lane> otherLanesToConsider = new LinkedHashSet<>();
  975.                             otherLanesToConsider.addAll(this.currentLanes.keySet());
  976.                             double distance = getDistanceAtOtherLane(lane, when, upstream, 0.0, p, otherLanesToConsider);
  977.                             // distance can be positive on an upstream lane due to a loop
  978.                             if (!Double.isNaN(distance))
  979.                             {
  980.                                 if (i == 1 && !Double.isNaN(loc))
  981.                                 {
  982.                                     // loc was determined in both loops, this constitutes a lane-loop, select nearest
  983.                                     double loc2 = upstream ? -distance : distance + lane.getLength().si;
  984.                                     double d1 = loc < 0.0 ? -loc : loc - lane.getLength().si;
  985.                                     double d2 = loc2 < 0.0 ? -loc2 : loc2 - lane.getLength().si;
  986.                                     loc = d1 < d2 ? loc : loc2;
  987.                                     break;
  988.                                 }
  989.                                 else
  990.                                 {
  991.                                     // loc was determined in second loop
  992.                                     loc = upstream ? -distance : distance + lane.getLength().si;
  993.                                 }
  994.                             }
  995.                             else if (!Double.isNaN(loc))
  996.                             {
  997.                                 // loc was determined in first loop
  998.                                 break;
  999.                             }
  1000.                             else if (i == 1)
  1001.                             {
  1002.                                 // loc was determined in neither loop
  1003.                                 // Lane change ended while moving to next link. The source lanes are left and for a leave-lane
  1004.                                 // event the position is required. This may depend on upstream or downstream lanes as the
  1005.                                 // reference position is projected to that lane. But if we already left that lane, we can't use
  1006.                                 // it. We thus use ENDPOINT fallback instead.
  1007.                                 loc = lane.getLength().si * lane.getCenterLine().projectFractional(null, null, p.x, p.y,
  1008.                                         FractionalFallback.ENDPOINT);
  1009.                                 break;
  1010.                             }
  1011.                             // try other direction
  1012.                             i++;
  1013.                             upstream = !upstream;
  1014.                         }
  1015.                     }
  1016.                 }
  1017.             }
  1018.             catch (NullPointerException e)
  1019.             {
  1020.                 throw new GTUException("lanesCurrentOperationalPlan or fractionalLinkPositions is null", e);
  1021.             }
  1022.             catch (Exception e)
  1023.             {
  1024.                 System.err.println(toString());
  1025.                 System.err.println(this.currentLanes.get(when));
  1026.                 System.err.println(this.fractionalLinkPositions.get(when));
  1027.                 throw new GTUException(e);
  1028.             }
  1029.             if (Double.isNaN(loc))
  1030.             {
  1031.                 System.out.println("loc is NaN");
  1032.             }
  1033.             Length length = Length.instantiateSI(loc);
  1034.             if (CACHING)
  1035.             {
  1036.                 this.cachedPositions.put(cacheIndex, length);
  1037.             }
  1038.             return length;
  1039.         }
  1040.     }

  1041.     /** Set of lane to attempt when determining the location with a deviative lane change. */
  1042.     // private Set<Lane> otherLanesToConsider;

  1043.     /**
  1044.      * In case of a deviative operational plan (not on the center lines), positions are projected fractionally to the center
  1045.      * lines. For points upstream or downstream of a lane, fractional projection is not valid. In such cases we need to project
  1046.      * the position to an upstream or downstream lane instead, and adjust length along the center lines.
  1047.      * @param lane Lane; lane to determine the position on
  1048.      * @param when Time; time
  1049.      * @param upstream boolean; whether to check upstream (or downstream)
  1050.      * @param distance double; cumulative distance in recursive search, starts at 0.0
  1051.      * @param point DirectedPoint; absolute point of GTU to be projected to center line
  1052.      * @param otherLanesToConsider Set&lt;Lane&gt;; lanes to consider
  1053.      * @return Length; position on lane being &lt;0 or &gt;{@code lane.getLength()}
  1054.      * @throws GTUException if GTU is not on the lane
  1055.      */
  1056.     private double getDistanceAtOtherLane(final Lane lane, final Time when, final boolean upstream, final double distance,
  1057.             final DirectedPoint point, final Set<Lane> otherLanesToConsider) throws GTUException
  1058.     {
  1059.         Set<Lane> nextLanes = new LinkedHashSet<>(upstream == getDirection(lane).isPlus()
  1060.                 ? lane.prevLanes(getGTUType()).keySet() : lane.nextLanes(getGTUType()).keySet()); // safe copy
  1061.         nextLanes.retainAll(otherLanesToConsider); // as we delete here
  1062.         if (!upstream && nextLanes.size() > 1)
  1063.         {
  1064.             LaneDirection laneDir = new LaneDirection(lane, getDirection(lane)).getNextLaneDirection(this);
  1065.             if (nextLanes.contains(laneDir.getLane()))
  1066.             {
  1067.                 nextLanes.clear();
  1068.                 nextLanes.add(laneDir.getLane());
  1069.             }
  1070.             else
  1071.             {
  1072.                 getSimulator().getLogger().always().error("Distance on downstream lane could not be determined.");
  1073.             }
  1074.         }
  1075.         // TODO When requesting the position at the end of the plan, which will be on a further lane, this lane is not yet
  1076.         // part of the lanes in the current operational plan. This can be upstream or downstream depending on the direction of
  1077.         // travel. We might check whether getDirection(lane)=DIR_PLUS and upstream=false, or getDirection(lane)=DIR_MINUS and
  1078.         // upstream=true, to then use LaneDirection.getNextLaneDirection(this) to obtain the next lane. This is only required if
  1079.         // nextLanes originally had more than 1 lane, otherwise we can simply use that one lane. Problem is that the search
  1080.         // might go on far or even eternally (on a circular network), as projection simply keeps failing because the GTU is
  1081.         // actually towards the other longitudinal direction. Hence, the heuristic used before this method is called should
  1082.         // change and first always search against the direction of travel, and only consider lanes in currentLanes, while the
  1083.         // consecutive search in the direction of travel should then always find a point. We could build in a counter to prevent
  1084.         // a hanging software.
  1085.         if (nextLanes.size() == 0)
  1086.         {
  1087.             return Double.NaN; // point must be in the other direction
  1088.         }
  1089.         Throw.when(nextLanes.size() > 1, IllegalStateException.class,
  1090.                 "A position (%s) of GTU %s is not on any of the current registered lanes.", point, this.getId());
  1091.         Lane nextLane = nextLanes.iterator().next();
  1092.         otherLanesToConsider.remove(lane);
  1093.         double f = nextLane.getCenterLine().projectFractional(null, null, point.x, point.y, FractionalFallback.NaN);
  1094.         if (Double.isNaN(f))
  1095.         {
  1096.             return getDistanceAtOtherLane(nextLane, when, upstream, distance + nextLane.getLength().si, point,
  1097.                     otherLanesToConsider);
  1098.         }
  1099.         return distance + (upstream == this.currentLanes.get(nextLane).isPlus() ? 1.0 - f : f) * nextLane.getLength().si;
  1100.     }

  1101.     /** Time of reference position cache. */
  1102.     private double referencePositionTime = Double.NaN;

  1103.     /** Cached reference position. */
  1104.     private DirectedLanePosition cachedReferencePosition = null;

  1105.     /** {@inheritDoc} */
  1106.     @Override
  1107.     @SuppressWarnings("checkstyle:designforextension")
  1108.     public DirectedLanePosition getReferencePosition() throws GTUException
  1109.     {
  1110.         if (this.referencePositionTime == getSimulator().getSimulatorTime().si)
  1111.         {
  1112.             return this.cachedReferencePosition;
  1113.         }
  1114.         boolean anyOnLink = false;
  1115.         Lane refLane = null;
  1116.         double closest = Double.POSITIVE_INFINITY;
  1117.         double minEps = Double.POSITIVE_INFINITY;
  1118.         for (Lane lane : this.currentLanes.keySet())
  1119.         {
  1120.             double fraction = fractionalPosition(lane, getReference());
  1121.             if (fraction >= 0.0 && fraction <= 1.0)
  1122.             {
  1123.                 // TODO widest lane in case we are registered on more than one lane with the reference point?
  1124.                 // TODO lane that leads to our location or not if we are registered on parallel lanes?
  1125.                 if (!anyOnLink)
  1126.                 {
  1127.                     refLane = lane;
  1128.                 }
  1129.                 else
  1130.                 {
  1131.                     DirectedPoint loc = getLocation();
  1132.                     double f = lane.getCenterLine().projectFractional(null, null, loc.x, loc.y, FractionalFallback.ENDPOINT);
  1133.                     double distance = loc.distance(lane.getCenterLine().getLocationFractionExtended(f));
  1134.                     if (refLane != null && Double.isInfinite(closest))
  1135.                     {
  1136.                         f = refLane.getCenterLine().projectFractional(null, null, loc.x, loc.y, FractionalFallback.ENDPOINT);
  1137.                         closest = loc.distance(refLane.getCenterLine().getLocationFractionExtended(f));
  1138.                     }
  1139.                     if (distance < closest)
  1140.                     {
  1141.                         refLane = lane;
  1142.                         closest = distance;
  1143.                     }
  1144.                 }
  1145.                 anyOnLink = true;
  1146.             }
  1147.             else if (!anyOnLink && Double.isInfinite(closest))// && getOperationalPlan() instanceof LaneBasedOperationalPlan
  1148.             // && ((LaneBasedOperationalPlan) getOperationalPlan()).isDeviative())
  1149.             {
  1150.                 double eps = (fraction > 1.0 ? lane.getCenterLine().getLast() : lane.getCenterLine().getFirst())
  1151.                         .distanceSI(new OTSPoint3D(getLocation()));
  1152.                 if (eps < minEps)
  1153.                 {
  1154.                     minEps = eps;
  1155.                     refLane = lane;
  1156.                 }
  1157.             }
  1158.         }
  1159.         if (refLane != null)
  1160.         {
  1161.             this.cachedReferencePosition =
  1162.                     new DirectedLanePosition(refLane, position(refLane, getReference()), this.getDirection(refLane));
  1163.             this.referencePositionTime = getSimulator().getSimulatorTime().si;
  1164.             return this.cachedReferencePosition;
  1165.         }
  1166.         // for (Lane lane : this.currentLanes.keySet())
  1167.         // {
  1168.         // Length relativePosition = position(lane, RelativePosition.REFERENCE_POSITION);
  1169.         // System.err
  1170.         // .println(String.format("Lane %s of Link %s: absolute position %s, relative position %5.1f%%", lane.getId(),
  1171.         // lane.getParentLink().getId(), relativePosition, relativePosition.si * 100 / lane.getLength().si));
  1172.         // }
  1173.         throw new GTUException("The reference point of GTU " + this + " is not on any of the lanes on which it is registered");
  1174.     }

  1175.     /**
  1176.      * Schedule the triggers for this GTU that are going to happen until the next evaluation time. Also schedule the
  1177.      * registration and deregistration of lanes when the vehicle enters or leaves them, at the exact right time. <br>
  1178.      * Note: when the GTU makes a lane change, the vehicle will be registered for both lanes during the entire maneuver.
  1179.      * @throws NetworkException on network inconsistency
  1180.      * @throws SimRuntimeException should never happen
  1181.      * @throws GTUException when a branch is reached where the GTU does not know where to go next
  1182.      */
  1183.     @SuppressWarnings("checkstyle:designforextension")
  1184.     protected void scheduleEnterLeaveTriggers() throws NetworkException, SimRuntimeException, GTUException
  1185.     {

  1186.         LaneBasedOperationalPlan plan = null;
  1187.         double moveSI;
  1188.         if (getOperationalPlan() instanceof LaneBasedOperationalPlan)
  1189.         {
  1190.             plan = (LaneBasedOperationalPlan) getOperationalPlan();
  1191.             moveSI = plan.getTotalLengthAlongLane(this).si;
  1192.         }
  1193.         else
  1194.         {
  1195.             moveSI = getOperationalPlan().getTotalLength().si;
  1196.         }

  1197.         // Figure out which lanes this GTU will enter with its FRONT, and schedule the lane enter events
  1198.         Map<Lane, GTUDirectionality> lanesCopy = new LinkedHashMap<>(this.currentLanes);
  1199.         Iterator<Lane> it = lanesCopy.keySet().iterator();
  1200.         Lane enteredLane = null;
  1201.         // LateralDirectionality forceSide = LateralDirectionality.NONE;
  1202.         while (it.hasNext() || enteredLane != null) // use a copy because this.currentLanes can change
  1203.         {
  1204.             // next lane from 'lanesCopy', or asses the lane we just entered as it may be very short and also passed fully
  1205.             Lane lane;
  1206.             GTUDirectionality laneDir;
  1207.             if (enteredLane == null)
  1208.             {
  1209.                 lane = it.next();
  1210.                 laneDir = lanesCopy.get(lane);
  1211.             }
  1212.             else
  1213.             {
  1214.                 lane = enteredLane;
  1215.                 laneDir = this.currentLanes.get(lane);
  1216.             }
  1217.             double sign = laneDir.isPlus() ? 1.0 : -1.0;
  1218.             enteredLane = null;

  1219.             // skip if already on next lane
  1220.             if (!Collections.disjoint(this.currentLanes.keySet(),
  1221.                     lane.downstreamLanes(laneDir, getGTUType()).keySet().toCollection()))
  1222.             {
  1223.                 continue;
  1224.             }

  1225.             // schedule triggers on this lane
  1226.             double referenceStartSI = this.fractionalLinkPositions.get(lane.getParentLink()) * lane.getLength().getSI();
  1227.             // referenceStartSI is position of reference of GTU on current lane
  1228.             if (laneDir.isPlus())
  1229.             {
  1230.                 lane.scheduleSensorTriggers(this, referenceStartSI, moveSI);
  1231.             }
  1232.             else
  1233.             {
  1234.                 lane.scheduleSensorTriggers(this, referenceStartSI - moveSI, moveSI);
  1235.             }

  1236.             double nextFrontPosSI = referenceStartSI + sign * (moveSI + getFront().getDx().si);
  1237.             Lane nextLane = null;
  1238.             GTUDirectionality nextDirection = null;
  1239.             Length refPosAtLastTimestep = null;
  1240.             DirectedPoint end = null;
  1241.             if (laneDir.isPlus() ? nextFrontPosSI > lane.getLength().si : nextFrontPosSI < 0.0)
  1242.             {
  1243.                 LaneDirection next = new LaneDirection(lane, laneDir).getNextLaneDirection(this);
  1244.                 if (null == next)
  1245.                 {
  1246.                     // A sink should delete the GTU, or a lane change should end, before reaching the end of the lane
  1247.                     continue;
  1248.                 }
  1249.                 nextLane = next.getLane();
  1250.                 nextDirection = next.getDirection();
  1251.                 double endPos = laneDir.isPlus() ? lane.getLength().si - getFront().getDx().si : getFront().getDx().si;
  1252.                 Lane endLane = lane;
  1253.                 GTUDirectionality endLaneDir = laneDir;
  1254.                 while (endLaneDir.isPlus() ? endPos < 0.0 : endPos > endLane.getLength().si)
  1255.                 {
  1256.                     // GTU front is more than lane length, so end location can not be extracted from the lane, let's move then
  1257.                     Map<Lane, GTUDirectionality> map = endLane.upstreamLanes(endLaneDir, getGTUType()).toMap();
  1258.                     map.keySet().retainAll(this.currentLanes.keySet());
  1259.                     double remain = endLaneDir.isPlus() ? -endPos : endPos - endLane.getLength().si;
  1260.                     endLane = map.keySet().iterator().next();
  1261.                     endLaneDir = map.get(endLane);
  1262.                     endPos = endLaneDir.isPlus() ? endLane.getLength().si - remain : remain;
  1263.                 }
  1264.                 end = endLane.getCenterLine().getLocationExtendedSI(endPos);
  1265.                 if (laneDir.isPlus())
  1266.                 {
  1267.                     refPosAtLastTimestep = nextDirection.isPlus() ? Length.instantiateSI(referenceStartSI - lane.getLength().si)
  1268.                             : Length.instantiateSI(nextLane.getLength().si - referenceStartSI + lane.getLength().si);
  1269.                 }
  1270.                 else
  1271.                 {
  1272.                     refPosAtLastTimestep = nextDirection.isPlus() ? Length.instantiateSI(-referenceStartSI)
  1273.                             : Length.instantiateSI(nextLane.getLength().si + referenceStartSI);
  1274.                 }
  1275.             }

  1276.             if (end != null)
  1277.             {
  1278.                 Time enterTime = getOperationalPlan().timeAtPoint(end, false);
  1279.                 if (enterTime != null)
  1280.                 {
  1281.                     if (Double.isNaN(enterTime.si))
  1282.                     {
  1283.                         // TODO: this escape was in timeAtPoint, where it was changed to return null for leave lane events
  1284.                         enterTime = Time.instantiateSI(getOperationalPlan().getEndTime().si - 1e-9);
  1285.                         // -1e-9 prevents that next move() reschedules enter
  1286.                     }
  1287.                     addLaneToGtu(nextLane, refPosAtLastTimestep, nextDirection);
  1288.                     enteredLane = nextLane;
  1289.                     Length coveredDistance;
  1290.                     if (plan == null || !plan.isDeviative())
  1291.                     {
  1292.                         try
  1293.                         {
  1294.                             coveredDistance = getOperationalPlan().getTraveledDistance(enterTime);
  1295.                         }
  1296.                         catch (OperationalPlanException exception)
  1297.                         {
  1298.                             throw new RuntimeException("Enter time of lane beyond plan.", exception);
  1299.                         }
  1300.                     }
  1301.                     else
  1302.                     {
  1303.                         coveredDistance = plan.getDistanceAlongLane(this, end);
  1304.                     }
  1305.                     SimEventInterface<SimTimeDoubleUnit> event = getSimulator().scheduleEventAbs(enterTime, this, this,
  1306.                             "addGtuToLane", new Object[] { nextLane, refPosAtLastTimestep.plus(coveredDistance) });
  1307.                     addEnterTrigger(nextLane, event);
  1308.                 }
  1309.             }
  1310.         }

  1311.         // Figure out which lanes this GTU will exit with its BACK, and schedule the lane exit events
  1312.         for (Lane lane : this.currentLanes.keySet())
  1313.         {

  1314.             double referenceStartSI = this.fractionalLinkPositions.get(lane.getParentLink()) * lane.getLength().getSI();
  1315.             Time exitTime = null;

  1316.             GTUDirectionality laneDir = getDirection(lane);

  1317.             if (plan == null || !plan.isDeviative())
  1318.             {
  1319.                 double sign = laneDir.isPlus() ? 1.0 : -1.0;
  1320.                 double nextRearPosSI = referenceStartSI + sign * (getRear().getDx().si + moveSI);
  1321.                 if (laneDir.isPlus() ? nextRearPosSI > lane.getLength().si : nextRearPosSI < 0.0)
  1322.                 {
  1323.                     exitTime = getOperationalPlan().timeAtDistance(
  1324.                             Length.instantiateSI((laneDir.isPlus() ? lane.getLength().si - referenceStartSI : referenceStartSI)
  1325.                                     - getRear().getDx().si));
  1326.                 }
  1327.             }
  1328.             else
  1329.             {
  1330.                 DirectedPoint end = null;
  1331.                 double endPos = laneDir.isPlus() ? lane.getLength().si - getRear().getDx().si : getRear().getDx().si;
  1332.                 Lane endLane = lane;
  1333.                 GTUDirectionality endLaneDir = laneDir;
  1334.                 while (endLaneDir.isPlus() ? endPos > endLane.getLength().si : endPos < 0.0)
  1335.                 {
  1336.                     Map<Lane, GTUDirectionality> map = endLane.downstreamLanes(endLaneDir, getGTUType()).toMap();
  1337.                     map.keySet().retainAll(this.currentLanes.keySet());
  1338.                     if (!map.isEmpty())
  1339.                     {
  1340.                         double remain = endLaneDir.isPlus() ? endPos - endLane.getLength().si : -endPos;
  1341.                         endLane = map.keySet().iterator().next();
  1342.                         endLaneDir = map.get(endLane);
  1343.                         endPos = endLaneDir.isPlus() ? remain : endLane.getLength().si - remain;
  1344.                     }
  1345.                     else
  1346.                     {
  1347.                         endPos = endLaneDir.isPlus() ? endLane.getLength().si - getRear().getDx().si : getRear().getDx().si;
  1348.                         break;
  1349.                     }
  1350.                 }
  1351.                 end = endLane.getCenterLine().getLocationExtendedSI(endPos);
  1352.                 if (end != null)
  1353.                 {
  1354.                     exitTime = getOperationalPlan().timeAtPoint(end, false);
  1355.                     if (Double.isNaN(exitTime.si))
  1356.                     {
  1357.                         // code below will leave entered lanes if exitTime is null, make this so if NaN results due to the lane
  1358.                         // end being beyond the plan (rather than the GTU never having been there, but being registered there
  1359.                         // upon lane change initiation)
  1360.                         double sign = laneDir.isPlus() ? 1.0 : -1.0;
  1361.                         double nextRearPosSI = referenceStartSI + sign * (getRear().getDx().si + moveSI);
  1362.                         if (laneDir.isPlus() ? nextRearPosSI < lane.getLength().si : nextRearPosSI > 0.0)
  1363.                         {
  1364.                             exitTime = null;
  1365.                         }
  1366.                     }
  1367.                 }
  1368.             }

  1369.             if (exitTime != null && !Double.isNaN(exitTime.si))
  1370.             {
  1371.                 SimEvent<SimTimeDoubleUnit> event = new SimEvent<>(new SimTimeDoubleUnit(exitTime), this, this, "leaveLane",
  1372.                         new Object[] { lane, new Boolean(false) });
  1373.                 getSimulator().scheduleEvent(event);
  1374.                 addTrigger(lane, event);
  1375.             }
  1376.             else if (exitTime != null && this.enteredLanes.contains(lane))
  1377.             {
  1378.                 // This lane was entered when initiating the lane change due to a fractional calculation. Now, the deviative
  1379.                 // plan indicates we will never reach this location.
  1380.                 SimEvent<SimTimeDoubleUnit> event = new SimEvent<>(getSimulator().getSimTime(), this, this, "leaveLane",
  1381.                         new Object[] { lane, new Boolean(false) });
  1382.                 getSimulator().scheduleEvent(event);
  1383.                 addTrigger(lane, event);
  1384.             }
  1385.         }

  1386.         this.enteredLanes.clear();
  1387.     }

  1388.     /** {@inheritDoc} */
  1389.     @Override
  1390.     public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition) throws GTUException
  1391.     {
  1392.         return fractionalPositions(relativePosition, getSimulator().getSimulatorTime());
  1393.     }

  1394.     /** {@inheritDoc} */
  1395.     @Override
  1396.     public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition, final Time when)
  1397.             throws GTUException
  1398.     {
  1399.         Map<Lane, Double> positions = new LinkedHashMap<>();
  1400.         for (Lane lane : this.currentLanes.keySet())
  1401.         {
  1402.             positions.put(lane, fractionalPosition(lane, relativePosition, when));
  1403.         }
  1404.         return positions;
  1405.     }

  1406.     /** {@inheritDoc} */
  1407.     @Override
  1408.     public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition, final Time when)
  1409.             throws GTUException
  1410.     {
  1411.         return position(lane, relativePosition, when).getSI() / lane.getLength().getSI();
  1412.     }

  1413.     /** {@inheritDoc} */
  1414.     @Override
  1415.     public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition) throws GTUException
  1416.     {
  1417.         return position(lane, relativePosition).getSI() / lane.getLength().getSI();
  1418.     }

  1419.     /** {@inheritDoc} */
  1420.     @Override
  1421.     public final void addTrigger(final Lane lane, final SimEventInterface<SimTimeDoubleUnit> event)
  1422.     {
  1423.         List<SimEventInterface<SimTimeDoubleUnit>> list = this.pendingLeaveTriggers.get(lane);
  1424.         if (null == list)
  1425.         {
  1426.             list = new ArrayList<>();
  1427.         }
  1428.         list.add(event);
  1429.         this.pendingLeaveTriggers.put(lane, list);
  1430.     }

  1431.     /**
  1432.      * Add enter trigger.
  1433.      * @param lane Lane; lane
  1434.      * @param event SimEventInterface&lt;SimTimeDoubleUnit&gt;; event
  1435.      */
  1436.     private void addEnterTrigger(final Lane lane, final SimEventInterface<SimTimeDoubleUnit> event)
  1437.     {
  1438.         List<SimEventInterface<SimTimeDoubleUnit>> list = this.pendingEnterTriggers.get(lane);
  1439.         if (null == list)
  1440.         {
  1441.             list = new ArrayList<>();
  1442.         }
  1443.         list.add(event);
  1444.         this.pendingEnterTriggers.put(lane, list);
  1445.     }

  1446.     /**
  1447.      * Sets a vehicle model.
  1448.      * @param vehicleModel VehicleModel; vehicle model
  1449.      */
  1450.     public void setVehicleModel(final VehicleModel vehicleModel)
  1451.     {
  1452.         this.vehicleModel = vehicleModel;
  1453.     }

  1454.     /** {@inheritDoc} */
  1455.     @Override
  1456.     public VehicleModel getVehicleModel()
  1457.     {
  1458.         return this.vehicleModel;
  1459.     }

  1460.     /** {@inheritDoc} */
  1461.     @Override
  1462.     @SuppressWarnings("checkstyle:designforextension")
  1463.     public void destroy()
  1464.     {
  1465.         DirectedLanePosition dlp = null;
  1466.         try
  1467.         {
  1468.             dlp = getReferencePosition();
  1469.         }
  1470.         catch (GTUException e)
  1471.         {
  1472.             // ignore. not important at destroy
  1473.         }
  1474.         DirectedPoint location = this.getOperationalPlan() == null ? new DirectedPoint(0.0, 0.0, 0.0) : getLocation();

  1475.         synchronized (this.lock)
  1476.         {
  1477.             Set<Lane> laneSet = new LinkedHashSet<>(this.currentLanes.keySet()); // Operate on a copy of the key
  1478.                                                                                  // set
  1479.             for (Lane lane : laneSet)
  1480.             {
  1481.                 try
  1482.                 {
  1483.                     leaveLane(lane, true);
  1484.                 }
  1485.                 catch (GTUException e)
  1486.                 {
  1487.                     // ignore. not important at destroy
  1488.                 }
  1489.             }
  1490.         }

  1491.         if (dlp != null && dlp.getLane() != null)
  1492.         {
  1493.             Lane referenceLane = dlp.getLane();
  1494.             fireTimedEvent(LaneBasedGTU.LANEBASED_DESTROY_EVENT,
  1495.                     new Object[] { getId(), location, getOdometer(), referenceLane, dlp.getPosition(), dlp.getGtuDirection() },
  1496.                     getSimulator().getSimulatorTime());
  1497.         }
  1498.         else
  1499.         {
  1500.             fireTimedEvent(LaneBasedGTU.LANEBASED_DESTROY_EVENT,
  1501.                     new Object[] { getId(), location, getOdometer(), null, Length.ZERO, null },
  1502.                     getSimulator().getSimulatorTime());
  1503.         }
  1504.         if (this.finalizeLaneChangeEvent != null)
  1505.         {
  1506.             getSimulator().cancelEvent(this.finalizeLaneChangeEvent);
  1507.         }

  1508.         super.destroy();
  1509.     }

  1510.     /** {@inheritDoc} */
  1511.     @Override
  1512.     public final Bounds getBounds()
  1513.     {
  1514.         double dx = 0.5 * getLength().doubleValue();
  1515.         double dy = 0.5 * getWidth().doubleValue();
  1516.         return new BoundingBox(new Point3d(-dx, -dy, 0.0), new Point3d(dx, dy, 0.0));
  1517.     }

  1518.     /** {@inheritDoc} */
  1519.     @Override
  1520.     public final LaneBasedStrategicalPlanner getStrategicalPlanner()
  1521.     {
  1522.         return (LaneBasedStrategicalPlanner) super.getStrategicalPlanner();
  1523.     }

  1524.     /** {@inheritDoc} */
  1525.     @Override
  1526.     public final LaneBasedStrategicalPlanner getStrategicalPlanner(final Time time)
  1527.     {
  1528.         return (LaneBasedStrategicalPlanner) super.getStrategicalPlanner(time);
  1529.     }

  1530.     /** {@inheritDoc} */
  1531.     @Override
  1532.     public RoadNetwork getNetwork()
  1533.     {
  1534.         return (RoadNetwork) super.getPerceivableContext();
  1535.     }

  1536.     /** {@inheritDoc} */
  1537.     @Override
  1538.     public Speed getDesiredSpeed()
  1539.     {
  1540.         Time simTime = getSimulator().getSimulatorTime();
  1541.         if (this.desiredSpeedTime == null || this.desiredSpeedTime.si < simTime.si)
  1542.         {
  1543.             InfrastructurePerception infra =
  1544.                     getTacticalPlanner().getPerception().getPerceptionCategoryOrNull(InfrastructurePerception.class);
  1545.             SpeedLimitInfo speedInfo;
  1546.             if (infra == null)
  1547.             {
  1548.                 speedInfo = new SpeedLimitInfo();
  1549.                 speedInfo.addSpeedInfo(SpeedLimitTypes.MAX_VEHICLE_SPEED, getMaximumSpeed());
  1550.             }
  1551.             else
  1552.             {
  1553.                 // Throw.whenNull(infra, "InfrastructurePerception is required to determine the desired speed.");
  1554.                 speedInfo = infra.getSpeedLimitProspect(RelativeLane.CURRENT).getSpeedLimitInfo(Length.ZERO);
  1555.             }
  1556.             this.cachedDesiredSpeed =
  1557.                     Try.assign(() -> getTacticalPlanner().getCarFollowingModel().desiredSpeed(getParameters(), speedInfo),
  1558.                             "Parameter exception while obtaining the desired speed.");
  1559.             this.desiredSpeedTime = simTime;
  1560.         }
  1561.         return this.cachedDesiredSpeed;
  1562.     }

  1563.     /** {@inheritDoc} */
  1564.     @Override
  1565.     public Acceleration getCarFollowingAcceleration()
  1566.     {
  1567.         Time simTime = getSimulator().getSimulatorTime();
  1568.         if (this.carFollowingAccelerationTime == null || this.carFollowingAccelerationTime.si < simTime.si)
  1569.         {
  1570.             LanePerception perception = getTacticalPlanner().getPerception();
  1571.             // speed
  1572.             EgoPerception<?, ?> ego = perception.getPerceptionCategoryOrNull(EgoPerception.class);
  1573.             Throw.whenNull(ego, "EgoPerception is required to determine the speed.");
  1574.             Speed speed = ego.getSpeed();
  1575.             // speed limit info
  1576.             InfrastructurePerception infra = perception.getPerceptionCategoryOrNull(InfrastructurePerception.class);
  1577.             Throw.whenNull(infra, "InfrastructurePerception is required to determine the desired speed.");
  1578.             SpeedLimitInfo speedInfo = infra.getSpeedLimitProspect(RelativeLane.CURRENT).getSpeedLimitInfo(Length.ZERO);
  1579.             // leaders
  1580.             NeighborsPerception neighbors = perception.getPerceptionCategoryOrNull(NeighborsPerception.class);
  1581.             Throw.whenNull(neighbors, "NeighborsPerception is required to determine the car-following acceleration.");
  1582.             PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders = neighbors.getLeaders(RelativeLane.CURRENT);
  1583.             // obtain
  1584.             this.cachedCarFollowingAcceleration =
  1585.                     Try.assign(() -> getTacticalPlanner().getCarFollowingModel().followingAcceleration(getParameters(), speed,
  1586.                             speedInfo, leaders), "Parameter exception while obtaining the desired speed.");
  1587.             this.carFollowingAccelerationTime = simTime;
  1588.         }
  1589.         return this.cachedCarFollowingAcceleration;
  1590.     }

  1591.     /** {@inheritDoc} */
  1592.     @Override
  1593.     public final TurnIndicatorStatus getTurnIndicatorStatus()
  1594.     {
  1595.         return this.turnIndicatorStatus.get();
  1596.     }

  1597.     /** {@inheritDoc} */
  1598.     @Override
  1599.     public final TurnIndicatorStatus getTurnIndicatorStatus(final Time time)
  1600.     {
  1601.         return this.turnIndicatorStatus.get(time);
  1602.     }

  1603.     /** {@inheritDoc} */
  1604.     @Override
  1605.     public final void setTurnIndicatorStatus(final TurnIndicatorStatus turnIndicatorStatus)
  1606.     {
  1607.         this.turnIndicatorStatus.set(turnIndicatorStatus);
  1608.     }

  1609.     /** {@inheritDoc} */
  1610.     @Override
  1611.     @SuppressWarnings("checkstyle:designforextension")
  1612.     public String toString()
  1613.     {
  1614.         return String.format("GTU " + getId());
  1615.     }

  1616. }