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