View Javadoc
1   package org.opentrafficsim.road.gtu.lane.tactical;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.HashMap;
6   import java.util.List;
7   import java.util.Map;
8   
9   import org.djunits.unit.AccelerationUnit;
10  import org.djunits.unit.LengthUnit;
11  import org.djunits.unit.TimeUnit;
12  import org.djunits.value.StorageType;
13  import org.djunits.value.ValueException;
14  import org.djunits.value.vdouble.scalar.Acceleration;
15  import org.djunits.value.vdouble.scalar.Duration;
16  import org.djunits.value.vdouble.scalar.Length;
17  import org.djunits.value.vdouble.scalar.Speed;
18  import org.djunits.value.vdouble.scalar.Time;
19  import org.djunits.value.vdouble.vector.AccelerationVector;
20  import org.opentrafficsim.core.geometry.OTSLine3D;
21  import org.opentrafficsim.core.gtu.GTUException;
22  import org.opentrafficsim.core.gtu.GTUType;
23  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
24  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
25  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
26  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan.Segment;
27  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
28  import org.opentrafficsim.core.network.LateralDirectionality;
29  import org.opentrafficsim.core.network.Link;
30  import org.opentrafficsim.core.network.NetworkException;
31  import org.opentrafficsim.core.network.Node;
32  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
33  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
34  import org.opentrafficsim.road.gtu.lane.perception.categories.DefaultSimplePerception;
35  import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
36  import org.opentrafficsim.road.gtu.lane.tactical.following.GTUFollowingModelOld;
37  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.LaneChangeModel;
38  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.LaneMovementStep;
39  import org.opentrafficsim.road.network.lane.CrossSectionElement;
40  import org.opentrafficsim.road.network.lane.CrossSectionLink;
41  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
42  import org.opentrafficsim.road.network.lane.Lane;
43  import org.opentrafficsim.road.network.lane.object.sensor.Sensor;
44  import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor;
45  
46  import nl.tudelft.simulation.language.d3.DirectedPoint;
47  
48  /**
49   * Lane-based tactical planner that implements car following and lane change behavior. This lane-based tactical planner makes
50   * decisions based on headway (GTU following model) and lane change (Lane Change model), and will generate an operational plan
51   * for the GTU. It can ask the strategic planner for assistance on the route to take when the network splits.
52   * <p>
53   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
54   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
55   * </p>
56   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
57   * initial version Nov 25, 2015 <br>
58   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
59   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
60   */
61  public class LaneBasedCFLCTacticalPlanner extends AbstractLaneBasedTacticalPlanner
62  {
63      /** */
64      private static final long serialVersionUID = 20151125L;
65  
66      /** Standard incentive to stay in the current lane. */
67      private static final Acceleration STAYINCURRENTLANEINCENTIVE = new Acceleration(0.1, AccelerationUnit.METER_PER_SECOND_2);
68  
69      /** Standard incentive to stay in the current lane. */
70      private static final Acceleration PREFERREDLANEINCENTIVE = new Acceleration(0.3, AccelerationUnit.METER_PER_SECOND_2);
71  
72      /** Standard incentive to stay in the current lane. */
73      private static final Acceleration NONPREFERREDLANEINCENTIVE = new Acceleration(-0.3, AccelerationUnit.METER_PER_SECOND_2);
74  
75      /** Return value of suitability when no lane change is required within the time horizon. */
76      public static final Length NOLANECHANGENEEDED = new Length(Double.MAX_VALUE, LengthUnit.SI);
77  
78      /** Return value of suitability when a lane change is required <i>right now</i>. */
79      public static final Length GETOFFTHISLANENOW = Length.ZERO;
80  
81      /** Standard time horizon for route choices. */
82      private static final Duration TIMEHORIZON = new Duration(90, TimeUnit.SECOND);
83  
84      /** Lane change model for this tactical planner. */
85      private LaneChangeModel laneChangeModel;
86  
87      /**
88       * Instantiated a tactical planner with GTU following and lane change behavior.
89       * @param carFollowingModel Car-following model.
90       * @param laneChangeModel Lane change model.
91       * @param gtu GTU
92       */
93      public LaneBasedCFLCTacticalPlanner(final GTUFollowingModelOld carFollowingModel, final LaneChangeModel laneChangeModel,
94              final LaneBasedGTU gtu)
95      {
96          super(carFollowingModel, gtu);
97          this.laneChangeModel = laneChangeModel;
98      }
99  
100     /** {@inheritDoc} */
101     @Override
102     public final OperationalPlan generateOperationalPlan(final Time startTime, final DirectedPoint locationAtStartTime)
103             throws OperationalPlanException, NetworkException, GTUException, ParameterException
104     {
105         try
106         {
107             // define some basic variables
108             LaneBasedGTU laneBasedGTU = getGtu();
109             LanePerception perception = getPerception();
110 
111             // if the GTU's maximum speed is zero (block), generate a stand still plan for one second
112             if (laneBasedGTU.getMaximumSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
113             {
114                 return new OperationalPlan(getGtu(), locationAtStartTime, startTime, new Duration(1.0, TimeUnit.SECOND));
115             }
116 
117             // perceive every time step... This is the 'classical' way of tactical planning.
118             // NOTE: delete this if perception takes place independent of the tactical planning (different frequency)
119             perception.perceive();
120 
121             Length maximumForwardHeadway = laneBasedGTU.getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD);
122             Length maximumReverseHeadway = laneBasedGTU.getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKBACKOLD);
123             Time now = getGtu().getSimulator().getSimulatorTime().getTime();
124             Speed speedLimit = perception.getPerceptionCategory(DefaultSimplePerception.class).getSpeedLimit();
125 
126             // look at the conditions for headway on the current lane
127             Headway sameLaneLeader = perception.getPerceptionCategory(DefaultSimplePerception.class).getForwardHeadway();
128             Headway sameLaneFollower = perception.getPerceptionCategory(DefaultSimplePerception.class).getBackwardHeadway();
129             Collection<Headway> sameLaneTraffic = new ArrayList<Headway>();
130             if (sameLaneLeader.getObjectType().isGtu())
131             {
132                 sameLaneTraffic.add(sameLaneLeader);
133             }
134             if (sameLaneFollower.getObjectType().isGtu())
135             {
136                 sameLaneTraffic.add(sameLaneFollower);
137             }
138 
139             // Are we in the right lane for the route?
140             LanePathInfo lanePathInfo = buildLanePathInfo(laneBasedGTU, maximumForwardHeadway);
141             NextSplitInfo nextSplitInfo = determineNextSplit(laneBasedGTU, maximumForwardHeadway);
142             boolean currentLaneFine = nextSplitInfo.getCorrectCurrentLanes().contains(lanePathInfo.getReferenceLane());
143 
144             // calculate the lane change step
145             // TODO skip if:
146             // - we are in the right lane and drive at max speed or we accelerate maximally
147             // - there are no other lanes
148             Collection<Headway> leftLaneTraffic =
149                     perception.getPerceptionCategory(DefaultSimplePerception.class).getNeighboringHeadwaysLeft();
150             Collection<Headway> rightLaneTraffic =
151                     perception.getPerceptionCategory(DefaultSimplePerception.class).getNeighboringHeadwaysRight();
152 
153             // FIXME: whether we drive on the right should be stored in some central place.
154             final LateralDirectionality preferred = LateralDirectionality.RIGHT;
155             final Acceleration defaultLeftLaneIncentive =
156                     preferred.isLeft() ? PREFERREDLANEINCENTIVE : NONPREFERREDLANEINCENTIVE;
157             final Acceleration defaultRightLaneIncentive =
158                     preferred.isRight() ? PREFERREDLANEINCENTIVE : NONPREFERREDLANEINCENTIVE;
159 
160             AccelerationVector defaultLaneIncentives =
161                     new AccelerationVector(new double[] { defaultLeftLaneIncentive.getSI(), STAYINCURRENTLANEINCENTIVE.getSI(),
162                             defaultRightLaneIncentive.getSI() }, AccelerationUnit.SI, StorageType.DENSE);
163             AccelerationVector laneIncentives = laneIncentives(laneBasedGTU, defaultLaneIncentives);
164             LaneMovementStep lcmr = this.laneChangeModel.computeLaneChangeAndAcceleration(laneBasedGTU, sameLaneTraffic,
165                     rightLaneTraffic, leftLaneTraffic, speedLimit,
166                     new Acceleration(laneIncentives.get(preferred.isRight() ? 2 : 0)), new Acceleration(laneIncentives.get(1)),
167                     new Acceleration(laneIncentives.get(preferred.isRight() ? 0 : 2)));
168             Duration duration = lcmr.getGfmr().getValidUntil().minus(getGtu().getSimulator().getSimulatorTime().getTime());
169             if (lcmr.getLaneChangeDirection() != null)
170             {
171                 laneBasedGTU.changeLaneInstantaneously(lcmr.getLaneChangeDirection());
172 
173                 // create the path to drive in this timestep.
174                 lanePathInfo = buildLanePathInfo(laneBasedGTU, maximumForwardHeadway);
175             }
176 
177             // build a list of lanes forward, with a maximum headway.
178             if (lcmr.getGfmr().getAcceleration().si < 1E-6 && laneBasedGTU.getSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
179             {
180                 return new OperationalPlan(getGtu(), locationAtStartTime, startTime, duration);
181             }
182 
183             // build a list of lanes forward, with a maximum headway.
184             OTSLine3D path = lanePathInfo.getPath();
185             List<Segment> operationalPlanSegmentList = new ArrayList<>();
186             if (lcmr.getGfmr().getAcceleration().si == 0.0)
187             {
188                 Segment segment = new OperationalPlan.SpeedSegment(duration);
189                 operationalPlanSegmentList.add(segment);
190             }
191             else
192             {
193                 Segment segment = new OperationalPlan.AccelerationSegment(duration, lcmr.getGfmr().getAcceleration());
194                 operationalPlanSegmentList.add(segment);
195             }
196             OperationalPlan op =
197                     new OperationalPlan(getGtu(), path, startTime, getGtu().getSpeed(), operationalPlanSegmentList);
198             return op;
199         }
200         catch (ValueException exception)
201         {
202             throw new GTUException(exception);
203         }
204     }
205 
206     /**
207      * TODO: move laneIncentives to LanePerception? Figure out if the default lane incentives are OK, or override them with
208      * values that should keep this GTU on the intended route.
209      * @param gtu the GTU for which to calculate the incentives
210      * @param defaultLaneIncentives AccelerationVector; the three lane incentives for the next left adjacent lane, the current
211      *            lane and the next right adjacent lane
212      * @return AccelerationVector; the (possibly adjusted) lane incentives
213      * @throws NetworkException on network inconsistency
214      * @throws ValueException cannot happen
215      * @throws GTUException when the position of the GTU cannot be correctly determined
216      * @throws OperationalPlanException if DefaultAlexander perception category is not present
217      */
218     private AccelerationVector laneIncentives(final LaneBasedGTU gtu, final AccelerationVector defaultLaneIncentives)
219             throws NetworkException, ValueException, GTUException, OperationalPlanException
220     {
221         Length leftSuitability = suitability(gtu, LateralDirectionality.LEFT);
222         Length currentSuitability = suitability(gtu, null);
223         Length rightSuitability = suitability(gtu, LateralDirectionality.RIGHT);
224         if (leftSuitability == NOLANECHANGENEEDED && currentSuitability == NOLANECHANGENEEDED
225                 && rightSuitability == NOLANECHANGENEEDED)
226         {
227             return checkLaneDrops(gtu, defaultLaneIncentives);
228         }
229         if ((leftSuitability == NOLANECHANGENEEDED || leftSuitability == GETOFFTHISLANENOW)
230                 && currentSuitability == NOLANECHANGENEEDED
231                 && (rightSuitability == NOLANECHANGENEEDED || rightSuitability == GETOFFTHISLANENOW))
232         {
233             return checkLaneDrops(gtu,
234                     new AccelerationVector(new double[] { acceleration(gtu, leftSuitability),
235                             defaultLaneIncentives.get(1).getSI(), acceleration(gtu, rightSuitability) }, AccelerationUnit.SI,
236                             StorageType.DENSE));
237         }
238         if (currentSuitability == NOLANECHANGENEEDED)
239         {
240             return new AccelerationVector(new double[] { acceleration(gtu, leftSuitability),
241                     defaultLaneIncentives.get(1).getSI(), acceleration(gtu, rightSuitability) }, AccelerationUnit.SI,
242                     StorageType.DENSE);
243         }
244         return new AccelerationVector(new double[] { acceleration(gtu, leftSuitability), acceleration(gtu, currentSuitability),
245                 acceleration(gtu, rightSuitability) }, AccelerationUnit.SI, StorageType.DENSE);
246     }
247 
248     /**
249      * Figure out if the default lane incentives are OK, or override them with values that should keep this GTU from running out
250      * of road at an upcoming lane drop.
251      * @param gtu the GTU for which to check the lane drops
252      * @param defaultLaneIncentives DoubleVector.Rel.Dense&lt;AccelerationUnit&gt; the three lane incentives for the next left
253      *            adjacent lane, the current lane and the next right adjacent lane
254      * @return DoubleVector.Rel.Dense&lt;AccelerationUnit&gt;; the (possibly adjusted) lane incentives
255      * @throws NetworkException on network inconsistency
256      * @throws ValueException cannot happen
257      * @throws GTUException when the positions of the GTU cannot be determined
258      * @throws OperationalPlanException if DefaultAlexander perception category is not present
259      */
260     private AccelerationVector checkLaneDrops(final LaneBasedGTU gtu, final AccelerationVector defaultLaneIncentives)
261             throws NetworkException, ValueException, GTUException, OperationalPlanException
262     {
263         // FIXME: these comparisons to -10 is ridiculous.
264         Length leftSuitability = Double.isNaN(defaultLaneIncentives.get(0).si) || defaultLaneIncentives.get(0).si < -10
265                 ? GETOFFTHISLANENOW : laneDrop(gtu, LateralDirectionality.LEFT);
266         Length currentSuitability = laneDrop(gtu, null);
267         Length rightSuitability = Double.isNaN(defaultLaneIncentives.get(2).si) || defaultLaneIncentives.get(2).si < -10
268                 ? GETOFFTHISLANENOW : laneDrop(gtu, LateralDirectionality.RIGHT);
269         // @formatter:off
270         if ((leftSuitability == NOLANECHANGENEEDED || leftSuitability == GETOFFTHISLANENOW)
271                 && currentSuitability == NOLANECHANGENEEDED
272                 && (rightSuitability == NOLANECHANGENEEDED || rightSuitability == GETOFFTHISLANENOW))
273         {
274             return defaultLaneIncentives;
275         }
276         // @formatter:on
277         if (currentSuitability == NOLANECHANGENEEDED)
278         {
279             return new AccelerationVector(new double[] { acceleration(gtu, leftSuitability),
280                     defaultLaneIncentives.get(1).getSI(), acceleration(gtu, rightSuitability) }, AccelerationUnit.SI,
281                     StorageType.DENSE);
282         }
283         if (currentSuitability.le(leftSuitability))
284         {
285             return new AccelerationVector(new double[] { PREFERREDLANEINCENTIVE.getSI(), NONPREFERREDLANEINCENTIVE.getSI(),
286                     GETOFFTHISLANENOW.getSI() }, AccelerationUnit.SI, StorageType.DENSE);
287         }
288         if (currentSuitability.le(rightSuitability))
289         {
290             return new AccelerationVector(new double[] { GETOFFTHISLANENOW.getSI(), NONPREFERREDLANEINCENTIVE.getSI(),
291                     PREFERREDLANEINCENTIVE.getSI() }, AccelerationUnit.SI, StorageType.DENSE);
292         }
293         return new AccelerationVector(new double[] { acceleration(gtu, leftSuitability), acceleration(gtu, currentSuitability),
294                 acceleration(gtu, rightSuitability) }, AccelerationUnit.SI, StorageType.DENSE);
295     }
296 
297     /**
298      * Return the distance until the next lane drop in the specified (nearby) lane.
299      * @param gtu the GTU to determine the next lane drop for
300      * @param direction LateralDirectionality; one of the values <cite>LateralDirectionality.LEFT</cite> (use the left-adjacent
301      *            lane), or <cite>LateralDirectionality.RIGHT</cite> (use the right-adjacent lane), or <cite>null</cite> (use
302      *            the current lane)
303      * @return DoubleScalar.Rel&lt;LengthUnit&gt;; distance until the next lane drop if it occurs within the TIMEHORIZON, or
304      *         LaneBasedRouteNavigator.NOLANECHANGENEEDED if this lane can be followed until the next split junction or until
305      *         beyond the TIMEHORIZON
306      * @throws NetworkException on network inconsistency
307      * @throws GTUException when the positions of the GTU cannot be determined
308      * @throws OperationalPlanException if DefaultAlexander perception category is not present
309      */
310     private Length laneDrop(final LaneBasedGTU gtu, final LateralDirectionality direction)
311             throws NetworkException, GTUException, OperationalPlanException
312     {
313         DirectedLanePosition dlp = gtu.getReferencePosition();
314         Lane lane = dlp.getLane();
315         Length longitudinalPosition = dlp.getPosition();
316         if (null != direction)
317         {
318             lane = getPerception().getPerceptionCategory(DefaultSimplePerception.class).bestAccessibleAdjacentLane(lane, direction,
319                     longitudinalPosition);
320         }
321         if (null == lane)
322         {
323             return GETOFFTHISLANENOW;
324         }
325         double remainingLength = lane.getLength().getSI() - longitudinalPosition.getSI();
326         double remainingTimeSI = TIMEHORIZON.getSI() - remainingLength / lane.getSpeedLimit(gtu.getGTUType()).getSI();
327         while (remainingTimeSI >= 0)
328         {
329             for (Sensor s : lane.getSensors())
330             {
331                 if (s instanceof SinkSensor)
332                 {
333                     return NOLANECHANGENEEDED;
334                 }
335             }
336             int branching = lane.nextLanes(gtu.getGTUType()).size();
337             if (branching == 0)
338             {
339                 return new Length(remainingLength, LengthUnit.SI);
340             }
341             if (branching > 1)
342             {
343                 return NOLANECHANGENEEDED;
344             }
345             lane = lane.nextLanes(gtu.getGTUType()).keySet().iterator().next();
346             remainingTimeSI -= lane.getLength().getSI() / lane.getSpeedLimit(gtu.getGTUType()).getSI();
347             remainingLength += lane.getLength().getSI();
348         }
349         return NOLANECHANGENEEDED;
350     }
351 
352     /**
353      * TODO: move suitability to LanePerception? Return the suitability for the current lane, left adjacent lane or right
354      * adjacent lane.
355      * @param gtu the GTU for which to calculate the incentives
356      * @param direction LateralDirectionality; one of the values <cite>null</cite>, <cite>LateralDirectionality.LEFT</cite>, or
357      *            <cite>LateralDirectionality.RIGHT</cite>
358      * @return DoubleScalar.Rel&lt;LengthUnit&gt;; the suitability of the lane for reaching the (next) destination
359      * @throws NetworkException on network inconsistency
360      * @throws GTUException when position cannot be determined
361      * @throws OperationalPlanException if DefaultAlexander perception category is not present
362      */
363     private Length suitability(final LaneBasedGTU gtu, final LateralDirectionality direction)
364             throws NetworkException, GTUException, OperationalPlanException
365     {
366         DirectedLanePosition dlp = gtu.getReferencePosition();
367         Lane lane = dlp.getLane();
368         Length longitudinalPosition = dlp.getPosition();
369         if (null != direction)
370         {
371             lane = getPerception().getPerceptionCategory(DefaultSimplePerception.class).bestAccessibleAdjacentLane(lane, direction,
372                     longitudinalPosition);
373         }
374         if (null == lane)
375         {
376             return GETOFFTHISLANENOW;
377         }
378         try
379         {
380             return suitability(lane, longitudinalPosition, gtu, TIMEHORIZON);
381         }
382         catch (NetworkException ne)
383         {
384             System.err.println(gtu + " has a route problem in suitability: " + ne.getMessage());
385             return NOLANECHANGENEEDED;
386         }
387     }
388 
389     /**
390      * Compute deceleration needed to stop at a specified distance.
391      * @param gtu the GTU for which to calculate the acceleration to come to a full stop at the distance
392      * @param stopDistance DoubleScalar.Rel&lt;LengthUnit&gt;; the distance
393      * @return double; the acceleration (deceleration) needed to stop at the specified distance in m/s/s
394      */
395     private double acceleration(final LaneBasedGTU gtu, final Length stopDistance)
396     {
397         // What is the deceleration that will bring this GTU to a stop at exactly the suitability distance?
398         // Answer: a = -v^2 / 2 / suitabilityDistance
399         double v = gtu.getSpeed().getSI();
400         double a = -v * v / 2 / stopDistance.getSI();
401         return a;
402     }
403 
404     /**
405      * Determine the suitability of being at a particular longitudinal position in a particular Lane for following this Route.
406      * @param lane Lane; the lane to consider
407      * @param longitudinalPosition DoubleScalar.Rel&lt;LengthUnit&gt;; the longitudinal position in the lane
408      * @param gtu GTU; the GTU (used to check lane compatibility of lanes, and current lane the GTU is on)
409      * @param timeHorizon DoubleScalar.Rel&lt;TimeUnit&gt;; the maximum time that a driver may want to look ahead
410      * @return DoubleScalar.Rel&lt;LengthUnit&gt;; a value that indicates within what distance the GTU should try to vacate this
411      *         lane.
412      * @throws NetworkException on network inconsistency, or when the continuation Link at a branch cannot be determined
413      */
414     private Length suitability(final Lane lane, final Length longitudinalPosition, final LaneBasedGTU gtu,
415             final Duration timeHorizon) throws NetworkException
416     {
417         double remainingDistance = lane.getLength().getSI() - longitudinalPosition.getSI();
418         double spareTime = timeHorizon.getSI() - remainingDistance / lane.getSpeedLimit(gtu.getGTUType()).getSI();
419         // Find the first upcoming Node where there is a branch
420         Node nextNode = lane.getParentLink().getEndNode();
421         Link lastLink = lane.getParentLink();
422         Node nextSplitNode = null;
423         Lane currentLane = lane;
424         CrossSectionLink linkBeforeBranch = lane.getParentLink();
425         while (null != nextNode)
426         {
427             if (spareTime <= 0)
428             {
429                 return NOLANECHANGENEEDED; // It is not yet time to worry; this lane will do as well as any other
430             }
431             int laneCount = countCompatibleLanes(linkBeforeBranch, gtu.getGTUType());
432             if (0 == laneCount)
433             {
434                 throw new NetworkException("No compatible Lanes on Link " + linkBeforeBranch);
435             }
436             if (1 == laneCount)
437             {
438                 return NOLANECHANGENEEDED; // Only one compatible lane available; we'll get there "automatically";
439                 // i.e. without influence from the Route
440             }
441             int branching = nextNode.getLinks().size();
442             if (branching > 2)
443             { // Found a split
444                 nextSplitNode = nextNode;
445                 break;
446             }
447             else if (1 == branching)
448             {
449                 return NOLANECHANGENEEDED; // dead end; no more choices to make
450             }
451             else
452             { // Look beyond this nextNode
453                 Link nextLink = gtu.getStrategicalPlanner().nextLinkDirection(nextNode, lastLink, gtu.getGTUType()).getLink();
454                 if (nextLink instanceof CrossSectionLink)
455                 {
456                     nextNode = nextLink.getEndNode();
457                     // Oops: wrong code added the length of linkBeforeBranch in stead of length of nextLink
458                     remainingDistance += nextLink.getLength().getSI();
459                     linkBeforeBranch = (CrossSectionLink) nextLink;
460                     // Figure out the new currentLane
461                     if (currentLane.nextLanes(gtu.getGTUType()).size() == 0)
462                     {
463                         // Lane drop; our lane disappears. This is a compulsory lane change; which is not controlled
464                         // by the Route. Perform the forced lane change.
465                         if (currentLane.accessibleAdjacentLanes(LateralDirectionality.RIGHT, gtu.getGTUType()).size() > 0)
466                         {
467                             for (Lane adjacentLane : currentLane.accessibleAdjacentLanes(LateralDirectionality.RIGHT,
468                                     gtu.getGTUType()))
469                             {
470                                 if (adjacentLane.nextLanes(gtu.getGTUType()).size() > 0)
471                                 {
472                                     currentLane = adjacentLane;
473                                     break;
474                                 }
475                                 // If there are several adjacent lanes that have non empty nextLanes, we simple take the
476                                 // first in the set
477                             }
478                         }
479                         for (Lane adjacentLane : currentLane.accessibleAdjacentLanes(LateralDirectionality.LEFT,
480                                 gtu.getGTUType()))
481                         {
482                             if (adjacentLane.nextLanes(gtu.getGTUType()).size() > 0)
483                             {
484                                 currentLane = adjacentLane;
485                                 break;
486                             }
487                             // If there are several adjacent lanes that have non empty nextLanes, we simple take the
488                             // first in the set
489                         }
490                         if (currentLane.nextLanes(gtu.getGTUType()).size() == 0)
491                         {
492                             throw new NetworkException(
493                                     "Lane ends and there is not a compatible adjacent lane that does " + "not end");
494                         }
495                     }
496                     // Any compulsory lane change(s) have been performed and there is guaranteed a compatible next lane.
497                     for (Lane nextLane : currentLane.nextLanes(gtu.getGTUType()).keySet())
498                     {
499                         if (nextLane.getLaneType().isCompatible(gtu.getGTUType()))
500                         {
501                             currentLane = currentLane.nextLanes(gtu.getGTUType()).keySet().iterator().next();
502                             break;
503                         }
504                     }
505                     spareTime -= currentLane.getLength().getSI() / currentLane.getSpeedLimit(gtu.getGTUType()).getSI();
506                 }
507                 else
508                 {
509                     // There is a non-CrossSectionLink on the path to the next branch. A non-CrossSectionLink does not
510                     // have identifiable Lanes, therefore we can't aim for a particular Lane
511                     return NOLANECHANGENEEDED; // Any Lane will do equally well
512                 }
513                 lastLink = nextLink;
514             }
515         }
516         if (null == nextNode)
517         {
518             throw new NetworkException("Cannot find the next branch or sink node");
519         }
520         // We have now found the first upcoming branching Node
521         // Which continuing link is the one we need?
522         Map<Lane, Length> suitabilityOfLanesBeforeBranch = new HashMap<Lane, Length>();
523         Link linkAfterBranch =
524                 gtu.getStrategicalPlanner().nextLinkDirection(nextSplitNode, lastLink, gtu.getGTUType()).getLink();
525         for (CrossSectionElement cse : linkBeforeBranch.getCrossSectionElementList())
526         {
527             if (cse instanceof Lane)
528             {
529                 Lane l = (Lane) cse;
530                 if (l.getLaneType().isCompatible(gtu.getGTUType()))
531                 {
532                     for (Lane connectingLane : l.nextLanes(gtu.getGTUType()).keySet())
533                     {
534                         if (connectingLane.getParentLink() == linkAfterBranch
535                                 && connectingLane.getLaneType().isCompatible(gtu.getGTUType()))
536                         {
537                             Length currentValue = suitabilityOfLanesBeforeBranch.get(l);
538                             // Use recursion to find out HOW suitable this continuation lane is, but don't revert back
539                             // to the maximum time horizon (or we could end up in infinite recursion when there are
540                             // loops in the network).
541                             Length value = suitability(connectingLane, new Length(0, LengthUnit.SI), gtu,
542                                     new Duration(spareTime, TimeUnit.SI));
543                             // Use the minimum of the value computed for the first split junction (if there is one)
544                             // and the value computed for the second split junction.
545                             suitabilityOfLanesBeforeBranch.put(l,
546                                     null == currentValue || value.le(currentValue) ? value : currentValue);
547                         }
548                     }
549                 }
550             }
551         }
552         if (suitabilityOfLanesBeforeBranch.size() == 0)
553         {
554             throw new NetworkException("No lanes available on Link " + linkBeforeBranch);
555         }
556         Length currentLaneSuitability = suitabilityOfLanesBeforeBranch.get(currentLane);
557         if (null != currentLaneSuitability)
558         {
559             return currentLaneSuitability; // Following the current lane will keep us on the Route
560         }
561         // Performing one or more lane changes (left or right) is required.
562         int totalLanes = countCompatibleLanes(currentLane.getParentLink(), gtu.getGTUType());
563         Length leftSuitability = computeSuitabilityWithLaneChanges(currentLane, remainingDistance,
564                 suitabilityOfLanesBeforeBranch, totalLanes, LateralDirectionality.LEFT, gtu.getGTUType());
565         Length rightSuitability = computeSuitabilityWithLaneChanges(currentLane, remainingDistance,
566                 suitabilityOfLanesBeforeBranch, totalLanes, LateralDirectionality.RIGHT, gtu.getGTUType());
567         if (leftSuitability.ge(rightSuitability))
568         {
569             return leftSuitability;
570         }
571         else if (rightSuitability.ge(leftSuitability))
572         {
573             return rightSuitability;
574         }
575         if (leftSuitability.le(GETOFFTHISLANENOW))
576         {
577             throw new NetworkException("Changing lanes in any direction does not get the GTU on a suitable lane");
578         }
579         return leftSuitability; // left equals right; this is odd but topologically possible
580     }
581 
582     /**
583      * Compute the suitability of a lane from which lane changes are required to get to the next point on the Route.<br>
584      * This method weighs the suitability of the nearest suitable lane by (m - n) / m where n is the number of lane changes
585      * required and m is the total number of lanes in the CrossSectionLink.
586      * @param startLane Lane; the current lane of the GTU
587      * @param remainingDistance double; distance in m of GTU to first branch
588      * @param suitabilities Map&lt;Lane, Double&gt;; the set of suitable lanes and their suitability
589      * @param totalLanes integer; total number of lanes compatible with the GTU type
590      * @param direction LateralDirectionality; the direction of the lane changes to attempt
591      * @param gtuType GTUType; the type of the GTU
592      * @return double; the suitability of the <cite>startLane</cite> for following the Route
593      */
594     protected final Length computeSuitabilityWithLaneChanges(final Lane startLane, final double remainingDistance,
595             final Map<Lane, Length> suitabilities, final int totalLanes, final LateralDirectionality direction,
596             final GTUType gtuType)
597     {
598         /*-
599          * The time per required lane change seems more relevant than distance per required lane change.
600          * Total time required does not grow linearly with the number of required lane changes. Logarithmic, arc tangent 
601          * is more like it.
602          * Rijkswaterstaat appears to use a fixed time for ANY number of lane changes (about 60s). 
603          * TomTom navigation systems give more time (about 90s).
604          * In this method the returned suitability decreases linearly with the number of required lane changes. This
605          * ensures that there is a gradient that coaches the GTU towards the most suitable lane.
606          */
607         int laneChangesUsed = 0;
608         Lane currentLane = startLane;
609         Length currentSuitability = null;
610         while (null == currentSuitability)
611         {
612             laneChangesUsed++;
613             if (currentLane.accessibleAdjacentLanes(direction, gtuType).size() == 0)
614             {
615                 return GETOFFTHISLANENOW;
616             }
617             currentLane = currentLane.accessibleAdjacentLanes(direction, gtuType).iterator().next();
618             currentSuitability = suitabilities.get(currentLane);
619         }
620         double fraction = currentSuitability == NOLANECHANGENEEDED ? 0 : 0.5;
621         int notSuitableLaneCount = totalLanes - suitabilities.size();
622         return new Length(
623                 remainingDistance * (notSuitableLaneCount - laneChangesUsed + 1 + fraction) / (notSuitableLaneCount + fraction),
624                 LengthUnit.SI);
625     }
626 
627     /**
628      * Determine how many lanes on a CrossSectionLink are compatible with a particular GTU type.<br>
629      * TODO: this method should probably be moved into the CrossSectionLink class
630      * @param link CrossSectionLink; the link
631      * @param gtuType GTUType; the GTU type
632      * @return integer; the number of lanes on the link that are compatible with the GTU type
633      */
634     protected final int countCompatibleLanes(final CrossSectionLink link, final GTUType gtuType)
635     {
636         int result = 0;
637         for (CrossSectionElement cse : link.getCrossSectionElementList())
638         {
639             if (cse instanceof Lane)
640             {
641                 Lane l = (Lane) cse;
642                 if (l.getLaneType().isCompatible(gtuType))
643                 {
644                     result++;
645                 }
646             }
647         }
648         return result;
649     }
650 
651     /** {@inheritDoc} */
652     @Override
653     public final String toString()
654     {
655         return "LaneBasedCFLCTacticalPlanner [laneChangeModel=" + this.laneChangeModel + "]";
656     }
657 
658 }