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.HashSet;
7   import java.util.LinkedHashMap;
8   import java.util.List;
9   import java.util.Map;
10  
11  import nl.tudelft.simulation.language.d3.DirectedPoint;
12  
13  import org.djunits.unit.AccelerationUnit;
14  import org.djunits.unit.LengthUnit;
15  import org.djunits.unit.TimeUnit;
16  import org.djunits.value.StorageType;
17  import org.djunits.value.ValueException;
18  import org.djunits.value.vdouble.scalar.Acceleration;
19  import org.djunits.value.vdouble.scalar.Duration;
20  import org.djunits.value.vdouble.scalar.Length;
21  import org.djunits.value.vdouble.scalar.Speed;
22  import org.djunits.value.vdouble.scalar.Time;
23  import org.djunits.value.vdouble.vector.AccelerationVector;
24  import org.opentrafficsim.core.geometry.OTSLine3D;
25  import org.opentrafficsim.core.gtu.GTUException;
26  import org.opentrafficsim.core.gtu.GTUType;
27  import org.opentrafficsim.core.gtu.RelativePosition;
28  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
29  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
30  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
31  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan.Segment;
32  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
33  import org.opentrafficsim.core.network.LateralDirectionality;
34  import org.opentrafficsim.core.network.Link;
35  import org.opentrafficsim.core.network.NetworkException;
36  import org.opentrafficsim.core.network.Node;
37  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
38  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
39  import org.opentrafficsim.road.gtu.lane.perception.categories.DefaultAlexander;
40  import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
41  import org.opentrafficsim.road.gtu.lane.tactical.following.GTUFollowingModelOld;
42  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.LaneChangeModel;
43  import org.opentrafficsim.road.gtu.lane.tactical.lanechangemobil.LaneMovementStep;
44  import org.opentrafficsim.road.network.lane.CrossSectionElement;
45  import org.opentrafficsim.road.network.lane.CrossSectionLink;
46  import org.opentrafficsim.road.network.lane.Lane;
47  import org.opentrafficsim.road.network.lane.Sensor;
48  import org.opentrafficsim.road.network.lane.SinkSensor;
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 =
70          new Acceleration(0.1, AccelerationUnit.METER_PER_SECOND_2);
71  
72      /** Standard incentive to stay in the current lane. */
73      private static final Acceleration PREFERREDLANEINCENTIVE = new Acceleration(0.3, AccelerationUnit.METER_PER_SECOND_2);
74  
75      /** Standard incentive to stay in the current lane. */
76      private static final Acceleration NONPREFERREDLANEINCENTIVE =
77          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, TimeUnit.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);
101         this.laneChangeModel = laneChangeModel;
102     }
103 
104     /** {@inheritDoc} */
105     @Override
106     public final OperationalPlan generateOperationalPlan(final Time startTime, final DirectedPoint locationAtStartTime)
107         throws OperationalPlanException, NetworkException, GTUException, ParameterException
108     {
109         try
110         {
111             // define some basic variables
112             LaneBasedGTU laneBasedGTU = getGtu();
113             LanePerception perception = getPerception();
114 
115             // if the GTU's maximum speed is zero (block), generate a stand still plan for one second
116             if (laneBasedGTU.getMaximumSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
117             {
118                 return new OperationalPlan(getGtu(), locationAtStartTime, startTime, new Duration(1.0, TimeUnit.SECOND));
119             }
120 
121             // perceive every time step... This is the 'classical' way of tactical planning.
122             // NOTE: delete this if perception takes place independent of the tactical planning (different frequency)
123             perception.perceive();
124 
125             Length maximumForwardHeadway =
126                 laneBasedGTU.getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD);
127             Length maximumReverseHeadway =
128                 laneBasedGTU.getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKBACKOLD);
129             Time now = getGtu().getSimulator().getSimulatorTime().getTime();
130             Speed speedLimit = perception.getPerceptionCategory(DefaultAlexander.class).getSpeedLimit();
131 
132             // look at the conditions for headway on the current lane
133             Headway sameLaneLeader = perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway();
134             Headway sameLaneFollower = perception.getPerceptionCategory(DefaultAlexander.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             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(DefaultAlexander.class).getNeighboringHeadwaysLeft();
156             Collection<Headway> rightLaneTraffic =
157                 perception.getPerceptionCategory(DefaultAlexander.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 =
171                 this.laneChangeModel.computeLaneChangeAndAcceleration(laneBasedGTU, sameLaneTraffic, rightLaneTraffic,
172                     leftLaneTraffic, speedLimit, new Acceleration(laneIncentives.get(preferred.isRight() ? 2 : 0)),
173                     new Acceleration(laneIncentives.get(1)), new Acceleration(laneIncentives
174                         .get(preferred.isRight() ? 0 : 2)));
175             Duration duration = lcmr.getGfmr().getValidUntil().minus(getGtu().getSimulator().getSimulatorTime().getTime());
176             // if ("1".equals(gtu.getId()))
177             // {
178             // System.out.println(String.format("%s: laneIncentives %s, lcmr %s", gtu, laneIncentives, lcmr));
179             // }
180             if (lcmr.getLaneChange() != null)
181             {
182                 Collection<Lane> oldLaneSet = new HashSet<Lane>(laneBasedGTU.getLanes().keySet());
183                 Collection<Lane> newLaneSet = new HashSet<Lane>(2);
184                 Map<Lane, Double> oldFractionalPositions = new LinkedHashMap<Lane, Double>();
185                 for (Lane l : laneBasedGTU.getLanes().keySet())
186                 {
187                     oldFractionalPositions.put(l, laneBasedGTU.fractionalPosition(l, getGtu().getReference(), now));
188                     if (lcmr.getLaneChange().isLeft())
189                     {
190                         newLaneSet.addAll(getPerception().getPerceptionCategory(DefaultAlexander.class)
191                             .getAccessibleAdjacentLanesLeft().get(l));
192                     }
193                     else if (lcmr.getLaneChange().isRight())
194                     {
195                         newLaneSet.addAll(getPerception().getPerceptionCategory(DefaultAlexander.class)
196                             .getAccessibleAdjacentLanesRight().get(l));
197                     }
198                     else
199                     {
200                         throw new GTUException("Lane change is neither left nor right");
201                     }
202                 }
203                 // Add this GTU to the lanes in newLaneSet.
204                 // This could be rewritten to be more efficient.
205                 for (Lane newLane : newLaneSet)
206                 {
207                     Double fractionalPosition = null;
208                     // find ONE lane in oldLaneSet that has l as neighbor
209                     Lane foundOldLane = null;
210                     for (Lane oldLane : oldLaneSet)
211                     {
212                         if ((lcmr.getLaneChange().isLeft() && getPerception().getPerceptionCategory(DefaultAlexander.class)
213                             .getAccessibleAdjacentLanesLeft().get(oldLane).contains(newLane))
214                             || (lcmr.getLaneChange().isRight() && getPerception().getPerceptionCategory(
215                                 DefaultAlexander.class).getAccessibleAdjacentLanesRight().get(oldLane).contains(newLane)))
216                         {
217                             fractionalPosition = oldFractionalPositions.get(oldLane);
218                             foundOldLane = oldLane;
219                             break;
220                         }
221                     }
222                     if (null == fractionalPosition)
223                     {
224                         throw new Error("Program error: Cannot find an oldLane that has newLane " + newLane + " as "
225                             + lcmr.getLaneChange() + " neighbor");
226                     }
227                     laneBasedGTU.enterLane(newLane, newLane.getLength().multiplyBy(fractionalPosition), laneBasedGTU
228                         .getLanes().get(foundOldLane));
229                 }
230                 // if ("41".equals(gtu.getId()) && oldLaneSet.size() > 1)
231                 // {
232                 // System.out.println("Problem");
233                 // AbstractLaneBasedGTU lbg = (AbstractLaneBasedGTU) gtu;
234                 // for (Lane l : lbg.getLanes().keySet())
235                 // {
236                 // System.out.println("reference on lane " + l + " is "
237                 // + lbg.position(l, RelativePosition.REFERENCE_POSITION) + " length of lane is " + l.getLength());
238                 // }
239                 // }
240                 System.out.println(getGtu() + " changes lane from " + oldLaneSet + " to " + newLaneSet + " at time "
241                     + getGtu().getSimulator().getSimulatorTime().get());
242                 // Remove this GTU from all of the Lanes that it is on and remember the fractional position on each
243                 // one
244                 for (Lane l : oldFractionalPositions.keySet())
245                 {
246                     laneBasedGTU.leaveLane(l);
247                 }
248                 // create the path to drive in this timestep.
249                 lanePathInfo = buildLanePathInfo(laneBasedGTU, maximumForwardHeadway);
250 
251                 // System.out.println("lane incentives: " + laneIncentives);
252                 // build a list of lanes forward, with a maximum headway.
253                 if (lcmr.getGfmr().getAcceleration().si < 1E-6
254                     && laneBasedGTU.getSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
255                 {
256                     // TODO Make a 100% lateral move from standing still...
257                     return new OperationalPlan(getGtu(), locationAtStartTime, startTime, duration);
258                 }
259 
260                 // TODO make a gradual lateral move
261                 OTSLine3D path = lanePathInfo.getPath();
262                 List<Segment> operationalPlanSegmentList = new ArrayList<>();
263                 if (lcmr.getGfmr().getAcceleration().si == 0.0)
264                 {
265                     Segment segment = new OperationalPlan.SpeedSegment(duration);
266                     operationalPlanSegmentList.add(segment);
267                 }
268                 else
269                 {
270                     Segment segment = new OperationalPlan.AccelerationSegment(duration, lcmr.getGfmr().getAcceleration());
271                     operationalPlanSegmentList.add(segment);
272                 }
273                 OperationalPlan op =
274                     new OperationalPlan(getGtu(), path, startTime, getGtu().getSpeed(), operationalPlanSegmentList);
275                 return op;
276             }
277             else
278             // NO LANE CHANGE
279             {
280                 // see if we have to continue standing still. In that case, generate a stand still plan
281                 if (lcmr.getGfmr().getAcceleration().si < 1E-6
282                     && laneBasedGTU.getSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
283                 {
284                     return new OperationalPlan(getGtu(), locationAtStartTime, startTime, duration);
285                 }
286                 // build a list of lanes forward, with a maximum headway.
287                 OTSLine3D path = lanePathInfo.getPath();
288                 List<Segment> operationalPlanSegmentList = new ArrayList<>();
289                 if (lcmr.getGfmr().getAcceleration().si == 0.0)
290                 {
291                     Segment segment = new OperationalPlan.SpeedSegment(duration);
292                     operationalPlanSegmentList.add(segment);
293                 }
294                 else
295                 {
296                     Segment segment = new OperationalPlan.AccelerationSegment(duration, lcmr.getGfmr().getAcceleration());
297                     operationalPlanSegmentList.add(segment);
298                 }
299                 OperationalPlan op =
300                     new OperationalPlan(getGtu(), path, startTime, getGtu().getSpeed(), operationalPlanSegmentList);
301                 return op;
302             }
303         }
304         catch (ValueException exception)
305         {
306             throw new GTUException(exception);
307         }
308     }
309 
310     /**
311      * TODO: move laneIncentives to LanePerception? Figure out if the default lane incentives are OK, or override them with
312      * values that should keep this GTU on the intended route.
313      * @param gtu the GTU for which to calculate the incentives
314      * @param defaultLaneIncentives AccelerationVector; the three lane incentives for the next left adjacent lane, the current
315      *            lane and the next right adjacent lane
316      * @return AccelerationVector; the (possibly adjusted) lane incentives
317      * @throws NetworkException on network inconsistency
318      * @throws ValueException cannot happen
319      * @throws GTUException when the position of the GTU cannot be correctly determined
320      * @throws OperationalPlanException if DefaultAlexander perception category is not present
321      */
322     private AccelerationVector laneIncentives(final LaneBasedGTU gtu, final AccelerationVector defaultLaneIncentives)
323         throws NetworkException, ValueException, GTUException, OperationalPlanException
324     {
325         Length leftSuitability = suitability(gtu, LateralDirectionality.LEFT);
326         Length currentSuitability = suitability(gtu, null);
327         Length rightSuitability = suitability(gtu, LateralDirectionality.RIGHT);
328         if (leftSuitability == NOLANECHANGENEEDED && currentSuitability == NOLANECHANGENEEDED
329             && rightSuitability == NOLANECHANGENEEDED)
330         {
331             return checkLaneDrops(gtu, defaultLaneIncentives);
332         }
333         if ((leftSuitability == NOLANECHANGENEEDED || leftSuitability == GETOFFTHISLANENOW)
334             && currentSuitability == NOLANECHANGENEEDED
335             && (rightSuitability == NOLANECHANGENEEDED || rightSuitability == GETOFFTHISLANENOW))
336         {
337             return checkLaneDrops(gtu, new AccelerationVector(new double[] {acceleration(gtu, leftSuitability),
338                 defaultLaneIncentives.get(1).getSI(), acceleration(gtu, rightSuitability)}, AccelerationUnit.SI,
339                 StorageType.DENSE));
340         }
341         if (currentSuitability == NOLANECHANGENEEDED)
342         {
343             return new AccelerationVector(new double[] {acceleration(gtu, leftSuitability),
344                 defaultLaneIncentives.get(1).getSI(), acceleration(gtu, rightSuitability)}, AccelerationUnit.SI,
345                 StorageType.DENSE);
346         }
347         return new AccelerationVector(new double[] {acceleration(gtu, leftSuitability),
348             acceleration(gtu, currentSuitability), acceleration(gtu, rightSuitability)}, AccelerationUnit.SI,
349             StorageType.DENSE);
350     }
351 
352     /**
353      * Figure out if the default lane incentives are OK, or override them with values that should keep this GTU from running out
354      * of road at an upcoming lane drop.
355      * @param gtu the GTU for which to check the lane drops
356      * @param defaultLaneIncentives DoubleVector.Rel.Dense&lt;AccelerationUnit&gt; the three lane incentives for the next left
357      *            adjacent lane, the current lane and the next right adjacent lane
358      * @return DoubleVector.Rel.Dense&lt;AccelerationUnit&gt;; the (possibly adjusted) lane incentives
359      * @throws NetworkException on network inconsistency
360      * @throws ValueException cannot happen
361      * @throws GTUException when the positions of the GTU cannot be determined
362      * @throws OperationalPlanException if DefaultAlexander perception category is not present
363      */
364     private AccelerationVector checkLaneDrops(final LaneBasedGTU gtu, final AccelerationVector defaultLaneIncentives)
365         throws NetworkException, ValueException, GTUException, OperationalPlanException
366     {
367         // FIXME: these comparisons to -10 is ridiculous.
368         Length leftSuitability =
369             Double.isNaN(defaultLaneIncentives.get(0).si) || defaultLaneIncentives.get(0).si < -10 ? GETOFFTHISLANENOW
370                 : laneDrop(gtu, LateralDirectionality.LEFT);
371         Length currentSuitability = laneDrop(gtu, null);
372         Length rightSuitability =
373             Double.isNaN(defaultLaneIncentives.get(2).si) || defaultLaneIncentives.get(2).si < -10 ? GETOFFTHISLANENOW
374                 : laneDrop(gtu, LateralDirectionality.RIGHT);
375         // @formatter:off
376         if ((leftSuitability == NOLANECHANGENEEDED || leftSuitability == GETOFFTHISLANENOW)
377                 && currentSuitability == NOLANECHANGENEEDED
378                 && (rightSuitability == NOLANECHANGENEEDED || rightSuitability == GETOFFTHISLANENOW))
379         {
380             return defaultLaneIncentives;
381         }
382         // @formatter:on
383         if (currentSuitability == NOLANECHANGENEEDED)
384         {
385             return new AccelerationVector(new double[] {acceleration(gtu, leftSuitability),
386                 defaultLaneIncentives.get(1).getSI(), acceleration(gtu, rightSuitability)}, AccelerationUnit.SI,
387                 StorageType.DENSE);
388         }
389         if (currentSuitability.le(leftSuitability))
390         {
391             return new AccelerationVector(new double[] {PREFERREDLANEINCENTIVE.getSI(), NONPREFERREDLANEINCENTIVE.getSI(),
392                 GETOFFTHISLANENOW.getSI()}, AccelerationUnit.SI, StorageType.DENSE);
393         }
394         if (currentSuitability.le(rightSuitability))
395         {
396             return new AccelerationVector(new double[] {GETOFFTHISLANENOW.getSI(), NONPREFERREDLANEINCENTIVE.getSI(),
397                 PREFERREDLANEINCENTIVE.getSI()}, AccelerationUnit.SI, StorageType.DENSE);
398         }
399         return new AccelerationVector(new double[] {acceleration(gtu, leftSuitability),
400             acceleration(gtu, currentSuitability), acceleration(gtu, rightSuitability)}, AccelerationUnit.SI,
401             StorageType.DENSE);
402     }
403 
404     /**
405      * Return the distance until the next lane drop in the specified (nearby) lane.
406      * @param gtu the GTU to determine the next lane drop for
407      * @param direction LateralDirectionality; one of the values <cite>LateralDirectionality.LEFT</cite> (use the left-adjacent
408      *            lane), or <cite>LateralDirectionality.RIGHT</cite> (use the right-adjacent lane), or <cite>null</cite> (use
409      *            the current lane)
410      * @return DoubleScalar.Rel&lt;LengthUnit&gt;; distance until the next lane drop if it occurs within the TIMEHORIZON, or
411      *         LaneBasedRouteNavigator.NOLANECHANGENEEDED if this lane can be followed until the next split junction or until
412      *         beyond the TIMEHORIZON
413      * @throws NetworkException on network inconsistency
414      * @throws GTUException when the positions of the GTU cannot be determined
415      * @throws OperationalPlanException if DefaultAlexander perception category is not present
416      */
417     private Length laneDrop(final LaneBasedGTU gtu, final LateralDirectionality direction) throws NetworkException,
418         GTUException, OperationalPlanException
419     {
420         Lane lane = null;
421         Length longitudinalPosition = null;
422         Map<Lane, Length> positions = gtu.positions(RelativePosition.REFERENCE_POSITION);
423         if (null == direction)
424         {
425             for (Lane l : gtu.getLanes().keySet())
426             {
427                 if (l.getLaneType().isCompatible(gtu.getGTUType()))
428                 {
429                     lane = l;
430                 }
431             }
432             if (null == lane)
433             {
434                 throw new NetworkException(this + " is not on any compatible lane");
435             }
436             longitudinalPosition = positions.get(lane);
437         }
438         else
439         {
440             lane = positions.keySet().iterator().next();
441             longitudinalPosition = positions.get(lane);
442             lane =
443                 getPerception().getPerceptionCategory(DefaultAlexander.class).bestAccessibleAdjacentLane(lane, direction,
444                     longitudinalPosition); // XXX
445             // correct??
446         }
447         if (null == lane)
448         {
449             return GETOFFTHISLANENOW;
450         }
451         double remainingLength = lane.getLength().getSI() - longitudinalPosition.getSI();
452         double remainingTimeSI = TIMEHORIZON.getSI() - remainingLength / lane.getSpeedLimit(gtu.getGTUType()).getSI();
453         while (remainingTimeSI >= 0)
454         {
455             for (Sensor s : lane.getSensors())
456             {
457                 if (s instanceof SinkSensor)
458                 {
459                     return NOLANECHANGENEEDED;
460                 }
461             }
462             int branching = lane.nextLanes(gtu.getGTUType()).size();
463             if (branching == 0)
464             {
465                 return new Length(remainingLength, LengthUnit.SI);
466             }
467             if (branching > 1)
468             {
469                 return NOLANECHANGENEEDED;
470             }
471             lane = lane.nextLanes(gtu.getGTUType()).keySet().iterator().next();
472             remainingTimeSI -= lane.getLength().getSI() / lane.getSpeedLimit(gtu.getGTUType()).getSI();
473             remainingLength += lane.getLength().getSI();
474         }
475         return NOLANECHANGENEEDED;
476     }
477 
478     /**
479      * TODO: move suitability to LanePerception? Return the suitability for the current lane, left adjacent lane or right
480      * adjacent lane.
481      * @param gtu the GTU for which to calculate the incentives
482      * @param direction LateralDirectionality; one of the values <cite>null</cite>, <cite>LateralDirectionality.LEFT</cite>, or
483      *            <cite>LateralDirectionality.RIGHT</cite>
484      * @return DoubleScalar.Rel&lt;LengthUnit&gt;; the suitability of the lane for reaching the (next) destination
485      * @throws NetworkException on network inconsistency
486      * @throws GTUException when position cannot be determined
487      * @throws OperationalPlanException if DefaultAlexander perception category is not present
488      */
489     private Length suitability(final LaneBasedGTU gtu, final LateralDirectionality direction) throws NetworkException,
490         GTUException, OperationalPlanException
491     {
492         Lane lane = null;
493         Length longitudinalPosition = null;
494         Map<Lane, Length> positions = gtu.positions(RelativePosition.REFERENCE_POSITION);
495         if (null == direction)
496         {
497             for (Lane l : gtu.getLanes().keySet())
498             {
499                 if (l.getLaneType().isCompatible(gtu.getGTUType()))
500                 {
501                     lane = l;
502                 }
503             }
504             if (null == lane)
505             {
506                 throw new NetworkException(this + " is not on any compatible lane");
507             }
508             longitudinalPosition = positions.get(lane);
509         }
510         else
511         {
512             lane = positions.keySet().iterator().next();
513             longitudinalPosition = positions.get(lane);
514             // if ("1051".equals(gtu.getId()) && "Lane lane.1 of FirstVia to SecondVia".equals(lane.toString())
515             // && longitudinalPosition.si >= -1.85699 && longitudinalPosition.si < 0)
516             // {
517             // System.out.println(gtu + " direction " + direction + " position on " + lane + " is " + longitudinalPosition);
518             // }
519             lane =
520                 getPerception().getPerceptionCategory(DefaultAlexander.class).bestAccessibleAdjacentLane(lane, direction,
521                     longitudinalPosition); // XXX
522             // correct??
523             // These nested if statements can be combined, but that would reduce readability of the code
524             if (null != lane)
525             {
526                 // Cancel lane change opportunity if front or rear of the GTU is not able to make the lane change
527                 if ((!canChangeLane(gtu.positions(gtu.getRear()), positions, gtu, direction))
528                     || (!canChangeLane(gtu.positions(gtu.getFront()), positions, gtu, direction)))
529                 {
530                     // System.out.println("Canceling " + direction + " lane change opportunity for " + gtu);
531                     lane = null;
532                 }
533             }
534         }
535         if (null == lane)
536         {
537             return GETOFFTHISLANENOW;
538         }
539         try
540         {
541             return suitability(lane, longitudinalPosition, gtu, TIMEHORIZON);
542         }
543         catch (NetworkException ne)
544         {
545             System.err.println(gtu + " has a route problem in suitability: " + ne.getMessage());
546             return NOLANECHANGENEEDED;
547         }
548     }
549 
550     /**
551      * Check that front or back of GTU can (also) change lane.
552      * @param laneMap Map&lt;Lane, Length&gt;; Map of lanes that the front or back GTU is in
553      * @param positions Map&lt;Lane, Length&gt;; Map of lanes that the reference point of the GTU is in
554      * @param gtu LaneBasedGTU; the GTU
555      * @param direction LateralDirectionality; direction of the intended lane change
556      * @return boolean; true if the lane change appears possible; false if the lane change is not possible for the front or back
557      *         of the GTU
558      * @throws OperationalPlanException if DefaultAlexander perception category is not present
559      */
560     private boolean canChangeLane(final Map<Lane, Length> laneMap, final Map<Lane, Length> positions,
561         final LaneBasedGTU gtu, final LateralDirectionality direction) throws OperationalPlanException
562     {
563         for (Lane lane : laneMap.keySet())
564         {
565             Length positionInLane = laneMap.get(lane);
566             if (positionInLane.si < 0 || positionInLane.gt(lane.getLength()))
567             {
568                 continue;
569             }
570             if (null == getPerception().getPerceptionCategory(DefaultAlexander.class).bestAccessibleAdjacentLane(lane,
571                 direction, positions.get(lane)))
572             {
573                 // System.out.println("Front or back of " + gtu + " cannot change lane");
574                 return false;
575             }
576         }
577         return true;
578     }
579 
580     /**
581      * Compute deceleration needed to stop at a specified distance.
582      * @param gtu the GTU for which to calculate the acceleration to come to a full stop at the distance
583      * @param stopDistance DoubleScalar.Rel&lt;LengthUnit&gt;; the distance
584      * @return double; the acceleration (deceleration) needed to stop at the specified distance in m/s/s
585      */
586     private double acceleration(final LaneBasedGTU gtu, final Length stopDistance)
587     {
588         // What is the deceleration that will bring this GTU to a stop at exactly the suitability distance?
589         // Answer: a = -v^2 / 2 / suitabilityDistance
590         double v = gtu.getSpeed().getSI();
591         double a = -v * v / 2 / stopDistance.getSI();
592         return a;
593     }
594 
595     /**
596      * Determine the suitability of being at a particular longitudinal position in a particular Lane for following this Route.
597      * @param lane Lane; the lane to consider
598      * @param longitudinalPosition DoubleScalar.Rel&lt;LengthUnit&gt;; the longitudinal position in the lane
599      * @param gtu GTU; the GTU (used to check lane compatibility of lanes, and current lane the GTU is on)
600      * @param timeHorizon DoubleScalar.Rel&lt;TimeUnit&gt;; the maximum time that a driver may want to look ahead
601      * @return DoubleScalar.Rel&lt;LengthUnit&gt;; a value that indicates within what distance the GTU should try to vacate this
602      *         lane.
603      * @throws NetworkException on network inconsistency, or when the continuation Link at a branch cannot be determined
604      */
605     private Length suitability(final Lane lane, final Length longitudinalPosition, final LaneBasedGTU gtu,
606         final Duration timeHorizon) throws NetworkException
607     {
608         double remainingDistance = lane.getLength().getSI() - longitudinalPosition.getSI();
609         double spareTime = timeHorizon.getSI() - remainingDistance / lane.getSpeedLimit(gtu.getGTUType()).getSI();
610         // Find the first upcoming Node where there is a branch
611         Node nextNode = lane.getParentLink().getEndNode();
612         Link lastLink = lane.getParentLink();
613         Node nextSplitNode = null;
614         Lane currentLane = lane;
615         CrossSectionLink linkBeforeBranch = lane.getParentLink();
616         while (null != nextNode)
617         {
618             if (spareTime <= 0)
619             {
620                 return NOLANECHANGENEEDED; // It is not yet time to worry; this lane will do as well as any other
621             }
622             int laneCount = countCompatibleLanes(linkBeforeBranch, gtu.getGTUType());
623             if (0 == laneCount)
624             {
625                 throw new NetworkException("No compatible Lanes on Link " + linkBeforeBranch);
626             }
627             if (1 == laneCount)
628             {
629                 return NOLANECHANGENEEDED; // Only one compatible lane available; we'll get there "automatically";
630                 // i.e. without influence from the Route
631             }
632             int branching = nextNode.getLinks().size();
633             if (branching > 2)
634             { // Found a split
635                 nextSplitNode = nextNode;
636                 break;
637             }
638             else if (1 == branching)
639             {
640                 return NOLANECHANGENEEDED; // dead end; no more choices to make
641             }
642             else
643             { // Look beyond this nextNode
644                 Link nextLink =
645                     gtu.getStrategicalPlanner().nextLinkDirection(nextNode, lastLink, gtu.getGTUType()).getLink();
646                 if (nextLink instanceof CrossSectionLink)
647                 {
648                     nextNode = nextLink.getEndNode();
649                     // Oops: wrong code added the length of linkBeforeBranch in stead of length of nextLink
650                     remainingDistance += nextLink.getLength().getSI();
651                     linkBeforeBranch = (CrossSectionLink) nextLink;
652                     // Figure out the new currentLane
653                     if (currentLane.nextLanes(gtu.getGTUType()).size() == 0)
654                     {
655                         // Lane drop; our lane disappears. This is a compulsory lane change; which is not controlled
656                         // by the Route. Perform the forced lane change.
657                         if (currentLane.accessibleAdjacentLanes(LateralDirectionality.RIGHT, gtu.getGTUType()).size() > 0)
658                         {
659                             for (Lane adjacentLane : currentLane.accessibleAdjacentLanes(LateralDirectionality.RIGHT, gtu
660                                 .getGTUType()))
661                             {
662                                 if (adjacentLane.nextLanes(gtu.getGTUType()).size() > 0)
663                                 {
664                                     currentLane = adjacentLane;
665                                     break;
666                                 }
667                                 // If there are several adjacent lanes that have non empty nextLanes, we simple take the
668                                 // first in the set
669                             }
670                         }
671                         for (Lane adjacentLane : currentLane.accessibleAdjacentLanes(LateralDirectionality.LEFT, gtu
672                             .getGTUType()))
673                         {
674                             if (adjacentLane.nextLanes(gtu.getGTUType()).size() > 0)
675                             {
676                                 currentLane = adjacentLane;
677                                 break;
678                             }
679                             // If there are several adjacent lanes that have non empty nextLanes, we simple take the
680                             // first in the set
681                         }
682                         if (currentLane.nextLanes(gtu.getGTUType()).size() == 0)
683                         {
684                             throw new NetworkException("Lane ends and there is not a compatible adjacent lane that does "
685                                 + "not end");
686                         }
687                     }
688                     // Any compulsory lane change(s) have been performed and there is guaranteed a compatible next lane.
689                     for (Lane nextLane : currentLane.nextLanes(gtu.getGTUType()).keySet())
690                     {
691                         if (nextLane.getLaneType().isCompatible(gtu.getGTUType()))
692                         {
693                             currentLane = currentLane.nextLanes(gtu.getGTUType()).keySet().iterator().next();
694                             break;
695                         }
696                     }
697                     spareTime -= currentLane.getLength().getSI() / currentLane.getSpeedLimit(gtu.getGTUType()).getSI();
698                 }
699                 else
700                 {
701                     // There is a non-CrossSectionLink on the path to the next branch. A non-CrossSectionLink does not
702                     // have identifiable Lanes, therefore we can't aim for a particular Lane
703                     return NOLANECHANGENEEDED; // Any Lane will do equally well
704                 }
705                 lastLink = nextLink;
706             }
707         }
708         if (null == nextNode)
709         {
710             throw new NetworkException("Cannot find the next branch or sink node");
711         }
712         // We have now found the first upcoming branching Node
713         // Which continuing link is the one we need?
714         Map<Lane, Length> suitabilityOfLanesBeforeBranch = new HashMap<Lane, Length>();
715         Link linkAfterBranch =
716             gtu.getStrategicalPlanner().nextLinkDirection(nextSplitNode, lastLink, gtu.getGTUType()).getLink();
717         for (CrossSectionElement cse : linkBeforeBranch.getCrossSectionElementList())
718         {
719             if (cse instanceof Lane)
720             {
721                 Lane l = (Lane) cse;
722                 if (l.getLaneType().isCompatible(gtu.getGTUType()))
723                 {
724                     for (Lane connectingLane : l.nextLanes(gtu.getGTUType()).keySet())
725                     {
726                         if (connectingLane.getParentLink() == linkAfterBranch
727                             && connectingLane.getLaneType().isCompatible(gtu.getGTUType()))
728                         {
729                             Length currentValue = suitabilityOfLanesBeforeBranch.get(l);
730                             // Use recursion to find out HOW suitable this continuation lane is, but don't revert back
731                             // to the maximum time horizon (or we could end up in infinite recursion when there are
732                             // loops in the network).
733                             Length value =
734                                 suitability(connectingLane, new Length(0, LengthUnit.SI), gtu, new Duration(spareTime,
735                                     TimeUnit.SI));
736                             // Use the minimum of the value computed for the first split junction (if there is one)
737                             // and the value computed for the second split junction.
738                             suitabilityOfLanesBeforeBranch.put(l, null == currentValue || value.le(currentValue) ? value
739                                 : currentValue);
740                         }
741                     }
742                 }
743             }
744         }
745         if (suitabilityOfLanesBeforeBranch.size() == 0)
746         {
747             throw new NetworkException("No lanes available on Link " + linkBeforeBranch);
748         }
749         Length currentLaneSuitability = suitabilityOfLanesBeforeBranch.get(currentLane);
750         if (null != currentLaneSuitability)
751         {
752             return currentLaneSuitability; // Following the current lane will keep us on the Route
753         }
754         // Performing one or more lane changes (left or right) is required.
755         int totalLanes = countCompatibleLanes(currentLane.getParentLink(), gtu.getGTUType());
756         Length leftSuitability =
757             computeSuitabilityWithLaneChanges(currentLane, remainingDistance, suitabilityOfLanesBeforeBranch, totalLanes,
758                 LateralDirectionality.LEFT, gtu.getGTUType());
759         Length rightSuitability =
760             computeSuitabilityWithLaneChanges(currentLane, remainingDistance, suitabilityOfLanesBeforeBranch, totalLanes,
761                 LateralDirectionality.RIGHT, gtu.getGTUType());
762         if (leftSuitability.ge(rightSuitability))
763         {
764             return leftSuitability;
765         }
766         else if (rightSuitability.ge(leftSuitability))
767         {
768             return rightSuitability;
769         }
770         if (leftSuitability.le(GETOFFTHISLANENOW))
771         {
772             throw new NetworkException("Changing lanes in any direction does not get the GTU on a suitable lane");
773         }
774         return leftSuitability; // left equals right; this is odd but topologically possible
775     }
776 
777     /**
778      * Compute the suitability of a lane from which lane changes are required to get to the next point on the Route.<br>
779      * This method weighs the suitability of the nearest suitable lane by (m - n) / m where n is the number of lane changes
780      * required and m is the total number of lanes in the CrossSectionLink.
781      * @param startLane Lane; the current lane of the GTU
782      * @param remainingDistance double; distance in m of GTU to first branch
783      * @param suitabilities Map&lt;Lane, Double&gt;; the set of suitable lanes and their suitability
784      * @param totalLanes integer; total number of lanes compatible with the GTU type
785      * @param direction LateralDirectionality; the direction of the lane changes to attempt
786      * @param gtuType GTUType; the type of the GTU
787      * @return double; the suitability of the <cite>startLane</cite> for following the Route
788      */
789     protected final Length computeSuitabilityWithLaneChanges(final Lane startLane, final double remainingDistance,
790         final Map<Lane, Length> suitabilities, final int totalLanes, final LateralDirectionality direction,
791         final GTUType gtuType)
792     {
793         /*-
794          * The time per required lane change seems more relevant than distance per required lane change.
795          * Total time required does not grow linearly with the number of required lane changes. Logarithmic, arc tangent 
796          * is more like it.
797          * Rijkswaterstaat appears to use a fixed time for ANY number of lane changes (about 60s). 
798          * TomTom navigation systems give more time (about 90s).
799          * In this method the returned suitability decreases linearly with the number of required lane changes. This
800          * ensures that there is a gradient that coaches the GTU towards the most suitable lane.
801          */
802         int laneChangesUsed = 0;
803         Lane currentLane = startLane;
804         Length currentSuitability = null;
805         while (null == currentSuitability)
806         {
807             laneChangesUsed++;
808             if (currentLane.accessibleAdjacentLanes(direction, gtuType).size() == 0)
809             {
810                 return GETOFFTHISLANENOW;
811             }
812             currentLane = currentLane.accessibleAdjacentLanes(direction, gtuType).iterator().next();
813             currentSuitability = suitabilities.get(currentLane);
814         }
815         double fraction = currentSuitability == NOLANECHANGENEEDED ? 0 : 0.5;
816         int notSuitableLaneCount = totalLanes - suitabilities.size();
817         return new Length(remainingDistance * (notSuitableLaneCount - laneChangesUsed + 1 + fraction)
818             / (notSuitableLaneCount + fraction), LengthUnit.SI);
819     }
820 
821     /**
822      * Determine how many lanes on a CrossSectionLink are compatible with a particular GTU type.<br>
823      * TODO: this method should probably be moved into the CrossSectionLink class
824      * @param link CrossSectionLink; the link
825      * @param gtuType GTUType; the GTU type
826      * @return integer; the number of lanes on the link that are compatible with the GTU type
827      */
828     protected final int countCompatibleLanes(final CrossSectionLink link, final GTUType gtuType)
829     {
830         int result = 0;
831         for (CrossSectionElement cse : link.getCrossSectionElementList())
832         {
833             if (cse instanceof Lane)
834             {
835                 Lane l = (Lane) cse;
836                 if (l.getLaneType().isCompatible(gtuType))
837                 {
838                     result++;
839                 }
840             }
841         }
842         return result;
843     }
844 
845     /** {@inheritDoc} */
846     @Override
847     public final String toString()
848     {
849         return "LaneBasedCFLCTacticalPlanner [laneChangeModel=" + this.laneChangeModel + "]";
850     }
851 
852 }