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 nl.tudelft.simulation.language.d3.DirectedPoint;
10  
11  import org.djunits.unit.AccelerationUnit;
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.Time;
17  import org.opentrafficsim.core.gtu.GTUDirectionality;
18  import org.opentrafficsim.core.gtu.GTUException;
19  import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
20  import org.opentrafficsim.core.gtu.behavioralcharacteristics.BehavioralCharacteristics;
21  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
22  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
23  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
24  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan.Segment;
25  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
26  import org.opentrafficsim.core.network.LateralDirectionality;
27  import org.opentrafficsim.core.network.NetworkException;
28  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
29  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
30  import org.opentrafficsim.road.gtu.lane.perception.categories.DefaultAlexander;
31  import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
32  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedAltruistic;
33  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedEgoistic;
34  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedLaneChangeModel;
35  import org.opentrafficsim.road.gtu.lane.tactical.directedlanechange.DirectedLaneMovementStep;
36  import org.opentrafficsim.road.gtu.lane.tactical.following.AccelerationStep;
37  import org.opentrafficsim.road.gtu.lane.tactical.following.GTUFollowingModelOld;
38  import org.opentrafficsim.road.network.lane.Lane;
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 LaneBasedGTUFollowingChange0TacticalPlanner extends AbstractLaneBasedTacticalPlanner
72  {
73      /** */
74      private static final long serialVersionUID = 20160129L;
75  
76      /** Earliest next lane change time. */
77      private Time earliestNexLaneChangeTime = Time.ZERO;
78  
79      /** Lane we changed to at instantaneous lane change. */
80      private Lane referenceLane = null;
81  
82      /** Position on the reference lane. */
83      private Length referencePos = 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 LaneBasedGTUFollowingChange0TacticalPlanner(final GTUFollowingModelOld carFollowingModel, final LaneBasedGTU gtu)
94      {
95          super(carFollowingModel, gtu);
96      }
97  
98      /**
99       * Returns the car-following model.
100      * @return The car-following model.
101      */
102     public GTUFollowingModelOld getCarFollowingModelOld()
103     {
104         return (GTUFollowingModelOld) super.getCarFollowingModel();
105     }
106 
107     /** {@inheritDoc} */
108     @Override
109     public OperationalPlan generateOperationalPlan(final Time startTime, final DirectedPoint locationAtStartTime)
110         throws OperationalPlanException, NetworkException, GTUException, ParameterException
111     {
112         try
113         {
114             // ask Perception for the local situation
115             LaneBasedGTU laneBasedGTU = getGtu();
116             LanePerception perception = getPerception();
117             BehavioralCharacteristics behavioralCharacteristics = laneBasedGTU.getBehavioralCharacteristics();
118             behavioralCharacteristics.setParameter(ParameterTypes.LOOKAHEAD, ParameterTypes.LOOKAHEAD.getDefaultValue());
119 
120             // start with the turn indicator off -- this can change during the method
121             laneBasedGTU.setTurnIndicatorStatus(TurnIndicatorStatus.NONE);
122 
123             // if the GTU's maximum speed is zero (block), generate a stand still plan for one second
124             if (laneBasedGTU.getMaximumSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
125             {
126                 return new OperationalPlan(getGtu(), locationAtStartTime, startTime, new Duration(1.0, TimeUnit.SECOND));
127             }
128 
129             // perceive the forward headway, accessible lanes and speed limit.
130             perception.getPerceptionCategory(DefaultAlexander.class).updateForwardHeadway();
131             perception.getPerceptionCategory(DefaultAlexander.class).updateAccessibleAdjacentLanesLeft();
132             perception.getPerceptionCategory(DefaultAlexander.class).updateAccessibleAdjacentLanesRight();
133             perception.getPerceptionCategory(DefaultAlexander.class).updateSpeedLimit();
134 
135             // find out where we are going
136             Length forwardHeadway = behavioralCharacteristics.getParameter(ParameterTypes.LOOKAHEAD);
137             LanePathInfo lanePathInfo = buildLanePathInfo(laneBasedGTU, forwardHeadway);
138             NextSplitInfo nextSplitInfo = determineNextSplit(laneBasedGTU, forwardHeadway);
139             Set<Lane> correctLanes = laneBasedGTU.getLanes().keySet();
140             correctLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
141 
142             // Step 1: Do we want to change lanes because of the current lane not leading to our destination?
143             if (lanePathInfo.getPath().getLength().lt(forwardHeadway))
144             {
145                 if (correctLanes.isEmpty())
146                 {
147                     LateralDirectionality direction = determineLeftRight(laneBasedGTU, nextSplitInfo);
148                     if (direction != null)
149                     {
150                         getGtu().setTurnIndicatorStatus(
151                             direction.isLeft() ? TurnIndicatorStatus.LEFT : TurnIndicatorStatus.RIGHT);
152                         if (canChange(laneBasedGTU, perception, lanePathInfo, direction))
153                         {
154                             DirectedPoint newLocation = changeLane(laneBasedGTU, direction);
155                             lanePathInfo =
156                                 buildLanePathInfo(laneBasedGTU, this.referenceLane, this.referencePos, forwardHeadway);
157                             return currentLanePlan(laneBasedGTU, startTime, newLocation, lanePathInfo);
158                         }
159                     }
160                 }
161             }
162 
163             // Condition, if we have just changed lane, let's not change immediately again.
164             // TODO make direction dependent!
165             if (getGtu().getSimulator().getSimulatorTime().getTime().lt(this.earliestNexLaneChangeTime))
166             {
167                 return currentLanePlan(laneBasedGTU, startTime, locationAtStartTime, lanePathInfo);
168             }
169 
170             // Step 2. Do we want to change lanes to the left because of our predecessor on the current lane?
171             // does the lane left of us [TODO: driving direction] bring us to our destination as well?
172             Set<Lane> leftLanes =
173                 perception.getPerceptionCategory(DefaultAlexander.class).getAccessibleAdjacentLanesLeft().get(
174                     lanePathInfo.getReferenceLane());
175             if (nextSplitInfo.isSplit())
176             {
177                 leftLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
178             }
179             if (!leftLanes.isEmpty() && laneBasedGTU.getSpeed().si > 4.0) // XXX we are driving...
180             {
181                 perception.getPerceptionCategory(DefaultAlexander.class).updateBackwardHeadway();
182                 perception.getPerceptionCategory(DefaultAlexander.class).updateParallelHeadwaysLeft();
183                 perception.getPerceptionCategory(DefaultAlexander.class).updateNeighboringHeadwaysLeft();
184                 if (perception.getPerceptionCategory(DefaultAlexander.class).getParallelHeadwaysLeft().isEmpty())
185                 {
186                     Collection<Headway> sameLaneTraffic = new HashSet<>();
187                     // TODO should it be getObjectType().isGtu() or !getObjectType().isDistanceOnly() ?
188                     if (perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway() != null
189                         && perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway().getObjectType()
190                             .isGtu())
191                     {
192                         sameLaneTraffic.add(perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway());
193                     }
194                     if (perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway() != null
195                         && perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway().getObjectType()
196                             .isGtu())
197                     {
198                         sameLaneTraffic.add(perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway());
199                     }
200                     DirectedLaneChangeModel dlcm = new DirectedAltruistic(getPerception());
201                     DirectedLaneMovementStep dlms =
202                         dlcm.computeLaneChangeAndAcceleration(laneBasedGTU, LateralDirectionality.LEFT, sameLaneTraffic,
203                             perception.getPerceptionCategory(DefaultAlexander.class).getNeighboringHeadwaysLeft(),
204                             behavioralCharacteristics.getParameter(ParameterTypes.LOOKAHEAD), perception
205                                 .getPerceptionCategory(DefaultAlexander.class).getSpeedLimit(), new Acceleration(1.0,
206                                 AccelerationUnit.SI), new Acceleration(0.5, AccelerationUnit.SI), new Duration(0.5,
207                                 TimeUnit.SECOND));
208                     if (dlms.getLaneChange() != null)
209                     {
210                         getGtu().setTurnIndicatorStatus(TurnIndicatorStatus.LEFT);
211                         if (canChange(laneBasedGTU, perception, lanePathInfo, LateralDirectionality.LEFT))
212                         {
213                             DirectedPoint newLocation = changeLane(laneBasedGTU, LateralDirectionality.LEFT);
214                             lanePathInfo =
215                                 buildLanePathInfo(laneBasedGTU, this.referenceLane, this.referencePos, forwardHeadway);
216                             return currentLanePlan(laneBasedGTU, startTime, newLocation, lanePathInfo);
217                         }
218                     }
219                 }
220             }
221 
222             // Step 3. Do we want to change lanes to the right because of TODO traffic rules?
223             Set<Lane> rightLanes =
224                 perception.getPerceptionCategory(DefaultAlexander.class).getAccessibleAdjacentLanesRight().get(
225                     lanePathInfo.getReferenceLane());
226             if (nextSplitInfo.isSplit())
227             {
228                 rightLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
229             }
230             if (!rightLanes.isEmpty() && laneBasedGTU.getSpeed().si > 4.0) // XXX we are driving...
231             {
232                 perception.getPerceptionCategory(DefaultAlexander.class).updateBackwardHeadway();
233                 perception.getPerceptionCategory(DefaultAlexander.class).updateParallelHeadwaysRight();
234                 perception.getPerceptionCategory(DefaultAlexander.class).updateNeighboringHeadwaysRight();
235                 if (perception.getPerceptionCategory(DefaultAlexander.class).getParallelHeadwaysRight().isEmpty())
236                 {
237                     Collection<Headway> sameLaneTraffic = new HashSet<>();
238                     // TODO should it be getObjectType().isGtu() or !getObjectType().isDistanceOnly() ?
239                     if (perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway() != null
240                         && perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway().getObjectType()
241                             .isGtu())
242                     {
243                         sameLaneTraffic.add(perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway());
244                     }
245                     if (perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway() != null
246                         && perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway().getObjectType()
247                             .isGtu())
248                     {
249                         sameLaneTraffic.add(perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway());
250                     }
251                     DirectedLaneChangeModel dlcm = new DirectedAltruistic(getPerception());
252                     DirectedLaneMovementStep dlms =
253                         dlcm.computeLaneChangeAndAcceleration(laneBasedGTU, LateralDirectionality.RIGHT, sameLaneTraffic,
254                             perception.getPerceptionCategory(DefaultAlexander.class).getNeighboringHeadwaysRight(),
255                             behavioralCharacteristics.getParameter(ParameterTypes.LOOKAHEAD), perception
256                                 .getPerceptionCategory(DefaultAlexander.class).getSpeedLimit(), new Acceleration(1.0,
257                                 AccelerationUnit.SI), new Acceleration(0.5, AccelerationUnit.SI), new Duration(0.5,
258                                 TimeUnit.SECOND));
259                     if (dlms.getLaneChange() != null)
260                     {
261                         getGtu().setTurnIndicatorStatus(TurnIndicatorStatus.RIGHT);
262                         if (canChange(laneBasedGTU, perception, lanePathInfo, LateralDirectionality.RIGHT))
263                         {
264                             DirectedPoint newLocation = changeLane(laneBasedGTU, LateralDirectionality.RIGHT);
265                             lanePathInfo =
266                                 buildLanePathInfo(laneBasedGTU, this.referenceLane, this.referencePos, forwardHeadway);
267                             return currentLanePlan(laneBasedGTU, startTime, newLocation, lanePathInfo);
268                         }
269                     }
270                 }
271             }
272 
273             return currentLanePlan(laneBasedGTU, startTime, locationAtStartTime, lanePathInfo);
274         }
275         catch (GTUException | NetworkException | OperationalPlanException exception)
276         {
277             if (isDestroyGtuOnFailure())
278             {
279                 System.err.println("LaneBasedGTUFollowingChange0TacticalPlanner.generateOperationalPlan() failed for "
280                     + getGtu() + " because of " + exception.getMessage() + " -- GTU destroyed");
281                 getGtu().destroy();
282                 return new OperationalPlan(getGtu(), locationAtStartTime, startTime, new Duration(1.0, TimeUnit.SECOND));
283             }
284             throw exception;
285         }
286     }
287 
288     /**
289      * Make a plan for the current lane.
290      * @param laneBasedGTU the gtu to generate the plan for
291      * @param startTime the time from which the new operational plan has to be operational
292      * @param locationAtStartTime the location of the GTU at the start time of the new plan
293      * @param lanePathInfo the lane path for the current lane.
294      * @return An operation plan for staying in the current lane.
295      * @throws OperationalPlanException when there is a problem planning a path in the network
296      * @throws GTUException when there is a problem with the state of the GTU when planning a path
297      */
298     private OperationalPlan currentLanePlan(final LaneBasedGTU laneBasedGTU, final Time startTime,
299         final DirectedPoint locationAtStartTime, final LanePathInfo lanePathInfo) throws OperationalPlanException,
300         GTUException
301     {
302         LanePerception perception = getPerception();
303 
304         // No lane change. Continue on current lane.
305         AccelerationStep accelerationStep;
306         accelerationStep =
307             this.getCarFollowingModelOld().computeAccelerationStep(laneBasedGTU,
308                 perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway().getSpeed(),
309                 perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway().getDistance(),
310                 lanePathInfo.getPath().getLength().minus(laneBasedGTU.getLength().multiplyBy(2.0)),
311                 perception.getPerceptionCategory(DefaultAlexander.class).getSpeedLimit());
312 
313         // see if we have to continue standing still. In that case, generate a stand still plan
314         if (accelerationStep.getAcceleration().si < 1E-6 && laneBasedGTU.getSpeed().si < OperationalPlan.DRIFTING_SPEED_SI)
315         {
316             return new OperationalPlan(laneBasedGTU, locationAtStartTime, startTime, accelerationStep.getDuration());
317         }
318 
319         // build a list of lanes forward, with a maximum headway.
320         List<Segment> operationalPlanSegmentList = new ArrayList<>();
321         if (accelerationStep.getAcceleration().si == 0.0)
322         {
323             Segment segment = new OperationalPlan.SpeedSegment(accelerationStep.getDuration());
324             operationalPlanSegmentList.add(segment);
325         }
326         else
327         {
328             Segment segment =
329                 new OperationalPlan.AccelerationSegment(accelerationStep.getDuration(), accelerationStep.getAcceleration());
330             operationalPlanSegmentList.add(segment);
331         }
332         OperationalPlan op =
333             new OperationalPlan(laneBasedGTU, lanePathInfo.getPath(), startTime, laneBasedGTU.getSpeed(),
334                 operationalPlanSegmentList);
335         return op;
336     }
337 
338     /**
339      * We are not on a lane that leads to our destination. Determine whether the lateral direction to go is left or right.
340      * @param laneBasedGTU the gtu
341      * @param nextSplitInfo the information about the next split
342      * @return the lateral direction to go, or null if this cannot be determined
343      */
344     private LateralDirectionality determineLeftRight(final LaneBasedGTU laneBasedGTU, final NextSplitInfo nextSplitInfo)
345     {
346         // are the lanes in nextSplitInfo.getCorrectCurrentLanes() left or right of the current lane(s) of the GTU?
347         for (Lane correctLane : nextSplitInfo.getCorrectCurrentLanes())
348         {
349             for (Lane currentLane : laneBasedGTU.getLanes().keySet())
350             {
351                 if (correctLane.getParentLink().equals(currentLane.getParentLink()))
352                 {
353                     double deltaOffset =
354                         correctLane.getDesignLineOffsetAtBegin().si - currentLane.getDesignLineOffsetAtBegin().si;
355                     if (laneBasedGTU.getLanes().get(currentLane).equals(GTUDirectionality.DIR_PLUS))
356                     {
357                         return deltaOffset > 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
358                     }
359                     else
360                     {
361                         return deltaOffset < 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
362                     }
363                 }
364             }
365         }
366         return null;
367     }
368 
369     /**
370      * See if a lane change in the given direction if possible.
371      * @param gtu the GTU that has to make the lane change
372      * @param perception the perception, where forward headway, accessible lanes and speed limit have been assessed
373      * @param lanePathInfo the information for the path on the current lane
374      * @param direction the lateral direction, either LEFT or RIGHT
375      * @return whether a lane change is possible.
376      * @throws NetworkException when there is a network inconsistency in updating the perception
377      * @throws GTUException when there is an issue retrieving GTU information for the perception update
378      * @throws ParameterException when there is a parameter problem.
379      * @throws OperationalPlanException in case a perception category is not present
380      */
381     private boolean canChange(final LaneBasedGTU gtu, final LanePerception perception, final LanePathInfo lanePathInfo,
382         final LateralDirectionality direction) throws GTUException, NetworkException, ParameterException,
383         OperationalPlanException
384     {
385         Collection<Headway> otherLaneTraffic;
386         perception.getPerceptionCategory(DefaultAlexander.class).updateForwardHeadway();
387         perception.getPerceptionCategory(DefaultAlexander.class).updateBackwardHeadway();
388         if (direction.isLeft())
389         {
390             perception.getPerceptionCategory(DefaultAlexander.class).updateParallelHeadwaysLeft();
391             perception.getPerceptionCategory(DefaultAlexander.class).updateNeighboringHeadwaysLeft();
392             otherLaneTraffic = perception.getPerceptionCategory(DefaultAlexander.class).getNeighboringHeadwaysLeft();
393         }
394         else if (direction.isRight())
395         {
396             perception.getPerceptionCategory(DefaultAlexander.class).updateParallelHeadwaysRight();
397             perception.getPerceptionCategory(DefaultAlexander.class).updateNeighboringHeadwaysRight();
398             otherLaneTraffic = perception.getPerceptionCategory(DefaultAlexander.class).getNeighboringHeadwaysRight();
399         }
400         else
401         {
402             throw new GTUException("Lateral direction is neither LEFT nor RIGHT during a lane change");
403         }
404         if (!perception.getPerceptionCategory(DefaultAlexander.class).getParallelHeadways(direction).isEmpty())
405         {
406             return false;
407         }
408 
409         Collection<Headway> sameLaneTraffic = new HashSet<>();
410         // TODO should it be getObjectType().isGtu() or !getObjectType().isDistanceOnly() ?
411         if (perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway() != null
412             && perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway().getObjectType().isGtu())
413         {
414             sameLaneTraffic.add(perception.getPerceptionCategory(DefaultAlexander.class).getForwardHeadway());
415         }
416         if (perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway() != null
417             && perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway().getObjectType().isGtu())
418         {
419             sameLaneTraffic.add(perception.getPerceptionCategory(DefaultAlexander.class).getBackwardHeadway());
420         }
421 
422         // TODO make type of plan (Egoistic, Altruistic) parameter of the class
423         DirectedLaneChangeModel dlcm = new DirectedEgoistic(getPerception());
424         // TODO make the elasticities 2.0 and 0.1 parameters of the class
425         DirectedLaneMovementStep dlms =
426             dlcm.computeLaneChangeAndAcceleration(gtu, direction, sameLaneTraffic, otherLaneTraffic, gtu
427                 .getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD), perception.getPerceptionCategory(
428                 DefaultAlexander.class).getSpeedLimit(), new Acceleration(2.0, AccelerationUnit.SI), new Acceleration(0.1,
429                 AccelerationUnit.SI), new Duration(0.5, TimeUnit.SECOND));
430         if (dlms.getLaneChange() == null)
431         {
432             return false;
433         }
434 
435         return true;
436     }
437 
438     /**
439      * Change lanes instantaneously.
440      * @param gtu the gtu
441      * @param direction the direction
442      * @return the new location of the reference point of the GTU
443      * @throws GTUException in case the enterlane fails
444      */
445     private DirectedPoint changeLane(final LaneBasedGTU gtu, final LateralDirectionality direction) throws GTUException
446     {
447         DirectedPoint p = null;
448         Set<Lane> lanes = new HashSet<>(gtu.getLanes().keySet());
449         for (Lane lane : lanes)
450         {
451             Set<Lane> adjacentLanes = lane.accessibleAdjacentLanes(direction, gtu.getGTUType());
452             double fraction = gtu.fractionalPosition(lane, gtu.getReference());
453             for (Lane adjacentLane : adjacentLanes)
454             {
455                 gtu.enterLane(adjacentLane, adjacentLane.getLength().multiplyBy(fraction), gtu.getLanes().get(lane));
456                 if (fraction >= 0.0 && fraction <= 1.0)
457                 {
458                     p = adjacentLane.getCenterLine().getLocationFractionExtended(fraction);
459                     this.referenceLane = adjacentLane;
460                     this.referencePos = adjacentLane.getLength().multiplyBy(fraction);
461                 }
462                 System.out.println("gtu " + gtu.getId() + " entered lane " + adjacentLane + " at pos "
463                     + adjacentLane.getLength().multiplyBy(fraction));
464             }
465             gtu.leaveLane(lane);
466         }
467 
468         if (p == null)
469         {
470             System.err.println("Warning: " + gtu + " not with its reference point on any lane. Came from " + lanes
471                 + ", now at " + gtu.getLanes().keySet());
472             Lane l = gtu.getLanes().keySet().iterator().next();
473             double fraction = gtu.fractionalPosition(l, gtu.getReference());
474             p = l.getCenterLine().getLocationFractionExtended(fraction);
475             this.referenceLane = l;
476             this.referencePos = l.getLength().multiplyBy(fraction);
477         }
478 
479         // stay at least 15 seconds in the current lane
480         this.earliestNexLaneChangeTime =
481             gtu.getSimulator().getSimulatorTime().getTime().plus(new Duration(15, TimeUnit.SECOND));
482 
483         // make sure out turn indicator is on!
484         gtu.setTurnIndicatorStatus(direction.isLeft() ? TurnIndicatorStatus.LEFT : TurnIndicatorStatus.RIGHT);
485 
486         return p;
487     }
488 
489     /**
490      * @return destroyGtuOnFailure, indicating when a failure in planning occurs, whether we should destroy the GTU to avoid
491      *         halting of the model
492      */
493     public final boolean isDestroyGtuOnFailure()
494     {
495         return this.destroyGtuOnFailure;
496     }
497 
498     /**
499      * When a failure in planning occurs, should we destroy the GTU to avoid halting of the model?
500      * @param destroyGtuOnFailure set destroyGtuOnFailure to true or false
501      */
502     public final void setDestroyGtuOnFailure(final boolean destroyGtuOnFailure)
503     {
504         this.destroyGtuOnFailure = destroyGtuOnFailure;
505     }
506 
507     /** {@inheritDoc} */
508     @Override
509     public final String toString()
510     {
511         return "LaneBasedGTUFollowingChange0TacticalPlanner [earliestNexLaneChangeTime=" + this.earliestNexLaneChangeTime
512             + ", referenceLane=" + this.referenceLane + ", referencePos=" + this.referencePos + ", destroyGtuOnFailure="
513             + this.destroyGtuOnFailure + "]";
514     }
515 
516 }