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