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