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 LaneBasedGTUFollowingLaneChangeTacticalPlanner2 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 = 5.0;
83  
84      /** Earliest next lane change time. */
85      private Time.Abs earliestNexLaneChangeTime = Time.Abs.ZERO;
86  
87      /** The defined lane change path in the space domain. null if no lane change busy. */
88      private OTSLine3D laneChangePath = null;
89  
90      /** Progress on the lane change plan along the defined lane change path in the space domain. */
91      private double laneChangeFraction = 0.0;
92  
93      /** Lane change direction for the turn indicator. */
94      private LateralDirectionality laneChangeDirection;
95  
96      /** Bezier curve points for gradual lane change. */
97      private static final double[] SCURVE;
98  
99      static
100     {
101         SCURVE = new double[65];
102         for (int i = 0; i <= 64; i++)
103         {
104             double t = i / 64.0;
105             double ot = 1.0 - t;
106             double t3 = t * t * t;
107             SCURVE[i] = 10.0 * t3 * ot * ot + 5.0 * t3 * t * ot + t3 * t * t;
108         }
109     }
110 
111     /**
112      * Instantiated a tactical planner with just GTU following behavior and no lane changes.
113      */
114     public LaneBasedGTUFollowingLaneChangeTacticalPlanner2()
115     {
116         super();
117     }
118 
119     /** {@inheritDoc} */
120     @Override
121     public OperationalPlan generateOperationalPlan(final GTU gtu, final Time.Abs startTime,
122         final DirectedPoint locationAtStartTime) throws OperationalPlanException, NetworkException, GTUException
123     {
124         // ask Perception for the local situation
125         LaneBasedGTU laneBasedGTU = (LaneBasedGTU) gtu;
126         LanePerception perception = laneBasedGTU.getPerception();
127         LaneBasedBehavioralCharacteristics drivingCharacteristics = laneBasedGTU.getBehavioralCharacteristics();
128 
129         // start with the turn indicator off -- this can change during the method
130         laneBasedGTU.setTurnIndicatorStatus(TurnIndicatorStatus.NONE);
131 
132         // if the GTU's maximum speed is zero (block), generate a stand still plan for one second
133         if (laneBasedGTU.getMaximumVelocity().si < OperationalPlan.DRIFTING_SPEED_SI)
134         {
135             return new OperationalPlan(gtu, locationAtStartTime, startTime, new Time.Rel(1.0, TimeUnit.SECOND));
136         }
137 
138         // perceive the forward headway, accessible lanes and speed limit.
139         perception.updateForwardHeadwayGTU();
140         perception.updateSpeedLimit();
141 
142         // find out where we are going
143         Length.Rel forwardHeadway = drivingCharacteristics.getForwardHeadwayDistance();
144         LanePathInfo lanePathInfo = buildLanePathInfo(laneBasedGTU, forwardHeadway);
145         NextSplitInfo nextSplitInfo = determineNextSplit(laneBasedGTU, forwardHeadway);
146         Set<Lane> correctLanes = laneBasedGTU.getLanes().keySet();
147         correctLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
148 
149         // step 0. if we are making a lane change: continue till the end in small steps.
150         if (this.laneChangePath != null)
151         {
152             try
153             {
154                 OTSLine3D pathRemainder = this.laneChangePath.extractFractional(this.laneChangeFraction, 1.0);
155                 GTUFollowingModelOld gfm = laneBasedGTU.getBehavioralCharacteristics().getGTUFollowingModel();
156                 AccelerationStep accelerationStep;
157                 HeadwayGTU headwayGTU = perception.getForwardHeadwayGTU(); // XXX multiple lanes!!!!!!
158                 Length.Rel maxDistance = lanePathInfo.getPath().getLength();
159                 if (headwayGTU.getGtuId() == null)
160                 {
161                     headwayGTU = new HeadwayGTU("ENDPATH", Speed.ZERO, maxDistance, GTUType.NONE);
162                 }
163                 accelerationStep =
164                     gfm.computeAccelerationStep(laneBasedGTU, headwayGTU.getGtuSpeed(), headwayGTU.getDistance(),
165                         maxDistance, perception.getSpeedLimit());
166 
167                 List<Segment> operationalPlanSegmentList = new ArrayList<>();
168                 Speed v = gtu.getVelocity();
169                 if (accelerationStep.getAcceleration().si == 0.0)
170                 {
171                     Time.Rel t = accelerationStep.getDuration();
172                     Length.Rel x = v.multiplyBy(t);
173                     if (x.gt(pathRemainder.getLength()))
174                     {
175                         t = pathRemainder.getLength().divideBy(v);
176                         this.laneChangePath = null;
177                         // TODO book out of not covered lanes...
178                     }
179                     Segment segment = new OperationalPlan.SpeedSegment(t);
180                     operationalPlanSegmentList.add(segment);
181                 }
182                 else
183                 {
184                     Time.Rel t = accelerationStep.getDuration();
185                     Acceleration a = accelerationStep.getAcceleration();
186                     Length.Rel x = v.multiplyBy(t).plus(a.multiplyBy(t).multiplyBy(t).multiplyBy(0.5));
187                     if (x.gt(pathRemainder.getLength()))
188                     {
189                         // constant speed for the last small part...
190                         t = pathRemainder.getLength().divideBy(v);
191                         this.laneChangePath = null;
192                         Segment segment = new OperationalPlan.SpeedSegment(t);
193                         operationalPlanSegmentList.add(segment);
194                         // TODO book out of not covered lanes...
195                     }
196                     else
197                     {
198                         Segment segment =
199                             new OperationalPlan.AccelerationSegment(accelerationStep.getDuration(),
200                                 accelerationStep.getAcceleration());
201                         operationalPlanSegmentList.add(segment);
202                     }
203                 }
204                 OperationalPlan op =
205                     new OperationalPlan(laneBasedGTU, lanePathInfo.getPath(), startTime, laneBasedGTU.getVelocity(),
206                         operationalPlanSegmentList);
207                 laneBasedGTU.setTurnIndicatorStatus(this.laneChangeDirection.isLeft() ? TurnIndicatorStatus.LEFT
208                     : TurnIndicatorStatus.RIGHT);
209                 return op;
210             }
211             catch (OTSGeometryException ge)
212             {
213                 throw new GTUException(ge);
214             }
215         }
216 
217         // Step 1: Do we want to change lanes because of the current lane not leading to our destination?
218         if (lanePathInfo.getPath().getLength().lt(forwardHeadway))
219         {
220             if (correctLanes.isEmpty())
221             {
222                 LateralDirectionality direction = determineLeftRight(laneBasedGTU, nextSplitInfo);
223                 if (direction != null)
224                 {
225                     gtu.setTurnIndicatorStatus(direction.isLeft() ? TurnIndicatorStatus.LEFT
226                         : TurnIndicatorStatus.RIGHT);
227                     OperationalPlan laneChangePlan =
228                         makeLaneChangePlanMobil(laneBasedGTU, perception, lanePathInfo, direction);
229                     if (laneChangePlan != null)
230                     {
231                         return laneChangePlan;
232                     }
233                 }
234             }
235         }
236 
237         // Condition, if we have just changed lane, let's not change immediately again.
238         if (gtu.getSimulator().getSimulatorTime().getTime().lt(this.earliestNexLaneChangeTime))
239         {
240             return currentLanePlan(laneBasedGTU, startTime, locationAtStartTime, lanePathInfo);
241         }
242 
243         // Step 2. Do we want to change lanes to the left because of our predecessor on the current lane?
244         // does the lane left of us [TODO: driving direction] bring us to our destination as well?
245         perception.updateAccessibleAdjacentLanesLeft();
246         perception.updateAccessibleAdjacentLanesRight();
247         Set<Lane> leftLanes = perception.getAccessibleAdjacentLanesLeft().get(lanePathInfo.getReferenceLane());
248         if (nextSplitInfo.isSplit())
249         {
250             leftLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
251         }
252         if (!leftLanes.isEmpty() && laneBasedGTU.getVelocity().si > 4.0) // XXX we are driving...
253         {
254             perception.updateBackwardHeadwayGTU();
255             perception.updateParallelGTUsLeft();
256             perception.updateLaneTrafficLeft();
257             if (perception.getParallelGTUsLeft().isEmpty())
258             {
259                 Collection<HeadwayGTU> sameLaneTraffic = new HashSet<>();
260                 if (perception.getForwardHeadwayGTU() != null && perception.getForwardHeadwayGTU().getGtuId() != null)
261                 {
262                     sameLaneTraffic.add(perception.getForwardHeadwayGTU());
263                 }
264                 if (perception.getBackwardHeadwayGTU() != null && perception.getBackwardHeadwayGTU().getGtuId() != null)
265                 {
266                     sameLaneTraffic.add(perception.getBackwardHeadwayGTU());
267                 }
268                 DirectedLaneChangeModel dlcm = new DirectedAltruistic();
269                 DirectedLaneMovementStep dlms =
270                     dlcm.computeLaneChangeAndAcceleration(laneBasedGTU, LateralDirectionality.LEFT, sameLaneTraffic,
271                         perception.getNeighboringGTUsLeft(), laneBasedGTU.getBehavioralCharacteristics()
272                             .getForwardHeadwayDistance(), perception.getSpeedLimit(), new Acceleration(1.0,
273                             AccelerationUnit.SI), new Acceleration(0.5, AccelerationUnit.SI), new Time.Rel(
274                             LANECHANGETIME, TimeUnit.SECOND));
275                 if (dlms.getLaneChange() != null)
276                 {
277                     gtu.setTurnIndicatorStatus(TurnIndicatorStatus.LEFT);
278                     OperationalPlan laneChangePlan =
279                         makeLaneChangePlanMobil(laneBasedGTU, perception, lanePathInfo, LateralDirectionality.LEFT);
280                     if (laneChangePlan != null)
281                     {
282                         return laneChangePlan;
283                     }
284                 }
285             }
286         }
287 
288         // Step 3. Do we want to change lanes to the right because of traffic rules?
289         Set<Lane> rightLanes = perception.getAccessibleAdjacentLanesRight().get(lanePathInfo.getReferenceLane());
290         if (nextSplitInfo.isSplit())
291         {
292             rightLanes.retainAll(nextSplitInfo.getCorrectCurrentLanes());
293         }
294         if (!rightLanes.isEmpty() && laneBasedGTU.getVelocity().si > 4.0) // XXX we are driving...
295         {
296             perception.updateBackwardHeadwayGTU();
297             perception.updateParallelGTUsRight();
298             perception.updateLaneTrafficRight();
299             if (perception.getParallelGTUsRight().isEmpty())
300             {
301                 Collection<HeadwayGTU> sameLaneTraffic = new HashSet<>();
302                 if (perception.getForwardHeadwayGTU() != null && perception.getForwardHeadwayGTU().getGtuId() != null)
303                 {
304                     sameLaneTraffic.add(perception.getForwardHeadwayGTU());
305                 }
306                 if (perception.getBackwardHeadwayGTU() != null && perception.getBackwardHeadwayGTU().getGtuId() != null)
307                 {
308                     sameLaneTraffic.add(perception.getBackwardHeadwayGTU());
309                 }
310                 DirectedLaneChangeModel dlcm = new DirectedAltruistic();
311                 DirectedLaneMovementStep dlms =
312                     dlcm.computeLaneChangeAndAcceleration(laneBasedGTU, LateralDirectionality.RIGHT, sameLaneTraffic,
313                         perception.getNeighboringGTUsRight(), laneBasedGTU.getBehavioralCharacteristics()
314                             .getForwardHeadwayDistance(), perception.getSpeedLimit(), new Acceleration(1.0,
315                             AccelerationUnit.SI), new Acceleration(0.5, AccelerationUnit.SI), new Time.Rel(
316                             LANECHANGETIME, TimeUnit.SECOND));
317                 if (dlms.getLaneChange() != null)
318                 {
319                     gtu.setTurnIndicatorStatus(TurnIndicatorStatus.RIGHT);
320                     OperationalPlan laneChangePlan =
321                         makeLaneChangePlanMobil(laneBasedGTU, perception, lanePathInfo, LateralDirectionality.RIGHT);
322                     if (laneChangePlan != null)
323                     {
324                         return laneChangePlan;
325                     }
326                 }
327             }
328         }
329 
330         return currentLanePlan(laneBasedGTU, startTime, locationAtStartTime, lanePathInfo);
331     }
332 
333     /**
334      * Make a plan for the current lane.
335      * @param laneBasedGTU the gtu to generate the plan for
336      * @param startTime the time from which the new operational plan has to be operational
337      * @param locationAtStartTime the location of the GTU at the start time of the new plan
338      * @param lanePathInfo the lane path for the current lane.
339      * @return An operation plan for staying in the current lane.
340      * @throws OperationalPlanException when there is a problem planning a path in the network
341      * @throws GTUException when there is a problem with the state of the GTU when planning a path
342      */
343     private OperationalPlan currentLanePlan(final LaneBasedGTU laneBasedGTU, final Time.Abs startTime,
344         final DirectedPoint locationAtStartTime, final LanePathInfo lanePathInfo) throws OperationalPlanException,
345         GTUException
346     {
347         LanePerception perception = laneBasedGTU.getPerception();
348         GTUFollowingModelOld gfm = laneBasedGTU.getBehavioralCharacteristics().getGTUFollowingModel();
349 
350         // No lane change. Continue on current lane.
351         AccelerationStep accelerationStep;
352         HeadwayGTU headwayGTU = perception.getForwardHeadwayGTU();
353         Length.Rel maxDistance = lanePathInfo.getPath().getLength().minus(laneBasedGTU.getLength().multiplyBy(2.0));
354         if (headwayGTU.getGtuId() == null)
355         {
356             headwayGTU = new HeadwayGTU("ENDPATH", Speed.ZERO, maxDistance, GTUType.NONE);
357         }
358         accelerationStep =
359             gfm.computeAccelerationStep(laneBasedGTU, headwayGTU.getGtuSpeed(), headwayGTU.getDistance(), maxDistance,
360                 perception.getSpeedLimit());
361 
362         // see if we have to continue standing still. In that case, generate a stand still plan
363         if (accelerationStep.getAcceleration().si < 1E-6
364             && laneBasedGTU.getVelocity().si < OperationalPlan.DRIFTING_SPEED_SI)
365         {
366             return new OperationalPlan(laneBasedGTU, locationAtStartTime, startTime, accelerationStep.getDuration());
367         }
368 
369         // build a list of lanes forward, with a maximum headway.
370         List<Segment> operationalPlanSegmentList = new ArrayList<>();
371         if (accelerationStep.getAcceleration().si == 0.0)
372         {
373             Segment segment = new OperationalPlan.SpeedSegment(accelerationStep.getDuration());
374             operationalPlanSegmentList.add(segment);
375         }
376         else
377         {
378             Segment segment =
379                 new OperationalPlan.AccelerationSegment(accelerationStep.getDuration(),
380                     accelerationStep.getAcceleration());
381             operationalPlanSegmentList.add(segment);
382         }
383         OperationalPlan op =
384             new OperationalPlan(laneBasedGTU, lanePathInfo.getPath(), startTime, laneBasedGTU.getVelocity(),
385                 operationalPlanSegmentList);
386         return op;
387     }
388 
389     /**
390      * We are not on a lane that leads to our destination. Determine whether the lateral direction to go is left or right.
391      * @param laneBasedGTU the gtu
392      * @param nextSplitInfo the information about the next split
393      * @return the lateral direction to go, or null if this cannot be determined
394      */
395     private LateralDirectionality
396         determineLeftRight(final LaneBasedGTU laneBasedGTU, final NextSplitInfo nextSplitInfo)
397     {
398         // are the lanes in nextSplitInfo.getCorrectCurrentLanes() left or right of the current lane(s) of the GTU?
399         for (Lane correctLane : nextSplitInfo.getCorrectCurrentLanes())
400         {
401             for (Lane currentLane : laneBasedGTU.getLanes().keySet())
402             {
403                 if (correctLane.getParentLink().equals(currentLane.getParentLink()))
404                 {
405                     double deltaOffset =
406                         correctLane.getDesignLineOffsetAtBegin().si - currentLane.getDesignLineOffsetAtBegin().si;
407                     if (laneBasedGTU.getLanes().get(currentLane).equals(GTUDirectionality.DIR_PLUS))
408                     {
409                         return deltaOffset > 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
410                     }
411                     else
412                     {
413                         return deltaOffset < 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
414                     }
415                 }
416             }
417         }
418         return null;
419     }
420 
421     /**
422      * Make a lane change in the given direction if possible, and return the operational plan, or null if a lane change is not
423      * possible.
424      * @param gtu the GTU that has to make the lane change
425      * @param perception the perception, where forward headway, accessible lanes and speed limit have been assessed
426      * @param lanePathInfo the information for the path on the current lane
427      * @param direction the lateral direction, either LEFT or RIGHT
428      * @return the operational plan for the required lane change, or null if a lane change is not possible.
429      * @throws NetworkException when there is a network inconsistency in updating the perception
430      * @throws GTUException when there is an issue retrieving GTU information for the perception update
431      */
432     private OperationalPlan makeLaneChangePlanMobil(final LaneBasedGTU gtu, final LanePerception perception,
433         final LanePathInfo lanePathInfo, final LateralDirectionality direction) throws GTUException, NetworkException
434     {
435         Collection<HeadwayGTU> otherLaneTraffic;
436         perception.updateForwardHeadwayGTU();
437         perception.updateBackwardHeadwayGTU();
438         if (direction.isLeft())
439         {
440             perception.updateParallelGTUsLeft();
441             perception.updateLaneTrafficLeft();
442             otherLaneTraffic = perception.getNeighboringGTUsLeft();
443         }
444         else
445         {
446             perception.updateParallelGTUsRight();
447             perception.updateLaneTrafficRight();
448             otherLaneTraffic = perception.getNeighboringGTUsRight();
449         }
450         if (!perception.parallelGTUs(direction).isEmpty())
451         {
452             return null;
453         }
454 
455         Collection<HeadwayGTU> sameLaneTraffic = new HashSet<>();
456         if (perception.getForwardHeadwayGTU() != null && perception.getForwardHeadwayGTU().getGtuId() != null)
457         {
458             sameLaneTraffic.add(perception.getForwardHeadwayGTU());
459         }
460         if (perception.getBackwardHeadwayGTU() != null && perception.getBackwardHeadwayGTU().getGtuId() != null)
461         {
462             sameLaneTraffic.add(perception.getBackwardHeadwayGTU());
463         }
464         Length.Rel maxDistance = lanePathInfo.getPath().getLength().minus(gtu.getLength().multiplyBy(2.0));
465         sameLaneTraffic.add(new HeadwayGTU("ENDPATH", Speed.ZERO, maxDistance, GTUType.NONE));
466         otherLaneTraffic.add(new HeadwayGTU("ENDPATH", Speed.ZERO, maxDistance, GTUType.NONE));
467 
468         // TODO if we move from standstill, create a longer plan, e.g. 4-5 seconds, with high acceleration!
469         // TODO make type of plan (Egoistic, Altruistic) parameter of the class
470         DirectedLaneChangeModel dlcm = new DirectedEgoistic();
471         // TODO make the elasticities 2.0 and 0.1 parameters of the class
472         DirectedLaneMovementStep dlms =
473             dlcm.computeLaneChangeAndAcceleration(gtu, direction, sameLaneTraffic, otherLaneTraffic, gtu
474                 .getBehavioralCharacteristics().getForwardHeadwayDistance(), perception.getSpeedLimit(),
475                 new Acceleration(2.0, AccelerationUnit.SI), new Acceleration(0.1, AccelerationUnit.SI), new Time.Rel(
476                     LANECHANGETIME, TimeUnit.SECOND));
477         if (dlms.getLaneChange() == null)
478         {
479             return null;
480         }
481 
482         Lane startLane = getReferenceLane(gtu);
483         Set<Lane> adjacentLanes = startLane.accessibleAdjacentLanes(direction, gtu.getGTUType());
484         // TODO take the widest (now a random one)
485         Lane adjacentLane = adjacentLanes.iterator().next();
486         Length.Rel startPosition = gtu.position(startLane, gtu.getReference());
487         double fraction2 = startLane.fraction(startPosition);
488         LanePathInfo lanePathInfo2 =
489             buildLanePathInfo(gtu, gtu.getBehavioralCharacteristics().getForwardHeadwayDistance(), adjacentLane,
490                 fraction2, gtu.getLanes().get(startLane));
491 
492         // interpolate the path for the most conservative one
493         AccelerationStep accelerationStep = dlms.getGfmr();
494         Speed v0 = gtu.getVelocity();
495         double t = accelerationStep.getDuration().si;
496         double distanceSI = v0.si * t + 0.5 * accelerationStep.getAcceleration().si * t * t;
497         Speed vt = v0.plus(accelerationStep.getAcceleration().multiplyBy(accelerationStep.getDuration()));
498 
499         // XXX if the distance is too small, do not build a path. Minimum = 0.5 * vehicle length
500         // TODO this should be solved in the time domain, not in the distance domain...
501         if (distanceSI < 2.0) // XXX arbitrary...
502         {
503             return null;
504         }
505 
506         if (perception.getForwardHeadwayGTU() == null
507             || (perception.getForwardHeadwayGTU() != null && perception.getForwardHeadwayGTU().getDistance().si < 5.0))
508         {
509             return null;
510         }
511 
512         OTSLine3D path;
513         try
514         {
515             path = interpolateScurve(lanePathInfo.getPath(), lanePathInfo2.getPath(), distanceSI);
516         }
517         catch (OTSGeometryException exception)
518         {
519             System.err.println("GTU          : " + gtu);
520             System.err.println("LanePathInfo : " + lanePathInfo.getPath());
521             System.err.println("LanePathInfo2: " + lanePathInfo2.getPath());
522             System.err.println("distanceSI   : " + distanceSI);
523             System.err.println("v0, t, vt, a : " + v0 + ", " + t + ", " + vt + ", "
524                 + accelerationStep.getAcceleration());
525             throw new GTUException(exception);
526         }
527 
528         try
529         {
530             double a = accelerationStep.getAcceleration().si;
531             // recalculate based on actual path length...
532             if (path.getLengthSI() > distanceSI * 1.5) // XXX arbitrary...
533             {
534                 a = (path.getLengthSI() - v0.si) / LANECHANGETIME;
535                 vt = new Speed(v0.si + LANECHANGETIME * a, SpeedUnit.SI);
536             }
537 
538             // enter the other lane(s) at the same fractional position as the current position on the lane(s)
539             // schedule leaving the current lane(s) that do not overlap with the target lane(s)
540             for (Lane lane : gtu.getLanes().keySet())
541             {
542                 gtu.getSimulator().scheduleEventRel(new Time.Rel(LANECHANGETIME - 0.001, TimeUnit.SI), this, gtu,
543                     "leaveLane", new Object[]{lane});
544             }
545 
546             // also leave the lanes that we will still ENTER from the 'old' lanes:
547             for (LaneDirection laneDirection : lanePathInfo.getLaneDirectionList())
548             {
549                 if (!gtu.getLanes().keySet().contains(laneDirection.getLane()))
550                 {
551                     gtu.getSimulator().scheduleEventRel(new Time.Rel(LANECHANGETIME - 0.001, TimeUnit.SI), this, gtu,
552                         "leaveLane", new Object[]{laneDirection.getLane()});
553                 }
554             }
555 
556             gtu.enterLane(adjacentLane, adjacentLane.getLength().multiplyBy(fraction2), gtu.getLanes().get(startLane));
557             System.out.println("gtu " + gtu.getId() + " entered lane " + adjacentLane + " at pos "
558                 + adjacentLane.getLength().multiplyBy(fraction2));
559 
560             List<Segment> operationalPlanSegmentList = new ArrayList<>();
561             // Segment segment = new OperationalPlan.SpeedSegment(new Time.Rel(0.01, TimeUnit.SI));
562             // operationalPlanSegmentList.add(segment);
563             if (a == 0.0)
564             {
565                 Segment segment = new OperationalPlan.SpeedSegment(new Time.Rel(LANECHANGETIME, TimeUnit.SI));
566                 operationalPlanSegmentList.add(segment);
567             }
568             else
569             {
570                 Segment segment =
571                     new OperationalPlan.AccelerationSegment(new Time.Rel(LANECHANGETIME, TimeUnit.SI),
572                         new Acceleration(a, AccelerationUnit.SI));
573                 operationalPlanSegmentList.add(segment);
574             }
575             /*-
576             if (v0.si == 0.0)
577             {
578                 v0 = new Speed(0.1, SpeedUnit.SI);
579             }
580              */
581             OperationalPlan op =
582                 new OperationalPlan(gtu, path, gtu.getSimulator().getSimulatorTime().getTime(), v0,
583                     operationalPlanSegmentList);
584             this.earliestNexLaneChangeTime =
585                 gtu.getSimulator().getSimulatorTime().getTime().plus(new Time.Rel(17, TimeUnit.SECOND));
586 
587             // make sure out turn indicator is on!
588             gtu.setTurnIndicatorStatus(direction.isLeft() ? TurnIndicatorStatus.LEFT : TurnIndicatorStatus.RIGHT);
589 
590             // this.laneChangeDirection = direction;
591             // this.laneChangeFraction = 0.0;
592             // this.laneChangePath = path;
593 
594             return op;
595         }
596         catch (OperationalPlanException | SimRuntimeException exception)
597         {
598             throw new GTUException(exception);
599         }
600     }
601 
602     /**
603      * Linearly interpolate between two lines.
604      * @param line1 first line
605      * @param line2 second line
606      * @param lengthSI length of the interpolation (at this point 100% line 2)
607      * @return a line between line 1 and line 2
608      * @throws OTSGeometryException when interpolation fails
609      */
610     private static OTSLine3D interpolateLinear(OTSLine3D line1, OTSLine3D line2, final double lengthSI)
611         throws OTSGeometryException
612     {
613         OTSLine3D l1 = line1.extract(0, lengthSI);
614         OTSLine3D l2 = line2.extract(0, lengthSI);
615         List<OTSPoint3D> line = new ArrayList<>();
616         int num = 32;
617         for (int i = 0; i <= num; i++)
618         {
619             double f0 = 1.0 * i / num;
620             double f1 = 1.0 - f0;
621             DirectedPoint p1 = l1.getLocationFraction(f0);
622             DirectedPoint p2 = l2.getLocationFraction(f0);
623             line.add(new OTSPoint3D(p1.x * f1 + p2.x * f0, p1.y * f1 + p2.y * f0, p1.z * f1 + p2.z * f0));
624         }
625         return new OTSLine3D(line);
626     }
627 
628     /**
629      * S-curve interpolation between two lines. We use a 5th order Bezier curve for this.
630      * @param line1 first line
631      * @param line2 second line
632      * @param lengthSI length of the interpolation (at this point 100% line 2)
633      * @return a line between line 1 and line 2
634      * @throws OTSGeometryException when interpolation fails
635      */
636     private static OTSLine3D interpolateScurve(OTSLine3D line1, OTSLine3D line2, final double lengthSI)
637         throws OTSGeometryException
638     {
639         OTSLine3D l1 = line1.extract(0, lengthSI);
640         OTSLine3D l2 = line2.extract(0, lengthSI);
641         List<OTSPoint3D> line = new ArrayList<>();
642         int num = 64;
643         for (int i = 0; i <= num; i++)
644         {
645             double factor = SCURVE[i];
646             DirectedPoint p1 = l1.getLocationFraction(i / 64.0);
647             DirectedPoint p2 = l2.getLocationFraction(i / 64.0);
648             line.add(new OTSPoint3D(p1.x + factor * (p2.x - p1.x), p1.y + factor * (p2.y - p1.y), p1.z + factor
649                 * (p2.z - p1.z)));
650         }
651         return new OTSLine3D(line);
652     }
653 }