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