View Javadoc
1   package org.opentrafficsim.road.gtu.lane.tactical;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.HashSet;
6   import java.util.Iterator;
7   import java.util.List;
8   import java.util.Map;
9   import java.util.Set;
10  
11  import org.djunits.value.vdouble.scalar.Length;
12  import org.djunits.value.vdouble.scalar.Time;
13  import org.opentrafficsim.core.geometry.OTSGeometryException;
14  import org.opentrafficsim.core.geometry.OTSLine3D;
15  import org.opentrafficsim.core.gtu.GTUDirectionality;
16  import org.opentrafficsim.core.gtu.GTUException;
17  import org.opentrafficsim.core.gtu.behavioralcharacteristics.BehavioralCharacteristics;
18  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
19  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
20  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
21  import org.opentrafficsim.core.network.LateralDirectionality;
22  import org.opentrafficsim.core.network.Link;
23  import org.opentrafficsim.core.network.LinkDirection;
24  import org.opentrafficsim.core.network.NetworkException;
25  import org.opentrafficsim.core.network.Node;
26  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
27  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
28  import org.opentrafficsim.road.gtu.lane.plan.operational.LaneBasedOperationalPlan;
29  import org.opentrafficsim.road.gtu.lane.plan.operational.LaneOperationalPlanBuilder;
30  import org.opentrafficsim.road.gtu.lane.plan.operational.LaneOperationalPlanBuilder.LaneChange;
31  import org.opentrafficsim.road.gtu.lane.plan.operational.SimpleOperationalPlan;
32  import org.opentrafficsim.road.gtu.lane.tactical.following.CarFollowingModel;
33  import org.opentrafficsim.road.network.lane.CrossSectionElement;
34  import org.opentrafficsim.road.network.lane.CrossSectionLink;
35  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
36  import org.opentrafficsim.road.network.lane.Lane;
37  import org.opentrafficsim.road.network.lane.LaneDirection;
38  
39  /**
40   * A lane-based tactical planner generates an operational plan for the lane-based GTU. It can ask the strategic planner for
41   * assistance on the route to take when the network splits. This abstract class contains a number of helper methods that make it
42   * easy to implement a tactical planner.
43   * <p>
44   * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
45   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
46   * </p>
47   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
48   * initial version Nov 25, 2015 <br>
49   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
50   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
51   */
52  public abstract class AbstractLaneBasedTacticalPlanner implements LaneBasedTacticalPlanner, Serializable
53  {
54      /** */
55      private static final long serialVersionUID = 20151125L;
56  
57      /** The car-following model. */
58      private CarFollowingModel carFollowingModel;
59  
60      /** The perception. */
61      private final LanePerception lanePerception;
62  
63      /** GTU. */
64      private final LaneBasedGTU gtu;
65  
66      /**
67       * Instantiates a tactical planner.
68       * @param carFollowingModel car-following model
69       * @param gtu GTU
70       * @param lanePerception perception
71       */
72      public AbstractLaneBasedTacticalPlanner(final CarFollowingModel carFollowingModel, final LaneBasedGTU gtu,
73              final LanePerception lanePerception)
74      {
75          setCarFollowingModel(carFollowingModel);
76          this.gtu = gtu;
77          this.lanePerception = lanePerception;
78      }
79  
80      /** {@inheritDoc} */
81      @Override
82      public final LaneBasedGTU getGtu()
83      {
84          return this.gtu;
85      }
86  
87      /**
88       * Build a list of lanes forward, with a maximum headway relative to the reference point of the GTU.
89       * @param gtu LaneBasedGTU; the GTU for which to calculate the lane list
90       * @param maxHeadway Length; the maximum length for which lanes should be returned
91       * @return LanePathInfo; an instance that provides the following information for an operational plan: the lanes to follow,
92       *         and the path to follow when staying on the same lane.
93       * @throws GTUException when the vehicle is not on one of the lanes on which it is registered
94       * @throws NetworkException when the strategic planner is not able to return a next node in the route
95       */
96      public static LanePathInfo buildLanePathInfo(final LaneBasedGTU gtu, final Length maxHeadway)
97              throws GTUException, NetworkException
98      {
99          DirectedLanePosition dlp = gtu.getReferencePosition();
100         return buildLanePathInfo(gtu, maxHeadway, dlp.getLane(), dlp.getPosition(), dlp.getGtuDirection());
101     }
102 
103     /**
104      * Build a list of lanes forward, with a maximum headway relative to the reference point of the GTU.
105      * @param gtu LaneBasedGTU; the GTU for which to calculate the lane list
106      * @param maxHeadway Length; the maximum length for which lanes should be returned
107      * @param startLane Lane; the lane in which the path starts
108      * @param position Length; the position on the start lane
109      * @param startDirectionality GTUDirectionality; the driving direction on the start lane
110      * @return LanePathInfo; an instance that provides the following information for an operational plan: the lanes to follow,
111      *         and the path to follow when staying on the same lane.
112      * @throws GTUException when the vehicle is not on one of the lanes on which it is registered
113      * @throws NetworkException when the strategic planner is not able to return a next node in the route
114      * @throws GTUException when the vehicle is not on one of the lanes on which it is registered
115      * @throws NetworkException when the strategic planner is not able to return a next node in the route
116      */
117     public static LanePathInfo buildLanePathInfo(final LaneBasedGTU gtu, final Length maxHeadway, final Lane startLane,
118             final Length position, final GTUDirectionality startDirectionality) throws GTUException, NetworkException
119     {
120         List<LaneDirection> laneListForward = new ArrayList<>();
121         Lane lane = startLane;
122         GTUDirectionality lastGtuDir = startDirectionality;
123         Length startPosition = position;
124         Lane lastLane = lane;
125         laneListForward.add(new LaneDirection(lastLane, lastGtuDir));
126         Length distanceToEndOfLane;
127         OTSLine3D path;
128         try
129         {
130             if (lastGtuDir.equals(GTUDirectionality.DIR_PLUS))
131             {
132                 distanceToEndOfLane = lane.getLength().minus(position);
133                 path = lane.getCenterLine().extract(position, lane.getLength());
134             }
135             else
136             {
137                 distanceToEndOfLane = position;
138                 path = lane.getCenterLine().extract(Length.ZERO, position).reverse();
139             }
140         }
141         catch (OTSGeometryException exception)
142         {
143             // System.err.println(gtu + ": " + exception.getMessage());
144             // System.err.println(lane + ", len=" + lane.getLength());
145             // System.err.println(position);
146             // throw new GTUException(exception);
147 
148             // section on current lane too short, floating point operations cause only a single point at the end of the lane
149             path = null;
150             distanceToEndOfLane = Length.ZERO;
151             laneListForward.clear();
152             startPosition = Length.ZERO;
153         }
154 
155         while (distanceToEndOfLane.lt(maxHeadway))
156         {
157             Map<Lane, GTUDirectionality> lanes = lastGtuDir.equals(GTUDirectionality.DIR_PLUS)
158                     ? lane.nextLanes(gtu.getGTUType()) : lane.prevLanes(gtu.getGTUType());
159             if (lanes.size() == 0)
160             {
161                 // Dead end. Return with the list as is.
162                 return new LanePathInfo(path, laneListForward, startPosition);
163             }
164             else if (lanes.size() == 1)
165             {
166                 // Ask the strategical planner what the next link should be (if known), because the strategical planner knows
167                 // best!
168                 LinkDirection ld = null;
169                 ld = gtu.getStrategicalPlanner().nextLinkDirection(lane.getParentLink(), lastGtuDir, gtu.getGTUType());
170                 lane = lanes.keySet().iterator().next();
171                 if (ld != null && !lane.getParentLink().equals(ld.getLink()))
172                 {
173                     // Lane not on route anymore. return with the list as is.
174                     return new LanePathInfo(path, laneListForward, startPosition);
175                 }
176             }
177             else
178             {
179                 // Multiple next lanes; ask the strategical planner where to go.
180                 // Note: this is not necessarily a split; it could e.g. be a bike path on a road
181                 LinkDirection ld;
182                 try
183                 {
184                     ld = gtu.getStrategicalPlanner().nextLinkDirection(lane.getParentLink(), /* gtu.getLanes().get(lane), */
185                             lastGtuDir, gtu.getGTUType());
186                 }
187                 catch (NetworkException ne)
188                 {
189                     // no route found.
190                     // return the data structure up to this point...
191                     return new LanePathInfo(path, laneListForward, startPosition);
192                 }
193                 Link nextLink = ld.getLink();
194                 Lane newLane = null;
195                 for (Lane nextLane : lanes.keySet())
196                 {
197                     if (nextLane.getParentLink().equals(nextLink))
198                     {
199                         newLane = nextLane;
200                         break;
201                     }
202                 }
203                 if (newLane == null)
204                 {
205                     // we cannot reach the next node on this lane -- we have to make a lane change!
206                     // return the data structure up to this point...
207                     return new LanePathInfo(path, laneListForward, startPosition);
208                 }
209                 // otherwise: continue!
210                 lane = newLane;
211             }
212 
213             // determine direction for the path
214             try
215             {
216                 if (lastGtuDir.equals(GTUDirectionality.DIR_PLUS))
217                 {
218                     if (lastLane.getParentLink().getEndNode().equals(lane.getParentLink().getStartNode()))
219                     {
220                         // -----> O ----->, GTU moves ---->
221                         path = LaneOperationalPlanBuilder.concatenateNull(path, lane.getCenterLine());
222                         // path = OTSLine3D.concatenate(Lane.MARGIN.si, path, lane.getCenterLine());
223                         lastGtuDir = GTUDirectionality.DIR_PLUS;
224                     }
225                     else
226                     {
227                         // -----> O <-----, GTU moves ---->
228                         path = LaneOperationalPlanBuilder.concatenateNull(path, lane.getCenterLine().reverse());
229                         // path = OTSLine3D.concatenate(Lane.MARGIN.si, path, lane.getCenterLine().reverse());
230                         lastGtuDir = GTUDirectionality.DIR_MINUS;
231                     }
232                 }
233                 else
234                 {
235                     if (lastLane.getParentLink().getStartNode().equals(lane.getParentLink().getStartNode()))
236                     {
237                         // <----- O ----->, GTU moves ---->
238                         path = LaneOperationalPlanBuilder.concatenateNull(path, lane.getCenterLine());
239                         // path = OTSLine3D.concatenate(Lane.MARGIN.si, path, lane.getCenterLine());
240                         lastGtuDir = GTUDirectionality.DIR_PLUS;
241                     }
242                     else
243                     {
244                         // <----- O <-----, GTU moves ---->
245                         path = LaneOperationalPlanBuilder.concatenateNull(path, lane.getCenterLine().reverse());
246                         // path = OTSLine3D.concatenate(Lane.MARGIN.si, path, lane.getCenterLine().reverse());
247                         lastGtuDir = GTUDirectionality.DIR_MINUS;
248                     }
249                 }
250                 lastLane = lane;
251             }
252             catch (OTSGeometryException exception)
253             {
254                 throw new GTUException(exception);
255             }
256 
257             laneListForward.add(new LaneDirection(lastLane, lastGtuDir));
258             distanceToEndOfLane = distanceToEndOfLane.plus(lastLane.getLength());
259 
260         }
261         return new LanePathInfo(path, laneListForward, startPosition);
262     }
263 
264     /**
265      * Calculate the next location where the network splits, with a maximum headway relative to the reference point of the GTU.
266      * Note: a lane drop is also considered a split (!).
267      * @param gtu LaneBasedGTU; the GTU for which to calculate the lane list
268      * @param maxHeadway Length; the maximum length for which lanes should be returned
269      * @return NextSplitInfo; an instance that provides the following information for an operational plan: whether the network
270      *         splits, the node where it splits, and the current lanes that lead to the right node after the split node.
271      * @throws GTUException when the vehicle is not on one of the lanes on which it is registered
272      * @throws NetworkException when the strategic planner is not able to return a next node in the route
273      */
274     public static NextSplitInfo determineNextSplit(final LaneBasedGTU gtu, final Length maxHeadway)
275             throws GTUException, NetworkException
276     {
277         Node nextSplitNode = null;
278         Set<Lane> correctCurrentLanes = new HashSet<>();
279         DirectedLanePosition dlp = gtu.getReferencePosition();
280         Lane referenceLane = dlp.getLane();
281         double refFrac = dlp.getPosition().si / referenceLane.getLength().si;
282         Link lastLink = referenceLane.getParentLink();
283         GTUDirectionality lastGtuDir = dlp.getGtuDirection();
284         GTUDirectionality referenceLaneDirectionality = lastGtuDir;
285         Length lengthForward;
286         Length position = dlp.getPosition();
287         Node lastNode;
288         if (lastGtuDir.equals(GTUDirectionality.DIR_PLUS))
289         {
290             lengthForward = referenceLane.getLength().minus(position);
291             lastNode = referenceLane.getParentLink().getEndNode();
292         }
293         else
294         {
295             lengthForward = gtu.position(referenceLane, gtu.getReference());
296             lastNode = referenceLane.getParentLink().getStartNode();
297         }
298 
299         // see if we have a split within maxHeadway distance
300         while (lengthForward.lt(maxHeadway) && nextSplitNode == null)
301         {
302             // calculate the number of "outgoing" links
303             Set<Link> links = lastNode.getLinks().toSet(); // safe copy
304             Iterator<Link> linkIterator = links.iterator();
305             while (linkIterator.hasNext())
306             {
307                 Link link = linkIterator.next();
308                 if (link.equals(lastLink) || !link.getLinkType().isCompatible(gtu.getGTUType())
309                         || (link.getDirectionality(gtu.getGTUType()).isForward() && link.getEndNode().equals(lastNode))
310                         || (link.getDirectionality(gtu.getGTUType()).isBackward() && link.getStartNode().equals(lastNode)))
311                 {
312                     linkIterator.remove();
313                 }
314             }
315 
316             // calculate the number of incoming and outgoing lanes on the link
317             boolean laneChange = false;
318             if (links.size() == 1)
319             {
320                 for (CrossSectionElement cse : ((CrossSectionLink) lastLink).getCrossSectionElementList())
321                 {
322                     if (cse instanceof Lane && lastGtuDir.isPlus())
323                     {
324                         Lane lane = (Lane) cse;
325                         if (lane.nextLanes(gtu.getGTUType()).size() == 0)
326                         {
327                             laneChange = true;
328                         }
329                     }
330                     if (cse instanceof Lane && lastGtuDir.isMinus())
331                     {
332                         Lane lane = (Lane) cse;
333                         if (lane.prevLanes(gtu.getGTUType()).size() == 0)
334                         {
335                             laneChange = true;
336                         }
337                     }
338                 }
339             }
340 
341             // see if we have a lane drop
342             if (laneChange)
343             {
344                 nextSplitNode = lastNode;
345                 // which lane(s) we are registered on and adjacent lanes link to a lane
346                 // that does not drop?
347                 for (CrossSectionElement cse : referenceLane.getParentLink().getCrossSectionElementList())
348                 {
349                     if (cse instanceof Lane)
350                     {
351                         Lane l = (Lane) cse;
352                         // if (noLaneDrop(gtu, maxHeadway, l, position, referenceLaneDirectionality))
353                         if (noLaneDrop(gtu, maxHeadway, l, l.getLength().multiplyBy(refFrac), referenceLaneDirectionality))
354                         {
355                             correctCurrentLanes.add(l);
356                         }
357                     }
358                 }
359                 return new NextSplitInfo(nextSplitNode, correctCurrentLanes);
360             }
361 
362             // see if we have a split
363             if (links.size() > 1)
364             {
365                 nextSplitNode = lastNode;
366                 LinkDirection ld = gtu.getStrategicalPlanner().nextLinkDirection(nextSplitNode, lastLink, gtu.getGTUType());
367                 // which lane(s) we are registered on and adjacent lanes link to a lane
368                 // that is on the route at the next split?
369                 for (CrossSectionElement cse : referenceLane.getParentLink().getCrossSectionElementList())
370                 {
371                     if (cse instanceof Lane)
372                     {
373                         Lane l = (Lane) cse;
374                         // if (connectsToPath(gtu, maxHeadway, l, position, referenceLaneDirectionality, ld.getLink()))
375                         if (connectsToPath(gtu, maxHeadway, l, l.getLength().multiplyBy(refFrac), referenceLaneDirectionality,
376                                 ld.getLink()))
377                         {
378                             correctCurrentLanes.add(l);
379                         }
380                     }
381                 }
382                 if (correctCurrentLanes.size() > 0)
383                 {
384                     return new NextSplitInfo(nextSplitNode, correctCurrentLanes);
385                 }
386                 // split, but no lane on current link to right direction
387                 Set<Lane> correctLanes = new HashSet<>();
388                 Set<Lane> wrongLanes = new HashSet<>();
389                 for (CrossSectionElement cse : ((CrossSectionLink) lastLink).getCrossSectionElementList())
390                 {
391                     if (cse instanceof Lane)
392                     {
393                         Lane l = (Lane) cse;
394                         if (connectsToPath(gtu, maxHeadway.plus(l.getLength()), l, Length.ZERO, lastGtuDir, ld.getLink()))
395                         {
396                             correctLanes.add(l);
397                         }
398                         else
399                         {
400                             wrongLanes.add(l);
401                         }
402                     }
403                 }
404                 for (Lane wrongLane : wrongLanes)
405                 {
406                     for (Lane adjLane : wrongLane.accessibleAdjacentLanes(LateralDirectionality.LEFT, gtu.getGTUType()))
407                     {
408                         if (correctLanes.contains(adjLane))
409                         {
410                             return new NextSplitInfo(nextSplitNode, correctCurrentLanes, LateralDirectionality.LEFT);
411                         }
412                     }
413                     for (Lane adjLane : wrongLane.accessibleAdjacentLanes(LateralDirectionality.RIGHT, gtu.getGTUType()))
414                     {
415                         if (correctLanes.contains(adjLane))
416                         {
417                             return new NextSplitInfo(nextSplitNode, correctCurrentLanes, LateralDirectionality.RIGHT);
418                         }
419                     }
420                 }
421                 return new NextSplitInfo(nextSplitNode, correctCurrentLanes, null);
422             }
423 
424             if (links.size() == 0)
425             {
426                 return new NextSplitInfo(null, correctCurrentLanes);
427             }
428 
429             // just one link
430             Link link = links.iterator().next();
431 
432             // determine direction for the path
433             if (lastGtuDir.equals(GTUDirectionality.DIR_PLUS))
434             {
435                 if (lastLink.getEndNode().equals(link.getStartNode()))
436                 {
437                     // -----> O ----->, GTU moves ---->
438                     lastGtuDir = GTUDirectionality.DIR_PLUS;
439                     lastNode = link.getEndNode();
440                 }
441                 else
442                 {
443                     // -----> O <-----, GTU moves ---->
444                     lastGtuDir = GTUDirectionality.DIR_MINUS;
445                     lastNode = link.getEndNode();
446                 }
447             }
448             else
449             {
450                 if (lastLink.getStartNode().equals(link.getStartNode()))
451                 {
452                     // <----- O ----->, GTU moves ---->
453                     lastNode = link.getStartNode();
454                     lastGtuDir = GTUDirectionality.DIR_PLUS;
455                 }
456                 else
457                 {
458                     // <----- O <-----, GTU moves ---->
459                     lastNode = link.getStartNode();
460                     lastGtuDir = GTUDirectionality.DIR_MINUS;
461                 }
462             }
463             lastLink = links.iterator().next();
464             lengthForward = lengthForward.plus(lastLink.getLength());
465         }
466 
467         return new NextSplitInfo(null, correctCurrentLanes);
468     }
469 
470     /**
471      * Determine whether the lane is directly connected to our route, in other words: if we would (continue to) drive on the
472      * given lane, can we take the right branch at the nextSplitNode without switching lanes?
473      * @param gtu LaneBasedGTU; the GTU for which we have to determine the lane suitability
474      * @param maxHeadway Length; the maximum length for use in the calculation
475      * @param startLane Lane; the first lane in the list
476      * @param startLanePosition Length; the position on the start lane
477      * @param startDirectionality GTUDirectionality; the driving direction on the start lane
478      * @param linkAfterSplit Link; the link after the split to which we should connect
479      * @return boolean; true if the lane (XXXXX which lane?) is connected to our path
480      * @throws GTUException when the vehicle is not on one of the lanes on which it is registered
481      * @throws NetworkException when the strategic planner is not able to return a next node in the route
482      */
483     protected static boolean connectsToPath(final LaneBasedGTU gtu, final Length maxHeadway, final Lane startLane,
484             final Length startLanePosition, final GTUDirectionality startDirectionality, final Link linkAfterSplit)
485             throws GTUException, NetworkException
486     {
487         List<LaneDirection> laneDirections =
488                 buildLanePathInfo(gtu, maxHeadway, startLane, startLanePosition, startDirectionality).getLaneDirectionList();
489         for (LaneDirection laneDirection : laneDirections)
490         {
491             if (laneDirection.getLane().getParentLink().equals(linkAfterSplit))
492             {
493                 return true;
494             }
495         }
496         return false;
497     }
498 
499     /**
500      * Determine whether the lane does not drop, in other words: if we would (continue to) drive on the given lane, can we
501      * continue to drive at the nextSplitNode without switching lanes?
502      * @param gtu LaneBasedGTU; the GTU for which we have to determine the lane suitability
503      * @param maxHeadway Length; the maximum length for use in the calculation
504      * @param startLane Lane; the first lane in the list
505      * @param startLanePosition Length; the position on the start lane
506      * @param startDirectionality GTUDirectionality; the driving direction on the start lane
507      * @return boolean; true if the lane (XXX which lane?) is connected to our path
508      * @throws GTUException when the vehicle is not on one of the lanes on which it is registered
509      * @throws NetworkException when the strategic planner is not able to return a next node in the route
510      */
511     protected static boolean noLaneDrop(final LaneBasedGTU gtu, final Length maxHeadway, final Lane startLane,
512             final Length startLanePosition, final GTUDirectionality startDirectionality) throws GTUException, NetworkException
513     {
514         LanePathInfo lpi = buildLanePathInfo(gtu, maxHeadway, startLane, startLanePosition, startDirectionality);
515         if (lpi.getPath().getLength().lt(maxHeadway))
516         {
517             return false;
518         }
519         return true;
520     }
521 
522     /**
523      * Make a list of links on which to drive next, with a maximum headway relative to the reference point of the GTU.
524      * @param gtu LaneBasedGTU; the GTU for which to calculate the link list
525      * @param maxHeadway Length; the maximum length for which links should be returned
526      * @return List&lt;LinkDirection&gt;; a list of links on which to drive next
527      * @throws GTUException when the vehicle is not on one of the lanes on which it is registered
528      * @throws NetworkException when the strategic planner is not able to return a next node in the route
529      */
530     protected static List<LinkDirection> buildLinkListForward(final LaneBasedGTU gtu, final Length maxHeadway)
531             throws GTUException, NetworkException
532     {
533         List<LinkDirection> linkList = new ArrayList<>();
534         DirectedLanePosition dlp = gtu.getReferencePosition();
535         Lane referenceLane = dlp.getLane();
536         Link lastLink = referenceLane.getParentLink();
537         GTUDirectionality lastGtuDir = dlp.getGtuDirection();
538         linkList.add(new LinkDirection(lastLink, lastGtuDir));
539         Length lengthForward;
540         Length position = dlp.getPosition();
541         Node lastNode;
542         if (lastGtuDir.equals(GTUDirectionality.DIR_PLUS))
543         {
544             lengthForward = referenceLane.getLength().minus(position);
545             lastNode = referenceLane.getParentLink().getEndNode();
546         }
547         else
548         {
549             lengthForward = gtu.position(referenceLane, gtu.getReference());
550             lastNode = referenceLane.getParentLink().getStartNode();
551         }
552 
553         // see if we have a split within maxHeadway distance
554         while (lengthForward.lt(maxHeadway))
555         {
556             // calculate the number of "outgoing" links
557             Set<Link> links = lastNode.getLinks().toSet(); // is a safe copy
558             Iterator<Link> linkIterator = links.iterator();
559             while (linkIterator.hasNext())
560             {
561                 Link link = linkIterator.next();
562                 if (link.equals(lastLink) || !link.getLinkType().isCompatible(gtu.getGTUType()))
563                 {
564                     linkIterator.remove();
565                 }
566             }
567 
568             if (links.size() == 0)
569             {
570                 return linkList; // the path stops here...
571             }
572 
573             Link link;
574             if (links.size() > 1)
575             {
576                 LinkDirection ld = gtu.getStrategicalPlanner().nextLinkDirection(lastLink, lastGtuDir, gtu.getGTUType());
577                 link = ld.getLink();
578             }
579             else
580             {
581                 link = links.iterator().next();
582             }
583 
584             // determine direction for the path
585             if (lastGtuDir.equals(GTUDirectionality.DIR_PLUS))
586             {
587                 if (lastLink.getEndNode().equals(link.getStartNode()))
588                 {
589                     // -----> O ----->, GTU moves ---->
590                     lastGtuDir = GTUDirectionality.DIR_PLUS;
591                     lastNode = lastLink.getEndNode();
592                 }
593                 else
594                 {
595                     // -----> O <-----, GTU moves ---->
596                     lastGtuDir = GTUDirectionality.DIR_MINUS;
597                     lastNode = lastLink.getEndNode();
598                 }
599             }
600             else
601             {
602                 if (lastLink.getStartNode().equals(link.getStartNode()))
603                 {
604                     // <----- O ----->, GTU moves ---->
605                     lastNode = lastLink.getStartNode();
606                     lastGtuDir = GTUDirectionality.DIR_PLUS;
607                 }
608                 else
609                 {
610                     // <----- O <-----, GTU moves ---->
611                     lastNode = lastLink.getStartNode();
612                     lastGtuDir = GTUDirectionality.DIR_MINUS;
613                 }
614             }
615             lastLink = link;
616             linkList.add(new LinkDirection(lastLink, lastGtuDir));
617             lengthForward = lengthForward.plus(lastLink.getLength());
618         }
619         return linkList;
620     }
621 
622     /** {@inheritDoc} */
623     @Override
624     public final CarFollowingModel getCarFollowingModel()
625     {
626         return this.carFollowingModel;
627     }
628 
629     /**
630      * Sets the car-following model.
631      * @param carFollowingModel Car-following model to set.
632      */
633     public final void setCarFollowingModel(final CarFollowingModel carFollowingModel)
634     {
635         this.carFollowingModel = carFollowingModel;
636     }
637 
638     /** {@inheritDoc} */
639     @Override
640     public final LanePerception getPerception()
641     {
642         return this.lanePerception;
643     }
644 
645     /**
646      * Build an operational plan based on a simple operational plan and status info.
647      * @param gtu gtu
648      * @param startTime start time for plan
649      * @param bc behavioral characteristics
650      * @param simplePlan simple operational plan
651      * @param laneChange lane change status
652      * @return operational plan
653      * @throws ParameterException if parameter is not defined
654      * @throws GTUException gtu exception
655      * @throws NetworkException network exception
656      * @throws OperationalPlanException operational plan exeption
657      */
658     public static LaneBasedOperationalPlan buildPlanFromSimplePlan(final LaneBasedGTU gtu, final Time startTime,
659             final BehavioralCharacteristics bc, final SimpleOperationalPlan simplePlan, final LaneChange laneChange)
660             throws ParameterException, GTUException, NetworkException, OperationalPlanException
661     {
662         Length forwardHeadway = bc.getParameter(ParameterTypes.LOOKAHEAD);
663         List<Lane> lanes = null;
664         // START TEMP INSTANT CHANGE
665         if (simplePlan.isLaneChange())
666         {
667             gtu.changeLaneInstantaneously(simplePlan.getLaneChangeDirection());
668         }
669         if (true)
670         {
671             LanePathInfo lanePathInfo = buildLanePathInfo(gtu, forwardHeadway);
672             lanes = lanePathInfo.getLanes();
673             Length startPosition = lanePathInfo.getReferencePosition(); // gtu.getReferencePosition().getPosition();
674             try
675             {
676                 return LaneOperationalPlanBuilder.buildAccelerationPlan(gtu, lanes, startPosition, startTime, gtu.getSpeed(),
677                         simplePlan.getAcceleration(), bc.getParameter(ParameterTypes.DT));
678             }
679             catch (OTSGeometryException exception)
680             {
681                 throw new OperationalPlanException(exception);
682             }
683         }
684         // END TEMP INSTANT CHANGE
685         if (!laneChange.isChangingLane())
686         {
687             lanes = buildLanePathInfo(gtu, forwardHeadway).getLanes();
688         }
689         else
690         {
691             // during a lane change take the lanes on the from lane
692             // look in the opposite lateral direction relative to the reference lane, if that lane exists and the gtu is
693             // registered on it, the reference lane is the to lane, while the found adjacent lane is the required from lane
694             // TODO this is wrong, the other direction may not be allowed, i.e. is not accessible
695             Map<Lane, Length> positions = gtu.positions(gtu.getReference());
696             LateralDirectionality lat = laneChange.isChangingLeft() ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT;
697             DirectedLanePosition dlp = gtu.getReferencePosition();
698             Iterator<Lane> iterator = dlp.getLane().accessibleAdjacentLanes(lat, gtu.getGTUType()).iterator();
699             Lane adjLane = iterator.hasNext() ? iterator.next() : null;
700             if (adjLane != null && positions.containsKey(adjLane))
701             {
702                 // reference lane is from lane, this is ok
703                 lanes = buildLanePathInfo(gtu, forwardHeadway).getLanes();
704             }
705             else
706             {
707                 // reference lane is to lane, this should be accounted for
708                 for (Lane lane : positions.keySet())
709                 {
710                     if (lane.accessibleAdjacentLanes(lat, gtu.getGTUType()).contains(dlp.getLane()))
711                     {
712                         lanes = buildLanePathInfo(gtu, forwardHeadway, lane, positions.get(lane), dlp.getGtuDirection())
713                                 .getLanes();
714                     }
715                 }
716             }
717             if (lanes == null)
718             {
719                 throw new RuntimeException("From lane could not be determined during lane change.");
720             }
721         }
722         if ((!simplePlan.isLaneChange() && !laneChange.isChangingLane())
723                 || (gtu.getSpeed().si == 0.0 && simplePlan.getAcceleration().si <= 0.0))
724         {
725             Length firstLanePosition = gtu.getReferencePosition().getPosition();
726             try
727             {
728                 return LaneOperationalPlanBuilder.buildAccelerationPlan(gtu, lanes, firstLanePosition, startTime,
729                         gtu.getSpeed(), simplePlan.getAcceleration(), bc.getParameter(ParameterTypes.DT));
730             }
731             catch (OTSGeometryException exception)
732             {
733                 throw new OperationalPlanException(exception);
734             }
735         }
736 
737         try
738         {
739             return LaneOperationalPlanBuilder.buildAccelerationLaneChangePlan(gtu, lanes, simplePlan.getLaneChangeDirection(),
740                     gtu.getLocation(), startTime, gtu.getSpeed(), simplePlan.getAcceleration(),
741                     bc.getParameter(ParameterTypes.DT), laneChange);
742         }
743         catch (OTSGeometryException exception)
744         {
745             throw new OperationalPlanException(exception);
746         }
747     }
748 }