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