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<LinkDirection>; 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 }