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