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