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