AbstractLaneBasedGTU.java

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

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

  12. import javax.media.j3d.Bounds;
  13. import javax.vecmath.Point3d;

  14. import org.djunits.unit.DurationUnit;
  15. import org.djunits.unit.LengthUnit;
  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.opentrafficsim.base.parameters.ParameterException;
  22. import org.opentrafficsim.core.geometry.OTSGeometryException;
  23. import org.opentrafficsim.core.geometry.OTSLine3D;
  24. import org.opentrafficsim.core.geometry.OTSLine3D.FractionalFallback;
  25. import org.opentrafficsim.core.geometry.OTSPoint3D;
  26. import org.opentrafficsim.core.gtu.AbstractGTU;
  27. import org.opentrafficsim.core.gtu.GTU;
  28. import org.opentrafficsim.core.gtu.GTUDirectionality;
  29. import org.opentrafficsim.core.gtu.GTUException;
  30. import org.opentrafficsim.core.gtu.GTUType;
  31. import org.opentrafficsim.core.gtu.RelativePosition;
  32. import org.opentrafficsim.core.gtu.Try;
  33. import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
  34. import org.opentrafficsim.core.gtu.perception.EgoPerception;
  35. import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
  36. import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanBuilder;
  37. import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
  38. import org.opentrafficsim.core.network.LateralDirectionality;
  39. import org.opentrafficsim.core.network.Link;
  40. import org.opentrafficsim.core.network.NetworkException;
  41. import org.opentrafficsim.core.network.OTSNetwork;
  42. import org.opentrafficsim.core.perception.Historical;
  43. import org.opentrafficsim.core.perception.HistoricalValue;
  44. import org.opentrafficsim.core.perception.HistoryManager;
  45. import org.opentrafficsim.core.perception.collections.HistoricalLinkedHashMap;
  46. import org.opentrafficsim.core.perception.collections.HistoricalMap;
  47. import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
  48. import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
  49. import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
  50. import org.opentrafficsim.road.gtu.lane.perception.categories.DefaultSimplePerception;
  51. import org.opentrafficsim.road.gtu.lane.perception.categories.InfrastructurePerception;
  52. import org.opentrafficsim.road.gtu.lane.perception.categories.NeighborsPerception;
  53. import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTU;
  54. import org.opentrafficsim.road.gtu.lane.plan.operational.LaneBasedOperationalPlan;
  55. import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
  56. import org.opentrafficsim.road.network.lane.CrossSectionElement;
  57. import org.opentrafficsim.road.network.lane.CrossSectionLink;
  58. import org.opentrafficsim.road.network.lane.DirectedLanePosition;
  59. import org.opentrafficsim.road.network.lane.Lane;
  60. import org.opentrafficsim.road.network.lane.LaneDirection;
  61. import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
  62. import org.opentrafficsim.road.network.speed.SpeedLimitTypes;

  63. import nl.tudelft.simulation.dsol.SimRuntimeException;
  64. import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
  65. import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
  66. import nl.tudelft.simulation.dsol.simtime.SimTimeDoubleUnit;
  67. import nl.tudelft.simulation.dsol.simulators.DEVSSimulatorInterface;
  68. import nl.tudelft.simulation.language.Throw;
  69. import nl.tudelft.simulation.language.d3.BoundingBox;
  70. import nl.tudelft.simulation.language.d3.DirectedPoint;

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

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

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

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

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

  117.     /** Cached desired speed. */
  118.     private Speed cachedDesiredSpeed;

  119.     /** Time desired speed was cached. */
  120.     private Time desiredSpeedTime;

  121.     /** Cached car-following acceleration. */
  122.     private Acceleration cachedCarFollowingAcceleration;

  123.     /** Time car-following acceleration was cached. */
  124.     private Time carFollowingAccelerationTime;

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

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

  130.     /** Turn indicator status. */
  131.     private final Historical<TurnIndicatorStatus> turnIndicatorStatus;

  132.     /** Caching on or off. */
  133.     // TODO: should be indicated with a Parameter
  134.     public static boolean CACHING = true;

  135.     /** cached position count. */
  136.     // TODO: can be removed after testing period
  137.     public static int CACHED_POSITION = 0;

  138.     /** cached position count. */
  139.     // TODO: can be removed after testing period
  140.     public static int NON_CACHED_POSITION = 0;

  141.     /** Vehicle model. */
  142.     private VehicleModel vehicleModel = VehicleModel.MINMAX;

  143.     /**
  144.      * Construct a Lane Based GTU.
  145.      * @param id the id of the GTU
  146.      * @param gtuType the type of GTU, e.g. TruckType, CarType, BusType
  147.      * @param simulator to initialize the move method and to get the current time
  148.      * @param network the network that the GTU is initially registered in
  149.      * @throws GTUException when initial values are not correct
  150.      */
  151.     public AbstractLaneBasedGTU(final String id, final GTUType gtuType, final DEVSSimulatorInterface.TimeDoubleUnit simulator,
  152.             final OTSNetwork network) throws GTUException
  153.     {
  154.         super(id, gtuType, simulator, network);
  155.         this.fractionalLinkPositions = new HistoricalLinkedHashMap<>(HistoryManager.get(simulator));
  156.         this.currentLanes = new HistoricalLinkedHashMap<>(HistoryManager.get(simulator));
  157.         this.turnIndicatorStatus = new HistoricalValue<>(HistoryManager.get(simulator), TurnIndicatorStatus.NOTPRESENT);
  158.     }

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

  175.         DirectedPoint lastPoint = null;
  176.         for (DirectedLanePosition pos : initialLongitudinalPositions)
  177.         {
  178.             // Throw.when(lastPoint != null && pos.getLocation().distance(lastPoint) > initialLocationThresholdDifference.si,
  179.             // GTUException.class, "initial locations for GTU have distance > " + initialLocationThresholdDifference);
  180.             lastPoint = pos.getLocation();
  181.         }
  182.         DirectedPoint initialLocation = lastPoint;

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

  204.         // register the GTU on the lanes
  205.         for (DirectedLanePosition directedLanePosition : initialLongitudinalPositions)
  206.         {
  207.             Lane lane = directedLanePosition.getLane();
  208.             addLaneToGtu(lane, directedLanePosition.getPosition(), directedLanePosition.getGtuDirection()); // enter lane part 1
  209.         }

  210.         // init event
  211.         DirectedLanePosition referencePosition = getReferencePosition();
  212.         fireTimedEvent(LaneBasedGTU.LANEBASED_INIT_EVENT,
  213.                 new Object[] { getId(), initialLocation, getLength(), getWidth(), getBaseColor(), referencePosition.getLane(),
  214.                         referencePosition.getPosition(), referencePosition.getGtuDirection(), getGTUType() },
  215.                 getSimulator().getSimulatorTime());

  216.         // register the GTU on the lanes
  217.         for (DirectedLanePosition directedLanePosition : initialLongitudinalPositions)
  218.         {
  219.             Lane lane = directedLanePosition.getLane();
  220.             lane.addGTU(this, directedLanePosition.getPosition()); // enter lane part 2
  221.         }

  222.         // initiate the actual move
  223.         super.init(strategicalPlanner, initialLocation, initialSpeed);

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

  225.     }

  226.     /**
  227.      * {@inheritDoc} All lanes the GTU is on will be left.
  228.      */
  229.     @Override
  230.     public void setParent(final GTU gtu) throws GTUException
  231.     {
  232.         for (Lane lane : new HashSet<>(this.currentLanes.keySet())) // copy for concurrency problems
  233.         {
  234.             leaveLane(lane);
  235.         }
  236.         super.setParent(gtu);
  237.     }

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

  251.     /**
  252.      * Hack method. TODO remove and solve better
  253.      * @return safe to change
  254.      * @throws GTUException on error
  255.      */
  256.     public final boolean isSafeToChange() throws GTUException
  257.     {
  258.         return this.fractionalLinkPositions.get(getReferencePosition().getLane().getParentLink()) > 0.0;
  259.     }

  260.     /** {@inheritDoc} */
  261.     @Override
  262.     @SuppressWarnings("checkstyle:designforextension")
  263.     public void enterLane(final Lane lane, final Length position, final GTUDirectionality gtuDirection) throws GTUException
  264.     {
  265.         if (lane == null || gtuDirection == null || position == null)
  266.         {
  267.             throw new GTUException("enterLane - one of the arguments is null");
  268.         }
  269.         addLaneToGtu(lane, position, gtuDirection);
  270.         addGtuToLane(lane, position);
  271.     }

  272.     /**
  273.      * Registers the lane at the GTU.
  274.      * @param lane the lane to add to the list of lanes on which the GTU is registered.
  275.      * @param gtuDirection the direction of the GTU on the lane (which can be bidirectional). If the GTU has a positive speed,
  276.      *            it is moving in this direction.
  277.      * @param position the position on the lane.
  278.      * @throws GTUException when positioning the GTU on the lane causes a problem
  279.      */
  280.     private void addLaneToGtu(final Lane lane, final Length position, final GTUDirectionality gtuDirection) throws GTUException
  281.     {
  282.         if (this.currentLanes.containsKey(lane))
  283.         {
  284.             System.err.println(this + " is already registered on lane: " + lane + " at fractional position "
  285.                     + this.fractionalPosition(lane, RelativePosition.REFERENCE_POSITION) + " intended position is " + position
  286.                     + " length of lane is " + lane.getLength());
  287.             return;
  288.         }
  289.         // if the GTU is already registered on a lane of the same link, do not change its fractional position, as
  290.         // this might lead to a "jump".
  291.         if (!this.fractionalLinkPositions.containsKey(lane.getParentLink()))
  292.         {
  293.             this.fractionalLinkPositions.put(lane.getParentLink(), lane.fraction(position));
  294.         }
  295.         this.currentLanes.put(lane, gtuDirection);
  296.     }

  297.     /**
  298.      * Part of 'enterLane' which registers the GTU with the lane so the lane can report its GTUs.
  299.      * @param lane Lane; lane
  300.      * @param position Length; position
  301.      * @throws GTUException on exception
  302.      */
  303.     protected void addGtuToLane(final Lane lane, final Length position) throws GTUException
  304.     {
  305.         List<SimEventInterface<SimTimeDoubleUnit>> pending = this.pendingEnterTriggers.get(lane);
  306.         if (null != pending)
  307.         {
  308.             for (SimEventInterface<SimTimeDoubleUnit> event : pending)
  309.             {
  310.                 if (event.getAbsoluteExecutionTime().get().ge(getSimulator().getSimulatorTime()))
  311.                 {
  312.                     boolean result = getSimulator().cancelEvent(event);
  313.                     if (!result && event.getAbsoluteExecutionTime().get().ne(getSimulator().getSimulatorTime()))
  314.                     {
  315.                         System.err.println("addLaneToGtu, trying to remove event: NOTHING REMOVED -- result=" + result
  316.                                 + ", simTime=" + getSimulator().getSimulatorTime() + ", eventTime="
  317.                                 + event.getAbsoluteExecutionTime().get());
  318.                     }
  319.                 }
  320.             }
  321.             this.pendingEnterTriggers.remove(lane);
  322.         }
  323.         lane.addGTU(this, position);
  324.     }

  325.     /** {@inheritDoc} */
  326.     @Override
  327.     @SuppressWarnings("checkstyle:designforextension")
  328.     public void leaveLane(final Lane lane) throws GTUException
  329.     {
  330.         leaveLane(lane, false);
  331.     }

  332.     /**
  333.      * Leave a lane but do not complain about having no lanes left when beingDestroyed is true.
  334.      * @param lane the lane to leave
  335.      * @param beingDestroyed if true, no complaints about having no lanes left
  336.      * @throws GTUException in case leaveLane should not be called
  337.      */
  338.     @SuppressWarnings("checkstyle:designforextension")
  339.     public void leaveLane(final Lane lane, final boolean beingDestroyed) throws GTUException
  340.     {
  341.         Length position = position(lane, getReference());
  342.         this.currentLanes.remove(lane);
  343.         removePendingEvents(lane, this.pendingLeaveTriggers);
  344.         removePendingEvents(lane, this.pendingEnterTriggers);
  345.         // check if there are any lanes for this link left. If not, remove the link.
  346.         boolean found = false;
  347.         for (Lane l : this.currentLanes.keySet())
  348.         {
  349.             if (l.getParentLink().equals(lane.getParentLink()))
  350.             {
  351.                 found = true;
  352.             }
  353.         }
  354.         if (!found)
  355.         {
  356.             this.fractionalLinkPositions.remove(lane.getParentLink());
  357.         }
  358.         lane.removeGTU(this, !found, position);
  359.         if (this.currentLanes.size() == 0 && !beingDestroyed)
  360.         {
  361.             System.err.println("leaveLane: lanes.size() = 0 for GTU " + getId());
  362.         }
  363.     }

  364.     /**
  365.      * Removes and cancels events for the given lane.
  366.      * @param lane Lane; lane
  367.      * @param triggers Map; map to use
  368.      */
  369.     private void removePendingEvents(final Lane lane, final Map<Lane, List<SimEventInterface<SimTimeDoubleUnit>>> triggers)
  370.     {
  371.         List<SimEventInterface<SimTimeDoubleUnit>> pending = triggers.get(lane);
  372.         if (null != pending)
  373.         {
  374.             for (SimEventInterface<SimTimeDoubleUnit> event : pending)
  375.             {
  376.                 if (event.getAbsoluteExecutionTime().get().ge(getSimulator().getSimulatorTime()))
  377.                 {
  378.                     boolean result = getSimulator().cancelEvent(event);
  379.                     if (!result && event.getAbsoluteExecutionTime().get().ne(getSimulator().getSimulatorTime()))
  380.                     {
  381.                         System.err.println("leaveLane, trying to remove event: NOTHING REMOVED -- result=" + result
  382.                                 + ", simTime=" + getSimulator().getSimulatorTime() + ", eventTime="
  383.                                 + event.getAbsoluteExecutionTime().get());
  384.                     }
  385.                 }
  386.             }
  387.             triggers.remove(lane);
  388.         }
  389.     }

  390.     /** {@inheritDoc} */
  391.     @Override
  392.     public void changeLaneInstantaneously(final LateralDirectionality laneChangeDirection) throws GTUException
  393.     {

  394.         // from info
  395.         DirectedLanePosition from = getReferencePosition();

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

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

  402.         // obtain position on lane adjacent to reference lane and enter lanes upstream/downstream from there
  403.         Set<Lane> adjLanes = from.getLane().accessibleAdjacentLanesPhysical(laneChangeDirection, getGTUType(),
  404.                 this.currentLanes.get(from.getLane()));
  405.         Lane adjLane = adjLanes.iterator().next();
  406.         Length position = adjLane.position(from.getLane().fraction(from.getPosition()));
  407.         GTUDirectionality direction = getDirection(from.getLane());
  408.         Length planLength = Try.assign(() -> getOperationalPlan().getTraveledDistance(getSimulator().getSimulatorTime()),
  409.                 "Exception while determining plan length.");
  410.         enterLaneRecursive(new LaneDirection(adjLane, direction), position, newLinkPositionsLC, planLength, lanesToBeRemoved,
  411.                 0);

  412.         // update the positions on the lanes we are registered on
  413.         this.fractionalLinkPositions.clear();
  414.         this.fractionalLinkPositions.putAll(newLinkPositionsLC);

  415.         // leave the from lanes
  416.         for (Lane lane : lanesToBeRemoved)
  417.         {
  418.             leaveLane(lane);
  419.         }

  420.         // stored positions no longer valid
  421.         this.referencePositionTime = Double.NaN;
  422.         this.cachedPositions.clear();

  423.         // fire event
  424.         this.fireTimedEvent(LaneBasedGTU.LANE_CHANGE_EVENT, new Object[] { getId(), laneChangeDirection, from },
  425.                 getSimulator().getSimulatorTime());

  426.     }

  427.     /**
  428.      * Enters lanes upstream and downstream of the new location after an instantaneous lane change.
  429.      * @param lane LaneDirection; considered lane
  430.      * @param position Length; position to add GTU at
  431.      * @param newLinkPositionsLC Map; new link fractions to store
  432.      * @param planLength Length; length of plan, to consider fractions at start
  433.      * @param lanesToBeRemoved Set; lanes to leave, from which lanes are removed when entered (such that they arent then left)
  434.      * @param dir int; below 0 for upstream, above 0 for downstream, 0 for both
  435.      * @throws GTUException on exception
  436.      */
  437.     private void enterLaneRecursive(final LaneDirection lane, final Length position, final Map<Link, Double> newLinkPositionsLC,
  438.             final Length planLength, final Set<Lane> lanesToBeRemoved, final int dir) throws GTUException
  439.     {
  440.         enterLane(lane.getLane(), position, lane.getDirection());
  441.         lanesToBeRemoved.remove(lane);
  442.         Length adjusted = lane.getDirection().isPlus() ? position.minus(planLength) : position.plus(planLength);
  443.         newLinkPositionsLC.put(lane.getLane().getParentLink(), adjusted.si / lane.getLength().si);

  444.         // upstream
  445.         if (dir < 1)
  446.         {
  447.             Length rear = lane.getDirection().isPlus() ? position.plus(getRear().getDx()) : position.minus(getRear().getDx());
  448.             Length before = null;
  449.             if (lane.getDirection().isPlus() && rear.si < 0.0)
  450.             {
  451.                 before = rear.neg();
  452.             }
  453.             else if (lane.getDirection().isMinus() && rear.si > lane.getLength().si)
  454.             {
  455.                 before = rear.minus(lane.getLength());
  456.             }
  457.             if (before != null)
  458.             {
  459.                 GTUDirectionality upDir = lane.getDirection();
  460.                 Map<Lane, GTUDirectionality> upstream = lane.getLane().upstreamLanes(upDir, getGTUType());
  461.                 if (!upstream.isEmpty())
  462.                 {
  463.                     Lane upLane = null;
  464.                     for (Lane nextUp : upstream.keySet())
  465.                     {
  466.                         if (newLinkPositionsLC.containsKey(nextUp.getParentLink()))
  467.                         {
  468.                             // multiple upstream lanes could belong to the same link, we pick an arbitrary lane
  469.                             // (a conflict should solve this)
  470.                             upLane = nextUp;
  471.                             break;
  472.                         }
  473.                     }
  474.                     if (upLane == null)
  475.                     {
  476.                         // the rear is on an upstream section we weren't before the lane change, due to curvature, we pick an
  477.                         // arbitrary
  478.                         // lane (a conflict should solve this)
  479.                         upLane = upstream.keySet().iterator().next();
  480.                     }
  481.                     upDir = upstream.get(upLane);
  482.                     LaneDirection next = new LaneDirection(upLane, upDir);
  483.                     Length nextPos = upDir.isPlus() ? next.getLength().minus(before).minus(getRear().getDx())
  484.                             : before.plus(getRear().getDx());
  485.                     enterLaneRecursive(next, nextPos, newLinkPositionsLC, planLength, lanesToBeRemoved, -1);
  486.                 }
  487.             }
  488.         }

  489.         // downstream
  490.         if (dir > -1)
  491.         {
  492.             Length front =
  493.                     lane.getDirection().isPlus() ? position.plus(getFront().getDx()) : position.minus(getFront().getDx());
  494.             Length passed = null;
  495.             if (lane.getDirection().isPlus() && front.si > lane.getLength().si)
  496.             {
  497.                 passed = front.minus(lane.getLength());
  498.             }
  499.             else if (lane.getDirection().isMinus() && front.si < 0.0)
  500.             {
  501.                 passed = front.neg();
  502.             }
  503.             if (passed != null)
  504.             {
  505.                 LaneDirection next = lane.getNextLaneDirection(this);
  506.                 Length nextPos = next.getDirection().isPlus() ? passed.minus(getFront().getDx())
  507.                         : next.getLength().minus(passed).plus(getFront().getDx());
  508.                 enterLaneRecursive(next, nextPos, newLinkPositionsLC, planLength, lanesToBeRemoved, 1);
  509.             }
  510.         }
  511.     }

  512.     /**
  513.      * Register on lanes in target lane.
  514.      * @param laneChangeDirection direction of lane change
  515.      * @throws GTUException exception
  516.      */
  517.     @SuppressWarnings("checkstyle:designforextension")
  518.     public void initLaneChange(final LateralDirectionality laneChangeDirection) throws GTUException
  519.     {
  520.         Map<Lane, GTUDirectionality> lanesCopy = new LinkedHashMap<>(this.currentLanes);
  521.         Map<Lane, Double> fractionalLanePositions = new HashMap<>();
  522.         for (Lane lane : lanesCopy.keySet())
  523.         {
  524.             fractionalLanePositions.put(lane, fractionalPosition(lane, getReference()));
  525.         }
  526.         int numRegistered = 0;
  527.         for (Lane lane : lanesCopy.keySet())
  528.         {
  529.             Set<Lane> laneSet = lane.accessibleAdjacentLanesLegal(laneChangeDirection, getGTUType(), getDirection(lane));
  530.             if (laneSet.size() > 0)
  531.             {
  532.                 numRegistered++;
  533.                 Lane adjacentLane = laneSet.iterator().next();
  534.                 enterLane(adjacentLane, adjacentLane.getLength().multiplyBy(fractionalLanePositions.get(lane)),
  535.                         lanesCopy.get(lane));
  536.             }
  537.         }
  538.         Throw.when(numRegistered == 0, GTUException.class, "Gtu %s starting %s lane change, but no adjacent lane found.",
  539.                 getId(), laneChangeDirection);
  540.     }

  541.     /**
  542.      * Performs the finalization of a lane change by leaving the from lanes.
  543.      * @param laneChangeDirection direction of lane change
  544.      */
  545.     @SuppressWarnings("checkstyle:designforextension")
  546.     protected void finalizeLaneChange(final LateralDirectionality laneChangeDirection)
  547.     {
  548.         Map<Lane, GTUDirectionality> lanesCopy = new LinkedHashMap<>(this.currentLanes);
  549.         Set<Lane> lanesToBeRemoved = new LinkedHashSet<>();
  550.         Lane fromLane = null;
  551.         Length fromPosition = null;
  552.         GTUDirectionality fromDirection = null;
  553.         try
  554.         {
  555.             // find lanes to leave as they have an adjacent lane the GTU is also on in the lane change direction
  556.             for (Lane lane : lanesCopy.keySet())
  557.             {
  558.                 Iterator<Lane> iterator =
  559.                         lane.accessibleAdjacentLanesPhysical(laneChangeDirection, getGTUType(), getDirection(lane)).iterator();
  560.                 if (iterator.hasNext() && lanesCopy.keySet().contains(iterator.next()))
  561.                 {
  562.                     lanesToBeRemoved.add(lane);
  563.                 }
  564.             }
  565.             // some lanes registered to the GTU may be downstream of a split and have no adjacent lane, find longitudinally
  566.             boolean added = true;
  567.             while (added)
  568.             {
  569.                 added = false;
  570.                 Set<Lane> lanesToAlsoBeRemoved = new LinkedHashSet<>();
  571.                 for (Lane lane : lanesToBeRemoved)
  572.                 {
  573.                     GTUDirectionality direction = getDirection(lane);
  574.                     for (Lane nextLane : direction.isPlus() ? lane.nextLanes(getGTUType()).keySet()
  575.                             : lane.prevLanes(getGTUType()).keySet())
  576.                     {
  577.                         if (lanesCopy.containsKey(nextLane) && !lanesToBeRemoved.contains(nextLane))
  578.                         {
  579.                             added = true;
  580.                             lanesToAlsoBeRemoved.add(nextLane);
  581.                         }
  582.                     }
  583.                 }
  584.                 lanesToBeRemoved.addAll(lanesToAlsoBeRemoved);
  585.             }
  586.             for (Lane lane : lanesToBeRemoved)
  587.             {
  588.                 double fractionalPosition = this.fractionalLinkPositions.get(lane.getParentLink());
  589.                 if (0.0 <= fractionalPosition && fractionalPosition <= 1.0)
  590.                 {
  591.                     fromLane = lane;
  592.                     fromPosition = lane.getLength().multiplyBy(fractionalPosition);
  593.                     fromDirection = getDirection(lane);
  594.                 }
  595.                 leaveLane(lane);
  596.             }
  597.         }
  598.         catch (GTUException exception)
  599.         {
  600.             // should not happen, lane was obtained from GTU
  601.             throw new RuntimeException("fractionalPosition on lane not possible", exception);
  602.         }
  603.         this.referencePositionTime = Double.NaN;
  604.         Throw.when(fromLane == null, RuntimeException.class, "No from lane for lane change event.");
  605.         DirectedLanePosition from;
  606.         try
  607.         {
  608.             from = new DirectedLanePosition(fromLane, fromPosition, fromDirection);
  609.         }
  610.         catch (GTUException exception)
  611.         {
  612.             throw new RuntimeException(exception);
  613.         }
  614.         this.fireTimedEvent(LaneBasedGTU.LANE_CHANGE_EVENT, new Object[] { getId(), laneChangeDirection, from },
  615.                 getSimulator().getSimulatorTime());
  616.     }

  617.     /** {@inheritDoc} */
  618.     @Override
  619.     public final GTUDirectionality getDirection(final Lane lane) throws GTUException
  620.     {
  621.         Throw.when(!this.currentLanes.containsKey(lane), GTUException.class, "getDirection: Lanes %s does not contain %s",
  622.                 this.currentLanes.keySet(), lane);
  623.         return this.currentLanes.get(lane);
  624.     }

  625.     /** {@inheritDoc} */
  626.     @Override
  627.     @SuppressWarnings("checkstyle:designforextension")
  628.     protected void move(final DirectedPoint fromLocation)
  629.             throws SimRuntimeException, GTUException, OperationalPlanException, NetworkException, ParameterException
  630.     {
  631.         DirectedPoint currentPoint = getLocation();
  632.         // Only carry out move() if we still have lane(s) to drive on.
  633.         // Note: a (Sink) trigger can have 'destroyed' us between the previous evaluation step and this one.
  634.         if (this.currentLanes.isEmpty())
  635.         {
  636.             destroy();
  637.             return; // Done; do not re-schedule execution of this move method.
  638.         }

  639.         // remove enter events
  640.         // WS: why?
  641.         // for (Lane lane : this.pendingEnterTriggers.keySet())
  642.         // {
  643.         // System.out.println("GTU " + getId() + " is canceling event on lane " + lane.getFullId());
  644.         // List<SimEventInterface<SimTimeDoubleUnit>> events = this.pendingEnterTriggers.get(lane);
  645.         // for (SimEventInterface<SimTimeDoubleUnit> event : events)
  646.         // {
  647.         // // also unregister from lane
  648.         // this.currentLanes.remove(lane);
  649.         // getSimulator().cancelEvent(event);
  650.         // }
  651.         // }
  652.         // this.pendingEnterTriggers.clear();

  653.         // get distance covered in previous plan, to aid a shift in link fraction (from which a plan moves onwards)
  654.         Length covered = getOperationalPlan().getTraveledDistance(getSimulator().getSimulatorTime());

  655.         // generate the next operational plan and carry it out
  656.         super.move(fromLocation);

  657.         // update the positions on the lanes we are registered on
  658.         // WS: this was previously done using fractions calculated before super.move() based on the GTU position, but an
  659.         // instantaneous lane change while e.g. the nose is on the next lane which is curved, results in a different fraction on
  660.         // the next link (the GTU doesn't stretch or shrink)
  661.         Map<Link, Double> newLinkPositions = new LinkedHashMap<>(this.fractionalLinkPositions);
  662.         Set<Link> done = new LinkedHashSet<>();
  663.         for (Lane lane : this.currentLanes.keySet())
  664.         {
  665.             if (newLinkPositions.containsKey(lane.getParentLink()) && !done.contains(lane.getParentLink()))
  666.             {
  667.                 double f = newLinkPositions.get(lane.getParentLink());
  668.                 f = getDirection(lane).isPlus() ? f + covered.si / lane.getLength().si : f - covered.si / lane.getLength().si;
  669.                 newLinkPositions.put(lane.getParentLink(), f);
  670.                 done.add(lane.getParentLink());
  671.             }
  672.         }
  673.         this.fractionalLinkPositions.clear();
  674.         this.fractionalLinkPositions.putAll(newLinkPositions);

  675.         DirectedLanePosition dlp = getReferencePosition();
  676.         fireTimedEvent(
  677.                 LaneBasedGTU.LANEBASED_MOVE_EVENT, new Object[] { getId(), fromLocation, getSpeed(), getAcceleration(),
  678.                         getTurnIndicatorStatus(), getOdometer(), dlp.getLane(), dlp.getPosition(), dlp.getGtuDirection() },
  679.                 getSimulator().getSimulatorTime());

  680.         if (getOperationalPlan().getAcceleration(Duration.ZERO).si < -10
  681.                 && getOperationalPlan().getSpeed(Duration.ZERO).si > 2.5)
  682.         {
  683.             System.err.println("GTU: " + getId() + " - getOperationalPlan().getAcceleration(Duration.ZERO).si < -10)");
  684.             System.err.println("Lanes in current plan: " + this.currentLanes.keySet());
  685.             if (getTacticalPlanner().getPerception().contains(DefaultSimplePerception.class))
  686.             {
  687.                 DefaultSimplePerception p =
  688.                         getTacticalPlanner().getPerception().getPerceptionCategory(DefaultSimplePerception.class);
  689.                 System.err.println("HeadwayGTU: " + p.getForwardHeadwayGTU());
  690.                 System.err.println("HeadwayObject: " + p.getForwardHeadwayObject());
  691.             }
  692.         }
  693.         DirectedPoint currentPointAfterMove = getLocation();
  694.         if (currentPoint.distance(currentPointAfterMove) > 0.1)
  695.         {
  696.             System.err.println(this.getId() + " jumped");
  697.         }
  698.         // schedule triggers and determine when to enter lanes with front and leave lanes with rear
  699.         scheduleEnterLeaveTriggers();
  700.     }

  701.     /** {@inheritDoc} */
  702.     @Override
  703.     public final Map<Lane, Length> positions(final RelativePosition relativePosition) throws GTUException
  704.     {
  705.         return positions(relativePosition, getSimulator().getSimulatorTime());
  706.     }

  707.     /** {@inheritDoc} */
  708.     @Override
  709.     public final Map<Lane, Length> positions(final RelativePosition relativePosition, final Time when) throws GTUException
  710.     {
  711.         Map<Lane, Length> positions = new LinkedHashMap<>();
  712.         for (Lane lane : this.currentLanes.keySet())
  713.         {
  714.             positions.put(lane, position(lane, relativePosition, when));
  715.         }
  716.         return positions;
  717.     }

  718.     /** {@inheritDoc} */
  719.     @Override
  720.     public final Length position(final Lane lane, final RelativePosition relativePosition) throws GTUException
  721.     {
  722.         return position(lane, relativePosition, getSimulator().getSimulatorTime());
  723.     }

  724.     /** {@inheritDoc} */
  725.     @Override
  726.     @SuppressWarnings("checkstyle:designforextension")
  727.     public Length translatedPosition(final Lane projectionLane, final RelativePosition relativePosition, final Time when)
  728.             throws GTUException
  729.     {
  730.         CrossSectionLink link = projectionLane.getParentLink();
  731.         for (CrossSectionElement cse : link.getCrossSectionElementList())
  732.         {
  733.             if (cse instanceof Lane)
  734.             {
  735.                 Lane cseLane = (Lane) cse;
  736.                 if (null != this.currentLanes.get(cseLane))
  737.                 {
  738.                     double fractionalPosition = fractionalPosition(cseLane, RelativePosition.REFERENCE_POSITION, when);
  739.                     Length pos = new Length(projectionLane.getLength().getSI() * fractionalPosition, LengthUnit.SI);
  740.                     if (this.currentLanes.get(cseLane).isPlus())
  741.                     {
  742.                         return pos.plus(relativePosition.getDx());
  743.                     }
  744.                     return pos.minus(relativePosition.getDx());
  745.                 }
  746.             }
  747.         }
  748.         throw new GTUException(this + " is not on any lane of Link " + link);
  749.     }

  750.     /** {@inheritDoc} */
  751.     @Override
  752.     @SuppressWarnings("checkstyle:designforextension")
  753.     public Length projectedPosition(final Lane projectionLane, final RelativePosition relativePosition, final Time when)
  754.             throws GTUException
  755.     {
  756.         CrossSectionLink link = projectionLane.getParentLink();
  757.         for (CrossSectionElement cse : link.getCrossSectionElementList())
  758.         {
  759.             if (cse instanceof Lane)
  760.             {
  761.                 Lane cseLane = (Lane) cse;
  762.                 if (null != this.currentLanes.get(cseLane))
  763.                 {
  764.                     double fractionalPosition = fractionalPosition(cseLane, relativePosition, when);
  765.                     return new Length(projectionLane.getLength().getSI() * fractionalPosition, LengthUnit.SI);
  766.                 }
  767.             }
  768.         }
  769.         throw new GTUException(this + " is not on any lane of Link " + link);
  770.     }

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

  773.     /** caching of last stored position(s). */
  774.     private Map<Integer, Length> cachedPositions = new HashMap<>();

  775.     /** {@inheritDoc} */
  776.     @Override
  777.     @SuppressWarnings("checkstyle:designforextension")
  778.     public Length position(final Lane lane, final RelativePosition relativePosition, final Time when) throws GTUException
  779.     {
  780.         int cacheIndex = 0;
  781.         if (CACHING)
  782.         {
  783.             cacheIndex = 17 * lane.hashCode() + relativePosition.hashCode();
  784.             Length l;
  785.             if (when.si == this.cachePositionsTime && (l = this.cachedPositions.get(cacheIndex)) != null)
  786.             {
  787.                 // PK verify the result; uncomment if you don't trust the cache
  788.                 // this.cachedPositions.clear();
  789.                 // Length difficultWay = position(lane, relativePosition, when);
  790.                 // if (Math.abs(l.si - difficultWay.si) > 0.00001)
  791.                 // {
  792.                 // System.err.println("Whoops: cache returns bad value for GTU " + getId());
  793.                 // }
  794.                 CACHED_POSITION++;
  795.                 return l;
  796.             }
  797.             if (when.si != this.cachePositionsTime)
  798.             {
  799.                 this.cachedPositions.clear();
  800.                 this.cachePositionsTime = when.si;
  801.             }
  802.         }
  803.         NON_CACHED_POSITION++;

  804.         synchronized (this.lock)
  805.         {
  806.             double longitudinalPosition;
  807.             try
  808.             {
  809.                 longitudinalPosition = lane.positionSI(this.fractionalLinkPositions.get(when).get(lane.getParentLink()));
  810.             }
  811.             catch (NullPointerException exception)
  812.             {
  813.                 throw exception;
  814.             }
  815.             double loc;
  816.             try
  817.             {
  818.                 OperationalPlan plan = getOperationalPlan(when);
  819.                 if (!(plan instanceof LaneBasedOperationalPlan) || !((LaneBasedOperationalPlan) plan).isDeviative())
  820.                 {
  821.                     if (this.currentLanes.get(when).get(lane).isPlus())
  822.                     {
  823.                         loc = longitudinalPosition + plan.getTraveledDistanceSI(when) + relativePosition.getDx().si;
  824.                     }
  825.                     else
  826.                     {
  827.                         loc = longitudinalPosition - plan.getTraveledDistanceSI(when) - relativePosition.getDx().si;
  828.                     }
  829.                 }
  830.                 else
  831.                 {
  832.                     // deviative LaneBasedOperationalPlan, i.e. the GTU is not on a center line
  833.                     DirectedPoint p = plan.getLocation(when, relativePosition);
  834.                     double f = lane.getCenterLine().projectFractional(null, null, p.x, p.y, FractionalFallback.NaN);
  835.                     if (!Double.isNaN(f))
  836.                     {
  837.                         loc = f * lane.getLength().si;
  838.                     }
  839.                     else
  840.                     {
  841.                         // the point does not project fractionally to this lane, it has to be up- or downstream of the lane

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

  844.                         // use loop up to 2 times (for loop creates 'loc not initialized' warning)
  845.                         int i = 0;
  846.                         while (true)
  847.                         {
  848.                             double distance = getDistanceAtOtherLane(lane, when, upstream, 0.0, p);
  849.                             if (!Double.isNaN(distance))
  850.                             {
  851.                                 loc = upstream ? -distance : distance + lane.getLength().si;
  852.                                 break;
  853.                             }
  854.                             else if (i == 1)
  855.                             {
  856.                                 // Lane change ended while moving to next link. The source lanes are left and for a leave-lane
  857.                                 // event the position is required. This may depend on upstream or downstream lanes as the
  858.                                 // reference position is projected to that lane. But if we already left that lane, we can't use
  859.                                 // it. We thus use ENDPOINT fallback instead.
  860.                                 loc = lane.getLength().si * lane.getCenterLine().projectFractional(null, null, p.x, p.y,
  861.                                         FractionalFallback.ENDPOINT);
  862.                                 break;
  863.                             }
  864.                             // try other direction
  865.                             i++;
  866.                             upstream = !upstream;
  867.                         }
  868.                     }
  869.                 }
  870.             }
  871.             catch (NullPointerException e)
  872.             {
  873.                 throw new GTUException("lanesCurrentOperationalPlan or fractionalLinkPositions is null", e);
  874.             }
  875.             catch (Exception e)
  876.             {
  877.                 System.err.println(toString());
  878.                 System.err.println(this.currentLanes.get(when));
  879.                 System.err.println(this.fractionalLinkPositions.get(when));
  880.                 throw new GTUException(e);
  881.             }
  882.             if (Double.isNaN(loc))
  883.             {
  884.                 System.out.println("loc is NaN");
  885.             }
  886.             Length length = Length.createSI(loc);
  887.             if (CACHING)
  888.             {
  889.                 this.cachedPositions.put(cacheIndex, length);
  890.             }
  891.             return length;
  892.         }
  893.     }

  894.     /**
  895.      * In case of a deviative operational plan (not on the center lines), positions are projected fractionally to the center
  896.      * lines. For points upstream or downstream of a lane, fractional projection is not valid. In such cases we need to project
  897.      * the position to an upstream or downstream lane instead, and adjust length along the center lines.
  898.      * @param lane Lane; lane to determine the position on
  899.      * @param when Time; time
  900.      * @param upstream boolean; whether to check upstream (or downstream)
  901.      * @param distance double; cumulative distance in recursive search, starts at 0.0
  902.      * @param point DirectedPoint; absolute point of GTU to be projected to center line
  903.      * @return Length; position on lane being &lt;0 or &gt;{@code lane.getLength()}
  904.      */
  905.     private double getDistanceAtOtherLane(final Lane lane, final Time when, final boolean upstream, final double distance,
  906.             final DirectedPoint point)
  907.     {
  908.         Set<Lane> nextLanes = new HashSet<>(upstream == this.currentLanes.get(lane).isPlus()
  909.                 ? lane.prevLanes(getGTUType()).keySet() : lane.nextLanes(getGTUType()).keySet()); // safe copy
  910.         nextLanes.retainAll(this.currentLanes.keySet()); // as we delete here
  911.         // TODO When requesting the position at the end of the plan, which will be on a further lane, this lane is not yet
  912.         // part of the lanes in the current operational plan. This can be upstream or downstream depending on the direction of
  913.         // travel. We might check whether getDirection(lane)=DIR_PLUS and upstream=false, or getDirection(lane)=DIR_MINUS and
  914.         // upstream=true, to then use LaneDirection.getNextLaneDirection(this) to obtain the next lane. This is only required if
  915.         // nextLanes originally had more than 1 lane, otherwise we can simply use that one lane. Problem is that the search
  916.         // might go on far or even eternally (on a circular network), as projection simply keeps failing because the GTU is
  917.         // actually towards the other longitudinal direction. Hence, the heuristic used before this method is called should
  918.         // change and first always search against the direction of travel, and only consider lanes in currentLanes, while the
  919.         // consecutive search in the direction of travel should then always find a point. We could build in a counter to prevent
  920.         // a hanging software.
  921.         if (nextLanes.size() == 0)
  922.         {
  923.             return Double.NaN; // point must be in the other direction
  924.         }
  925.         Throw.when(nextLanes.size() > 1, IllegalStateException.class,
  926.                 "A position (%s) of GTU %s is not on any of the current registered lanes.", point, this.getId());
  927.         Lane nextLane = nextLanes.iterator().next();
  928.         double f = nextLane.getCenterLine().projectFractional(null, null, point.x, point.y, FractionalFallback.NaN);
  929.         if (Double.isNaN(f))
  930.         {
  931.             getDistanceAtOtherLane(nextLane, when, upstream, distance + nextLane.getLength().si, point);
  932.         }
  933.         return distance + (upstream == this.currentLanes.get(nextLane).isPlus() ? 1.0 - f : f) * nextLane.getLength().si;
  934.     }

  935.     /** Time of reference position cache. */
  936.     private double referencePositionTime = Double.NaN;

  937.     /** Cached reference position. */
  938.     private DirectedLanePosition cachedReferencePosition = null;

  939.     /** {@inheritDoc} */
  940.     @Override
  941.     @SuppressWarnings("checkstyle:designforextension")
  942.     public DirectedLanePosition getReferencePosition() throws GTUException
  943.     {
  944.         if (this.referencePositionTime == getSimulator().getSimulatorTime().si)
  945.         {
  946.             return this.cachedReferencePosition;
  947.         }
  948.         Lane refLane = null;
  949.         Double closest = Double.POSITIVE_INFINITY;
  950.         for (Lane lane : this.currentLanes.keySet())
  951.         {
  952.             double fraction = fractionalPosition(lane, getReference());
  953.             if (fraction >= 0.0 && fraction <= 1.0)
  954.             {
  955.                 // TODO widest lane in case we are registered on more than one lane with the reference point?
  956.                 // TODO lane that leads to our location or not if we are registered on parallel lanes?
  957.                 if (refLane == null)
  958.                 {
  959.                     refLane = lane;
  960.                 }
  961.                 else
  962.                 {
  963.                     DirectedPoint loc = getLocation();
  964.                     try
  965.                     {
  966.                         double f =
  967.                                 lane.getCenterLine().projectFractional(null, null, loc.x, loc.y, FractionalFallback.ENDPOINT);
  968.                         double distance = loc.distance(lane.getCenterLine().getLocationFraction(f));
  969.                         if (distance < closest)
  970.                         {
  971.                             refLane = lane;
  972.                             closest = distance;
  973.                         }
  974.                     }
  975.                     catch (OTSGeometryException exception)
  976.                     {
  977.                         throw new RuntimeException("Exception while determining reference position between lanes.", exception);
  978.                     }
  979.                 }
  980.             }
  981.         }
  982.         if (refLane != null)
  983.         {
  984.             this.cachedReferencePosition =
  985.                     new DirectedLanePosition(refLane, position(refLane, getReference()), this.getDirection(refLane));
  986.             this.referencePositionTime = getSimulator().getSimulatorTime().si;
  987.             return this.cachedReferencePosition;
  988.         }
  989.         // for (Lane lane : this.currentLanes.keySet())
  990.         // {
  991.         // Length relativePosition = position(lane, RelativePosition.REFERENCE_POSITION);
  992.         // System.err
  993.         // .println(String.format("Lane %s of Link %s: absolute position %s, relative position %5.1f%%", lane.getId(),
  994.         // lane.getParentLink().getId(), relativePosition, relativePosition.si * 100 / lane.getLength().si));
  995.         // }
  996.         throw new GTUException("The reference point of GTU " + this + " is not on any of the lanes on which it is registered");
  997.     }

  998.     /**
  999.      * Schedule the triggers for this GTU that are going to happen until the next evaluation time. Also schedule the
  1000.      * registration and deregistration of lanes when the vehicle enters or leaves them, at the exact right time. <br>
  1001.      * Note: when the GTU makes a lane change, the vehicle will be registered for both lanes during the entire maneuver.
  1002.      * @throws NetworkException on network inconsistency
  1003.      * @throws SimRuntimeException should never happen
  1004.      * @throws GTUException when a branch is reached where the GTU does not know where to go next
  1005.      */
  1006.     @SuppressWarnings("checkstyle:designforextension")
  1007.     protected void scheduleEnterLeaveTriggers() throws NetworkException, SimRuntimeException, GTUException
  1008.     {
  1009.         // DirectedLanePosition ref = getReferencePosition();
  1010.         // double endPosition = position(ref.getLane(), getReference(), getOperationalPlan().getEndTime()).si;
  1011.         double moveSI = getOperationalPlan().getTotalLength().si; // endPosition - ref.getPosition().si;

  1012.         // Figure out which lanes this GTU will enter with its FRONT, and schedule the lane enter events
  1013.         Map<Lane, GTUDirectionality> lanesCopy = new LinkedHashMap<>(this.currentLanes);
  1014.         Iterator<Lane> it = lanesCopy.keySet().iterator();
  1015.         Lane enteredLane = null;
  1016.         while (it.hasNext() || enteredLane != null) // use a copy because this.lanesCurrentOperationalPlan can change
  1017.         {
  1018.             // next lane from 'lanesCopy', or asses the lane we just entered as it may be very short and also passed fully
  1019.             Lane lane;
  1020.             GTUDirectionality laneDir;
  1021.             if (enteredLane == null)
  1022.             {
  1023.                 lane = it.next();
  1024.                 laneDir = lanesCopy.get(lane);
  1025.             }
  1026.             else
  1027.             {
  1028.                 lane = enteredLane;
  1029.                 laneDir = this.currentLanes.get(lane);
  1030.             }
  1031.             double sign = laneDir.isPlus() ? 1.0 : -1.0;
  1032.             enteredLane = null;

  1033.             // skip if already on next lane
  1034.             if (!Collections.disjoint(this.currentLanes.keySet(), lane.downstreamLanes(laneDir, getGTUType()).keySet()))
  1035.             {
  1036.                 continue;
  1037.             }

  1038.             // schedule triggers on this lane
  1039.             double referenceStartSI = this.fractionalLinkPositions.get(lane.getParentLink()) * lane.getLength().getSI();
  1040.             // System.out.println("referenceStartSI = " + referenceStartSI);
  1041.             // referenceStartSI is position of reference of GTU on current lane
  1042.             if (laneDir.isPlus())
  1043.             {
  1044.                 lane.scheduleSensorTriggers(this, referenceStartSI, moveSI);
  1045.             }
  1046.             else
  1047.             {
  1048.                 lane.scheduleSensorTriggers(this, referenceStartSI - moveSI, moveSI);
  1049.             }

  1050.             double nextFrontPosSI = referenceStartSI + sign * (moveSI + getFront().getDx().si);
  1051.             // System.out.println("nextFrontPosSI = " + nextFrontPosSI);
  1052.             // System.out.println("lane.getLength().si = " + lane.getLength().si);
  1053.             Lane nextLane = null;
  1054.             GTUDirectionality nextDirection = null;
  1055.             Length refPosAtLastTimestep = null;
  1056.             DirectedPoint end = null;
  1057.             if (laneDir.isPlus() ? nextFrontPosSI > lane.getLength().si : nextFrontPosSI < 0.0)
  1058.             {
  1059.                 LaneDirection next = new LaneDirection(lane, laneDir).getNextLaneDirection(this);
  1060.                 // if (next == null)
  1061.                 // {
  1062.                 // LaneDirection tmp = new LaneDirection(lane, laneDir);
  1063.                 // tmp.getNextLaneDirection(this);
  1064.                 // }
  1065.                 nextLane = next.getLane();
  1066.                 nextDirection = next.getDirection();
  1067.                 double endPos = laneDir.isPlus() ? lane.getLength().si - getFront().getDx().si : getFront().getDx().si;
  1068.                 Lane endLane = lane;
  1069.                 GTUDirectionality endLaneDir = laneDir;
  1070.                 while (endLaneDir.isPlus() ? endPos < 0.0 : endPos > endLane.getLength().si)
  1071.                 {
  1072.                     // GTU front is more than lane length, so end location can not be extracted from the lane, let's move then
  1073.                     Map<Lane, GTUDirectionality> map = endLane.upstreamLanes(endLaneDir, getGTUType());
  1074.                     map.keySet().retainAll(this.currentLanes.keySet());
  1075.                     double remain = endLaneDir.isPlus() ? -endPos : endPos - endLane.getLength().si;
  1076.                     endLane = map.keySet().iterator().next();
  1077.                     endLaneDir = map.get(endLane);
  1078.                     endPos = endLaneDir.isPlus() ? endLane.getLength().si - remain : remain;
  1079.                 }
  1080.                 end = endLane.getCenterLine().getLocationExtendedSI(endPos);
  1081.                 if (laneDir.isPlus())
  1082.                 {
  1083.                     refPosAtLastTimestep = nextDirection.isPlus() ? Length.createSI(referenceStartSI - lane.getLength().si)
  1084.                             : Length.createSI(nextLane.getLength().si - referenceStartSI + lane.getLength().si);
  1085.                 }
  1086.                 else
  1087.                 {
  1088.                     refPosAtLastTimestep = nextDirection.isPlus() ? Length.createSI(-referenceStartSI)
  1089.                             : Length.createSI(nextLane.getLength().si + referenceStartSI);
  1090.                 }
  1091.             }

  1092.             if (end != null)
  1093.             {
  1094.                 Time enterTime = getOperationalPlan().timeAtPoint(end, false);
  1095.                 if (!Double.isNaN(enterTime.si))
  1096.                 {
  1097.                     addLaneToGtu(nextLane, refPosAtLastTimestep, nextDirection);
  1098.                     enteredLane = nextLane;
  1099.                     Length coveredDistance = Try.assign(() -> getOperationalPlan().getTraveledDistance(enterTime),
  1100.                             "Enter time of lane beyond plan.");
  1101.                     SimEventInterface<SimTimeDoubleUnit> event = getSimulator().scheduleEventAbs(enterTime, this, this,
  1102.                             "addGtuToLane", new Object[] { nextLane, refPosAtLastTimestep.plus(coveredDistance) });
  1103.                     addEnterTrigger(nextLane, event);
  1104.                     // schedule any sensor triggers on this lane for the remainder time
  1105.                     // nextLane.scheduleSensorTriggers(this, refPosAtLastTimestep.si, direction.isPlus() ? moveSI : -moveSI);
  1106.                 }
  1107.             }
  1108.         }

  1109.         // Figure out which lanes this GTU will exit with its BACK, and schedule the lane exit events
  1110.         for (Lane lane : this.currentLanes.keySet())
  1111.         {
  1112.             double referenceStartSI = this.fractionalLinkPositions.get(lane.getParentLink()) * lane.getLength().getSI();
  1113.             Time exitTime = null;
  1114.             // heuristic to perform accurate timeAtPoint check: reference position beyond lane
  1115.             if (this.currentLanes.get(lane).equals(GTUDirectionality.DIR_PLUS))
  1116.             {
  1117.                 if (referenceStartSI + getRear().getDx().si + moveSI > lane.getLength().si)
  1118.                 {
  1119.                     try
  1120.                     {
  1121.                         // if ("1".equals(this.getId()) && this.getSimulator().getSimulatorTime().si >= 9.5
  1122.                         // && "FORWARD1".equals(lane.getId()) && "1020".equals(lane.getParentLink().getId()))
  1123.                         // {
  1124.                         // System.err.println("About to determine wrong exit time for lane " + lane.getId() + " of link "
  1125.                         // + lane.getParentLink().getId());
  1126.                         // }
  1127.                         exitTime = getOperationalPlan().timeAtPoint(lane.getCenterLine().getLocationFraction(1.0),
  1128.                                 this.fractionalLinkPositions.get(lane.getParentLink()) > 1.0);
  1129.                     }
  1130.                     catch (OTSGeometryException exception)
  1131.                     {
  1132.                         throw new RuntimeException(exception);
  1133.                     }
  1134.                 }
  1135.             }
  1136.             else
  1137.             {
  1138.                 if (referenceStartSI - getRear().getDx().si - moveSI < 0.0)
  1139.                 {
  1140.                     try
  1141.                     {
  1142.                         exitTime = getOperationalPlan().timeAtPoint(lane.getCenterLine().getLocationFraction(0.0),
  1143.                                 this.fractionalLinkPositions.get(lane.getParentLink()) < 0.0);
  1144.                     }
  1145.                     catch (OTSGeometryException exception)
  1146.                     {
  1147.                         throw new RuntimeException(exception);
  1148.                     }
  1149.                 }
  1150.             }
  1151.             if (exitTime != null && !Double.isNaN(exitTime.si))
  1152.             {
  1153.                 // if ("1".equals(this.getId()) && this.getSimulator().getSimulatorTime().si >= 9.5)
  1154.                 // {
  1155.                 // System.err.println("Scheduling leaveLane event for lane " + lane.getId() + " of link "
  1156.                 // + lane.getParentLink().getId() + " at time " + exitTime);
  1157.                 // }
  1158.                 SimEvent<SimTimeDoubleUnit> event = new SimEvent<>(new SimTimeDoubleUnit(exitTime), this, this, "leaveLane",
  1159.                         new Object[] { lane, new Boolean(false) });
  1160.                 getSimulator().scheduleEvent(event);
  1161.                 addTrigger(lane, event);
  1162.             }
  1163.         }
  1164.     }

  1165.     /** {@inheritDoc} */
  1166.     @Override
  1167.     public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition) throws GTUException
  1168.     {
  1169.         return fractionalPositions(relativePosition, getSimulator().getSimulatorTime());
  1170.     }

  1171.     /** {@inheritDoc} */
  1172.     @Override
  1173.     public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition, final Time when)
  1174.             throws GTUException
  1175.     {
  1176.         Map<Lane, Double> positions = new LinkedHashMap<>();
  1177.         for (Lane lane : this.currentLanes.keySet())
  1178.         {
  1179.             positions.put(lane, fractionalPosition(lane, relativePosition, when));
  1180.         }
  1181.         return positions;
  1182.     }

  1183.     /** {@inheritDoc} */
  1184.     @Override
  1185.     public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition, final Time when)
  1186.             throws GTUException
  1187.     {
  1188.         return position(lane, relativePosition, when).getSI() / lane.getLength().getSI();
  1189.     }

  1190.     /** {@inheritDoc} */
  1191.     @Override
  1192.     public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition) throws GTUException
  1193.     {
  1194.         return position(lane, relativePosition).getSI() / lane.getLength().getSI();
  1195.     }

  1196.     /** {@inheritDoc} */
  1197.     @Override
  1198.     public final void addTrigger(final Lane lane, final SimEventInterface<SimTimeDoubleUnit> event)
  1199.     {
  1200.         List<SimEventInterface<SimTimeDoubleUnit>> list = this.pendingLeaveTriggers.get(lane);
  1201.         if (null == list)
  1202.         {
  1203.             list = new ArrayList<>();
  1204.         }
  1205.         list.add(event);
  1206.         this.pendingLeaveTriggers.put(lane, list);
  1207.     }

  1208.     /**
  1209.      * Add enter trigger.
  1210.      * @param lane Lane; lane
  1211.      * @param event SimEvent; event
  1212.      */
  1213.     private void addEnterTrigger(final Lane lane, final SimEventInterface<SimTimeDoubleUnit> event)
  1214.     {
  1215.         List<SimEventInterface<SimTimeDoubleUnit>> list = this.pendingEnterTriggers.get(lane);
  1216.         if (null == list)
  1217.         {
  1218.             list = new ArrayList<>();
  1219.         }
  1220.         list.add(event);
  1221.         this.pendingEnterTriggers.put(lane, list);
  1222.     }

  1223.     /**
  1224.      * Sets a vehicle model.
  1225.      * @param vehicleModel VehicleModel; vehicle model
  1226.      */
  1227.     public void setVehicleModel(final VehicleModel vehicleModel)
  1228.     {
  1229.         this.vehicleModel = vehicleModel;
  1230.     }

  1231.     /** {@inheritDoc} */
  1232.     @Override
  1233.     public VehicleModel getVehicleModel()
  1234.     {
  1235.         return this.vehicleModel;
  1236.     }

  1237.     /** {@inheritDoc} */
  1238.     @Override
  1239.     @SuppressWarnings("checkstyle:designforextension")
  1240.     public void destroy()
  1241.     {
  1242.         DirectedLanePosition dlp = null;
  1243.         try
  1244.         {
  1245.             dlp = getReferencePosition();
  1246.         }
  1247.         catch (@SuppressWarnings("unused") GTUException e)
  1248.         {
  1249.             // ignore. not important at destroy
  1250.         }
  1251.         DirectedPoint location = this.getOperationalPlan() == null ? new DirectedPoint(0.0, 0.0, 0.0) : getLocation();

  1252.         synchronized (this.lock)
  1253.         {
  1254.             Set<Lane> laneSet = new LinkedHashSet<>(this.currentLanes.keySet()); // Operate on a copy of the key
  1255.                                                                                  // set
  1256.             for (Lane lane : laneSet)
  1257.             {
  1258.                 try
  1259.                 {
  1260.                     leaveLane(lane, true);
  1261.                 }
  1262.                 catch (@SuppressWarnings("unused") GTUException e)
  1263.                 {
  1264.                     // ignore. not important at destroy
  1265.                 }
  1266.             }
  1267.         }

  1268.         if (dlp != null && dlp.getLane() != null)
  1269.         {
  1270.             Lane referenceLane = dlp.getLane();
  1271.             fireTimedEvent(LaneBasedGTU.LANEBASED_DESTROY_EVENT,
  1272.                     new Object[] { getId(), location, getOdometer(), referenceLane, dlp.getPosition(), dlp.getGtuDirection() },
  1273.                     getSimulator().getSimulatorTime());
  1274.         }
  1275.         else
  1276.         {
  1277.             fireTimedEvent(LaneBasedGTU.LANEBASED_DESTROY_EVENT,
  1278.                     new Object[] { getId(), location, getOdometer(), null, Length.ZERO, null },
  1279.                     getSimulator().getSimulatorTime());
  1280.         }

  1281.         super.destroy();
  1282.     }

  1283.     /** {@inheritDoc} */
  1284.     @Override
  1285.     public final Bounds getBounds()
  1286.     {
  1287.         double dx = 0.5 * getLength().doubleValue();
  1288.         double dy = 0.5 * getWidth().doubleValue();
  1289.         return new BoundingBox(new Point3d(-dx, -dy, 0.0), new Point3d(dx, dy, 0.0));
  1290.     }

  1291.     /** {@inheritDoc} */
  1292.     @Override
  1293.     public final LaneBasedStrategicalPlanner getStrategicalPlanner()
  1294.     {
  1295.         return (LaneBasedStrategicalPlanner) super.getStrategicalPlanner();
  1296.     }

  1297.     /** {@inheritDoc} */
  1298.     @Override
  1299.     public final LaneBasedStrategicalPlanner getStrategicalPlanner(final Time time)
  1300.     {
  1301.         return (LaneBasedStrategicalPlanner) super.getStrategicalPlanner(time);
  1302.     }

  1303.     /** {@inheritDoc} */
  1304.     @Override
  1305.     public Speed getDesiredSpeed()
  1306.     {
  1307.         Time simTime = getSimulator().getSimulatorTime();
  1308.         if (this.desiredSpeedTime == null || this.desiredSpeedTime.si < simTime.si)
  1309.         {
  1310.             InfrastructurePerception infra =
  1311.                     getTacticalPlanner().getPerception().getPerceptionCategoryOrNull(InfrastructurePerception.class);
  1312.             SpeedLimitInfo speedInfo;
  1313.             if (infra == null)
  1314.             {
  1315.                 speedInfo = new SpeedLimitInfo();
  1316.                 speedInfo.addSpeedInfo(SpeedLimitTypes.MAX_VEHICLE_SPEED, getMaximumSpeed());
  1317.             }
  1318.             else
  1319.             {
  1320.                 // Throw.whenNull(infra, "InfrastructurePerception is required to determine the desired speed.");
  1321.                 speedInfo = infra.getSpeedLimitProspect(RelativeLane.CURRENT).getSpeedLimitInfo(Length.ZERO);
  1322.             }
  1323.             this.cachedDesiredSpeed =
  1324.                     Try.assign(() -> getTacticalPlanner().getCarFollowingModel().desiredSpeed(getParameters(), speedInfo),
  1325.                             "Parameter exception while obtaining the desired speed.");
  1326.             this.desiredSpeedTime = simTime;
  1327.         }
  1328.         return this.cachedDesiredSpeed;
  1329.     }

  1330.     /** {@inheritDoc} */
  1331.     @Override
  1332.     public Acceleration getCarFollowingAcceleration()
  1333.     {
  1334.         Time simTime = getSimulator().getSimulatorTime();
  1335.         if (this.carFollowingAccelerationTime == null || this.carFollowingAccelerationTime.si < simTime.si)
  1336.         {
  1337.             LanePerception perception = getTacticalPlanner().getPerception();
  1338.             // speed
  1339.             EgoPerception ego = perception.getPerceptionCategoryOrNull(EgoPerception.class);
  1340.             Throw.whenNull(ego, "EgoPerception is required to determine the speed.");
  1341.             Speed speed = ego.getSpeed();
  1342.             // speed limit info
  1343.             InfrastructurePerception infra = perception.getPerceptionCategoryOrNull(InfrastructurePerception.class);
  1344.             Throw.whenNull(infra, "InfrastructurePerception is required to determine the desired speed.");
  1345.             SpeedLimitInfo speedInfo = infra.getSpeedLimitProspect(RelativeLane.CURRENT).getSpeedLimitInfo(Length.ZERO);
  1346.             // leaders
  1347.             NeighborsPerception neighbors = perception.getPerceptionCategoryOrNull(NeighborsPerception.class);
  1348.             Throw.whenNull(neighbors, "NeighborsPerception is required to determine the car-following acceleration.");
  1349.             PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders = neighbors.getLeaders(RelativeLane.CURRENT);
  1350.             // obtain
  1351.             this.cachedCarFollowingAcceleration =
  1352.                     Try.assign(() -> getTacticalPlanner().getCarFollowingModel().followingAcceleration(getParameters(), speed,
  1353.                             speedInfo, leaders), "Parameter exception while obtaining the desired speed.");
  1354.             this.carFollowingAccelerationTime = simTime;
  1355.         }
  1356.         return this.cachedCarFollowingAcceleration;
  1357.     }

  1358.     /** {@inheritDoc} */
  1359.     @Override
  1360.     public final TurnIndicatorStatus getTurnIndicatorStatus()
  1361.     {
  1362.         return this.turnIndicatorStatus.get();
  1363.     }

  1364.     /** {@inheritDoc} */
  1365.     @Override
  1366.     public final TurnIndicatorStatus getTurnIndicatorStatus(final Time time)
  1367.     {
  1368.         return this.turnIndicatorStatus.get(time);
  1369.     }

  1370.     /** {@inheritDoc} */
  1371.     @Override
  1372.     public final void setTurnIndicatorStatus(final TurnIndicatorStatus turnIndicatorStatus)
  1373.     {
  1374.         this.turnIndicatorStatus.set(turnIndicatorStatus);
  1375.     }

  1376.     /** {@inheritDoc} */
  1377.     @Override
  1378.     @SuppressWarnings("checkstyle:designforextension")
  1379.     public String toString()
  1380.     {
  1381.         return String.format("GTU " + getId());
  1382.     }

  1383. }