View Javadoc
1   package org.opentrafficsim.road.gtu.lane;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.EnumMap;
6   import java.util.HashMap;
7   import java.util.HashSet;
8   import java.util.LinkedHashMap;
9   import java.util.LinkedHashSet;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Set;
13  
14  import javax.media.j3d.Bounds;
15  import javax.vecmath.Point3d;
16  
17  import nl.tudelft.simulation.dsol.SimRuntimeException;
18  import nl.tudelft.simulation.language.d3.BoundingBox;
19  import nl.tudelft.simulation.language.d3.DirectedPoint;
20  
21  import org.djunits.unit.AccelerationUnit;
22  import org.djunits.unit.LengthUnit;
23  import org.djunits.unit.SpeedUnit;
24  import org.djunits.unit.TimeUnit;
25  import org.djunits.value.ValueException;
26  import org.djunits.value.vdouble.vector.DoubleVector;
27  import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
28  import org.opentrafficsim.core.gtu.AbstractGTU;
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.network.LateralDirectionality;
33  import org.opentrafficsim.core.network.Link;
34  import org.opentrafficsim.core.network.NetworkException;
35  import org.opentrafficsim.core.network.Node;
36  import org.opentrafficsim.road.gtu.animation.LaneChangeUrgeGTUColorer;
37  import org.opentrafficsim.road.gtu.following.GTUFollowingModel;
38  import org.opentrafficsim.road.gtu.following.HeadwayGTU;
39  import org.opentrafficsim.road.gtu.lane.changing.LaneChangeModel;
40  import org.opentrafficsim.road.gtu.lane.changing.LaneMovementStep;
41  import org.opentrafficsim.road.network.lane.CrossSectionElement;
42  import org.opentrafficsim.road.network.lane.CrossSectionLink;
43  import org.opentrafficsim.road.network.lane.Lane;
44  import org.opentrafficsim.road.network.route.AbstractLaneBasedRouteNavigator;
45  import org.opentrafficsim.road.network.route.LaneBasedRouteNavigator;
46  
47  /**
48   * This class contains most of the code that is needed to run a lane based GTU. <br>
49   * 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
50   * 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
51   * 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
52   * dozens of Lanes at the same time.
53   * <p>
54   * When calculating a headway, the GTU has to look in successive lanes. When Lanes (or underlying CrossSectionLinks) diverge,
55   * the headway algorithms have to look at multiple Lanes and return the minimum headway in each of the Lanes. When the Lanes (or
56   * underlying CrossSectionLinks) converge, "parallel" traffic is not taken into account in the headway calculation. Instead, gap
57   * acceptance algorithms or their equivalent should guide the merging behavior.
58   * <p>
59   * To decide its movement, an AbstractLaneBasedGTU applies its car following algorithm and lane change algorithm to set the
60   * acceleration and any lane change operation to perform. It then schedules the triggers that will add it to subsequent lanes
61   * and remove it from current lanes as needed during the time step that is has committed to. Finally, it re-schedules its next
62   * movement evaluation with the simulator.
63   * <p>
64   * Copyright (c) 2013-2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
65   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
66   * <p>
67   * @version $Revision: 1408 $, $LastChangedDate: 2015-09-24 15:17:25 +0200 (Thu, 24 Sep 2015) $, by $Author: pknoppers $,
68   *          initial version Oct 22, 2014 <br>
69   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
70   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
71   */
72  public abstract class AbstractLaneBasedGTU extends AbstractGTU implements LaneBasedGTU
73  {
74      /** */
75      private static final long serialVersionUID = 20140822L;
76  
77      /** Time of last evaluation. */
78      private Time.Abs lastEvaluationTime;
79  
80      /** Time of next evaluation. */
81      private Time.Abs nextEvaluationTime;
82  
83      /** Total traveled distance. */
84      private Length.Abs odometer = new Length.Abs(0, LengthUnit.SI);
85  
86      /** Distance to the next required lane change and lateral direction thereof. */
87      private LaneChangeUrgeGTUColorer.LaneChangeDistanceAndDirection lastLaneChangeDistanceAndDirection =
88              new LaneChangeUrgeGTUColorer.LaneChangeDistanceAndDirection(new Length.Rel(Double.MAX_VALUE, LengthUnit.SI), null);
89  
90      /**
91       * Fractional longitudinal positions of the reference point of the GTU on one or more links at the lastEvaluationTime.
92       * Because the reference point of the GTU might not be on all the links the GTU is registered on, the fractional
93       * longitudinal positions can be more than one, or less than zero.
94       */
95      private final Map<Link, Double> fractionalLinkPositions = new LinkedHashMap<>();
96  
97      /**
98       * The lanes the GTU is registered on. Each lane has to have its link registered in the fractionalLinkPositions as well to
99       * keep consistency. Each link from the fractionalLinkPositions can have one or more Lanes on which the vehicle is
100      * registered. This is a list to improve reproducibility: The 'oldest' lanes on which the vehicle is registered are at the
101      * front of the list, the later ones more to the back.
102      */
103     private final List<Lane> lanes = new ArrayList<>();
104 
105     /**
106      * The adjacent lanes that are accessible for this GTU per lane where the GTU drives. This information is cached, because it
107      * is used multiple times per timestep. The set of lanes is stored per LateralDirectionality (LEFT, RIGHT).
108      */
109     private final Map<Lane, EnumMap<LateralDirectionality, Set<Lane>>> accessibleAdjacentLanes = new HashMap<>();
110 
111     /** Speed at lastEvaluationTime. */
112     private Speed.Abs speed;
113 
114     /** lateral velocity at lastEvaluationTime. */
115     private Speed.Abs lateralVelocity;
116 
117     /** acceleration (negative values indicate deceleration) at the lastEvaluationTime. */
118     private Acceleration.Abs acceleration = new Acceleration.Abs(0, METER_PER_SECOND_2);
119 
120     /** CarFollowingModel used by this GTU. */
121     private final GTUFollowingModel gtuFollowingModel;
122 
123     /** LaneChangeModel used by this GTU. */
124     private final LaneChangeModel laneChangeModel;
125 
126     /** the route navigator with an indexed (complete) route. */
127     private final LaneBasedRouteNavigator routeNavigator;
128 
129     /** the object to lock to make the GTU thread safe. */
130     private Object lock = new Object();
131 
132     /**
133      * Construct a Lane Based GTU.
134      * @param id the id of the GTU
135      * @param gtuType the type of GTU, e.g. TruckType, CarType, BusType
136      * @param gtuFollowingModel the following model, including a reference to the simulator.
137      * @param laneChangeModel LaneChangeModel; the lane change model
138      * @param initialLongitudinalPositions the initial positions of the car on one or more lanes
139      * @param initialSpeed the initial speed of the car on the lane
140      * @param routeNavigator Route; the route that the GTU will take
141      * @param simulator to initialize the move method and to get the current time
142      * @throws NetworkException when the GTU cannot be placed on the given lane
143      * @throws SimRuntimeException when the move method cannot be scheduled
144      * @throws GTUException when gtuFollowingModel is null
145      */
146     @SuppressWarnings("checkstyle:parameternumber")
147     public AbstractLaneBasedGTU(final String id, final GTUType gtuType, final GTUFollowingModel gtuFollowingModel,
148             final LaneChangeModel laneChangeModel, final Map<Lane, Length.Rel> initialLongitudinalPositions,
149             final Speed.Abs initialSpeed, final LaneBasedRouteNavigator routeNavigator,
150             final OTSDEVSSimulatorInterface simulator) throws NetworkException, SimRuntimeException, GTUException
151     {
152         super(id, gtuType, routeNavigator);
153         this.routeNavigator = routeNavigator;
154         if (null == gtuFollowingModel)
155         {
156             throw new GTUException("gtuFollowingModel may not be null");
157         }
158 
159         this.gtuFollowingModel = gtuFollowingModel;
160         this.laneChangeModel = laneChangeModel;
161         this.lateralVelocity = new Speed.Abs(0.0, METER_PER_SECOND);
162 
163         // register the GTU on the lanes
164         for (Lane lane : initialLongitudinalPositions.keySet())
165         {
166             this.lanes.add(lane);
167             addAccessibleAdjacentLanes(lane);
168             this.fractionalLinkPositions.put(lane.getParentLink(), lane.fraction(initialLongitudinalPositions.get(lane)));
169             lane.addGTU(this, initialLongitudinalPositions.get(lane));
170         }
171 
172         this.lastEvaluationTime = simulator.getSimulatorTime().getTime();
173         this.speed = initialSpeed;
174         this.nextEvaluationTime = this.lastEvaluationTime;
175 
176         // start the movement of the GTU
177         simulator.scheduleEventNow(this, this, "move", null);
178     }
179 
180     /** very small speed to use for testing with rounding errors. */
181     private static final Speed.Abs DRIFTINGSPEED = new Speed.Abs(1E-10, SpeedUnit.SI);
182 
183     /** {@inheritDoc} */
184     @Override
185     public final Speed.Abs getLongitudinalVelocity(final Time.Abs when)
186     {
187         Time.Rel dT = when.minus(this.lastEvaluationTime);
188         Speed.Abs velocity = this.speed.plus(this.getAcceleration(when).toRel().multiplyBy(dT));
189         if (velocity.abs().lt(DRIFTINGSPEED))
190         {
191             velocity = new Speed.Abs(0.0, SpeedUnit.SI);
192         }
193         return velocity;
194     }
195 
196     /** {@inheritDoc} */
197     @Override
198     public final Speed.Abs getLongitudinalVelocity()
199     {
200         return getLongitudinalVelocity(getSimulator().getSimulatorTime().getTime());
201     }
202 
203     /** {@inheritDoc} */
204     @Override
205     public final Speed.Abs getVelocity()
206     {
207         return getLongitudinalVelocity();
208     }
209 
210     /** {@inheritDoc} */
211     @Override
212     public final Time.Abs getLastEvaluationTime()
213     {
214         return this.lastEvaluationTime;
215     }
216 
217     /** {@inheritDoc} */
218     @Override
219     public final Time.Abs getNextEvaluationTime()
220     {
221         return this.nextEvaluationTime;
222     }
223 
224     /** {@inheritDoc} */
225     @Override
226     public final Acceleration.Abs getAcceleration(final Time.Abs when)
227     {
228         // Currently the acceleration is independent of when; it is constant during the evaluation interval
229         return this.acceleration;
230     }
231 
232     /** {@inheritDoc} */
233     @Override
234     public final Acceleration.Abs getAcceleration()
235     {
236         return getAcceleration(getSimulator().getSimulatorTime().getTime());
237     }
238 
239     /** {@inheritDoc} */
240     @Override
241     public final Length.Abs getOdometer()
242     {
243         return this.odometer.plus(deltaX(getSimulator().getSimulatorTime().getTime()));
244     }
245 
246     /** {@inheritDoc} */
247     @Override
248     public final Speed.Abs getLateralVelocity()
249     {
250         return this.lateralVelocity;
251     }
252 
253     /** {@inheritDoc} */
254     @Override
255     public final void enterLane(final Lane lane, final Length.Rel position) throws NetworkException
256     {
257         if (this.lanes.contains(lane))
258         {
259             System.err.println("GTU " + toString() + " is already registered on this lane: " + lane);
260             return;
261         }
262         // if the GTU is already registered on a lane of the same link, do not change its fractional position, as
263         // this might lead to a "jump".
264         if (!this.fractionalLinkPositions.containsKey(lane.getParentLink()))
265         {
266             this.fractionalLinkPositions.put(lane.getParentLink(), lane.fraction(position));
267         }
268         this.lanes.add(lane);
269         addAccessibleAdjacentLanes(lane);
270         lane.addGTU(this, position);
271     }
272 
273     /** {@inheritDoc} */
274     @Override
275     public final void leaveLane(final Lane lane)
276     {
277         leaveLane(lane, false);
278     }
279 
280     /**
281      * Leave a lane but do not complain about having no lanes left when beingDestroyed is true.
282      * @param lane the lane to leave
283      * @param beingDestroyed if true, no complaints about having no lanes left
284      */
285     public final void leaveLane(final Lane lane, final boolean beingDestroyed)
286     {
287         // System.out.println("GTU " + toString() + " to be removed from lane: " + lane);
288         this.lanes.remove(lane);
289         removeAccessibleAdjacentLanes(lane);
290         // check of there are any lanes for this link left. If not, remove the link.
291         boolean found = false;
292         for (Lane l : this.lanes)
293         {
294             if (l.getParentLink().equals(lane.getParentLink()))
295             {
296                 found = true;
297             }
298         }
299         if (!found)
300         {
301             this.fractionalLinkPositions.remove(lane.getParentLink());
302         }
303         lane.removeGTU(this);
304         if (this.lanes.size() == 0 && !beingDestroyed)
305         {
306             System.err.println("lanes.size() = 0 for GTU " + getId());
307         }
308     }
309 
310     /**
311      * @return lanes.
312      */
313     public final List<Lane> getLanes()
314     {
315         return this.lanes;
316     }
317 
318     /** Standard incentive to stay in the current lane. */
319     private static final Acceleration.Rel STAYINCURRENTLANEINCENTIVE = new Acceleration.Rel(0.1, METER_PER_SECOND_2);
320 
321     /** Standard incentive to stay in the current lane. */
322     private static final Acceleration.Rel PREFERREDLANEINCENTIVE = new Acceleration.Rel(0.3, METER_PER_SECOND_2);
323 
324     /** Standard incentive to stay in the current lane. */
325     private static final Acceleration.Rel NONPREFERREDLANEINCENTIVE = new Acceleration.Rel(-0.3, METER_PER_SECOND_2);
326 
327     /** Standard time horizon for route choices. */
328     private static final Time.Rel TIMEHORIZON = new Time.Rel(90, SECOND);
329 
330     /**
331      * Move this GTU to it's current location, then compute (and commit to) the next movement step.
332      * @throws NetworkException on network inconsistency
333      * @throws GTUException when GTU has not lane change model
334      * @throws SimRuntimeException on not being able to reschedule this move() method.
335      * @throws ValueException cannot happen
336      */
337     protected final void move() throws NetworkException, GTUException, SimRuntimeException, ValueException
338     {
339         if (getLongitudinalVelocity().getSI() < 0)
340         {
341             System.out.println("negative velocity: " + this + " " + getLongitudinalVelocity().getSI() + "m/s");
342         }
343 
344         // Quick sanity check
345         if (getSimulator().getSimulatorTime().getTime().getSI() != getNextEvaluationTime().getSI())
346         {
347             throw new Error("move called at wrong time: expected time " + getNextEvaluationTime() + " simulator time is : "
348                     + getSimulator().getSimulatorTime().getTime());
349         }
350         // Only carry out move() if we still have lane(s) to drive on.
351         // Note: a (Sink) trigger can have 'destroyed' us between the previous evaluation step and this one.
352         if (this.lanes.isEmpty())
353         {
354             destroy();
355             return; // Done; do not re-schedule execution of this move method.
356         }
357         Length.Rel maximumForwardHeadway = new Length.Rel(500.0, METER);
358         // TODO 500?
359         Length.Rel maximumReverseHeadway = new Length.Rel(200.0, METER);
360         // TODO 200?
361         Speed.Abs speedLimit = this.getMaximumVelocity();
362         for (Lane lane : this.lanes)
363         {
364             if (lane.getSpeedLimit(getGTUType()).lt(speedLimit))
365             {
366                 speedLimit = lane.getSpeedLimit(getGTUType());
367             }
368         }
369         if (null == this.laneChangeModel)
370         {
371             throw new GTUException("LaneBasedGTUs MUST have a LaneChangeModel");
372         }
373         // TODO Collecting information about nearby traffic should be done in a separate class. This is a very basic
374         // operation in OTS.
375         Collection<HeadwayGTU> sameLaneTraffic = new ArrayList<HeadwayGTU>();
376         HeadwayGTU leader = headway(maximumForwardHeadway);
377         if (null != leader.getOtherGTU())
378         {
379             sameLaneTraffic.add(leader);
380         }
381         HeadwayGTU follower = headway(maximumReverseHeadway);
382         if (null != follower.getOtherGTU())
383         {
384             sameLaneTraffic.add(new HeadwayGTU(follower.getOtherGTU(), -follower.getDistanceSI()));
385         }
386         Time.Abs now = getSimulator().getSimulatorTime().getTime();
387         Collection<HeadwayGTU> leftLaneTraffic =
388                 collectNeighborLaneTraffic(LateralDirectionality.LEFT, now, maximumForwardHeadway, maximumReverseHeadway);
389         Collection<HeadwayGTU> rightLaneTraffic =
390                 collectNeighborLaneTraffic(LateralDirectionality.RIGHT, now, maximumForwardHeadway, maximumReverseHeadway);
391         // FIXME: whether we drive on the right should be stored in some central place.
392         final LateralDirectionality preferred = LateralDirectionality.RIGHT;
393         final Acceleration.Rel defaultLeftLaneIncentive =
394                 LateralDirectionality.LEFT == preferred ? PREFERREDLANEINCENTIVE : NONPREFERREDLANEINCENTIVE;
395         final Acceleration.Rel defaultRightLaneIncentive =
396                 LateralDirectionality.RIGHT == preferred ? PREFERREDLANEINCENTIVE : NONPREFERREDLANEINCENTIVE;
397         DoubleVector.Rel.Dense<AccelerationUnit> defaultLaneIncentives =
398                 new DoubleVector.Rel.Dense<AccelerationUnit>(new double[] { defaultLeftLaneIncentive.getSI(),
399                         STAYINCURRENTLANEINCENTIVE.getSI(), defaultRightLaneIncentive.getSI() }, AccelerationUnit.SI);
400         DoubleVector.Rel.Dense<AccelerationUnit> laneIncentives = laneIncentives(defaultLaneIncentives);
401         LaneMovementStep lcmr =
402                 this.laneChangeModel.computeLaneChangeAndAcceleration(this, sameLaneTraffic, rightLaneTraffic, leftLaneTraffic,
403                         speedLimit, new Acceleration.Rel(laneIncentives.get(preferred == LateralDirectionality.RIGHT ? 2 : 0)),
404                         new Acceleration.Rel(laneIncentives.get(1)),
405                         new Acceleration.Rel(laneIncentives.get(preferred == LateralDirectionality.RIGHT ? 0 : 2)));
406         // TODO detect that a required lane change was blocked and, if it was, do something to find/create a gap.
407         if (lcmr.getGfmr().getAcceleration().getSI() < -9999)
408         {
409             System.out.println("Problem");
410         }
411         // First move this GTU forward (to its current location) as determined in the PREVIOUS move step.
412         // GTUs move based on their fractional position to stay aligned when registered in parallel lanes.
413         // The "oldest" lane of parallel lanes takes preference when updating the fractional position.
414         // So we work from back to front.
415         // TODO Put the "update state to current time" code in a separate method and call that method at the start of
416         // this (move) method.
417         synchronized (this.lock)
418         {
419             for (int i = this.lanes.size() - 1; i >= 0; i--)
420             {
421                 Lane lane = this.lanes.get(i);
422                 this.fractionalLinkPositions.put(lane.getParentLink(),
423                         lane.fraction(position(lane, getReference(), this.nextEvaluationTime)));
424             }
425             // Update the odometer value
426             this.odometer = this.odometer.plus(deltaX(this.nextEvaluationTime));
427             // Compute and set the current speed using the "old" nextEvaluationTime and acceleration
428             this.speed = getLongitudinalVelocity(this.nextEvaluationTime);
429             // Now update last evaluation time
430             this.lastEvaluationTime = this.nextEvaluationTime;
431             // Set the next evaluation time
432             this.nextEvaluationTime = lcmr.getGfmr().getValidUntil();
433             // Set the acceleration (this totally defines the longitudinal motion until the next evaluation time)
434             this.acceleration = lcmr.getGfmr().getAcceleration();
435             // Execute all samplers
436             for (Lane lane : this.lanes)
437             {
438                 lane.sample(this);
439             }
440             // Change onto laterally adjacent lane(s) if the LaneMovementStep indicates a lane change
441             if (lcmr.getLaneChange() != null)
442             {
443                 // TODO make lane changes gradual (not instantaneous; like now)
444                 Collection<Lane> oldLaneSet = new HashSet<Lane>(this.lanes);
445                 Collection<Lane> newLaneSet = new HashSet<Lane>(2);
446                 // Prepare the remove of this GTU from all of the Lanes that it is on and remember the fractional
447                 // position on each one
448                 Map<Lane, Double> oldFractionalPositions = new LinkedHashMap<Lane, Double>();
449                 for (Lane l : this.lanes)
450                 {
451                     oldFractionalPositions.put(l, fractionalPosition(l, getReference(), getLastEvaluationTime()));
452                     this.fractionalLinkPositions.remove(l.getParentLink());
453                     newLaneSet.addAll(this.accessibleAdjacentLanes.get(l).get(lcmr.getLaneChange()));
454                 }
455                 // Add this GTU to the lanes in newLaneSet.
456                 // This could be rewritten to be more efficient.
457                 for (Lane newLane : newLaneSet)
458                 {
459                     Double fractionalPosition = null;
460                     // find ONE lane in oldLaneSet that has l as neighbor
461                     for (Lane oldLane : oldLaneSet)
462                     {
463                         if (this.accessibleAdjacentLanes.get(oldLane).get(lcmr.getLaneChange()).contains(newLane))
464                         {
465                             fractionalPosition = oldFractionalPositions.get(oldLane);
466                             break;
467                         }
468                     }
469                     if (null == fractionalPosition)
470                     {
471                         throw new Error("Program error: Cannot find an oldLane that has newLane " + newLane + " as "
472                                 + lcmr.getLaneChange() + " neighbor");
473                     }
474                     enterLane(newLane, newLane.getLength().multiplyBy(fractionalPosition));
475                 }
476 
477                 // Remove this GTU from all of the Lanes that it is on and remember the fractional position on each
478                 // one
479                 for (Lane l : oldFractionalPositions.keySet())
480                 {
481                     leaveLane(l);
482                 }
483                 // System.out.println("GTU " + this + " changed lanes from: " + oldLaneSet + " to " + replacementLanes);
484                 checkConsistency();
485             }
486             // The GTU is now committed to executed the entire movement stored in the LaneChangeModelResult
487             // Schedule all sensor triggers that are going to happen until the next evaluation time.
488             // Also schedule the registration and unregistration of lanes when the vehicle enters them.
489             scheduleTriggers();
490         }
491         // Re-schedule this move method at the end of the committed time step.
492         getSimulator().scheduleEventAbs(this.getNextEvaluationTime(), this, this, "move", null);
493     }
494 
495     /**
496      * Figure out if the default lane incentives are OK, or override them with values that should keep this GTU on the intended
497      * route.
498      * @param defaultLaneIncentives DoubleVector.Rel.Dense&lt;AccelerationUnit&gt; the three lane incentives for the next left
499      *            adjacent lane, the current lane and the next right adjacent lane
500      * @return DoubleVector.Rel.Dense&lt;AccelerationUnit&gt;; the (possibly adjusted) lane incentives
501      * @throws NetworkException on network inconsistency
502      * @throws ValueException cannot happen
503      */
504     private DoubleVector.Rel.Dense<AccelerationUnit> laneIncentives(
505             final DoubleVector.Rel.Dense<AccelerationUnit> defaultLaneIncentives) throws NetworkException, ValueException
506     {
507         Length.Rel leftSuitability = suitability(LateralDirectionality.LEFT);
508         Length.Rel currentSuitability = suitability(null);
509         Length.Rel rightSuitability = suitability(LateralDirectionality.RIGHT);
510         if (currentSuitability == AbstractLaneBasedRouteNavigator.NOLANECHANGENEEDED)
511         {
512             this.lastLaneChangeDistanceAndDirection =
513                     new LaneChangeUrgeGTUColorer.LaneChangeDistanceAndDirection(currentSuitability, null);
514         }
515         else
516         {
517             this.lastLaneChangeDistanceAndDirection =
518                     new LaneChangeUrgeGTUColorer.LaneChangeDistanceAndDirection(currentSuitability,
519                             rightSuitability.getSI() == 0 ? false : leftSuitability.gt(rightSuitability));
520         }
521         if ((leftSuitability == AbstractLaneBasedRouteNavigator.NOLANECHANGENEEDED || leftSuitability == AbstractLaneBasedRouteNavigator.GETOFFTHISLANENOW)
522                 && currentSuitability == AbstractLaneBasedRouteNavigator.NOLANECHANGENEEDED
523                 && (rightSuitability == AbstractLaneBasedRouteNavigator.NOLANECHANGENEEDED || rightSuitability == AbstractLaneBasedRouteNavigator.GETOFFTHISLANENOW))
524         {
525             return checkLaneDrops(defaultLaneIncentives);
526         }
527         if (currentSuitability == AbstractLaneBasedRouteNavigator.NOLANECHANGENEEDED)
528         {
529             return new DoubleVector.Rel.Dense<AccelerationUnit>(new double[] { acceleration(leftSuitability),
530                     defaultLaneIncentives.get(1).getSI(), acceleration(rightSuitability) }, AccelerationUnit.SI);
531         }
532         return new DoubleVector.Rel.Dense<AccelerationUnit>(new double[] { acceleration(leftSuitability),
533                 acceleration(currentSuitability), acceleration(rightSuitability) }, AccelerationUnit.SI);
534     }
535 
536     /**
537      * Figure out if the default lane incentives are OK, or override them with values that should keep this GTU from running out
538      * of road at an upcoming lane drop.
539      * @param defaultLaneIncentives DoubleVector.Rel.Dense&lt;AccelerationUnit&gt; the three lane incentives for the next left
540      *            adjacent lane, the current lane and the next right adjacent lane
541      * @return DoubleVector.Rel.Dense&lt;AccelerationUnit&gt;; the (possibly adjusted) lane incentives
542      * @throws NetworkException on network inconsistency
543      * @throws ValueException cannot happen
544      */
545     private DoubleVector.Rel.Dense<AccelerationUnit> checkLaneDrops(
546             final DoubleVector.Rel.Dense<AccelerationUnit> defaultLaneIncentives) throws NetworkException, ValueException
547     {
548         Length.Rel leftSuitability = laneDrop(LateralDirectionality.LEFT);
549         Length.Rel currentSuitability = laneDrop(null);
550         Length.Rel rightSuitability = laneDrop(LateralDirectionality.RIGHT);
551         //@formatter:off
552         if ((leftSuitability == AbstractLaneBasedRouteNavigator.NOLANECHANGENEEDED 
553                 || leftSuitability == AbstractLaneBasedRouteNavigator.GETOFFTHISLANENOW)
554             && currentSuitability == AbstractLaneBasedRouteNavigator.NOLANECHANGENEEDED
555             && (rightSuitability == AbstractLaneBasedRouteNavigator.NOLANECHANGENEEDED 
556                 || rightSuitability == AbstractLaneBasedRouteNavigator.GETOFFTHISLANENOW))
557         {
558             return defaultLaneIncentives;
559         }
560         //@formatter:on
561         if (currentSuitability == AbstractLaneBasedRouteNavigator.NOLANECHANGENEEDED)
562         {
563             return new DoubleVector.Rel.Dense<AccelerationUnit>(new double[] { acceleration(leftSuitability),
564                     defaultLaneIncentives.get(1).getSI(), acceleration(rightSuitability) }, AccelerationUnit.SI);
565         }
566         if (currentSuitability.le(leftSuitability))
567         {
568             return new DoubleVector.Rel.Dense<AccelerationUnit>(new double[] { PREFERREDLANEINCENTIVE.getSI(),
569                     NONPREFERREDLANEINCENTIVE.getSI(), AbstractLaneBasedRouteNavigator.GETOFFTHISLANENOW.getSI() },
570                     AccelerationUnit.SI);
571         }
572         if (currentSuitability.le(rightSuitability))
573         {
574             return new DoubleVector.Rel.Dense<AccelerationUnit>(new double[] {
575                     AbstractLaneBasedRouteNavigator.GETOFFTHISLANENOW.getSI(), NONPREFERREDLANEINCENTIVE.getSI(),
576                     PREFERREDLANEINCENTIVE.getSI() }, AccelerationUnit.SI);
577         }
578         return new DoubleVector.Rel.Dense<AccelerationUnit>(new double[] { acceleration(leftSuitability),
579                 acceleration(currentSuitability), acceleration(rightSuitability) }, AccelerationUnit.SI);
580     }
581 
582     /**
583      * Return the distance until the next lane drop in the specified (nearby) lane.
584      * @param direction LateralDirectionality; one of the values <cite>LateralDirectionality.LEFT</cite> (use the left-adjacent
585      *            lane), or <cite>LateralDirectionality.RIGHT</cite> (use the right-adjacent lane), or <cite>null</cite> (use
586      *            the current lane)
587      * @return DoubleScalar.Rel&lt;LengthUnit&gt;; distance until the next lane drop if it occurs within the TIMEHORIZON, or
588      *         LaneBasedRouteNavigator.NOLANECHANGENEEDED if this lane can be followed until the next split junction or until
589      *         beyond the TIMEHORIZON
590      * @throws NetworkException on network inconsistency
591      */
592     private Length.Rel laneDrop(final LateralDirectionality direction) throws NetworkException
593     {
594         Lane lane = null;
595         Length.Rel longitudinalPosition = null;
596         Map<Lane, Length.Rel> positions = positions(RelativePosition.REFERENCE_POSITION);
597         if (null == direction)
598         {
599             for (Lane l : getLanes())
600             {
601                 if (l.getLaneType().isCompatible(getGTUType()))
602                 {
603                     lane = l;
604                 }
605             }
606             if (null == lane)
607             {
608                 throw new NetworkException("GTU " + this + " is not on any compatible lane");
609             }
610             longitudinalPosition = positions.get(lane);
611         }
612         else
613         {
614             lane = positions.keySet().iterator().next();
615             longitudinalPosition = positions.get(lane);
616             lane = bestAccessibleAdjacentLane(lane, direction, longitudinalPosition); // XXX correct??
617         }
618         if (null == lane)
619         {
620             return AbstractLaneBasedRouteNavigator.GETOFFTHISLANENOW;
621         }
622         double remainingLength = lane.getLength().getSI() - longitudinalPosition.getSI();
623         double remainingTimeSI = TIMEHORIZON.getSI() - remainingLength / lane.getSpeedLimit(getGTUType()).getSI();
624         while (remainingTimeSI >= 0)
625         {
626             // TODO: if (lane.getSensors() contains SinkSensor => return LaneBasedRouteNavigator.NOLANECHANGENEEDED
627             int branching = lane.nextLanes(getGTUType()).size();
628             if (branching == 0)
629             {
630                 return new Length.Rel(remainingLength, LengthUnit.SI);
631             }
632             if (branching > 1)
633             {
634                 return AbstractLaneBasedRouteNavigator.NOLANECHANGENEEDED;
635             }
636             lane = lane.nextLanes(getGTUType()).iterator().next();
637             remainingTimeSI -= lane.getLength().getSI() / lane.getSpeedLimit(getGTUType()).getSI();
638             remainingLength += lane.getLength().getSI();
639         }
640         return AbstractLaneBasedRouteNavigator.NOLANECHANGENEEDED;
641     }
642 
643     /**
644      * Compute deceleration needed to stop at a specified distance.
645      * @param stopDistance DoubleScalar.Rel&lt;LengthUnit&gt;; the distance
646      * @return double; the acceleration (deceleration) needed to stop at the specified distance in m/s/s
647      */
648     private double acceleration(final Length.Rel stopDistance)
649     {
650         // What is the deceleration that will bring this GTU to a stop at exactly the suitability distance?
651         // Answer: a = -v^2 / 2 / suitabilityDistance
652         double v = getLongitudinalVelocity().getSI();
653         double a = -v * v / 2 / stopDistance.getSI();
654         return a;
655     }
656 
657     /**
658      * Return the suitability for the current lane, left adjacent lane or right adjacent lane.
659      * @param direction LateralDirectionality; one of the values <cite>null</cite>, <cite>LateralDirectionality.LEFT</cite>, or
660      *            <cite>LateralDirectionality.RIGHT</cite>
661      * @return DoubleScalar.Rel&lt;LengthUnit&gt;; the suitability of the lane for reaching the (next) destination
662      * @throws NetworkException on network inconsistency
663      */
664     private Length.Rel suitability(final LateralDirectionality direction) throws NetworkException
665     {
666         Lane lane = null;
667         Length.Rel longitudinalPosition = null;
668         Map<Lane, Length.Rel> positions = positions(RelativePosition.REFERENCE_POSITION);
669         if (null == direction)
670         {
671             for (Lane l : getLanes())
672             {
673                 if (l.getLaneType().isCompatible(getGTUType()))
674                 {
675                     lane = l;
676                 }
677             }
678             if (null == lane)
679             {
680                 throw new NetworkException("GTU " + this + " is not on any compatible lane");
681             }
682             longitudinalPosition = positions.get(lane);
683         }
684         else
685         {
686             lane = positions.keySet().iterator().next();
687             longitudinalPosition = positions.get(lane);
688             lane = bestAccessibleAdjacentLane(lane, direction, longitudinalPosition); // XXX correct??
689         }
690         if (null == lane)
691         {
692             return AbstractLaneBasedRouteNavigator.GETOFFTHISLANENOW;
693         }
694         return this.routeNavigator.suitability(lane, longitudinalPosition, getGTUType(), TIMEHORIZON);
695     }
696 
697     /**
698      * Verify that all the lanes registered in this GTU have this GTU registered as well and vice versa.
699      */
700     private void checkConsistency()
701     {
702         for (Lane l : this.lanes)
703         {
704             if (!this.fractionalLinkPositions.containsKey(l.getParentLink()))
705             {
706                 System.err.println("GTU " + this + " is in lane " + l
707                         + " but that GTU has no fractional position on the link of that lane");
708             }
709         }
710         for (Link csl : this.fractionalLinkPositions.keySet())
711         {
712             boolean found = false;
713             for (Lane l : this.lanes)
714             {
715                 if (l.getParentLink().equals(csl))
716                 {
717                     found = true;
718                     break;
719                 }
720             }
721             if (!found)
722             {
723                 System.err.println("GTU " + this + " has a fractional position " + this.fractionalLinkPositions.get(csl)
724                         + " on link " + csl + " but this GTU is not on any lane(s) of that link");
725             }
726         }
727     }
728 
729     /**
730      * Schedule the triggers for this GTU that are going to happen until the next evaluation time. Also schedule the
731      * registration and unregistration of lanes when the vehicle enters them, at the exact right time. When the method is
732      * called, the acceleration and velocity of this GTU are set to the right values for the coming timestep.
733      * @throws NetworkException on network inconsistency
734      * @throws SimRuntimeException should never happen
735      * @throws GTUException when a branch is reached where the GTU does not know where to go next
736      */
737     private void scheduleTriggers() throws NetworkException, SimRuntimeException, GTUException
738     {
739         // move the vehicle into any new lanes with the front, and schedule entrance during this time step
740         // and calculate the current position based on the fractional position, because THE POSITION METHOD DOES NOT WORK. IT
741         // CALCULATES THE POSITION BASED ON THE NEWLY CALCULATED ACCELERATION AND VELOCITY AND CAN THEREFORE MAKE AN ERROR.
742         double timestep = this.nextEvaluationTime.getSI() - this.lastEvaluationTime.getSI();
743         double moveSI = getVelocity().getSI() * timestep + 0.5 * getAcceleration().getSI() * timestep * timestep;
744         for (Lane lane : new ArrayList<Lane>(this.lanes)) // use a copy because this.lanes can change
745         {
746             // schedule triggers on this lane
747             double referenceStartSI = this.fractionalLinkPositions.get(lane.getParentLink()) * lane.getLength().getSI();
748             lane.scheduleTriggers(this, referenceStartSI, moveSI);
749 
750             // determine when our FRONT will pass the end of this registered lane.
751             // if the time is earlier than the end of the timestep: schedule the enterLane method.
752             // TODO look if more lanes are entered in one timestep, and continue the algorithm with the remainder of the time...
753             double frontPosSI = referenceStartSI + getFront().getDx().getSI();
754             double moveFrontOnNextLane = moveSI - (lane.getLength().getSI() - frontPosSI);
755             if (frontPosSI < lane.getLength().getSI() && moveFrontOnNextLane > 0)
756             {
757                 Lane nextLane = determineNextLane(lane);
758                 if (!this.lanes.contains(nextLane)) // XXX: this happens -- how can that be?
759                 {
760                     // we have to register the position at the previous timestep to keep calculations consistent.
761                     // And we have to correct for the position of the reference point.
762                     Length.Rel refPosAtLastTimestep =
763                             new Length.Rel(-(lane.getLength().getSI() - frontPosSI) - getFront().getDx().getSI(), LengthUnit.SI); // XXX:
764                                                                                                                                   // should
765                                                                                                                                   // be
766                                                                                                                                   // based
767                                                                                                                                   // on
768                                                                                                                                   // fractional?
769                     enterLane(nextLane, refPosAtLastTimestep);
770                     // schedule any sensor triggers on this lane for the remainder time
771                     nextLane.scheduleTriggers(this, refPosAtLastTimestep.getSI(), moveSI);
772                 }
773             }
774         }
775 
776         // move the vehicle out of any lanes with the back, and schedule exit during this time step
777         for (Lane lane : this.lanes)
778         {
779             // determine when our REAR will pass the end of this registered lane.
780             // if the time is earlier than the end of the timestep: schedule the exitLane method at the END of this timestep
781             // TODO look if more lanes are exited in one timestep, and continue the algorithm with the remainder of the time...
782             double referenceStartSI = this.fractionalLinkPositions.get(lane.getParentLink()) * lane.getLength().getSI();
783             double rearPosSI = referenceStartSI + getRear().getDx().getSI();
784             if (rearPosSI < lane.getLength().getSI() && rearPosSI + moveSI > lane.getLength().getSI())
785             {
786                 getSimulator().scheduleEventRel(new Time.Rel(timestep - Math.ulp(timestep), TimeUnit.SI), this, this,
787                         "leaveLane", new Object[] { lane, new Boolean(true) }); // XXX: should be false?
788             }
789         }
790     }
791 
792     /**
793      * @param lane the lane to find the successor for
794      * @return the next lane for this GTU
795      * @throws NetworkException when no next lane exists or the route branches into multiple next lanes
796      * @throws GTUException when no route could be found or the routeNavigator returns null
797      */
798     private Lane determineNextLane(final Lane lane) throws NetworkException, GTUException
799     {
800         Lane nextLane = null;
801         if (lane.nextLanes(getGTUType()).size() == 0)
802         {
803             throw new NetworkException(this + " - lane " + lane + " does not have a successor");
804         }
805         if (lane.nextLanes(getGTUType()).size() == 1)
806         {
807             nextLane = lane.nextLanes(getGTUType()).iterator().next();
808         }
809         else
810         {
811             if (null == this.getRouteNavigator())
812             {
813                 throw new GTUException(this + " reaches branch but has no route navigator");
814             }
815             Node nextNode = this.routeNavigator.nextNodeToVisit();
816             if (null == nextNode)
817             {
818                 throw new GTUException(this + " reaches branch and the route returns null as nextNodeToVisit");
819             }
820             int continuingLaneCount = 0;
821             for (Lane candidateLane : lane.nextLanes(getGTUType()))
822             {
823                 if (this.lanes.contains(candidateLane))
824                 {
825                     continue; // Already on this lane
826                 }
827                 if (nextNode == candidateLane.getParentLink().getEndNode())
828                 {
829                     nextLane = candidateLane;
830                     continuingLaneCount++;
831                 }
832             }
833             if (continuingLaneCount == 0)
834             {
835                 throw new NetworkException(this + " reached branch and the route specifies a nextNodeToVisit (" + nextNode
836                         + ") that is not a next node " + "at this branch at (" + lane.getParentLink().getEndNode() + ")");
837             }
838             if (continuingLaneCount > 1)
839             {
840                 throw new NetworkException(this
841                         + " reached branch and the route specifies multiple lanes to continue on at this branch ("
842                         + lane.getParentLink().getEndNode() + "). This is not yet supported");
843             }
844         }
845         return nextLane;
846     }
847 
848     /**
849      * Collect relevant traffic in adjacent lanes. Parallel traffic is included with headway equal to Double.NaN.
850      * @param directionality LateralDirectionality; either <cite>LateralDirectionality.LEFT</cite>, or
851      *            <cite>LateralDirectionality.RIGHT</cite>
852      * @param when DoubleScalar.Abs&lt;TimeUnit&gt;; the (current) time
853      * @param maximumForwardHeadway DoubleScalar.Rel&lt;LengthUnit&gt;; the maximum forward search distance
854      * @param maximumReverseHeadway DoubleScalar.Rel&lt;LengthUnit&gt;; the maximum reverse search distance
855      * @return Collection&lt;LaneBasedGTU&gt;;
856      * @throws NetworkException on network inconsistency
857      */
858     private Collection<HeadwayGTU> collectNeighborLaneTraffic(final LateralDirectionality directionality, final Time.Abs when,
859             final Length.Rel maximumForwardHeadway, final Length.Rel maximumReverseHeadway) throws NetworkException
860     {
861         Collection<HeadwayGTU> result = new HashSet<HeadwayGTU>();
862         for (LaneBasedGTU p : parallel(directionality, when))
863         {
864             result.add(new HeadwayGTU(p, Double.NaN));
865         }
866         for (Lane lane : this.lanes)
867         {
868             for (Lane adjacentLane : this.accessibleAdjacentLanes.get(lane).get(directionality))
869             {
870                 HeadwayGTU leader = headway(adjacentLane, maximumForwardHeadway);
871                 if (null != leader.getOtherGTU() && !result.contains(leader))
872                 {
873                     result.add(leader);
874                 }
875                 HeadwayGTU follower = headway(adjacentLane, maximumReverseHeadway);
876                 if (null != follower.getOtherGTU() && !result.contains(follower))
877                 {
878                     result.add(new HeadwayGTU(follower.getOtherGTU(), -follower.getDistanceSI()));
879                 }
880             }
881         }
882         return result;
883     }
884 
885     /** {@inheritDoc} */
886     @Override
887     public final Map<Lane, Length.Rel> positions(final RelativePosition relativePosition) throws NetworkException
888     {
889         return positions(relativePosition, getSimulator().getSimulatorTime().getTime());
890     }
891 
892     /** {@inheritDoc} */
893     @Override
894     public final Map<Lane, Length.Rel> positions(final RelativePosition relativePosition, final Time.Abs when)
895             throws NetworkException
896     {
897         Map<Lane, Length.Rel> positions = new LinkedHashMap<>();
898         for (Lane lane : this.lanes)
899         {
900             positions.put(lane, position(lane, relativePosition, when));
901         }
902         return positions;
903     }
904 
905     /** {@inheritDoc} */
906     @Override
907     public final Length.Rel position(final Lane lane, final RelativePosition relativePosition) throws NetworkException
908     {
909         return position(lane, relativePosition, getSimulator().getSimulatorTime().getTime());
910     }
911 
912     /** {@inheritDoc} */
913     public final Length.Rel projectedPosition(final Lane projectionLane, final RelativePosition relativePosition,
914             final Time.Abs when) throws NetworkException
915     {
916         CrossSectionLink link = projectionLane.getParentLink();
917         for (CrossSectionElement cse : link.getCrossSectionElementList())
918         {
919             if (cse instanceof Lane)
920             {
921                 Lane cseLane = (Lane) cse;
922                 if (this.lanes.contains(cseLane))
923                 {
924                     double fractionalPosition = fractionalPosition(cseLane, relativePosition, when);
925                     return new Length.Rel(projectionLane.getLength().getSI() * fractionalPosition, LengthUnit.SI);
926                 }
927             }
928         }
929         throw new NetworkException("GTU " + this + " is not on any lane of Link " + link);
930     }
931 
932     /** {@inheritDoc} */
933     @Override
934     public final Length.Rel position(final Lane lane, final RelativePosition relativePosition, final Time.Abs when)
935             throws NetworkException
936     {
937         if (null == lane)
938         {
939             throw new NetworkException("lane is null");
940         }
941         synchronized (this.lock)
942         {
943             if (!this.lanes.contains(lane))
944             {
945                 throw new NetworkException("position() : GTU " + toString() + " is not on lane " + lane);
946             }
947             if (!this.fractionalLinkPositions.containsKey(lane.getParentLink()))
948             {
949                 // DO NOT USE toString() here, as it will cause an endless loop...
950                 throw new NetworkException("GTU " + getId() + " does not have a fractional position on " + lane.toString());
951             }
952             Length.Rel longitudinalPosition = lane.position(this.fractionalLinkPositions.get(lane.getParentLink()));
953             if (longitudinalPosition == null)
954             {
955                 // According to FindBugs; this cannot happen; PK is unsure whether FindBugs is correct.
956                 throw new NetworkException("position(): GTU " + toString() + " no position for lane " + lane);
957             }
958             Length.Rel loc = longitudinalPosition.plus(deltaX(when)).plus(relativePosition.getDx());
959             if (Double.isNaN(loc.getSI()))
960             {
961                 System.out.println("loc is NaN");
962             }
963             return loc;
964         }
965     }
966 
967     /** {@inheritDoc} */
968     @Override
969     public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition) throws NetworkException
970     {
971         return fractionalPositions(relativePosition, getSimulator().getSimulatorTime().getTime());
972     }
973 
974     /** {@inheritDoc} */
975     @Override
976     public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition, final Time.Abs when)
977             throws NetworkException
978     {
979         Map<Lane, Double> positions = new LinkedHashMap<>();
980         for (Lane lane : this.lanes)
981         {
982             positions.put(lane, fractionalPosition(lane, relativePosition, when));
983         }
984         return positions;
985     }
986 
987     /** {@inheritDoc} */
988     @Override
989     public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition, final Time.Abs when)
990             throws NetworkException
991     {
992         return position(lane, relativePosition, when).getSI() / lane.getLength().getSI();
993     }
994 
995     /** {@inheritDoc} */
996     @Override
997     public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition) throws NetworkException
998     {
999         return position(lane, relativePosition).getSI() / lane.getLength().getSI();
1000     }
1001 
1002     /**
1003      * Calculate the minimum headway, possibly on subsequent lanes, in forward direction.
1004      * @param lane the lane where we are looking right now
1005      * @param lanePositionSI from which position on this lane do we start measuring? This is the current position of the GTU
1006      *            when we measure in the lane where the original GTU is positioned, and 0.0 for each subsequent lane
1007      * @param cumDistanceSI the distance we have already covered searching on previous lanes
1008      * @param maxDistanceSI the maximum distance to look for in SI units; stays the same in subsequent calls
1009      * @param when the current or future time for which to calculate the headway
1010      * @return the headway in SI units when we have found the GTU, or a null GTU with a distance of Double.MAX_VALUE meters when
1011      *         no other GTU could not be found within maxDistanceSI meters
1012      * @throws NetworkException when there is a problem with the geometry of the network
1013      */
1014     private HeadwayGTU headwayRecursiveForwardSI(final Lane lane, final double lanePositionSI, final double cumDistanceSI,
1015             final double maxDistanceSI, final Time.Abs when) throws NetworkException
1016     {
1017         LaneBasedGTU otherGTU = lane.getGtuAfter(new Length.Rel(lanePositionSI, METER), RelativePosition.REAR, when);
1018         if (otherGTU != null)
1019         {
1020             double distanceM = cumDistanceSI + otherGTU.position(lane, otherGTU.getRear(), when).getSI() - lanePositionSI;
1021             if (distanceM > 0 && distanceM <= maxDistanceSI)
1022             {
1023                 return new HeadwayGTU(otherGTU, distanceM);
1024             }
1025             return new HeadwayGTU(null, Double.MAX_VALUE);
1026         }
1027 
1028         // Continue search on successor lanes.
1029         if (cumDistanceSI + lane.getLength().getSI() - lanePositionSI < maxDistanceSI)
1030         {
1031             // is there a successor link?
1032             if (lane.nextLanes(getGTUType()).size() > 0)
1033             {
1034                 HeadwayGTU foundMaxGTUDistanceSI = new HeadwayGTU(null, Double.MAX_VALUE);
1035                 for (Lane nextLane : lane.nextLanes(getGTUType()))
1036                 {
1037                     // TODO Only follow links on the Route if there is a "real" Route
1038                     // TODO use new functions of the Navigator
1039                     // if (this.getRoute() == null || this.getRoute().size() == 0 /* XXX STUB dummy route */
1040                     // || (this.routeNavigator.getRoute().containsLink((Link) lane.getParentLink())))
1041                     {
1042                         double traveledDistanceSI = cumDistanceSI + lane.getLength().getSI() - lanePositionSI;
1043                         HeadwayGTU closest = headwayRecursiveForwardSI(nextLane, 0.0, traveledDistanceSI, maxDistanceSI, when);
1044                         if (closest.getDistanceSI() < maxDistanceSI
1045                                 && closest.getDistanceSI() < foundMaxGTUDistanceSI.getDistanceSI())
1046                         {
1047                             foundMaxGTUDistanceSI = closest;
1048                         }
1049                     }
1050                 }
1051                 return foundMaxGTUDistanceSI;
1052             }
1053         }
1054 
1055         // No other GTU was not on one of the current lanes or their successors.
1056         return new HeadwayGTU(null, Double.MAX_VALUE);
1057     }
1058 
1059     /**
1060      * Calculate the minimum headway, possibly on subsequent lanes, in backward direction (so between our back, and the other
1061      * GTU's front). Note: this method returns a POSITIVE number.
1062      * @param lane the lane where we are looking right now
1063      * @param lanePositionSI from which position on this lane do we start measuring? This is the current position of the rear of
1064      *            the GTU when we measure in the lane where the original GTU is positioned, and lane.getLength() for each
1065      *            subsequent lane.
1066      * @param cumDistanceSI the distance we have already covered searching on previous lanes. Note: This is a POSITIVE number.
1067      * @param maxDistanceSI the maximum distance to look for in SI units; stays the same in subsequent calls. Note: this is a
1068      *            POSITIVE number.
1069      * @param when the current or future time for which to calculate the headway
1070      * @return the headway in SI units when we have found the GTU, or a null GTU with a distance of Double.MAX_VALUE meters when
1071      *         no other GTU could not be found within maxDistanceSI meters
1072      * @throws NetworkException when there is a problem with the geometry of the network
1073      */
1074     private HeadwayGTU headwayRecursiveBackwardSI(final Lane lane, final double lanePositionSI, final double cumDistanceSI,
1075             final double maxDistanceSI, final Time.Abs when) throws NetworkException
1076     {
1077         LaneBasedGTU otherGTU = lane.getGtuBefore(new Length.Rel(lanePositionSI, METER), RelativePosition.FRONT, when);
1078         if (otherGTU != null)
1079         {
1080             double distanceM = cumDistanceSI + lanePositionSI - otherGTU.position(lane, otherGTU.getFront(), when).getSI();
1081             if (distanceM > 0 && distanceM <= maxDistanceSI)
1082             {
1083                 return new HeadwayGTU(otherGTU, distanceM);
1084             }
1085             return new HeadwayGTU(null, Double.MAX_VALUE);
1086         }
1087 
1088         // Continue search on predecessor lanes.
1089         if (cumDistanceSI + lanePositionSI < maxDistanceSI)
1090         {
1091             // is there a predecessor link?
1092             if (lane.prevLanes(getGTUType()).size() > 0)
1093             {
1094                 HeadwayGTU foundMaxGTUDistanceSI = new HeadwayGTU(null, Double.MAX_VALUE);
1095                 for (Lane prevLane : lane.prevLanes(getGTUType()))
1096                 {
1097                     // What is behind us is INDEPENDENT of the followed route!
1098                     double traveledDistanceSI = cumDistanceSI + lanePositionSI;
1099                     HeadwayGTU closest =
1100                             headwayRecursiveBackwardSI(prevLane, prevLane.getLength().getSI(), traveledDistanceSI,
1101                                     maxDistanceSI, when);
1102                     if (closest.getDistanceSI() < maxDistanceSI
1103                             && closest.getDistanceSI() < foundMaxGTUDistanceSI.getDistanceSI())
1104                     {
1105                         foundMaxGTUDistanceSI = closest;
1106                     }
1107                 }
1108                 return foundMaxGTUDistanceSI;
1109             }
1110         }
1111 
1112         // No other GTU was not on one of the current lanes or their successors.
1113         return new HeadwayGTU(null, Double.MAX_VALUE);
1114     }
1115 
1116     /**
1117      * @param maxDistanceSI the maximum distance to look for in SI units
1118      * @return the nearest GTU and the net headway to this GTU in SI units when we have found the GTU, or a null GTU with a
1119      *         distance of Double.MAX_VALUE meters when no other GTU could not be found within maxDistanceSI meters
1120      * @throws NetworkException when there is a problem with the geometry of the network
1121      */
1122     private HeadwayGTU headwayGTUSI(final double maxDistanceSI) throws NetworkException
1123     {
1124         Time.Abs when = getSimulator().getSimulatorTime().getTime();
1125         HeadwayGTU foundMaxGTUDistanceSI = new HeadwayGTU(null, Double.MAX_VALUE);
1126         // search for the closest GTU on all current lanes we are registered on.
1127         if (maxDistanceSI > 0.0)
1128         {
1129             // look forward.
1130             for (Lane lane : positions(getFront()).keySet())
1131             {
1132                 HeadwayGTU closest =
1133                         headwayRecursiveForwardSI(lane, this.position(lane, this.getFront(), when).getSI(), 0.0, maxDistanceSI,
1134                                 when);
1135                 if (closest.getDistanceSI() < maxDistanceSI && closest.getDistanceSI() < foundMaxGTUDistanceSI.getDistanceSI())
1136                 {
1137                     foundMaxGTUDistanceSI = closest;
1138                 }
1139             }
1140         }
1141         else
1142         {
1143             // look backward.
1144             for (Lane lane : positions(getRear()).keySet())
1145             {
1146                 HeadwayGTU closest =
1147                         headwayRecursiveBackwardSI(lane, this.position(lane, this.getRear(), when).getSI(), 0.0,
1148                                 -maxDistanceSI, when);
1149                 if (closest.getDistanceSI() < -maxDistanceSI && closest.getDistanceSI() < foundMaxGTUDistanceSI.getDistanceSI())
1150                 {
1151                     foundMaxGTUDistanceSI = closest;
1152                 }
1153             }
1154         }
1155         return foundMaxGTUDistanceSI;
1156     }
1157 
1158     /** {@inheritDoc} */
1159     @Override
1160     public final HeadwayGTU headway(final Length.Rel maxDistance) throws NetworkException
1161     {
1162         return headwayGTUSI(maxDistance.getSI());
1163     }
1164 
1165     /** {@inheritDoc} */
1166     @Override
1167     public final HeadwayGTU headway(final Lane lane, final Length.Rel maxDistance) throws NetworkException
1168     {
1169         Time.Abs when = getSimulator().getSimulatorTime().getTime();
1170         if (maxDistance.getSI() > 0.0)
1171         {
1172             return headwayRecursiveForwardSI(lane, this.projectedPosition(lane, this.getFront(), when).getSI(), 0.0,
1173                     maxDistance.getSI(), when);
1174         }
1175         else
1176         {
1177             return headwayRecursiveBackwardSI(lane, this.projectedPosition(lane, this.getRear(), when).getSI(), 0.0,
1178                     -maxDistance.getSI(), when);
1179         }
1180     }
1181 
1182     /**
1183      * Calculate the headway to a GTU, possibly on subsequent lanes, in forward direction.
1184      * @param lane the lane where we are looking right now
1185      * @param lanePositionSI from which position on this lane do we start measuring? This is the current position of the (front
1186      *            of the) GTU when we measure in the lane where the original GTU is positioned, and 0.0 for each subsequent lane
1187      * @param otherGTU the GTU to which the headway must be returned
1188      * @param cumDistanceSI the distance we have already covered searching on previous lanes
1189      * @param maxDistanceSI the maximum distance to look for; stays the same in subsequent calls
1190      * @param when the future time for which to calculate the headway
1191      * @return the headway in SI units when we have found the GTU, or Double.MAX_VALUE when the otherGTU could not be found
1192      *         within maxDistanceSI
1193      * @throws NetworkException when there is a problem with the geometry of the network
1194      */
1195     private double headwayRecursiveForwardSI(final Lane lane, final double lanePositionSI, final LaneBasedGTU otherGTU,
1196             final double cumDistanceSI, final double maxDistanceSI, final Time.Abs when) throws NetworkException
1197     {
1198         if (lane.getGtuList().contains(otherGTU))
1199         {
1200             double distanceM = cumDistanceSI + otherGTU.position(lane, otherGTU.getRear(), when).getSI() - lanePositionSI;
1201             if (distanceM > 0 && distanceM <= maxDistanceSI)
1202             {
1203                 return distanceM;
1204             }
1205             return Double.MAX_VALUE;
1206         }
1207 
1208         // Continue search on successor lanes.
1209         if (cumDistanceSI + lane.getLength().getSI() - lanePositionSI < maxDistanceSI)
1210         {
1211             // is there a successor link?
1212             for (Lane nextLane : lane.nextLanes(getGTUType()))
1213             {
1214                 // TODO Only follow links on the Route if there is a Route
1215                 // if (this.getRoute() == null || this.getRoute().size() == 0) /* XXX STUB dummy route */
1216                 // || this.routeNavigator.getRoute().containsLink((Link) lane.getParentLink()))
1217                 {
1218                     double traveledDistanceSI = cumDistanceSI + lane.getLength().getSI() - lanePositionSI;
1219                     double headwaySuccessor =
1220                             headwayRecursiveForwardSI(nextLane, 0.0, otherGTU, traveledDistanceSI, maxDistanceSI, when);
1221                     if (headwaySuccessor < maxDistanceSI)
1222                     {
1223                         return headwaySuccessor;
1224                     }
1225                 }
1226             }
1227         }
1228 
1229         // The otherGTU was not on one of the current lanes or their successors.
1230         return Double.MAX_VALUE;
1231     }
1232 
1233     /**
1234      * Calculate the headway to a GTU, possibly on subsequent lanes, in backward direction.
1235      * @param lane the lane where we are looking right now
1236      * @param lanePositionSI from which position on this lane do we start measuring? This is the current position of the (back
1237      *            of) the GTU when we measure in the lane where the original GTU is positioned, and the length of the lane for
1238      *            each subsequent lane
1239      * @param otherGTU the GTU to which the headway must be returned
1240      * @param cumDistanceSI the distance we have already covered searching on previous lanes, as a POSITIVE number
1241      * @param maxDistanceSI the maximum distance to look for; stays the same in subsequent calls, as a POSITIVE number
1242      * @param when the future time for which to calculate the headway
1243      * @return the headway in SI units when we have found the GTU, or Double.MAX_VALUE when the otherGTU could not be found
1244      *         within maxDistanceSI
1245      * @throws NetworkException when there is a problem with the geometry of the network
1246      */
1247     private double headwayRecursiveBackwardSI(final Lane lane, final double lanePositionSI, final LaneBasedGTU otherGTU,
1248             final double cumDistanceSI, final double maxDistanceSI, final Time.Abs when) throws NetworkException
1249     {
1250         if (lane.getGtuList().contains(otherGTU))
1251         {
1252             double distanceM = cumDistanceSI + lanePositionSI - otherGTU.position(lane, otherGTU.getFront(), when).getSI();
1253             if (distanceM > 0 && distanceM <= maxDistanceSI)
1254             {
1255                 return distanceM;
1256             }
1257             return Double.MAX_VALUE;
1258         }
1259 
1260         // Continue search on predecessor lanes.
1261         if (cumDistanceSI + lanePositionSI < maxDistanceSI)
1262         {
1263             // is there a predecessor link?
1264             for (Lane prevLane : lane.prevLanes(getGTUType()))
1265             {
1266                 // Routes are NOT IMPORTANT when we look backward.
1267                 double traveledDistanceSI = cumDistanceSI + lanePositionSI;
1268                 // PK: This looks like a bug; replacement code below this comment.
1269                 // double headwayPredecessor =
1270                 // headwayRecursiveForwardSI(prevLane, prevLane.getLength().getSI(), otherGTU,
1271                 // traveledDistanceSI, maxDistanceSI, when);
1272                 double headwayPredecessor =
1273                         headwayRecursiveBackwardSI(prevLane, prevLane.getLength().getSI(), otherGTU, traveledDistanceSI,
1274                                 maxDistanceSI, when);
1275                 if (headwayPredecessor < maxDistanceSI)
1276                 {
1277                     return headwayPredecessor;
1278                 }
1279             }
1280         }
1281 
1282         // The otherGTU was not on one of the current lanes or their successors.
1283         return Double.MAX_VALUE;
1284     }
1285 
1286     /**
1287      * Build a set of Lanes that is adjacent to the given lane that this GTU can enter, for both lateral directions.
1288      * @param lane Lane; the lane for which to add the accessible lanes.
1289      */
1290     private void addAccessibleAdjacentLanes(final Lane lane)
1291     {
1292         EnumMap<LateralDirectionality, Set<Lane>> adjacentMap = new EnumMap<>(LateralDirectionality.class);
1293         for (LateralDirectionality lateralDirection : LateralDirectionality.values())
1294         {
1295             Set<Lane> adjacentLanes = new HashSet<Lane>(1);
1296             adjacentLanes.addAll(lane.accessibleAdjacentLanes(lateralDirection, getGTUType()));
1297             adjacentMap.put(lateralDirection, adjacentLanes);
1298         }
1299         this.accessibleAdjacentLanes.put(lane, adjacentMap);
1300     }
1301 
1302     /**
1303      * Remove the set of adjacent lanes when we leave the lane.
1304      * @param lane Lane; the lane for which to remove the accessible lanes.
1305      */
1306     private void removeAccessibleAdjacentLanes(final Lane lane)
1307     {
1308         this.accessibleAdjacentLanes.remove(lane);
1309     }
1310 
1311     /** {@inheritDoc} */
1312     @Override
1313     public final Lane bestAccessibleAdjacentLane(final Lane currentLane, final LateralDirectionality lateralDirection,
1314             final Length.Rel longitudinalPosition)
1315     {
1316         Set<Lane> candidates = this.accessibleAdjacentLanes.get(currentLane).get(lateralDirection);
1317         if (candidates.isEmpty())
1318         {
1319             return null; // There is no adjacent Lane that this GTU type can cross into
1320         }
1321         if (candidates.size() == 1)
1322         {
1323             return candidates.iterator().next(); // There is exactly one adjacent Lane that this GTU type can cross into
1324         }
1325         // There are several candidates; find the one that is widest at the beginning.
1326         Lane bestLane = null;
1327         double widthM = -1.0;
1328         for (Lane lane : candidates)
1329         {
1330             if (lane.getWidth(longitudinalPosition).getSI() > widthM)
1331             {
1332                 widthM = lane.getWidth(longitudinalPosition).getSI();
1333                 bestLane = lane;
1334             }
1335         }
1336         return bestLane;
1337     }
1338 
1339     /** {@inheritDoc} */
1340     @Override
1341     public final Set<LaneBasedGTU> parallel(final Lane lane, final Time.Abs when) throws NetworkException
1342     {
1343         Set<LaneBasedGTU> gtuSet = new LinkedHashSet<LaneBasedGTU>();
1344         for (Lane l : this.lanes)
1345         {
1346             // only take lanes that we can compare based on a shared design line
1347             if (l.getParentLink().equals(lane.getParentLink()))
1348             {
1349                 // compare based on fractional positions.
1350                 double posFractionFront = Math.max(0.0, this.fractionalPosition(l, getFront(), when));
1351                 double posFractionRear = Math.min(1.0, this.fractionalPosition(l, getRear(), when));
1352                 for (LaneBasedGTU gtu : lane.getGtuList())
1353                 {
1354                     if (!gtu.equals(this))
1355                     {
1356                         double gtuFractionFront = Math.max(0.0, gtu.fractionalPosition(lane, gtu.getFront(), when));
1357                         double gtuFractionRear = Math.min(1.0, gtu.fractionalPosition(lane, gtu.getRear(), when));
1358                         // TODO is this formula for parallel() okay?
1359                         // TODO should it not be extended with several || clauses?
1360                         if (gtuFractionFront >= posFractionRear && gtuFractionRear <= posFractionFront)
1361                         {
1362                             gtuSet.add(gtu);
1363                         }
1364                     }
1365                 }
1366             }
1367         }
1368         return gtuSet;
1369     }
1370 
1371     /** {@inheritDoc} */
1372     @Override
1373     public final Set<LaneBasedGTU> parallel(final LateralDirectionality lateralDirection, final Time.Abs when)
1374             throws NetworkException
1375     {
1376         Set<LaneBasedGTU> gtuSet = new LinkedHashSet<LaneBasedGTU>();
1377         for (Lane lane : this.lanes)
1378         {
1379             for (Lane adjacentLane : this.accessibleAdjacentLanes.get(lane).get(lateralDirection))
1380             {
1381                 gtuSet.addAll(parallel(adjacentLane, when));
1382             }
1383         }
1384         return gtuSet;
1385     }
1386 
1387     /** {@inheritDoc} */
1388     public final Time.Abs timeAtDistance(final Length.Rel distance)
1389     {
1390         Double result = solveTimeForDistance(distance);
1391         if (null == result)
1392         {
1393             return null;
1394         }
1395         return new Time.Abs(this.lastEvaluationTime.getSI() + result, SECOND);
1396     }
1397 
1398     /** {@inheritDoc} */
1399     public final Time.Rel deltaTimeForDistance(final Length.Rel distance)
1400     {
1401         Double result = solveTimeForDistance(distance);
1402         if (null == result)
1403         {
1404             return null;
1405         }
1406         return new Time.Rel(result, SECOND);
1407     }
1408 
1409     /** {@inheritDoc} */
1410     @Override
1411     @SuppressWarnings("checkstyle:designforextension")
1412     public void destroy()
1413     {
1414         synchronized (this.lock)
1415         {
1416             while (!this.lanes.isEmpty())
1417             {
1418                 Lane lane = this.lanes.get(0);
1419                 leaveLane(lane, true);
1420             }
1421         }
1422     }
1423 
1424     /**
1425      * Determine longitudinal displacement.
1426      * @param when DoubleScalar.Abs&lt;TimeUnit&gt;; the current time
1427      * @return DoubleScalar.Rel&lt;LengthUnit&gt;; the displacement since last move evaluation
1428      */
1429     private Length.Rel deltaX(final Time.Abs when)
1430     {
1431         Time.Rel dT = when.minus(this.lastEvaluationTime);
1432         return this.speed.toRel().multiplyBy(dT).plus(this.getAcceleration().toRel().multiplyBy(dT).multiplyBy(dT).divideBy(2));
1433         /*-
1434         return Calc.speedTimesTime(this.speed, dT).plus(
1435             Calc.accelerationTimesTimeSquaredDiv2(this.getAcceleration(), dT));
1436          */
1437     }
1438 
1439     /**
1440      * Determine show long it will take for this GTU to cover the specified distance (both time and distance since the last
1441      * evaluation time).
1442      * @param distance DoubleScalar.Rel&lt;LengthUnit&gt;; the distance
1443      * @return Double; the relative time, or null when this GTU stops before covering the specified distance
1444      */
1445     private Double solveTimeForDistance(final Length.Rel distance)
1446     {
1447         return solveTimeForDistanceSI(distance.getSI());
1448     }
1449 
1450     /**
1451      * Determine show long it will take for this GTU to cover the specified distance (both time and distance since the last
1452      * evaluation time).
1453      * @param distanceSI double; the distance in SI units
1454      * @return Double; the relative time, or null when this GTU stops before covering the specified distance
1455      */
1456     private Double solveTimeForDistanceSI(final double distanceSI)
1457     {
1458         /*
1459          * Currently (!) a (Lane based) GTU commits to a constant acceleration until the next evaluation time. When/If that is
1460          * changed, this method will have to be re-written.
1461          */
1462         double c = -distanceSI;
1463         double a = this.acceleration.getSI() / 2;
1464         double b = this.speed.getSI();
1465         if (Math.abs(a) < 0.001)
1466         {
1467             if (b > 0)
1468             {
1469                 return -c / b;
1470             }
1471             return null;
1472         }
1473         // Solve a * t^2 + b * t + c = 0
1474         double discriminant = b * b - 4 * a * c;
1475         if (discriminant < 0)
1476         {
1477             return null;
1478         }
1479         // The solutions are (-b +/- sqrt(discriminant)) / 2 / a
1480         double solution1 = (-b - Math.sqrt(discriminant)) / (2 * a);
1481         double solution2 = (-b + Math.sqrt(discriminant)) / (2 * a);
1482         if (solution1 < 0 && solution2 < 0)
1483         {
1484             return null;
1485         }
1486         if (solution1 < 0)
1487         {
1488             return solution2;
1489         }
1490         if (solution2 < 0)
1491         {
1492             return solution1;
1493         }
1494         // Both are >= 0; return the smallest one
1495         if (solution1 < solution2)
1496         {
1497             return solution1;
1498         }
1499         return solution2;
1500     }
1501 
1502     /**
1503      * Retrieve the GTUFollowingModel of this GTU.
1504      * @return GTUFollowingModel
1505      */
1506     public final GTUFollowingModel getGTUFollowingModel()
1507     {
1508         return this.gtuFollowingModel;
1509     }
1510 
1511     /** {@inheritDoc} */
1512     @Override
1513     public final DirectedPoint getLocation()
1514     {
1515         synchronized (this.lock)
1516         {
1517             try
1518             {
1519                 if (this.lanes.size() == 0)
1520                 {
1521                     // This happens temporarily when a GTU is moved to another Lane
1522                     return new DirectedPoint(0, 0, 0);
1523                 }
1524                 Lane lane = this.lanes.get(0);
1525                 DirectedPoint location = lane.getCenterLine().getLocationExtended(position(lane, getReference()));
1526                 location.z += 0.01; // raise the location a bit above the lane
1527                 return location;
1528             }
1529             catch (NetworkException exception)
1530             {
1531                 return null;
1532             }
1533         }
1534     }
1535 
1536     /** {@inheritDoc} */
1537     @Override
1538     public final Bounds getBounds()
1539     {
1540         double dx = 0.5 * getLength().doubleValue();
1541         double dy = 0.5 * getWidth().doubleValue();
1542         return new BoundingBox(new Point3d(-dx, -dy, 0.0), new Point3d(dx, dy, 0.0));
1543     }
1544 
1545     public LaneChangeUrgeGTUColorer.LaneChangeDistanceAndDirection getLaneChangeDistanceAndDirection()
1546     {
1547         return this.lastLaneChangeDistanceAndDirection;
1548     }
1549 
1550     /**
1551      * Description of Car at specified time.
1552      * @param lane the position on this lane will be returned.
1553      * @param when DoubleScalarAbs&lt;TimeUnit&gt;; the time
1554      * @return String; description of this Car at the specified time
1555      */
1556     public final String toString(final Lane lane, final Time.Abs when)
1557     {
1558         double pos;
1559         try
1560         {
1561             pos = this.position(lane, getFront(), when).getSI();
1562         }
1563         catch (NetworkException exception)
1564         {
1565             pos = Double.NaN;
1566         }
1567         // A space in the format after the % becomes a space for positive numbers or a minus for negative numbers
1568         return String.format("Car %5d lastEval %6.1fs, nextEval %6.1fs, % 9.3fm, v % 6.3fm/s, a % 6.3fm/s^2", getId(),
1569                 this.lastEvaluationTime.getSI(), getNextEvaluationTime().getSI(), pos, this.getLongitudinalVelocity(when)
1570                         .getSI(), this.getAcceleration(when).getSI());
1571     }
1572 
1573 }