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