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.HashSet;
6   import java.util.List;
7   import java.util.Set;
8   
9   import org.djunits.unit.AccelerationUnit;
10  import org.djunits.unit.TimeUnit;
11  import org.djunits.value.vdouble.scalar.Acceleration;
12  import org.djunits.value.vdouble.scalar.Duration;
13  import org.djunits.value.vdouble.scalar.Length;
14  import org.djunits.value.vdouble.scalar.Time;
15  import org.opentrafficsim.core.gtu.GTUDirectionality;
16  import org.opentrafficsim.core.gtu.GTUException;
17  import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
18  import org.opentrafficsim.core.gtu.behavioralcharacteristics.BehavioralCharacteristics;
19  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
20  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
21  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
22  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan.Segment;
23  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
24  import org.opentrafficsim.core.network.LateralDirectionality;
25  import org.opentrafficsim.core.network.NetworkException;
26  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
27  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
28  import org.opentrafficsim.road.gtu.lane.perception.categories.DefaultSimplePerception;
29  import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
30  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedAltruistic;
31  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedEgoistic;
32  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedLaneChangeModel;
33  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedLaneMovementStep;
34  import org.opentrafficsim.road.gtu.lane.tactical.following.AccelerationStep;
35  import org.opentrafficsim.road.gtu.lane.tactical.following.GTUFollowingModelOld;
36  import org.opentrafficsim.road.network.lane.Lane;
37  
38  import nl.tudelft.simulation.language.d3.DirectedPoint;
39  
40  /**
41   * Lane-based tactical planner that implements car following behavior and rule-based lane change. This tactical planner
42   * retrieves the car following model from the strategical planner and will generate an operational plan for the GTU.
43   * <p>
44   * A lane change occurs when:
45   * <ol>
46   * <li>The route indicates that the current lane does not lead to the destination; main choices are the time when the GTU
47   * switches to the "right" lane, and what should happen when the split gets closer and the lane change has failed. Observations
48   * indicate that vehicles if necessary stop in their current lane until they can go to the desired lane. A lane drop is
49   * automatically part of this implementation, because the lane with a lane drop will not lead to the GTU's destination.</li>
50   * <li>The desired speed of the vehicle is a particular delta-speed higher than its predecessor, the headway to the predecessor
51   * in the current lane has exceeded a certain value, it is allowed to change to the target lane, the target lane lies on the
52   * GTU's route, and the gap in the target lane is acceptable (including the evaluation of the perceived speed of a following GTU
53   * in the target lane).</li>
54   * <li>The current lane is not the optimum lane given the traffic rules (for example, to keep right), the headway to the
55   * predecessor on the target lane is greater than a certain value, the speed of the predecessor on the target lane is greater
56   * than or equal to our speed, the target lane is on the route, it is allowed to switch to the target lane, and the gap at the
57   * target lane is acceptable (including the perceived speed of any vehicle in front or behind on the target lane).</li>
58   * </ol>
59   * <p>
60   * This lane-based tactical planner makes decisions based on headway (GTU following model). It can ask the strategic planner for
61   * assistance on the route to take when the network splits.
62   * <p>
63   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
64   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
65   * </p>
66   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
67   * initial version Nov 25, 2015 <br>
68   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
69   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
70   */
71  public class LaneBasedGTUFollowingDirectedChangeTacticalPlanner extends AbstractLaneBasedTacticalPlanner
72  {
73      /** */
74      private static final long serialVersionUID = 20160129L;
75  
76      /** Earliest next lane change time (unless we HAVE to change lanes). */
77      private Time earliestNextLaneChangeTime = Time.ZERO;
78  
79      /** Lane we changed to at instantaneous lane change. */
80      private Lane laneAfterLaneChange = null;
81  
82      /** Position on the reference lane. */
83      private Length posAfterLaneChange = null;
84  
85      /** When a failure in planning occurs, should we destroy the GTU to avoid halting of the model? */
86      private boolean destroyGtuOnFailure = false;
87  
88      /**
89       * Instantiated a tactical planner with just GTU following behavior and no lane changes.
90       * @param carFollowingModel Car-following model.
91       * @param gtu GTU
92       */
93      public LaneBasedGTUFollowingDirectedChangeTacticalPlanner(final GTUFollowingModelOld carFollowingModel,
94              final LaneBasedGTU gtu)
95      {
96          super(carFollowingModel, gtu);
97          getPerception().addPerceptionCategory(new DefaultSimplePerception(getPerception()));
98      }
99  
100     /**
101      * Returns the car-following model.
102      * @return The car-following model.
103      */
104     public final GTUFollowingModelOld getCarFollowingModelOld()
105     {
106         return (GTUFollowingModelOld) super.getCarFollowingModel();
107     }
108 
109     /** {@inheritDoc} */
110     @Override
111     @SuppressWarnings("checkstyle:methodlength")
112     public final OperationalPlan generateOperationalPlan(final Time startTime, final DirectedPoint locationAtStartTime)
113             throws OperationalPlanException, NetworkException, GTUException, ParameterException
114     {
115         try
116         {
117             // ask Perception for the local situation
118             LaneBasedGTU laneBasedGTU = getGtu();
119             DefaultSimplePerception simplePerception = getPerception().getPerceptionCategory(DefaultSimplePerception.class);
120             BehavioralCharacteristics behavioralCharacteristics = laneBasedGTU.getBehavioralCharacteristics();
121 
122             // start with the turn indicator off -- this can change during the method
123             laneBasedGTU.setTurnIndicatorStatus(TurnIndicatorStatus.NONE);
124 
125             // if the GTU's maximum speed is zero (block), generate a stand still plan for one second
126             if (laneBasedGTU.getMaximumSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
127             {
128                 return new OperationalPlan(getGtu(), locationAtStartTime, startTime, new Duration(1.0, TimeUnit.SECOND));
129             }
130 
131             // perceive the forward headway, accessible lanes and speed limit.
132             simplePerception.updateForwardHeadwayGTU();
133             simplePerception.updateForwardHeadwayObject();
134             simplePerception.updateAccessibleAdjacentLanesLeft();
135             simplePerception.updateAccessibleAdjacentLanesRight();
136             simplePerception.updateSpeedLimit();
137 
138             // find out where we are going
139             Length forwardHeadway = behavioralCharacteristics.getParameter(ParameterTypes.LOOKAHEAD);
140             LanePathInfo lanePathInfo = buildLanePathInfo(laneBasedGTU, forwardHeadway);
141             NextSplitInfo nextSplitInfo = determineNextSplit(laneBasedGTU, forwardHeadway);
142             Set<Lane> correctLanes = laneBasedGTU.positions(laneBasedGTU.getReference()).keySet();
143             correctLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
144 
145             // Step 1: Do we want to change lanes because of the current lane not leading to our destination?
146             if (lanePathInfo.getPath().getLength().lt(forwardHeadway) && correctLanes.isEmpty())
147             {
148                 LateralDirectionality direction = determineLeftRight(laneBasedGTU, nextSplitInfo);
149                 if (direction != null)
150                 {
151                     getGtu().setTurnIndicatorStatus(direction.isLeft() ? TurnIndicatorStatus.LEFT : TurnIndicatorStatus.RIGHT);
152                     if (canChange(laneBasedGTU, getPerception(), lanePathInfo, direction))
153                     {
154                         DirectedPoint newLocation = changeLane(laneBasedGTU, direction);
155                         lanePathInfo = buildLanePathInfo(laneBasedGTU, forwardHeadway, this.laneAfterLaneChange,
156                                 this.posAfterLaneChange, laneBasedGTU.getDirection(this.laneAfterLaneChange));
157                         return currentLanePlan(laneBasedGTU, startTime, newLocation, lanePathInfo);
158                     }
159                 }
160             }
161 
162             // Condition, if we have just changed lane, let's not change immediately again.
163             if (getGtu().getSimulator().getSimulatorTime().getTime().lt(this.earliestNextLaneChangeTime))
164             {
165                 return currentLanePlan(laneBasedGTU, startTime, locationAtStartTime, lanePathInfo);
166             }
167 
168             // Step 2. Do we want to change lanes to the left because of predecessor speed on the current lane?
169             // And does the lane left of us bring us to our destination as well?
170             Set<Lane> leftLanes = simplePerception.getAccessibleAdjacentLanesLeft().get(lanePathInfo.getReferenceLane());
171             if (nextSplitInfo.isSplit())
172             {
173                 leftLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
174             }
175             if (!leftLanes.isEmpty() && laneBasedGTU.getSpeed().si > 4.0) // only if we are driving...
176             {
177                 simplePerception.updateBackwardHeadway();
178                 simplePerception.updateParallelHeadwaysLeft();
179                 simplePerception.updateNeighboringHeadwaysLeft();
180                 if (simplePerception.getParallelHeadwaysLeft().isEmpty())
181                 {
182                     Collection<Headway> sameLaneTraffic = new HashSet<>();
183                     // TODO should it be getObjectType().isGtu() or !getObjectType().isDistanceOnly() ?
184                     // XXX Object & GTU
185                     if (simplePerception.getForwardHeadwayGTU() != null
186                             && simplePerception.getForwardHeadwayGTU().getObjectType().isGtu())
187                     {
188                         sameLaneTraffic.add(simplePerception.getForwardHeadwayGTU());
189                     }
190                     if (simplePerception.getBackwardHeadway() != null
191                             && simplePerception.getBackwardHeadway().getObjectType().isGtu())
192                     {
193                         sameLaneTraffic.add(simplePerception.getBackwardHeadway());
194                     }
195                     DirectedLaneChangeModel dlcm = new DirectedAltruistic(getPerception());
196                     DirectedLaneMovementStep dlms = dlcm.computeLaneChangeAndAcceleration(laneBasedGTU,
197                             LateralDirectionality.LEFT, sameLaneTraffic, simplePerception.getNeighboringHeadwaysLeft(),
198                             behavioralCharacteristics.getParameter(ParameterTypes.LOOKAHEAD), simplePerception.getSpeedLimit(),
199                             new Acceleration(1.0, AccelerationUnit.SI), new Acceleration(0.5, AccelerationUnit.SI),
200                             new Duration(0.5, TimeUnit.SECOND));
201                     if (dlms.getLaneChange() != null)
202                     {
203                         getGtu().setTurnIndicatorStatus(TurnIndicatorStatus.LEFT);
204                         if (canChange(laneBasedGTU, getPerception(), lanePathInfo, LateralDirectionality.LEFT))
205                         {
206                             DirectedPoint newLocation = changeLane(laneBasedGTU, LateralDirectionality.LEFT);
207                             lanePathInfo = buildLanePathInfo(laneBasedGTU, forwardHeadway, this.laneAfterLaneChange,
208                                     this.posAfterLaneChange, laneBasedGTU.getDirection(this.laneAfterLaneChange));
209                             return currentLanePlan(laneBasedGTU, startTime, newLocation, lanePathInfo);
210                         }
211                     }
212                 }
213             }
214 
215             // Step 3. Do we want to change lanes to the right because of TODO traffic rules?
216             Set<Lane> rightLanes = simplePerception.getAccessibleAdjacentLanesRight().get(lanePathInfo.getReferenceLane());
217             if (nextSplitInfo.isSplit())
218             {
219                 rightLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
220             }
221             if (!rightLanes.isEmpty() && laneBasedGTU.getSpeed().si > 4.0) // only if we are driving...
222             {
223                 simplePerception.updateBackwardHeadway();
224                 simplePerception.updateParallelHeadwaysRight();
225                 simplePerception.updateNeighboringHeadwaysRight();
226                 if (simplePerception.getParallelHeadwaysRight().isEmpty())
227                 {
228                     Collection<Headway> sameLaneTraffic = new HashSet<>();
229                     // TODO should it be getObjectType().isGtu() or !getObjectType().isDistanceOnly() ?
230                     // XXX GTU & Object
231                     if (simplePerception.getForwardHeadwayGTU() != null
232                             && simplePerception.getForwardHeadwayGTU().getObjectType().isGtu())
233                     {
234                         sameLaneTraffic.add(simplePerception.getForwardHeadwayGTU());
235                     }
236                     if (simplePerception.getBackwardHeadway() != null
237                             && simplePerception.getBackwardHeadway().getObjectType().isGtu())
238                     {
239                         sameLaneTraffic.add(simplePerception.getBackwardHeadway());
240                     }
241                     DirectedLaneChangeModel dlcm = new DirectedAltruistic(getPerception());
242                     DirectedLaneMovementStep dlms = dlcm.computeLaneChangeAndAcceleration(laneBasedGTU,
243                             LateralDirectionality.RIGHT, sameLaneTraffic, simplePerception.getNeighboringHeadwaysRight(),
244                             behavioralCharacteristics.getParameter(ParameterTypes.LOOKAHEAD), simplePerception.getSpeedLimit(),
245                             new Acceleration(1.0, AccelerationUnit.SI), new Acceleration(0.5, AccelerationUnit.SI),
246                             new Duration(0.5, TimeUnit.SECOND));
247                     if (dlms.getLaneChange() != null)
248                     {
249                         getGtu().setTurnIndicatorStatus(TurnIndicatorStatus.RIGHT);
250                         if (canChange(laneBasedGTU, getPerception(), lanePathInfo, LateralDirectionality.RIGHT))
251                         {
252                             DirectedPoint newLocation = changeLane(laneBasedGTU, LateralDirectionality.RIGHT);
253                             lanePathInfo = buildLanePathInfo(laneBasedGTU, forwardHeadway, this.laneAfterLaneChange,
254                                     this.posAfterLaneChange, laneBasedGTU.getDirection(this.laneAfterLaneChange));
255                             return currentLanePlan(laneBasedGTU, startTime, newLocation, lanePathInfo);
256                         }
257                     }
258                 }
259             }
260 
261             return currentLanePlan(laneBasedGTU, startTime, locationAtStartTime, lanePathInfo);
262         }
263         catch (GTUException | NetworkException | OperationalPlanException exception)
264         {
265             if (isDestroyGtuOnFailure())
266             {
267                 System.err.println("LaneBasedGTUFollowingChange0TacticalPlanner.generateOperationalPlan() failed for "
268                         + getGtu() + " because of " + exception.getMessage() + " -- GTU destroyed");
269                 getGtu().destroy();
270                 return new OperationalPlan(getGtu(), locationAtStartTime, startTime, new Duration(1.0, TimeUnit.SECOND));
271             }
272             throw exception;
273         }
274     }
275 
276     /**
277      * Make a plan for the current lane.
278      * @param laneBasedGTU the gtu to generate the plan for
279      * @param startTime the time from which the new operational plan has to be operational
280      * @param locationAtStartTime the location of the GTU at the start time of the new plan
281      * @param lanePathInfo the lane path for the current lane.
282      * @return An operation plan for staying in the current lane.
283      * @throws OperationalPlanException when there is a problem planning a path in the network
284      * @throws GTUException when there is a problem with the state of the GTU when planning a path
285      * @throws ParameterException in case LOOKAHEAD parameter cannot be found
286      * @throws NetworkException in case the headways to GTUs or objects cannot be calculated
287      */
288     private OperationalPlan currentLanePlan(final LaneBasedGTU laneBasedGTU, final Time startTime,
289             final DirectedPoint locationAtStartTime, final LanePathInfo lanePathInfo)
290             throws OperationalPlanException, GTUException, ParameterException, NetworkException
291     {
292         DefaultSimplePerception simplePerception = getPerception().getPerceptionCategory(DefaultSimplePerception.class);
293 
294         // No lane change. Continue on current lane.
295         AccelerationStep accelerationStep = mostLimitingAccelerationStep(lanePathInfo, simplePerception.getForwardHeadwayGTU(),
296                 simplePerception.getForwardHeadwayObject());
297 
298         // see if we have to continue standing still. In that case, generate a stand still plan
299         if (accelerationStep.getAcceleration().si < 1E-6 && laneBasedGTU.getSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
300         {
301             return new OperationalPlan(laneBasedGTU, locationAtStartTime, startTime, accelerationStep.getDuration());
302         }
303 
304         // build a list of lanes forward, with a maximum headway.
305         List<Segment> operationalPlanSegmentList = new ArrayList<>();
306         if (accelerationStep.getAcceleration().si == 0.0)
307         {
308             Segment segment = new OperationalPlan.SpeedSegment(accelerationStep.getDuration());
309             operationalPlanSegmentList.add(segment);
310         }
311         else
312         {
313             Segment segment =
314                     new OperationalPlan.AccelerationSegment(accelerationStep.getDuration(), accelerationStep.getAcceleration());
315             operationalPlanSegmentList.add(segment);
316         }
317         OperationalPlan op = new OperationalPlan(laneBasedGTU, lanePathInfo.getPath(), startTime, laneBasedGTU.getSpeed(),
318                 operationalPlanSegmentList);
319         return op;
320     }
321 
322     /**
323      * We are not on a lane that leads to our destination. Determine whether the lateral direction to go is left or right.
324      * @param laneBasedGTU the gtu
325      * @param nextSplitInfo the information about the next split
326      * @return the lateral direction to go, or null if this cannot be determined
327      */
328     private LateralDirectionality determineLeftRight(final LaneBasedGTU laneBasedGTU, final NextSplitInfo nextSplitInfo)
329     {
330         // are the lanes in nextSplitInfo.getCorrectCurrentLanes() left or right of the current lane(s) of the GTU?
331         try
332         {
333             Set<Lane> lanes = laneBasedGTU.positions(laneBasedGTU.getReference()).keySet();
334             for (Lane correctLane : nextSplitInfo.getCorrectCurrentLanes())
335             {
336                 for (Lane currentLane : lanes)
337                 {
338                     if (correctLane.getParentLink().equals(currentLane.getParentLink()))
339                     {
340                         double deltaOffset =
341                                 correctLane.getDesignLineOffsetAtBegin().si - currentLane.getDesignLineOffsetAtBegin().si;
342                         if (laneBasedGTU.getDirection(currentLane).equals(GTUDirectionality.DIR_PLUS))
343                         {
344                             return deltaOffset > 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
345                         }
346                         else
347                         {
348                             return deltaOffset < 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
349                         }
350                     }
351                 }
352             }
353         }
354         catch (GTUException exception)
355         {
356             System.err.println(
357                     "Exception in LaneBasedGTUFollowingChange0TacticalPlanner.determineLeftRight: " + exception.getMessage());
358         }
359         return null;
360     }
361 
362     /**
363      * See if a lane change in the given direction if possible.
364      * @param gtu the GTU that has to make the lane change
365      * @param perception the perception, where forward headway, accessible lanes and speed limit have been assessed
366      * @param lanePathInfo the information for the path on the current lane
367      * @param direction the lateral direction, either LEFT or RIGHT
368      * @return whether a lane change is possible.
369      * @throws NetworkException when there is a network inconsistency in updating the perception
370      * @throws GTUException when there is an issue retrieving GTU information for the perception update
371      * @throws ParameterException when there is a parameter problem.
372      * @throws OperationalPlanException in case a perception category is not present
373      */
374     private boolean canChange(final LaneBasedGTU gtu, final LanePerception perception, final LanePathInfo lanePathInfo,
375             final LateralDirectionality direction)
376             throws GTUException, NetworkException, ParameterException, OperationalPlanException
377     {
378         Collection<Headway> otherLaneTraffic;
379         DefaultSimplePerception simplePerception = getPerception().getPerceptionCategory(DefaultSimplePerception.class);
380         simplePerception.updateForwardHeadwayGTU();
381         simplePerception.updateForwardHeadwayObject();
382         simplePerception.updateBackwardHeadway();
383         if (direction.isLeft())
384         {
385             simplePerception.updateParallelHeadwaysLeft();
386             simplePerception.updateNeighboringHeadwaysLeft();
387             otherLaneTraffic = simplePerception.getNeighboringHeadwaysLeft();
388         }
389         else if (direction.isRight())
390         {
391             simplePerception.updateParallelHeadwaysRight();
392             simplePerception.updateNeighboringHeadwaysRight();
393             otherLaneTraffic = simplePerception.getNeighboringHeadwaysRight();
394         }
395         else
396         {
397             throw new GTUException("Lateral direction is neither LEFT nor RIGHT during a lane change");
398         }
399         if (!simplePerception.getParallelHeadways(direction).isEmpty())
400         {
401             return false;
402         }
403 
404         Collection<Headway> sameLaneTraffic = new HashSet<>();
405         // TODO should it be getObjectType().isGtu() or !getObjectType().isDistanceOnly() ?
406         // XXX Object & GTU
407         if (simplePerception.getForwardHeadwayGTU() != null && perception.getPerceptionCategory(DefaultSimplePerception.class)
408                 .getForwardHeadwayGTU().getObjectType().isGtu())
409         {
410             sameLaneTraffic.add(simplePerception.getForwardHeadwayGTU());
411         }
412         if (simplePerception.getBackwardHeadway() != null && simplePerception.getBackwardHeadway().getObjectType().isGtu())
413         {
414             sameLaneTraffic.add(simplePerception.getBackwardHeadway());
415         }
416 
417         // TODO make type of plan (Egoistic, Altruistic) parameter of the class
418         DirectedLaneChangeModel dlcm = new DirectedEgoistic(getPerception());
419         // TODO make the elasticities 2.0 and 0.1 parameters of the class
420         DirectedLaneMovementStep dlms = dlcm.computeLaneChangeAndAcceleration(gtu, direction, sameLaneTraffic, otherLaneTraffic,
421                 gtu.getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD), simplePerception.getSpeedLimit(),
422                 new Acceleration(2.0, AccelerationUnit.SI), new Acceleration(0.1, AccelerationUnit.SI),
423                 new Duration(0.5, TimeUnit.SECOND));
424         if (dlms.getLaneChange() == null)
425         {
426             return false;
427         }
428 
429         return true;
430     }
431 
432     /**
433      * Change lanes instantaneously.
434      * @param gtu the gtu
435      * @param direction the direction
436      * @return the new location of the GTU after the lane change
437      * @throws GTUException in case the enter lane fails
438      */
439     private DirectedPoint changeLane(final LaneBasedGTU gtu, final LateralDirectionality direction) throws GTUException
440     {
441         gtu.changeLaneInstantaneously(direction);
442 
443         // stay at least 15 seconds in the current lane (unless we HAVE to change lanes)
444         this.earliestNextLaneChangeTime =
445                 gtu.getSimulator().getSimulatorTime().getTime().plus(new Duration(15, TimeUnit.SECOND));
446 
447         // make sure out turn indicator is on!
448         gtu.setTurnIndicatorStatus(direction.isLeft() ? TurnIndicatorStatus.LEFT : TurnIndicatorStatus.RIGHT);
449 
450         this.laneAfterLaneChange = gtu.getReferencePosition().getLane();
451         this.posAfterLaneChange = gtu.getReferencePosition().getPosition();
452         return gtu.getLocation();
453     }
454 
455     /**
456      * Calculate which Headway in front of us is leading to the most limiting acceleration step (i.e. to the lowest or most
457      * negative acceleration). There could, e.g. be a GTU in front of us, a speed sign in front of us, and a traffic light in
458      * front of the GTU and speed sign. This method will return the acceleration based on the headway that limits us most.<br>
459      * The method can e.g., be called with:
460      * <code>mostLimitingHeadway(simplePerception.getForwardHeadwayGTU(), simplePerception.getForwardHeadwayObject());</code>
461      * @param lanePathInfo the lane path info that was calculated for this GTU.
462      * @param headways zero or more headways specifying possible limitations on our acceleration.
463      * @return the acceleration based on the most limiting headway.
464      * @throws OperationalPlanException in case the PerceptionCategory cannot be found
465      * @throws ParameterException in case LOOKAHEAD parameter cannot be found
466      * @throws GTUException in case the AccelerationStep cannot be calculated
467      * @throws NetworkException in case the headways to GTUs or objects cannot be calculated
468      */
469     private AccelerationStep mostLimitingAccelerationStep(final LanePathInfo lanePathInfo, final Headway... headways)
470             throws OperationalPlanException, ParameterException, GTUException, NetworkException
471     {
472         DefaultSimplePerception simplePerception = getPerception().getPerceptionCategory(DefaultSimplePerception.class);
473         simplePerception.updateForwardHeadwayGTU();
474         simplePerception.updateForwardHeadwayObject();
475         Length maxDistance = Length.min(getGtu().getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD),
476                 lanePathInfo.getPath().getLength().minus(getGtu().getLength().multiplyBy(2.0)));
477         AccelerationStep mostLimitingAccelerationStep = getCarFollowingModelOld().computeAccelerationStepWithNoLeader(getGtu(),
478                 maxDistance, simplePerception.getSpeedLimit());
479         for (Headway headway : headways)
480         {
481             if (headway != null && headway.getDistance().lt(maxDistance))
482             {
483                 AccelerationStep accelerationStep = getCarFollowingModelOld().computeAccelerationStep(getGtu(),
484                         headway.getSpeed(), headway.getDistance(), maxDistance, simplePerception.getSpeedLimit());
485                 if (accelerationStep.getAcceleration().lt(mostLimitingAccelerationStep.getAcceleration()))
486                 {
487                     mostLimitingAccelerationStep = accelerationStep;
488                 }
489             }
490         }
491         return mostLimitingAccelerationStep;
492     }
493 
494     /**
495      * @return destroyGtuOnFailure, indicating when a failure in planning occurs, whether we should destroy the GTU to avoid
496      *         halting of the model
497      */
498     public final boolean isDestroyGtuOnFailure()
499     {
500         return this.destroyGtuOnFailure;
501     }
502 
503     /**
504      * When a failure in planning occurs, should we destroy the GTU to avoid halting of the model?
505      * @param destroyGtuOnFailure set destroyGtuOnFailure to true or false
506      */
507     public final void setDestroyGtuOnFailure(final boolean destroyGtuOnFailure)
508     {
509         this.destroyGtuOnFailure = destroyGtuOnFailure;
510     }
511 
512     /** {@inheritDoc} */
513     @Override
514     public final String toString()
515     {
516         return "LaneBasedGTUFollowingChange0TacticalPlanner [earliestNexLaneChangeTime=" + this.earliestNextLaneChangeTime
517                 + ", referenceLane=" + this.laneAfterLaneChange + ", referencePos=" + this.posAfterLaneChange
518                 + ", destroyGtuOnFailure=" + this.destroyGtuOnFailure + "]";
519     }
520 
521 }