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