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