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.dsol.SimRuntimeException;
10  import nl.tudelft.simulation.language.d3.DirectedPoint;
11  
12  import org.djunits.unit.AccelerationUnit;
13  import org.djunits.unit.SpeedUnit;
14  import org.djunits.unit.TimeUnit;
15  import org.djunits.value.vdouble.scalar.Acceleration;
16  import org.djunits.value.vdouble.scalar.Length;
17  import org.djunits.value.vdouble.scalar.Speed;
18  import org.djunits.value.vdouble.scalar.Time;
19  import org.opentrafficsim.core.geometry.OTSGeometryException;
20  import org.opentrafficsim.core.geometry.OTSLine3D;
21  import org.opentrafficsim.core.geometry.OTSPoint3D;
22  import org.opentrafficsim.core.gtu.GTU;
23  import org.opentrafficsim.core.gtu.GTUDirectionality;
24  import org.opentrafficsim.core.gtu.GTUException;
25  import org.opentrafficsim.core.gtu.GTUType;
26  import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
27  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
28  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan.Segment;
29  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
30  import org.opentrafficsim.core.network.LateralDirectionality;
31  import org.opentrafficsim.core.network.NetworkException;
32  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
33  import org.opentrafficsim.road.gtu.lane.driver.LaneBasedBehavioralCharacteristics;
34  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
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.gtu.lane.tactical.following.HeadwayGTU;
42  import org.opentrafficsim.road.network.lane.Lane;
43  import org.opentrafficsim.road.network.lane.LaneDirection;
44  
45  /**
46   * Lane-based tactical planner that implements car following behavior and rule-based lane change. This tactical planner
47   * retrieves the car following model from the strategical planner and will generate an operational plan for the GTU.
48   * <p>
49   * A lane change occurs when:
50   * <ol>
51   * <li>The route indicates that the current lane does not lead to the destination; main choices are the time when the GTU
52   * switches to the "right" lane, and what should happen when the split gets closer and the lane change has failed. Observations
53   * indicate that vehicles if necessary stop in their current lane until they can go to the desired lane. A lane drop is
54   * automatically part of this implementation, because the lane with a lane drop will not lead to the GTU's destination.</li>
55   * <li>The desired speed of the vehicle is a particular delta-speed higher than its predecessor, the headway to the predecessor
56   * in the current lane has exceeded a certain value, it is allowed to change to the target lane, the target lane lies on the
57   * GTU's route, and the gap in the target lane is acceptable (including the evaluation of the perceived speed of a following GTU
58   * in the target lane).</li>
59   * <li>The current lane is not the optimum lane given the traffic rules (for example, to keep right), the headway to the
60   * predecessor on the target lane is greater than a certain value, the speed of the predecessor on the target lane is greater
61   * 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
62   * target lane is acceptable (including the perceived speed of any vehicle in front or behind on the target lane).</li>
63   * </ol>
64   * <p>
65   * This lane-based tactical planner makes decisions based on headway (GTU following model). It can ask the strategic planner for
66   * assistance on the route to take when the network splits.
67   * <p>
68   * Copyright (c) 2013-2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
69   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
70   * </p>
71   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
72   * initial version Nov 25, 2015 <br>
73   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
74   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
75   */
76  public class LaneBasedGTUFollowingLaneChangeTacticalPlanner extends AbstractLaneBasedTacticalPlanner
77  {
78      /** */
79      private static final long serialVersionUID = 20160129L;
80  
81      /** Lane change time (fixed foe now. */
82      private static final double LANECHANGETIME = 2.0;
83  
84      /** Earliest next lane change time. */
85      private Time.Abs earliestNexLaneChangeTime = Time.Abs.ZERO;
86  
87      /** Bezier curve points for gradual lane change. */
88      private static final double[] SCURVE;
89  
90      static
91      {
92          SCURVE = new double[65];
93          for (int i = 0; i <= 64; i++)
94          {
95              double t = i / 64.0;
96              double ot = 1.0 - t;
97              double t3 = t * t * t;
98              SCURVE[i] = 10.0 * t3 * ot * ot + 5.0 * t3 * t * ot + t3 * t * t;
99          }
100     }
101 
102     /**
103      * Instantiated a tactical planner with just GTU following behavior and no lane changes.
104      */
105     public LaneBasedGTUFollowingLaneChangeTacticalPlanner()
106     {
107         super();
108     }
109 
110     /** {@inheritDoc} */
111     @Override
112     public OperationalPlan generateOperationalPlan(final GTU gtu, final Time.Abs startTime,
113         final DirectedPoint locationAtStartTime) throws OperationalPlanException, NetworkException, GTUException
114     {
115         // ask Perception for the local situation
116         LaneBasedGTU laneBasedGTU = (LaneBasedGTU) gtu;
117         LanePerception perception = laneBasedGTU.getPerception();
118         LaneBasedBehavioralCharacteristics drivingCharacteristics = laneBasedGTU.getBehavioralCharacteristics();
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.getMaximumVelocity().si < OperationalPlan.DRIFTING_SPEED_SI)
125         {
126             return new OperationalPlan(gtu, locationAtStartTime, startTime, new Time.Rel(1.0, TimeUnit.SECOND));
127         }
128 
129         // perceive the forward headway, accessible lanes and speed limit.
130         perception.updateForwardHeadwayGTU();
131         perception.updateAccessibleAdjacentLanesLeft();
132         perception.updateAccessibleAdjacentLanesRight();
133         perception.updateSpeedLimit();
134 
135         // find out where we are going
136         Length.Rel forwardHeadway = drivingCharacteristics.getForwardHeadwayDistance();
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                     gtu.setTurnIndicatorStatus(direction.isLeft() ? TurnIndicatorStatus.LEFT
151                         : TurnIndicatorStatus.RIGHT);
152                     OperationalPlan laneChangePlan =
153                         makeLaneChangePlanMobil(laneBasedGTU, perception, lanePathInfo, direction);
154                     if (laneChangePlan != null)
155                     {
156                         return laneChangePlan;
157                     }
158                 }
159             }
160         }
161 
162         // Condition, if we have just changed lane, let's not change immediately again.
163         if (gtu.getSimulator().getSimulatorTime().getTime().lt(this.earliestNexLaneChangeTime))
164         {
165             return currentLanePlan(laneBasedGTU, startTime, locationAtStartTime, lanePathInfo);
166         }
167 
168         // Step 2. Do we want to change lanes to the left because of our predecessor on the current lane?
169         // does the lane left of us [TODO: driving direction] bring us to our destination as well?
170         Set<Lane> leftLanes = perception.getAccessibleAdjacentLanesLeft().get(lanePathInfo.getReferenceLane());
171         if (nextSplitInfo.isSplit())
172         {
173             leftLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
174         }
175         if (!leftLanes.isEmpty() && laneBasedGTU.getVelocity().si > 4.0) // XXX we are driving...
176         {
177             perception.updateBackwardHeadwayGTU();
178             perception.updateParallelGTUsLeft();
179             perception.updateLaneTrafficLeft();
180             if (perception.getParallelGTUsLeft().isEmpty())
181             {
182                 Collection<HeadwayGTU> sameLaneTraffic = new HashSet<>();
183                 if (perception.getForwardHeadwayGTU() != null && perception.getForwardHeadwayGTU().getGtuId() != null)
184                 {
185                     sameLaneTraffic.add(perception.getForwardHeadwayGTU());
186                 }
187                 if (perception.getBackwardHeadwayGTU() != null && perception.getBackwardHeadwayGTU().getGtuId() != null)
188                 {
189                     sameLaneTraffic.add(perception.getBackwardHeadwayGTU());
190                 }
191                 DirectedLaneChangeModel dlcm = new DirectedAltruistic();
192                 DirectedLaneMovementStep dlms =
193                     dlcm.computeLaneChangeAndAcceleration(laneBasedGTU, LateralDirectionality.LEFT, sameLaneTraffic,
194                         perception.getNeighboringGTUsLeft(), laneBasedGTU.getBehavioralCharacteristics()
195                             .getForwardHeadwayDistance(), perception.getSpeedLimit(), new Acceleration(1.0,
196                             AccelerationUnit.SI), new Acceleration(0.5, AccelerationUnit.SI), new Time.Rel(
197                             LANECHANGETIME, TimeUnit.SECOND));
198                 if (dlms.getLaneChange() != null)
199                 {
200                     gtu.setTurnIndicatorStatus(TurnIndicatorStatus.LEFT);
201                     OperationalPlan laneChangePlan =
202                         makeLaneChangePlanMobil(laneBasedGTU, perception, lanePathInfo, LateralDirectionality.LEFT);
203                     if (laneChangePlan != null)
204                     {
205                         return laneChangePlan;
206                     }
207                 }
208             }
209         }
210 
211         // Step 3. Do we want to change lanes to the right because of traffic rules?
212         Set<Lane> rightLanes = perception.getAccessibleAdjacentLanesRight().get(lanePathInfo.getReferenceLane());
213         if (nextSplitInfo.isSplit())
214         {
215             rightLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
216         }
217         if (!rightLanes.isEmpty() && laneBasedGTU.getVelocity().si > 4.0) // XXX we are driving...
218         {
219             perception.updateBackwardHeadwayGTU();
220             perception.updateParallelGTUsRight();
221             perception.updateLaneTrafficRight();
222             if (perception.getParallelGTUsRight().isEmpty())
223             {
224                 Collection<HeadwayGTU> sameLaneTraffic = new HashSet<>();
225                 if (perception.getForwardHeadwayGTU() != null && perception.getForwardHeadwayGTU().getGtuId() != null)
226                 {
227                     sameLaneTraffic.add(perception.getForwardHeadwayGTU());
228                 }
229                 if (perception.getBackwardHeadwayGTU() != null && perception.getBackwardHeadwayGTU().getGtuId() != null)
230                 {
231                     sameLaneTraffic.add(perception.getBackwardHeadwayGTU());
232                 }
233                 DirectedLaneChangeModel dlcm = new DirectedAltruistic();
234                 DirectedLaneMovementStep dlms =
235                     dlcm.computeLaneChangeAndAcceleration(laneBasedGTU, LateralDirectionality.RIGHT, sameLaneTraffic,
236                         perception.getNeighboringGTUsRight(), laneBasedGTU.getBehavioralCharacteristics()
237                             .getForwardHeadwayDistance(), perception.getSpeedLimit(), new Acceleration(1.0,
238                             AccelerationUnit.SI), new Acceleration(0.5, AccelerationUnit.SI), new Time.Rel(
239                             LANECHANGETIME, TimeUnit.SECOND));
240                 if (dlms.getLaneChange() != null)
241                 {
242                     gtu.setTurnIndicatorStatus(TurnIndicatorStatus.RIGHT);
243                     OperationalPlan laneChangePlan =
244                         makeLaneChangePlanMobil(laneBasedGTU, perception, lanePathInfo, LateralDirectionality.RIGHT);
245                     if (laneChangePlan != null)
246                     {
247                         return laneChangePlan;
248                     }
249                 }
250             }
251         }
252 
253         return currentLanePlan(laneBasedGTU, startTime, locationAtStartTime, lanePathInfo);
254     }
255 
256     /**
257      * Make a plan for the current lane.
258      * @param laneBasedGTU the gtu to generate the plan for
259      * @param startTime the time from which the new operational plan has to be operational
260      * @param locationAtStartTime the location of the GTU at the start time of the new plan
261      * @param lanePathInfo the lane path for the current lane.
262      * @return An operation plan for staying in the current lane.
263      * @throws OperationalPlanException when there is a problem planning a path in the network
264      * @throws GTUException when there is a problem with the state of the GTU when planning a path
265      */
266     private OperationalPlan currentLanePlan(final LaneBasedGTU laneBasedGTU, final Time.Abs startTime,
267         final DirectedPoint locationAtStartTime, final LanePathInfo lanePathInfo) throws OperationalPlanException,
268         GTUException
269     {
270         LanePerception perception = laneBasedGTU.getPerception();
271         GTUFollowingModelOld gfm = laneBasedGTU.getBehavioralCharacteristics().getGTUFollowingModel();
272 
273         // No lane change. Continue on current lane.
274         AccelerationStep accelerationStep;
275         HeadwayGTU headwayGTU = perception.getForwardHeadwayGTU();
276         Length.Rel maxDistance = lanePathInfo.getPath().getLength().minus(laneBasedGTU.getLength().multiplyBy(2.0));
277         if (headwayGTU.getGtuId() == null)
278         {
279             headwayGTU = new HeadwayGTU("ENDPATH", Speed.ZERO, maxDistance, GTUType.NONE);
280         }
281         accelerationStep =
282             gfm.computeAccelerationStep(laneBasedGTU, headwayGTU.getGtuSpeed(), headwayGTU.getDistance(), maxDistance,
283                 perception.getSpeedLimit());
284 
285         // see if we have to continue standing still. In that case, generate a stand still plan
286         if (accelerationStep.getAcceleration().si < 1E-6
287             && laneBasedGTU.getVelocity().si < OperationalPlan.DRIFTING_SPEED_SI)
288         {
289             return new OperationalPlan(laneBasedGTU, locationAtStartTime, startTime, accelerationStep.getDuration());
290         }
291 
292         // build a list of lanes forward, with a maximum headway.
293         List<Segment> operationalPlanSegmentList = new ArrayList<>();
294         if (accelerationStep.getAcceleration().si == 0.0)
295         {
296             Segment segment = new OperationalPlan.SpeedSegment(accelerationStep.getDuration());
297             operationalPlanSegmentList.add(segment);
298         }
299         else
300         {
301             Segment segment =
302                 new OperationalPlan.AccelerationSegment(accelerationStep.getDuration(),
303                     accelerationStep.getAcceleration());
304             operationalPlanSegmentList.add(segment);
305         }
306         OperationalPlan op =
307             new OperationalPlan(laneBasedGTU, lanePathInfo.getPath(), startTime, laneBasedGTU.getVelocity(),
308                 operationalPlanSegmentList);
309         return op;
310     }
311 
312     /**
313      * We are not on a lane that leads to our destination. Determine whether the lateral direction to go is left or right.
314      * @param laneBasedGTU the gtu
315      * @param nextSplitInfo the information about the next split
316      * @return the lateral direction to go, or null if this cannot be determined
317      */
318     private LateralDirectionality
319         determineLeftRight(final LaneBasedGTU laneBasedGTU, final NextSplitInfo nextSplitInfo)
320     {
321         // are the lanes in nextSplitInfo.getCorrectCurrentLanes() left or right of the current lane(s) of the GTU?
322         for (Lane correctLane : nextSplitInfo.getCorrectCurrentLanes())
323         {
324             for (Lane currentLane : laneBasedGTU.getLanes().keySet())
325             {
326                 if (correctLane.getParentLink().equals(currentLane.getParentLink()))
327                 {
328                     double deltaOffset =
329                         correctLane.getDesignLineOffsetAtBegin().si - currentLane.getDesignLineOffsetAtBegin().si;
330                     if (laneBasedGTU.getLanes().get(currentLane).equals(GTUDirectionality.DIR_PLUS))
331                     {
332                         return deltaOffset > 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
333                     }
334                     else
335                     {
336                         return deltaOffset < 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
337                     }
338                 }
339             }
340         }
341         return null;
342     }
343 
344     /**
345      * Make a lane change in the given direction if possible, and return the operational plan, or null if a lane change is not
346      * possible.
347      * @param gtu the GTU that has to make the lane change
348      * @param perception the perception, where forward headway, accessible lanes and speed limit have been assessed
349      * @param lanePathInfo the information for the path on the current lane
350      * @param direction the lateral direction, either LEFT or RIGHT
351      * @return the operational plan for the required lane change, or null if a lane change is not possible.
352      * @throws NetworkException when there is a network inconsistency in updating the perception
353      * @throws GTUException when there is an issue retrieving GTU information for the perception update
354      */
355     private OperationalPlan makeLaneChangePlanMobil(final LaneBasedGTU gtu, final LanePerception perception,
356         final LanePathInfo lanePathInfo, final LateralDirectionality direction) throws GTUException, NetworkException
357     {
358         Collection<HeadwayGTU> otherLaneTraffic;
359         perception.updateForwardHeadwayGTU();
360         perception.updateBackwardHeadwayGTU();
361         if (direction.isLeft())
362         {
363             perception.updateParallelGTUsLeft();
364             perception.updateLaneTrafficLeft();
365             otherLaneTraffic = perception.getNeighboringGTUsLeft();
366         }
367         else
368         {
369             perception.updateParallelGTUsRight();
370             perception.updateLaneTrafficRight();
371             otherLaneTraffic = perception.getNeighboringGTUsRight();
372         }
373         if (!perception.parallelGTUs(direction).isEmpty())
374         {
375             return null;
376         }
377 
378         Collection<HeadwayGTU> sameLaneTraffic = new HashSet<>();
379         if (perception.getForwardHeadwayGTU() != null && perception.getForwardHeadwayGTU().getGtuId() != null)
380         {
381             sameLaneTraffic.add(perception.getForwardHeadwayGTU());
382         }
383         if (perception.getBackwardHeadwayGTU() != null && perception.getBackwardHeadwayGTU().getGtuId() != null)
384         {
385             sameLaneTraffic.add(perception.getBackwardHeadwayGTU());
386         }
387         Length.Rel maxDistance = lanePathInfo.getPath().getLength().minus(gtu.getLength().multiplyBy(2.0));
388         sameLaneTraffic.add(new HeadwayGTU("ENDPATH", Speed.ZERO, maxDistance, GTUType.NONE));
389         otherLaneTraffic.add(new HeadwayGTU("ENDPATH", Speed.ZERO, maxDistance, GTUType.NONE));
390 
391         // TODO if we move from standstill, create a longer plan, e.g. 4-5 seconds, with high acceleration!
392         // TODO make type of plan (Egoistic, Altruistic) parameter of the class
393         DirectedLaneChangeModel dlcm = new DirectedEgoistic();
394         // TODO make the elasticities 2.0 and 0.1 parameters of the class
395         DirectedLaneMovementStep dlms =
396             dlcm.computeLaneChangeAndAcceleration(gtu, direction, sameLaneTraffic, otherLaneTraffic, gtu
397                 .getBehavioralCharacteristics().getForwardHeadwayDistance(), perception.getSpeedLimit(),
398                 new Acceleration(2.0, AccelerationUnit.SI), new Acceleration(0.1, AccelerationUnit.SI), new Time.Rel(
399                     LANECHANGETIME, TimeUnit.SECOND));
400         if (dlms.getLaneChange() == null)
401         {
402             return null;
403         }
404 
405         Lane startLane = getReferenceLane(gtu);
406         Set<Lane> adjacentLanes = startLane.accessibleAdjacentLanes(direction, gtu.getGTUType());
407         // TODO take the widest (now a random one)
408         Lane adjacentLane = adjacentLanes.iterator().next();
409         Length.Rel startPosition = gtu.position(startLane, gtu.getReference());
410         double fraction2 = startLane.fraction(startPosition);
411         LanePathInfo lanePathInfo2 =
412             buildLanePathInfo(gtu, gtu.getBehavioralCharacteristics().getForwardHeadwayDistance(), adjacentLane,
413                 fraction2, gtu.getLanes().get(startLane));
414 
415         // interpolate the path for the most conservative one
416         AccelerationStep accelerationStep = dlms.getGfmr();
417         Speed v0 = gtu.getVelocity();
418         double t = accelerationStep.getDuration().si;
419         double distanceSI = v0.si * t + 0.5 * accelerationStep.getAcceleration().si * t * t;
420         Speed vt = v0.plus(accelerationStep.getAcceleration().multiplyBy(accelerationStep.getDuration()));
421 
422         // XXX if the distance is too small, do not build a path. Minimum = 0.5 * vehicle length
423         // TODO this should be solved in the time domain, not in the distance domain...
424         if (distanceSI < 2.0) // XXX arbitrary...
425         {
426             return null;
427         }
428 
429         if (perception.getForwardHeadwayGTU() == null
430             || (perception.getForwardHeadwayGTU() != null && perception.getForwardHeadwayGTU().getDistance().si < 5.0))
431         {
432             return null;
433         }
434 
435         OTSLine3D path;
436         try
437         {
438             path = interpolateScurve(lanePathInfo.getPath(), lanePathInfo2.getPath(), distanceSI);
439         }
440         catch (OTSGeometryException exception)
441         {
442             System.err.println("GTU          : " + gtu);
443             System.err.println("LanePathInfo : " + lanePathInfo.getPath());
444             System.err.println("LanePathInfo2: " + lanePathInfo2.getPath());
445             System.err.println("distanceSI   : " + distanceSI);
446             System.err.println("v0, t, vt, a : " + v0 + ", " + t + ", " + vt + ", "
447                 + accelerationStep.getAcceleration());
448             throw new GTUException(exception);
449         }
450 
451         try
452         {
453             double a = accelerationStep.getAcceleration().si;
454             // recalculate based on actual path length...
455             if (path.getLengthSI() > distanceSI * 1.5) // XXX arbitrary...
456             {
457                 a = (path.getLengthSI() - v0.si) / LANECHANGETIME;
458                 vt = new Speed(v0.si + LANECHANGETIME * a, SpeedUnit.SI);
459             }
460 
461             // enter the other lane(s) at the same fractional position as the current position on the lane(s)
462             // schedule leaving the current lane(s) that do not overlap with the target lane(s)
463             for (Lane lane : gtu.getLanes().keySet())
464             {
465                 gtu.getSimulator().scheduleEventRel(new Time.Rel(LANECHANGETIME - 0.001, TimeUnit.SI), this, gtu,
466                     "leaveLane", new Object[]{lane});
467             }
468 
469             // also leave the lanes that we will still ENTER from the 'old' lanes:
470             for (LaneDirection laneDirection : lanePathInfo.getLaneDirectionList())
471             {
472                 if (!gtu.getLanes().keySet().contains(laneDirection.getLane()))
473                 {
474                     gtu.getSimulator().scheduleEventRel(new Time.Rel(LANECHANGETIME - 0.001, TimeUnit.SI), this, gtu,
475                         "leaveLane", new Object[]{laneDirection.getLane()});
476                 }
477             }
478 
479             gtu.enterLane(adjacentLane, adjacentLane.getLength().multiplyBy(fraction2), gtu.getLanes().get(startLane));
480             System.out.println("gtu " + gtu.getId() + " entered lane " + adjacentLane + " at pos "
481                 + adjacentLane.getLength().multiplyBy(fraction2));
482 
483             List<Segment> operationalPlanSegmentList = new ArrayList<>();
484             if (a == 0.0)
485             {
486                 Segment segment = new OperationalPlan.SpeedSegment(new Time.Rel(LANECHANGETIME, TimeUnit.SI));
487                 operationalPlanSegmentList.add(segment);
488             }
489             else
490             {
491                 Segment segment =
492                     new OperationalPlan.AccelerationSegment(new Time.Rel(LANECHANGETIME, TimeUnit.SI),
493                         new Acceleration(a, AccelerationUnit.SI));
494                 operationalPlanSegmentList.add(segment);
495             }
496             OperationalPlan op =
497                 new OperationalPlan(gtu, path, gtu.getSimulator().getSimulatorTime().getTime(), v0,
498                     operationalPlanSegmentList);
499             this.earliestNexLaneChangeTime =
500                 gtu.getSimulator().getSimulatorTime().getTime().plus(new Time.Rel(17, TimeUnit.SECOND));
501 
502             // make sure out turn indicator is on!
503             gtu.setTurnIndicatorStatus(direction.isLeft() ? TurnIndicatorStatus.LEFT : TurnIndicatorStatus.RIGHT);
504 
505             return op;
506         }
507         catch (OperationalPlanException | SimRuntimeException exception)
508         {
509             throw new GTUException(exception);
510         }
511     }
512 
513     /**
514      * Linearly interpolate between two lines.
515      * @param line1 first line
516      * @param line2 second line
517      * @param lengthSI length of the interpolation (at this point 100% line 2)
518      * @return a line between line 1 and line 2
519      * @throws OTSGeometryException when interpolation fails
520      */
521     private static OTSLine3D interpolateLinear(OTSLine3D line1, OTSLine3D line2, final double lengthSI)
522         throws OTSGeometryException
523     {
524         OTSLine3D l1 = line1.extract(0, lengthSI);
525         OTSLine3D l2 = line2.extract(0, lengthSI);
526         List<OTSPoint3D> line = new ArrayList<>();
527         int num = 32;
528         for (int i = 0; i <= num; i++)
529         {
530             double f0 = 1.0 * i / num;
531             double f1 = 1.0 - f0;
532             DirectedPoint p1 = l1.getLocationFraction(f0);
533             DirectedPoint p2 = l2.getLocationFraction(f0);
534             line.add(new OTSPoint3D(p1.x * f1 + p2.x * f0, p1.y * f1 + p2.y * f0, p1.z * f1 + p2.z * f0));
535         }
536         return new OTSLine3D(line);
537     }
538 
539     /**
540      * S-curve interpolation between two lines. We use a 5th order Bezier curve for this.
541      * @param line1 first line
542      * @param line2 second line
543      * @param lengthSI length of the interpolation (at this point 100% line 2)
544      * @return a line between line 1 and line 2
545      * @throws OTSGeometryException when interpolation fails
546      */
547     private static OTSLine3D interpolateScurve(OTSLine3D line1, OTSLine3D line2, final double lengthSI)
548         throws OTSGeometryException
549     {
550         OTSLine3D l1 = line1.extract(0, lengthSI);
551         OTSLine3D l2 = line2.extract(0, lengthSI);
552         List<OTSPoint3D> line = new ArrayList<>();
553         int num = 64;
554         for (int i = 0; i <= num; i++)
555         {
556             double factor = SCURVE[i];
557             DirectedPoint p1 = l1.getLocationFraction(i / 64.0);
558             DirectedPoint p2 = l2.getLocationFraction(i / 64.0);
559             line.add(new OTSPoint3D(p1.x + factor * (p2.x - p1.x), p1.y + factor * (p2.y - p1.y), p1.z + factor
560                 * (p2.z - p1.z)));
561         }
562         return new OTSLine3D(line);
563     }
564 }