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.Map;
8   import java.util.Set;
9   
10  import org.djunits.unit.AccelerationUnit;
11  import org.djunits.unit.DurationUnit;
12  import org.djunits.unit.LengthUnit;
13  import org.djunits.value.vdouble.scalar.Acceleration;
14  import org.djunits.value.vdouble.scalar.Duration;
15  import org.djunits.value.vdouble.scalar.Length;
16  import org.djunits.value.vdouble.scalar.Speed;
17  import org.djunits.value.vdouble.scalar.Time;
18  import org.opentrafficsim.core.gtu.GTUDirectionality;
19  import org.opentrafficsim.core.gtu.GTUException;
20  import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
21  import org.opentrafficsim.core.gtu.behavioralcharacteristics.BehavioralCharacteristics;
22  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
23  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
24  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
25  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan.Segment;
26  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
27  import org.opentrafficsim.core.network.LateralDirectionality;
28  import org.opentrafficsim.core.network.NetworkException;
29  import org.opentrafficsim.road.gtu.lane.AbstractLaneBasedGTU;
30  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
31  import org.opentrafficsim.road.gtu.lane.perception.CategorialLanePerception;
32  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
33  import org.opentrafficsim.road.gtu.lane.perception.categories.DefaultSimplePerception;
34  import org.opentrafficsim.road.gtu.lane.perception.categories.DirectDefaultSimplePerception;
35  import org.opentrafficsim.road.gtu.lane.perception.headway.AbstractHeadwayGTU;
36  import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
37  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedAltruistic;
38  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedEgoistic;
39  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedLaneChangeModel;
40  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedLaneMovementStep;
41  import org.opentrafficsim.road.gtu.lane.tactical.following.AccelerationStep;
42  import org.opentrafficsim.road.gtu.lane.tactical.following.GTUFollowingModelOld;
43  import org.opentrafficsim.road.network.lane.Lane;
44  import org.opentrafficsim.road.network.lane.object.sensor.SingleSensor;
45  import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor;
46  
47  import nl.tudelft.simulation.dsol.SimRuntimeException;
48  import nl.tudelft.simulation.language.Throw;
49  import nl.tudelft.simulation.language.d3.DirectedPoint;
50  
51  /**
52   * Lane-based tactical planner that implements car following behavior and rule-based lane change. This tactical planner
53   * retrieves the car following model from the strategical planner and will generate an operational plan for the GTU.
54   * <p>
55   * A lane change occurs when:
56   * <ol>
57   * <li>The route indicates that the current lane does not lead to the destination; main choices are the time when the GTU
58   * switches to the "right" lane, and what should happen when the split gets closer and the lane change has failed. Observations
59   * indicate that vehicles if necessary stop in their current lane until they can go to the desired lane. A lane drop is
60   * automatically part of this implementation, because the lane with a lane drop will not lead to the GTU's destination.</li>
61   * <li>The desired speed of the vehicle is a particular delta-speed higher than its predecessor, the headway to the predecessor
62   * in the current lane has exceeded a certain value, it is allowed to change to the target lane, the target lane lies on the
63   * GTU's route, and the gap in the target lane is acceptable (including the evaluation of the perceived speed of a following GTU
64   * in the target lane).</li>
65   * <li>The current lane is not the optimum lane given the traffic rules (for example, to keep right), the headway to the
66   * predecessor on the target lane is greater than a certain value, the speed of the predecessor on the target lane is greater
67   * 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
68   * target lane is acceptable (including the perceived speed of any vehicle in front or behind on the target lane).</li>
69   * </ol>
70   * <p>
71   * This lane-based tactical planner makes decisions based on headway (GTU following model). It can ask the strategic planner for
72   * assistance on the route to take when the network splits.
73   * <p>
74   * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
75   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
76   * </p>
77   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
78   * initial version Nov 25, 2015 <br>
79   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
80   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
81   */
82  public class LaneBasedGTUFollowingDirectedChangeTacticalPlanner extends AbstractLaneBasedTacticalPlanner
83  {
84      /** */
85      private static final long serialVersionUID = 20160129L;
86  
87      /** Earliest next lane change time (unless we HAVE to change lanes). */
88      private Time earliestNextLaneChangeTime = Time.ZERO;
89  
90      /** Time a GTU should stay in its current lane after a lane change. */
91      private Duration durationInLaneAfterLaneChange = new Duration(15.0, DurationUnit.SECOND);
92  
93      /** Lane we changed to at instantaneous lane change. */
94      private Lane laneAfterLaneChange = null;
95  
96      /** Position on the reference lane. */
97      private Length posAfterLaneChange = null;
98  
99      /** When a failure in planning occurs, should we destroy the GTU to avoid halting of the model? */
100     private boolean destroyGtuOnFailure = false;
101 
102     /**
103      * Instantiated a tactical planner with just GTU following behavior and no lane changes.
104      * @param carFollowingModel Car-following model.
105      * @param gtu GTU
106      */
107     public LaneBasedGTUFollowingDirectedChangeTacticalPlanner(final GTUFollowingModelOld carFollowingModel,
108             final LaneBasedGTU gtu)
109     {
110         super(carFollowingModel, gtu, new CategorialLanePerception(gtu));
111         getPerception().addPerceptionCategory(new DirectDefaultSimplePerception(getPerception()));
112         setNoLaneChange(new Duration(0.25, DurationUnit.SECOND));
113     }
114 
115     /**
116      * Returns the car-following model.
117      * @return The car-following model.
118      */
119     public final GTUFollowingModelOld getCarFollowingModelOld()
120     {
121         return (GTUFollowingModelOld) super.getCarFollowingModel();
122     }
123 
124     /**
125      * Indicate that no lane change should happen for the indicated duration.
126      * @param noLaneChangeDuration the duration for which no lane change should happen.
127      */
128     public final void setNoLaneChange(final Duration noLaneChangeDuration)
129     {
130         Throw.when(noLaneChangeDuration.lt0(), RuntimeException.class, "noLaneChangeDuration should be >= 0");
131         this.earliestNextLaneChangeTime = getGtu().getSimulator().getSimulatorTime().getTime().plus(noLaneChangeDuration);
132     }
133 
134     /**
135      * Headway for synchronization.
136      */
137     private Headway syncHeadway;
138 
139     /**
140      * Headway for cooperation.
141      */
142     private Headway coopHeadway;
143 
144     /**
145      * Time when (potential) dead-lock was first recognized.
146      */
147     private Time deadLock = null;
148 
149     /**
150      * Time after which situation is labeled a dead-lock.
151      */
152     private final Duration deadLockThreshold = new Duration(5.0, DurationUnit.SI);
153 
154     /**
155      * Headways that are causing the dead-lock.
156      */
157     private Collection<Headway> blockingHeadways = new HashSet<>();
158 
159     /** {@inheritDoc} */
160     @Override
161     @SuppressWarnings("checkstyle:methodlength")
162     public final OperationalPlan generateOperationalPlan(final Time startTime, final DirectedPoint locationAtStartTime)
163             throws OperationalPlanException, NetworkException, GTUException, ParameterException
164     {
165         try
166         {
167 
168             // ask Perception for the local situation
169             LaneBasedGTU laneBasedGTU = getGtu();
170             DefaultSimplePerception simplePerception = getPerception().getPerceptionCategory(DefaultSimplePerception.class);
171             BehavioralCharacteristics behavioralCharacteristics = laneBasedGTU.getBehavioralCharacteristics();
172             // This is the only interaction between the car-following model and the behavioral characteristics
173             getCarFollowingModelOld().setA(behavioralCharacteristics.getParameter(ParameterTypes.A));
174             getCarFollowingModelOld().setT(behavioralCharacteristics.getParameter(ParameterTypes.T));
175             getCarFollowingModelOld().setFspeed(behavioralCharacteristics.getParameter(ParameterTypes.FSPEED));
176 
177             // start with the turn indicator off -- this can change during the method
178             laneBasedGTU.setTurnIndicatorStatus(TurnIndicatorStatus.NONE);
179 
180             // if the GTU's maximum speed is zero (block), generate a stand still plan for one second
181             if (laneBasedGTU.getMaximumSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
182             {
183                 return new OperationalPlan(getGtu(), locationAtStartTime, startTime, new Duration(1.0, DurationUnit.SECOND));
184             }
185 
186             // perceive the forward headway, accessible lanes and speed limit.
187             simplePerception.updateForwardHeadwayGTU();
188             simplePerception.updateForwardHeadwayObject();
189             simplePerception.updateAccessibleAdjacentLanesLeft();
190             simplePerception.updateAccessibleAdjacentLanesRight();
191             simplePerception.updateSpeedLimit();
192 
193             // find out where we are going
194             Length forwardHeadway = behavioralCharacteristics.getParameter(ParameterTypes.LOOKAHEAD);
195             LanePathInfo lanePathInfo = buildLanePathInfo(laneBasedGTU, forwardHeadway);
196             NextSplitInfo nextSplitInfo = determineNextSplit(laneBasedGTU, forwardHeadway);
197             Set<Lane> correctLanes = laneBasedGTU.positions(laneBasedGTU.getReference()).keySet();
198             correctLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
199 
200             // Step 1: Do we want to change lanes because of the current lane not leading to our destination?
201             this.syncHeadway = null;
202             if (lanePathInfo.getPath().getLength().lt(forwardHeadway) && correctLanes.isEmpty())
203             {
204                 LateralDirectionality direction = determineLeftRight(laneBasedGTU, nextSplitInfo);
205                 if (direction != null)
206                 {
207                     getGtu().setTurnIndicatorStatus(direction.isLeft() ? TurnIndicatorStatus.LEFT : TurnIndicatorStatus.RIGHT);
208                     if (canChange(laneBasedGTU, getPerception(), lanePathInfo, direction))
209                     {
210                         DirectedPoint newLocation = changeLane(laneBasedGTU, direction);
211                         lanePathInfo = buildLanePathInfo(laneBasedGTU, forwardHeadway, this.laneAfterLaneChange,
212                                 this.posAfterLaneChange, laneBasedGTU.getDirection(this.laneAfterLaneChange));
213                         return currentLanePlan(laneBasedGTU, startTime, newLocation, lanePathInfo);
214                     }
215                     else
216                     {
217                         simplePerception.updateNeighboringHeadways(direction);
218                         Length minDistance = new Length(Double.MAX_VALUE, LengthUnit.SI);
219                         for (Headway headway : simplePerception.getNeighboringHeadways(direction))
220                         {
221                             if ((headway.isAhead() || headway.isParallel()) && (headway instanceof AbstractHeadwayGTU))
222                             {
223                                 if (headway.isParallel() || headway.getDistance().lt(minDistance))
224                                 {
225                                     this.syncHeadway = headway;
226                                     if (!headway.isParallel())
227                                     {
228                                         minDistance = headway.getDistance();
229                                     }
230                                 }
231                             }
232                         }
233                     }
234                 }
235             }
236             if (this.syncHeadway != null && this.syncHeadway.isParallel() && getGtu().getSpeed().si < 10)
237             {
238                 // do not sync at low speeds when being parallel
239                 this.syncHeadway = null;
240             }
241 
242             // Cooperation
243             this.coopHeadway = null;
244             for (LateralDirectionality direction : new LateralDirectionality[] { LateralDirectionality.LEFT,
245                     LateralDirectionality.RIGHT })
246             {
247                 simplePerception.updateNeighboringHeadways(direction);
248                 for (Headway headway : simplePerception.getNeighboringHeadways(direction))
249                 {
250                     // other vehicle ahead, its a vehicle, its the nearest, and its indicator is on
251                     if (headway.isAhead() && (headway instanceof AbstractHeadwayGTU)
252                             && (this.coopHeadway == null || headway.getDistance().lt(this.coopHeadway.getDistance()))
253                             && (direction.isLeft() ? ((AbstractHeadwayGTU) headway).isRightTurnIndicatorOn()
254                                     : ((AbstractHeadwayGTU) headway).isLeftTurnIndicatorOn()))
255                     {
256                         this.coopHeadway = headway;
257                     }
258                 }
259             }
260 
261             // Condition, if we have just changed lane, let's not change immediately again.
262             if (getGtu().getSimulator().getSimulatorTime().getTime().lt(this.earliestNextLaneChangeTime))
263             {
264                 return currentLanePlan(laneBasedGTU, startTime, locationAtStartTime, lanePathInfo);
265             }
266 
267             // Step 2. Do we want to change lanes to the left because of predecessor speed on the current lane?
268             // And does the lane left of us bring us to our destination as well?
269             Set<Lane> leftLanes = simplePerception.getAccessibleAdjacentLanesLeft().get(lanePathInfo.getReferenceLane());
270             if (nextSplitInfo.isSplit())
271             {
272                 leftLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
273             }
274             if (!leftLanes.isEmpty()) // && laneBasedGTU.getSpeed().si > 4.0) // only if we are driving...
275             {
276                 simplePerception.updateBackwardHeadway();
277                 simplePerception.updateParallelHeadwaysLeft();
278                 simplePerception.updateNeighboringHeadwaysLeft();
279                 if (simplePerception.getParallelHeadwaysLeft().isEmpty())
280                 {
281                     Collection<Headway> sameLaneTraffic = new HashSet<>();
282                     // TODO should it be getObjectType().isGtu() or !getObjectType().isDistanceOnly() ?
283                     // XXX Object & GTU
284                     if (simplePerception.getForwardHeadwayGTU() != null
285                             && simplePerception.getForwardHeadwayGTU().getObjectType().isGtu())
286                     {
287                         sameLaneTraffic.add(simplePerception.getForwardHeadwayGTU());
288                     }
289                     if (simplePerception.getBackwardHeadway() != null
290                             && simplePerception.getBackwardHeadway().getObjectType().isGtu())
291                     {
292                         sameLaneTraffic.add(simplePerception.getBackwardHeadway());
293                     }
294                     DirectedLaneChangeModel dlcm = new DirectedAltruistic(getPerception());
295                     DirectedLaneMovementStep dlms = dlcm.computeLaneChangeAndAcceleration(laneBasedGTU,
296                             LateralDirectionality.LEFT, sameLaneTraffic, simplePerception.getNeighboringHeadwaysLeft(),
297                             behavioralCharacteristics.getParameter(ParameterTypes.LOOKAHEAD), simplePerception.getSpeedLimit(),
298                             // changes 1.0 to 0.0, no bias to the left: changed 0.5 to 0.1 (threshold from MOBIL model)
299                             Acceleration.ZERO, new Acceleration(0.5, AccelerationUnit.SI),
300                             new Duration(0.5, DurationUnit.SECOND));
301                     if (dlms.getLaneChange() != null)
302                     {
303                         getGtu().setTurnIndicatorStatus(TurnIndicatorStatus.LEFT);
304                         if (canChange(laneBasedGTU, getPerception(), lanePathInfo, LateralDirectionality.LEFT))
305                         {
306                             DirectedPoint newLocation = changeLane(laneBasedGTU, LateralDirectionality.LEFT);
307                             lanePathInfo = buildLanePathInfo(laneBasedGTU, forwardHeadway, this.laneAfterLaneChange,
308                                     this.posAfterLaneChange, laneBasedGTU.getDirection(this.laneAfterLaneChange));
309                             return currentLanePlan(laneBasedGTU, startTime, newLocation, lanePathInfo);
310                         }
311                     }
312                 }
313             }
314 
315             // Step 3. Do we want to change lanes to the right because of TODO traffic rules?
316             Set<Lane> rightLanes = simplePerception.getAccessibleAdjacentLanesRight().get(lanePathInfo.getReferenceLane());
317             if (nextSplitInfo.isSplit())
318             {
319                 rightLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
320             }
321             if (!rightLanes.isEmpty()) // && laneBasedGTU.getSpeed().si > 4.0) // only if we are driving...
322             {
323                 simplePerception.updateBackwardHeadway();
324                 simplePerception.updateParallelHeadwaysRight();
325                 simplePerception.updateNeighboringHeadwaysRight();
326                 if (simplePerception.getParallelHeadwaysRight().isEmpty())
327                 {
328                     Collection<Headway> sameLaneTraffic = new HashSet<>();
329                     // TODO should it be getObjectType().isGtu() or !getObjectType().isDistanceOnly() ?
330                     // XXX GTU & Object
331                     if (simplePerception.getForwardHeadwayGTU() != null
332                             && simplePerception.getForwardHeadwayGTU().getObjectType().isGtu())
333                     {
334                         sameLaneTraffic.add(simplePerception.getForwardHeadwayGTU());
335                     }
336                     if (simplePerception.getBackwardHeadway() != null
337                             && simplePerception.getBackwardHeadway().getObjectType().isGtu())
338                     {
339                         sameLaneTraffic.add(simplePerception.getBackwardHeadway());
340                     }
341                     DirectedLaneChangeModel dlcm = new DirectedAltruistic(getPerception());
342                     DirectedLaneMovementStep dlms = dlcm.computeLaneChangeAndAcceleration(laneBasedGTU,
343                             LateralDirectionality.RIGHT, sameLaneTraffic, simplePerception.getNeighboringHeadwaysRight(),
344                             behavioralCharacteristics.getParameter(ParameterTypes.LOOKAHEAD), simplePerception.getSpeedLimit(),
345                             // 1.0 = bias?
346                             Acceleration.ZERO, new Acceleration(0.1, AccelerationUnit.SI),
347                             new Duration(0.5, DurationUnit.SECOND));
348                     if (dlms.getLaneChange() != null)
349                     {
350                         getGtu().setTurnIndicatorStatus(TurnIndicatorStatus.RIGHT);
351                         if (canChange(laneBasedGTU, getPerception(), lanePathInfo, LateralDirectionality.RIGHT))
352                         {
353                             DirectedPoint newLocation = changeLane(laneBasedGTU, LateralDirectionality.RIGHT);
354                             lanePathInfo = buildLanePathInfo(laneBasedGTU, forwardHeadway, this.laneAfterLaneChange,
355                                     this.posAfterLaneChange, laneBasedGTU.getDirection(this.laneAfterLaneChange));
356                             return currentLanePlan(laneBasedGTU, startTime, newLocation, lanePathInfo);
357                         }
358                     }
359                 }
360             }
361 
362             if (this.deadLock != null
363                     && getGtu().getSimulator().getSimulatorTime().getTime().minus(this.deadLock).ge(this.deadLockThreshold)
364                     && isDestroyGtuOnFailure())
365             {
366                 System.err.println("Deleting gtu " + getGtu().getId() + " to prevent dead-lock.");
367                 try
368                 {
369                     getGtu().getSimulator().scheduleEventRel(new Duration(0.001, DurationUnit.SI), this, getGtu(), "destroy",
370                             new Object[0]);
371                 }
372                 catch (SimRuntimeException exception)
373                 {
374                     throw new RuntimeException(exception);
375                 }
376             }
377 
378             return currentLanePlan(laneBasedGTU, startTime, locationAtStartTime, lanePathInfo);
379         }
380         catch (GTUException | NetworkException | OperationalPlanException exception)
381         {
382             if (isDestroyGtuOnFailure())
383             {
384                 System.err.println("LaneBasedGTUFollowingChange0TacticalPlanner.generateOperationalPlan() failed for "
385                         + getGtu() + " because of " + exception.getMessage() + " -- GTU destroyed");
386                 getGtu().destroy();
387                 return new OperationalPlan(getGtu(), locationAtStartTime, startTime, new Duration(1.0, DurationUnit.SECOND));
388             }
389             throw exception;
390         }
391     }
392 
393     /**
394      * Make a plan for the current lane.
395      * @param laneBasedGTU the gtu to generate the plan for
396      * @param startTime the time from which the new operational plan has to be operational
397      * @param locationAtStartTime the location of the GTU at the start time of the new plan
398      * @param lanePathInfo the lane path for the current lane.
399      * @return An operation plan for staying in the current lane.
400      * @throws OperationalPlanException when there is a problem planning a path in the network
401      * @throws GTUException when there is a problem with the state of the GTU when planning a path
402      * @throws ParameterException in case LOOKAHEAD parameter cannot be found
403      * @throws NetworkException in case the headways to GTUs or objects cannot be calculated
404      */
405     private OperationalPlan currentLanePlan(final LaneBasedGTU laneBasedGTU, final Time startTime,
406             final DirectedPoint locationAtStartTime, final LanePathInfo lanePathInfo)
407             throws OperationalPlanException, GTUException, ParameterException, NetworkException
408     {
409         DefaultSimplePerception simplePerception = getPerception().getPerceptionCategory(DefaultSimplePerception.class);
410 
411         // No lane change. Continue on current lane.
412         AccelerationStep accelerationStep = mostLimitingAccelerationStep(lanePathInfo, simplePerception.getForwardHeadwayGTU(),
413                 simplePerception.getForwardHeadwayObject());
414 
415         // see if we have to continue standing still. In that case, generate a stand still plan
416         if (accelerationStep.getAcceleration().si < 1E-6 && laneBasedGTU.getSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
417         {
418             return new OperationalPlan(laneBasedGTU, locationAtStartTime, startTime, accelerationStep.getDuration());
419         }
420 
421         // build a list of lanes forward, with a maximum headway.
422         List<Segment> operationalPlanSegmentList = new ArrayList<>();
423         if (accelerationStep.getAcceleration().si == 0.0)
424         {
425             Segment segment = new OperationalPlan.SpeedSegment(accelerationStep.getDuration());
426             operationalPlanSegmentList.add(segment);
427         }
428         else
429         {
430             Segment segment =
431                     new OperationalPlan.AccelerationSegment(accelerationStep.getDuration(), accelerationStep.getAcceleration());
432             operationalPlanSegmentList.add(segment);
433         }
434         OperationalPlan op = new OperationalPlan(laneBasedGTU, lanePathInfo.getPath(), startTime, laneBasedGTU.getSpeed(),
435                 operationalPlanSegmentList);
436         return op;
437     }
438 
439     /**
440      * We are not on a lane that leads to our destination. Determine whether the lateral direction to go is left or right.
441      * @param laneBasedGTU the gtu
442      * @param nextSplitInfo the information about the next split
443      * @return the lateral direction to go, or null if this cannot be determined
444      */
445     private LateralDirectionality determineLeftRight(final LaneBasedGTU laneBasedGTU, final NextSplitInfo nextSplitInfo)
446     {
447         // are the lanes in nextSplitInfo.getCorrectCurrentLanes() left or right of the current lane(s) of the GTU?
448         try
449         {
450             Set<Lane> lanes = laneBasedGTU.positions(laneBasedGTU.getReference()).keySet();
451             for (Lane correctLane : nextSplitInfo.getCorrectCurrentLanes())
452             {
453                 for (Lane currentLane : lanes)
454                 {
455                     if (correctLane.getParentLink().equals(currentLane.getParentLink()))
456                     {
457                         double deltaOffset =
458                                 correctLane.getDesignLineOffsetAtBegin().si - currentLane.getDesignLineOffsetAtBegin().si;
459                         if (laneBasedGTU.getDirection(currentLane).equals(GTUDirectionality.DIR_PLUS))
460                         {
461                             return deltaOffset > 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
462                         }
463                         else
464                         {
465                             return deltaOffset < 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
466                         }
467                     }
468                 }
469             }
470         }
471         catch (GTUException exception)
472         {
473             System.err.println(
474                     "Exception in LaneBasedGTUFollowingChange0TacticalPlanner.determineLeftRight: " + exception.getMessage());
475         }
476         // perhaps known from split info (if need to change away from all lanes on current link)
477         return nextSplitInfo.getRequiredDirection();
478     }
479 
480     /**
481      * See if a lane change in the given direction if possible.
482      * @param gtu the GTU that has to make the lane change
483      * @param perception the perception, where forward headway, accessible lanes and speed limit have been assessed
484      * @param lanePathInfo the information for the path on the current lane
485      * @param direction the lateral direction, either LEFT or RIGHT
486      * @return whether a lane change is possible.
487      * @throws NetworkException when there is a network inconsistency in updating the perception
488      * @throws GTUException when there is an issue retrieving GTU information for the perception update
489      * @throws ParameterException when there is a parameter problem.
490      * @throws OperationalPlanException in case a perception category is not present
491      */
492     private boolean canChange(final LaneBasedGTU gtu, final LanePerception perception, final LanePathInfo lanePathInfo,
493             final LateralDirectionality direction)
494             throws GTUException, NetworkException, ParameterException, OperationalPlanException
495     {
496 
497         // TODO remove this hack
498         if (!((AbstractLaneBasedGTU) gtu).isSafeToChange())
499         {
500             return false;
501         }
502 
503         // rear should be able to change
504         Map<Lane, Length> positions = getGtu().positions(getGtu().getRear());
505         for (Lane lane : positions.keySet())
506         {
507             Length pos = positions.get(lane);
508             if (pos.si > 0.0 && pos.si < lane.getLength().si
509                     && lane.accessibleAdjacentLanes(direction, getGtu().getGTUType()).isEmpty())
510             {
511                 return false;
512             }
513         }
514 
515         Collection<Headway> otherLaneTraffic;
516         DefaultSimplePerception simplePerception = getPerception().getPerceptionCategory(DefaultSimplePerception.class);
517         simplePerception.updateForwardHeadwayGTU();
518         simplePerception.updateForwardHeadwayObject();
519         simplePerception.updateBackwardHeadway();
520         if (direction.isLeft())
521         {
522             simplePerception.updateParallelHeadwaysLeft();
523             this.blockingHeadways = simplePerception.getParallelHeadwaysLeft();
524             simplePerception.updateNeighboringHeadwaysLeft();
525             otherLaneTraffic = simplePerception.getNeighboringHeadwaysLeft();
526         }
527         else if (direction.isRight())
528         {
529             simplePerception.updateParallelHeadwaysRight();
530             this.blockingHeadways = simplePerception.getParallelHeadwaysRight();
531             simplePerception.updateNeighboringHeadwaysRight();
532             otherLaneTraffic = simplePerception.getNeighboringHeadwaysRight();
533         }
534         else
535         {
536             throw new GTUException("Lateral direction is neither LEFT nor RIGHT during a lane change");
537         }
538         if (!simplePerception.getParallelHeadways(direction).isEmpty())
539         {
540             return false;
541         }
542 
543         Collection<Headway> sameLaneTraffic = new HashSet<>();
544         // TODO should it be getObjectType().isGtu() or !getObjectType().isDistanceOnly() ?
545         // XXX Object & GTU
546         if (simplePerception.getForwardHeadwayGTU() != null && perception.getPerceptionCategory(DefaultSimplePerception.class)
547                 .getForwardHeadwayGTU().getObjectType().isGtu())
548         {
549             sameLaneTraffic.add(simplePerception.getForwardHeadwayGTU());
550         }
551         if (simplePerception.getBackwardHeadway() != null && simplePerception.getBackwardHeadway().getObjectType().isGtu())
552         {
553             sameLaneTraffic.add(simplePerception.getBackwardHeadway());
554         }
555 
556         // TODO make type of plan (Egoistic, Altruistic) parameter of the class
557         DirectedLaneChangeModel dlcm = new DirectedEgoistic(getPerception());
558         // TODO make the elasticities 2.0 and 0.1 parameters of the class
559         DirectedLaneMovementStep dlms = dlcm.computeLaneChangeAndAcceleration(gtu, direction, sameLaneTraffic, otherLaneTraffic,
560                 gtu.getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD), simplePerception.getSpeedLimit(),
561                 new Acceleration(2.0, AccelerationUnit.SI), new Acceleration(0.1, AccelerationUnit.SI),
562                 new Duration(0.5, DurationUnit.SECOND));
563         if (dlms.getLaneChange() == null)
564         {
565             return false;
566         }
567 
568         return true;
569     }
570 
571     /**
572      * Change lanes instantaneously.
573      * @param gtu the gtu
574      * @param direction the direction
575      * @return the new location of the GTU after the lane change
576      * @throws GTUException in case the enter lane fails
577      */
578     private DirectedPoint changeLane(final LaneBasedGTU gtu, final LateralDirectionality direction) throws GTUException
579     {
580         gtu.changeLaneInstantaneously(direction);
581 
582         // stay at a certain number of seconds in the current lane (unless we HAVE to change lanes)
583         this.earliestNextLaneChangeTime =
584                 gtu.getSimulator().getSimulatorTime().getTime().plus(this.durationInLaneAfterLaneChange);
585 
586         // make sure out turn indicator is on!
587         gtu.setTurnIndicatorStatus(direction.isLeft() ? TurnIndicatorStatus.LEFT : TurnIndicatorStatus.RIGHT);
588 
589         this.laneAfterLaneChange = gtu.getReferencePosition().getLane();
590         this.posAfterLaneChange = gtu.getReferencePosition().getPosition();
591         return gtu.getLocation();
592     }
593 
594     /**
595      * Calculate which Headway in front of us is leading to the most limiting acceleration step (i.e. to the lowest or most
596      * 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
597      * front of the GTU and speed sign. This method will return the acceleration based on the headway that limits us most.<br>
598      * The method can e.g., be called with:
599      * <code>mostLimitingHeadway(simplePerception.getForwardHeadwayGTU(), simplePerception.getForwardHeadwayObject());</code>
600      * @param lanePathInfo the lane path info that was calculated for this GTU.
601      * @param headways zero or more headways specifying possible limitations on our acceleration.
602      * @return the acceleration based on the most limiting headway.
603      * @throws OperationalPlanException in case the PerceptionCategory cannot be found
604      * @throws ParameterException in case LOOKAHEAD parameter cannot be found
605      * @throws GTUException in case the AccelerationStep cannot be calculated
606      * @throws NetworkException in case the headways to GTUs or objects cannot be calculated
607      */
608     private AccelerationStep mostLimitingAccelerationStep(final LanePathInfo lanePathInfo, final Headway... headways)
609             throws OperationalPlanException, ParameterException, GTUException, NetworkException
610     {
611         DefaultSimplePerception simplePerception = getPerception().getPerceptionCategory(DefaultSimplePerception.class);
612         simplePerception.updateForwardHeadwayGTU();
613         simplePerception.updateForwardHeadwayObject();
614         boolean sinkAtEnd = false;
615         for (SingleSensor sensor : (lanePathInfo.getLanes().get(lanePathInfo.getLanes().size() - 1).getSensors()))
616         {
617             if (sensor instanceof SinkSensor)
618             {
619                 sinkAtEnd = true;
620             }
621         }
622         boolean stopForEndOrSplit = !sinkAtEnd;
623         BehavioralCharacteristics bc = getGtu().getBehavioralCharacteristics();
624         Length maxDistance = sinkAtEnd ? new Length(Double.MAX_VALUE, LengthUnit.SI)
625                 : Length.min(getGtu().getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD),
626                         lanePathInfo.getPath().getLength().minus(getGtu().getLength().multiplyBy(2.0)));
627         // bc.setParameter(ParameterTypes.B, bc.getParameter(ParameterTypes.B0));
628         AccelerationStep mostLimitingAccelerationStep = getCarFollowingModelOld().computeAccelerationStepWithNoLeader(getGtu(),
629                 maxDistance, simplePerception.getSpeedLimit());
630         // bc.resetParameter(ParameterTypes.B);
631         Acceleration minB = bc.getParameter(ParameterTypes.B).neg();
632         Acceleration numericallySafeB =
633                 Acceleration.max(minB, getGtu().getSpeed().divideBy(mostLimitingAccelerationStep.getDuration()).neg());
634         if ((this.syncHeadway != null || this.coopHeadway != null) && mostLimitingAccelerationStep.getAcceleration().gt(minB))
635         {
636             AccelerationStep sync;
637             if (this.syncHeadway == null)
638             {
639                 sync = null;
640             }
641             else if (this.syncHeadway.isParallel())
642             {
643                 sync = new AccelerationStep(numericallySafeB, mostLimitingAccelerationStep.getValidUntil(),
644                         mostLimitingAccelerationStep.getDuration());
645             }
646             else
647             {
648                 sync = getCarFollowingModelOld().computeAccelerationStep(getGtu(), this.syncHeadway.getSpeed(),
649                         this.syncHeadway.getDistance(), maxDistance, simplePerception.getSpeedLimit());
650             }
651             AccelerationStep coop;
652             if (this.coopHeadway == null)
653             {
654                 coop = null;
655             }
656             else
657             {
658                 coop = getCarFollowingModelOld().computeAccelerationStep(getGtu(), this.coopHeadway.getSpeed(),
659                         this.coopHeadway.getDistance(), maxDistance, simplePerception.getSpeedLimit());
660             }
661             AccelerationStep adjust;
662             if (sync == null)
663             {
664                 adjust = coop;
665             }
666             else if (coop == null)
667             {
668                 adjust = sync;
669             }
670             else
671             {
672                 adjust = sync.getAcceleration().lt(coop.getAcceleration()) ? sync : coop;
673             }
674             if (adjust.getAcceleration().lt(minB))
675             {
676                 mostLimitingAccelerationStep = new AccelerationStep(numericallySafeB,
677                         mostLimitingAccelerationStep.getValidUntil(), mostLimitingAccelerationStep.getDuration());
678             }
679             else
680             {
681                 mostLimitingAccelerationStep = adjust;
682             }
683         }
684 
685         for (Headway headway : headways)
686         {
687             if (headway != null && headway.getDistance().lt(maxDistance))
688             {
689                 AccelerationStep accelerationStep = getCarFollowingModelOld().computeAccelerationStep(getGtu(),
690                         headway.getSpeed(), headway.getDistance(), maxDistance, simplePerception.getSpeedLimit());
691                 if (accelerationStep.getAcceleration().lt(mostLimitingAccelerationStep.getAcceleration()))
692                 {
693                     stopForEndOrSplit = false;
694                     mostLimitingAccelerationStep = accelerationStep;
695                 }
696             }
697         }
698 
699         // recognize dead-lock
700         if (!this.blockingHeadways.isEmpty() && stopForEndOrSplit)
701         {
702             Speed maxSpeed = getGtu().getSpeed();
703             for (Headway headway : this.blockingHeadways)
704             {
705                 maxSpeed = Speed.max(maxSpeed, headway.getSpeed());
706             }
707             if (maxSpeed.si < OperationalPlan.DRIFTING_SPEED_SI)
708             {
709                 if (this.deadLock == null)
710                 {
711                     this.deadLock = getGtu().getSimulator().getSimulatorTime().getTime();
712                 }
713             }
714             else
715             {
716                 this.deadLock = null;
717             }
718         }
719         else
720         {
721             this.deadLock = null;
722         }
723 
724         return mostLimitingAccelerationStep;
725 
726     }
727 
728     /**
729      * @return destroyGtuOnFailure, indicating when a failure in planning occurs, whether we should destroy the GTU to avoid
730      *         halting of the model
731      */
732     public final boolean isDestroyGtuOnFailure()
733     {
734         return this.destroyGtuOnFailure;
735     }
736 
737     /**
738      * When a failure in planning occurs, should we destroy the GTU to avoid halting of the model?
739      * @param destroyGtuOnFailure set destroyGtuOnFailure to true or false
740      */
741     public final void setDestroyGtuOnFailure(final boolean destroyGtuOnFailure)
742     {
743         this.destroyGtuOnFailure = destroyGtuOnFailure;
744     }
745 
746     /**
747      * Get the duration to stay in a Lane after a lane change.
748      * @return Duration; durationInLaneAfterLaneChange
749      */
750     protected final Duration getDurationInLaneAfterLaneChange()
751     {
752         return this.durationInLaneAfterLaneChange;
753     }
754 
755     /**
756      * Set the duration to stay in a Lane after a lane change.
757      * @param durationInLaneAfterLaneChange set duration to stay in a Lane after a lane change
758      * @throws GTUException when durationInLaneAfterLaneChange less than zero
759      */
760     protected final void setDurationInLaneAfterLaneChange(final Duration durationInLaneAfterLaneChange) throws GTUException
761     {
762         Throw.when(durationInLaneAfterLaneChange.lt0(), GTUException.class, "durationInLaneAfterLaneChange should be >= 0");
763         this.durationInLaneAfterLaneChange = durationInLaneAfterLaneChange;
764     }
765 
766     /** {@inheritDoc} */
767     @Override
768     public final String toString()
769     {
770         return "LaneBasedGTUFollowingChange0TacticalPlanner [earliestNexLaneChangeTime=" + this.earliestNextLaneChangeTime
771                 + ", referenceLane=" + this.laneAfterLaneChange + ", referencePos=" + this.posAfterLaneChange
772                 + ", destroyGtuOnFailure=" + this.destroyGtuOnFailure + "]";
773     }
774 
775 }