View Javadoc
1   package org.opentrafficsim.road.gtu.lane.perception.categories;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.HashMap;
6   import java.util.HashSet;
7   import java.util.LinkedHashSet;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  
12  import org.djunits.unit.LengthUnit;
13  import org.djunits.value.vdouble.scalar.Acceleration;
14  import org.djunits.value.vdouble.scalar.Length;
15  import org.djunits.value.vdouble.scalar.Speed;
16  import org.djunits.value.vdouble.scalar.Time;
17  import org.djutils.exceptions.Throw;
18  import org.opentrafficsim.base.TimeStampedObject;
19  import org.opentrafficsim.base.parameters.ParameterException;
20  import org.opentrafficsim.base.parameters.ParameterTypeLength;
21  import org.opentrafficsim.base.parameters.ParameterTypes;
22  import org.opentrafficsim.core.gtu.GTUDirectionality;
23  import org.opentrafficsim.core.gtu.GTUException;
24  import org.opentrafficsim.core.gtu.RelativePosition;
25  import org.opentrafficsim.core.network.LateralDirectionality;
26  import org.opentrafficsim.core.network.NetworkException;
27  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
28  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
29  import org.opentrafficsim.road.gtu.lane.perception.headway.AbstractHeadwayGTU;
30  import org.opentrafficsim.road.gtu.lane.perception.headway.GTUStatus;
31  import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
32  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayDistance;
33  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTUSimple;
34  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayObject;
35  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayTrafficLight;
36  import org.opentrafficsim.road.gtu.lane.tactical.AbstractLaneBasedTacticalPlanner;
37  import org.opentrafficsim.road.gtu.lane.tactical.LanePathInfo;
38  import org.opentrafficsim.road.network.lane.Lane;
39  import org.opentrafficsim.road.network.lane.LaneDirection;
40  import org.opentrafficsim.road.network.lane.object.LaneBasedObject;
41  import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight;
42  
43  /**
44   * <p>
45   * Copyright (c) 2013-2019 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/current/license.html">OpenTrafficSim License</a>.
47   * <p>
48   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 22, 2016 <br>
49   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
50   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
51   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
52   */
53  public class DirectDefaultSimplePerception extends LaneBasedAbstractPerceptionCategory implements DefaultSimplePerception
54  {
55  
56      /** */
57      private static final long serialVersionUID = 20160811L;
58  
59      /** Look ahead parameter type. */
60      protected static final ParameterTypeLength LOOKAHEAD = ParameterTypes.LOOKAHEAD;
61  
62      /** Look back parameter type. */
63      protected static final ParameterTypeLength LOOKBACKOLD = ParameterTypes.LOOKBACKOLD;
64  
65      /** The forward headway and (leader) GTU. */
66      private TimeStampedObject<Headway> forwardHeadwayGTU;
67  
68      /** The forward headway and (leader) object. */
69      private TimeStampedObject<Headway> forwardHeadwayObject;
70  
71      /** The backward headway and (follower) object. */
72      private TimeStampedObject<Headway> backwardHeadway;
73  
74      /** The minimum speed limit of all lanes where the GTU is registered. */
75      private TimeStampedObject<Speed> speedLimit;
76  
77      /** The adjacent lanes that are accessible for the GTU at the left side. */
78      private TimeStampedObject<Map<Lane, Set<Lane>>> accessibleAdjacentLanesLeft;
79  
80      /** The adjacent lanes that are accessible for the GTU at the right side. */
81      private TimeStampedObject<Map<Lane, Set<Lane>>> accessibleAdjacentLanesRight;
82  
83      /** The objects parallel to us on the left side. */
84      private TimeStampedObject<Collection<Headway>> parallelHeadwaysLeft;
85  
86      /** The objects parallel to us on the right side. */
87      private TimeStampedObject<Collection<Headway>> parallelHeadwaysRight;
88  
89      /** The GTUs on the left side. */
90      private TimeStampedObject<Collection<Headway>> neighboringHeadwaysLeft;
91  
92      /** The GTUs on the right side. */
93      private TimeStampedObject<Collection<Headway>> neighboringHeadwaysRight;
94  
95      /** The lanes and path we expect to take if we do not change lanes. */
96      private TimeStampedObject<LanePathInfo> lanePathInfo;
97  
98      /**
99       * @param perception LanePerception; perception
100      */
101     public DirectDefaultSimplePerception(final LanePerception perception)
102     {
103         super(perception);
104     }
105 
106     /** {@inheritDoc} */
107     @Override
108     public final void updateLanePathInfo() throws GTUException, NetworkException, ParameterException
109     {
110         Time timestamp = getTimestamp();
111         this.lanePathInfo = new TimeStampedObject<>(
112                 AbstractLaneBasedTacticalPlanner.buildLanePathInfo(getGtu(), getGtu().getParameters().getParameter(LOOKAHEAD)),
113                 timestamp);
114     }
115 
116     /** {@inheritDoc} */
117     @Override
118     public final void updateForwardHeadwayGTU() throws GTUException, NetworkException, ParameterException
119     {
120         Time timestamp = getTimestamp();
121         if (this.lanePathInfo == null || this.lanePathInfo.getTimestamp().ne(timestamp))
122         {
123             updateLanePathInfo();
124         }
125         Length maximumForwardHeadway = getGtu().getParameters().getParameter(LOOKAHEAD);
126         this.forwardHeadwayGTU = new TimeStampedObject<>(forwardHeadway(maximumForwardHeadway, true), timestamp);
127     }
128 
129     /** {@inheritDoc} */
130     @Override
131     public final void updateForwardHeadwayObject() throws GTUException, NetworkException, ParameterException
132     {
133         Time timestamp = getTimestamp();
134         if (this.lanePathInfo == null || this.lanePathInfo.getTimestamp().ne(timestamp))
135         {
136             updateLanePathInfo();
137         }
138         Length maximumForwardHeadway = getGtu().getParameters().getParameter(LOOKAHEAD);
139         this.forwardHeadwayObject = new TimeStampedObject<>(forwardHeadway(maximumForwardHeadway, false), timestamp);
140     }
141 
142     /** {@inheritDoc} */
143     @Override
144     public final void updateBackwardHeadway() throws GTUException, ParameterException, NetworkException
145     {
146         Time timestamp = getTimestamp();
147         Length maximumReverseHeadway = getGtu().getParameters().getParameter(LOOKBACKOLD);
148         this.backwardHeadway = new TimeStampedObject<>(backwardHeadway(maximumReverseHeadway), timestamp);
149     }
150 
151     /** {@inheritDoc} */
152     @Override
153     public final void updateAccessibleAdjacentLanesLeft() throws GTUException
154     {
155         Time timestamp = getTimestamp();
156         Map<Lane, Set<Lane>> accessibleAdjacentLanesMap = new HashMap<>();
157         for (Lane lane : getGtu().positions(getGtu().getReference()).keySet())
158         {
159             Set<Lane> adjacentLanes = new HashSet<>(1);
160             adjacentLanes.addAll(lane.accessibleAdjacentLanesLegal(LateralDirectionality.LEFT, getGtu().getGTUType(),
161                     getGtu().getDirection(lane)));
162             accessibleAdjacentLanesMap.put(lane, adjacentLanes);
163         }
164         this.accessibleAdjacentLanesLeft = new TimeStampedObject<>(accessibleAdjacentLanesMap, timestamp);
165     }
166 
167     /** {@inheritDoc} */
168     @Override
169     public final void updateAccessibleAdjacentLanesRight() throws GTUException
170     {
171         Time timestamp = getTimestamp();
172         Map<Lane, Set<Lane>> accessibleAdjacentLanesMap = new HashMap<>();
173         for (Lane lane : getGtu().positions(getGtu().getReference()).keySet())
174         {
175             Set<Lane> adjacentLanes = new HashSet<>(1);
176             adjacentLanes.addAll(lane.accessibleAdjacentLanesLegal(LateralDirectionality.RIGHT, getGtu().getGTUType(),
177                     getGtu().getDirection(lane)));
178             accessibleAdjacentLanesMap.put(lane, adjacentLanes);
179         }
180         this.accessibleAdjacentLanesRight = new TimeStampedObject<>(accessibleAdjacentLanesMap, timestamp);
181     }
182 
183     /** {@inheritDoc} */
184     @Override
185     public final void updateNeighboringHeadwaysLeft() throws GTUException, ParameterException, NetworkException
186     {
187         Time timestamp = getTimestamp();
188         if (this.accessibleAdjacentLanesLeft == null || !timestamp.equals(this.accessibleAdjacentLanesLeft.getTimestamp()))
189         {
190             updateAccessibleAdjacentLanesLeft();
191         }
192 
193         if (this.parallelHeadwaysLeft == null || !timestamp.equals(this.parallelHeadwaysLeft.getTimestamp()))
194         {
195             updateParallelHeadwaysLeft();
196         }
197 
198         // for the accessible lanes, see who is ahead of us and in front of us
199         Length maximumForwardHeadway = getGtu().getParameters().getParameter(LOOKAHEAD);
200         Length maximumReverseHeadway = getGtu().getParameters().getParameter(LOOKBACKOLD);
201         this.neighboringHeadwaysLeft = new TimeStampedObject<>(
202                 collectNeighborLaneTraffic(LateralDirectionality.LEFT, timestamp, maximumForwardHeadway, maximumReverseHeadway),
203                 timestamp);
204     }
205 
206     /** {@inheritDoc} */
207     @Override
208     public final void updateNeighboringHeadwaysRight() throws GTUException, ParameterException, NetworkException
209     {
210         Time timestamp = getTimestamp();
211         if (this.accessibleAdjacentLanesRight == null || !timestamp.equals(this.accessibleAdjacentLanesRight.getTimestamp()))
212         {
213             updateAccessibleAdjacentLanesRight();
214         }
215 
216         if (this.parallelHeadwaysRight == null || !timestamp.equals(this.parallelHeadwaysRight.getTimestamp()))
217         {
218             updateParallelHeadwaysRight();
219         }
220 
221         // for the accessible lanes, see who is ahead of us and in front of us
222         Length maximumForwardHeadway = getGtu().getParameters().getParameter(LOOKAHEAD);
223         Length maximumReverseHeadway = getGtu().getParameters().getParameter(LOOKBACKOLD);
224         this.neighboringHeadwaysRight = new TimeStampedObject<>(collectNeighborLaneTraffic(LateralDirectionality.RIGHT,
225                 timestamp, maximumForwardHeadway, maximumReverseHeadway), timestamp);
226     }
227 
228     /** {@inheritDoc} */
229     @Override
230     public final void updateNeighboringHeadways(final LateralDirectionality lateralDirection)
231             throws GTUException, ParameterException, NetworkException
232     {
233         if (lateralDirection.equals(LateralDirectionality.LEFT))
234         {
235             updateNeighboringHeadwaysLeft();
236         }
237         else
238         {
239             updateNeighboringHeadwaysRight();
240         }
241     }
242 
243     /** {@inheritDoc} */
244     @Override
245     public final void updateParallelHeadwaysLeft() throws GTUException
246     {
247         Time timestamp = getTimestamp();
248         if (this.accessibleAdjacentLanesLeft == null || !timestamp.equals(this.accessibleAdjacentLanesLeft.getTimestamp()))
249         {
250             updateAccessibleAdjacentLanesLeft();
251         }
252         Set<Headway> parallelHeadwaySet = new HashSet<>();
253         for (Lane lane : this.accessibleAdjacentLanesLeft.getObject().keySet())
254         {
255             for (Lane adjacentLane : this.accessibleAdjacentLanesLeft.getObject().get(lane))
256             {
257                 parallelHeadwaySet.addAll(parallel(adjacentLane, timestamp));
258             }
259         }
260         this.parallelHeadwaysLeft = new TimeStampedObject<>(parallelHeadwaySet, timestamp);
261     }
262 
263     /** {@inheritDoc} */
264     @Override
265     public final void updateParallelHeadwaysRight() throws GTUException
266     {
267         Time timestamp = getTimestamp();
268         if (this.accessibleAdjacentLanesRight == null || !timestamp.equals(this.accessibleAdjacentLanesRight.getTimestamp()))
269         {
270             updateAccessibleAdjacentLanesRight();
271         }
272         Set<Headway> parallelHeadwaySet = new HashSet<>();
273         for (Lane lane : this.accessibleAdjacentLanesRight.getObject().keySet())
274         {
275             for (Lane adjacentLane : this.accessibleAdjacentLanesRight.getObject().get(lane))
276             {
277                 parallelHeadwaySet.addAll(parallel(adjacentLane, timestamp));
278             }
279         }
280         this.parallelHeadwaysRight = new TimeStampedObject<>(parallelHeadwaySet, timestamp);
281     }
282 
283     /** {@inheritDoc} */
284     @Override
285     public final void updateParallelHeadways(final LateralDirectionality lateralDirection) throws GTUException
286     {
287         if (lateralDirection.equals(LateralDirectionality.LEFT))
288         {
289             updateParallelHeadwaysLeft();
290         }
291         else
292         {
293             updateParallelHeadwaysRight();
294         }
295     }
296 
297     /** {@inheritDoc} */
298     @Override
299     public final void updateSpeedLimit() throws GTUException, NetworkException
300     {
301         Time timestamp = getTimestamp();
302         // assess the speed limit where we are right now
303         Lane lane = getGtu().getReferencePosition().getLane();
304         this.speedLimit = new TimeStampedObject<>(lane.getSpeedLimit(getGtu().getGTUType()), timestamp);
305     }
306 
307     /** {@inheritDoc} */
308     @Override
309     public final LanePathInfo getLanePathInfo()
310     {
311         return this.lanePathInfo.getObject();
312     }
313 
314     /** {@inheritDoc} */
315     @Override
316     public final Headway getForwardHeadwayGTU()
317     {
318         return this.forwardHeadwayGTU.getObject();
319     }
320 
321     /** {@inheritDoc} */
322     @Override
323     public final Headway getForwardHeadwayObject()
324     {
325         return this.forwardHeadwayObject.getObject();
326     }
327 
328     /** {@inheritDoc} */
329     @Override
330     public final Headway getBackwardHeadway()
331     {
332         return this.backwardHeadway.getObject();
333     }
334 
335     /** {@inheritDoc} */
336     @Override
337     public final Map<Lane, Set<Lane>> getAccessibleAdjacentLanesLeft()
338     {
339         return this.accessibleAdjacentLanesLeft.getObject();
340     }
341 
342     /** {@inheritDoc} */
343     @Override
344     public final Map<Lane, Set<Lane>> getAccessibleAdjacentLanesRight()
345     {
346         return this.accessibleAdjacentLanesRight.getObject();
347     }
348 
349     /** {@inheritDoc} */
350     @Override
351     public final Map<Lane, Set<Lane>> getAccessibleAdjacentLanes(final LateralDirectionality lateralDirection)
352     {
353         return lateralDirection.equals(LateralDirectionality.LEFT) ? this.accessibleAdjacentLanesLeft.getObject()
354                 : this.accessibleAdjacentLanesRight.getObject();
355     }
356 
357     /** {@inheritDoc} */
358     @Override
359     public final Collection<Headway> getNeighboringHeadwaysLeft()
360     {
361         return this.neighboringHeadwaysLeft.getObject();
362     }
363 
364     /** {@inheritDoc} */
365     @Override
366     public final Collection<Headway> getNeighboringHeadwaysRight()
367     {
368         return this.neighboringHeadwaysRight.getObject();
369     }
370 
371     /** {@inheritDoc} */
372     @Override
373     public final Collection<Headway> getNeighboringHeadways(final LateralDirectionality lateralDirection)
374     {
375         return lateralDirection.equals(LateralDirectionality.LEFT) ? this.neighboringHeadwaysLeft.getObject()
376                 : this.neighboringHeadwaysRight.getObject();
377     }
378 
379     /** {@inheritDoc} */
380     @Override
381     public final Collection<Headway> getParallelHeadwaysLeft()
382     {
383         return this.parallelHeadwaysLeft.getObject();
384     }
385 
386     /** {@inheritDoc} */
387     @Override
388     public final Collection<Headway> getParallelHeadwaysRight()
389     {
390         return this.parallelHeadwaysRight.getObject();
391     }
392 
393     /** {@inheritDoc} */
394     @Override
395     public final Collection<Headway> getParallelHeadways(final LateralDirectionality lateralDirection)
396     {
397         return lateralDirection.equals(LateralDirectionality.LEFT) ? this.parallelHeadwaysLeft.getObject()
398                 : this.parallelHeadwaysRight.getObject();
399     }
400 
401     /** {@inheritDoc} */
402     @Override
403     public final Speed getSpeedLimit()
404     {
405         return this.speedLimit.getObject();
406     }
407 
408     /**
409      * @return TimeStamped forwardHeadway, the forward headway and first object (GTU) in front
410      */
411     public final TimeStampedObject<Headway> getTimeStampedForwardHeadwayGTU()
412     {
413         return this.forwardHeadwayGTU;
414     }
415 
416     /**
417      * @return TimeStamped forwardHeadway, the forward headway and first object (not a GTU) in front
418      */
419     public final TimeStampedObject<Headway> getTimeStampedForwardHeadwayObject()
420     {
421         return this.forwardHeadwayObject;
422     }
423 
424     /**
425      * @return TimeStamped backwardHeadwayGTU, the backward headway and first object (e.g., a GTU) behind
426      */
427     public final TimeStampedObject<Headway> getTimeStampedBackwardHeadway()
428     {
429         return this.backwardHeadway;
430     }
431 
432     /**
433      * @return TimeStamped accessibleAdjacentLanesLeft, the accessible adjacent lanes on the left
434      */
435     public final TimeStampedObject<Map<Lane, Set<Lane>>> getTimeStampedAccessibleAdjacentLanesLeft()
436     {
437         return this.accessibleAdjacentLanesLeft;
438     }
439 
440     /**
441      * @return TimeStamped accessibleAdjacentLanesRight, the accessible adjacent lanes on the right
442      */
443     public final TimeStampedObject<Map<Lane, Set<Lane>>> getTimeStampedAccessibleAdjacentLanesRight()
444     {
445         return this.accessibleAdjacentLanesRight;
446     }
447 
448     /**
449      * @param lateralDirection LateralDirectionality; the direction to return the accessible adjacent lanes for
450      * @return TimeStamped accessibleAdjacentLanesRight, the accessible adjacent lanes on the right
451      */
452     public final TimeStampedObject<Map<Lane, Set<Lane>>> getTimeStampedAccessibleAdjacentLanes(
453             final LateralDirectionality lateralDirection)
454     {
455         return lateralDirection.equals(LateralDirectionality.LEFT) ? this.accessibleAdjacentLanesLeft
456                 : this.accessibleAdjacentLanesRight;
457     }
458 
459     /**
460      * @return TimeStamped neighboringHeadwaysLeft, the objects (e.g., GTUs) in parallel, in front and behind on the left
461      *         neighboring lane, with their headway relative to our GTU, and information about the status of the adjacent
462      *         objects
463      */
464     public final TimeStampedObject<Collection<Headway>> getTimeStampedNeighboringHeadwaysLeft()
465     {
466         return this.neighboringHeadwaysLeft;
467     }
468 
469     /**
470      * @return TimeStamped neighboringHeadwaysRight, the objects (e.g., GTUs) in parallel, in front and behind on the right
471      *         neighboring lane, with their headway relative to our GTU, and information about the status of the adjacent
472      *         objects
473      */
474     public final TimeStampedObject<Collection<Headway>> getTimeStampedNeighboringHeadwaysRight()
475     {
476         return this.neighboringHeadwaysRight;
477     }
478 
479     /**
480      * @param lateralDirection LateralDirectionality; the direction to return the neighboring headways for
481      * @return TimeStamped neighboringHeadwaysRight, the objects (e.g., GTUs) in parallel, in front and behind on the right
482      *         neighboring lane, with their headway relative to our GTU, and information about the status of the adjacent
483      *         objects
484      */
485     public final TimeStampedObject<Collection<Headway>> getTimeStampedNeighboringHeadways(
486             final LateralDirectionality lateralDirection)
487     {
488         return lateralDirection.equals(LateralDirectionality.LEFT) ? this.neighboringHeadwaysLeft
489                 : this.neighboringHeadwaysRight;
490     }
491 
492     /**
493      * @return TimeStamped parallelHeadwaysLeft, the parallel objects (e.g., GTUs) on the left, with information about their
494      *         status and parallel overlap with our GTU.
495      */
496     public final TimeStampedObject<Collection<Headway>> getTimeStampedParallelHeadwaysLeft()
497     {
498         return this.parallelHeadwaysLeft;
499     }
500 
501     /**
502      * @return TimeStamped parallelHeadwaysRight, the parallel objects (e.g., GTUs) on the right, with information about their
503      *         status and parallel overlap with our GTU.
504      */
505     public final TimeStampedObject<Collection<Headway>> getTimeStampedParallelHeadwaysRight()
506     {
507         return this.parallelHeadwaysRight;
508     }
509 
510     /**
511      * @param lateralDirection LateralDirectionality; the direction to return the parallel headways for
512      * @return TimeStamped parallelHeadwaysRight, the parallel objects (e.g., GTUs) on the right, with information about their
513      *         status and parallel overlap with our GTU.
514      */
515     public final TimeStampedObject<Collection<Headway>> getTimeStampedParallelHeadways(
516             final LateralDirectionality lateralDirection)
517     {
518         return lateralDirection.equals(LateralDirectionality.LEFT) ? this.parallelHeadwaysLeft : this.parallelHeadwaysRight;
519     }
520 
521     /**
522      * @return TimeStamped speedLimit
523      */
524     public final TimeStampedObject<Speed> getTimeStampedSpeedLimit()
525     {
526         return this.speedLimit;
527     }
528 
529     /**
530      * Retrieve the time stamped last perceived lane path info.
531      * @return LanePathInfo time stamped last perceived lane path info
532      */
533     public final TimeStampedObject<LanePathInfo> getTimeStampedLanePathInfo()
534     {
535         return this.lanePathInfo;
536     }
537 
538     /** {@inheritDoc} */
539     @Override
540     public final Lane bestAccessibleAdjacentLane(final Lane currentLane, final LateralDirectionality lateralDirection,
541             final Length longitudinalPosition)
542     {
543         Set<Lane> candidates = getAccessibleAdjacentLanes(lateralDirection).get(currentLane);
544         if (candidates == null || candidates.isEmpty())
545         {
546             return null; // There is no adjacent Lane that this GTU type can cross into
547         }
548         if (candidates.size() == 1)
549         {
550             return candidates.iterator().next(); // There is exactly one adjacent Lane that this GTU type can cross into
551         }
552         // There are several candidates; find the one that is widest at the beginning.
553         Lane bestLane = null;
554         double widestSeen = Double.NEGATIVE_INFINITY;
555         for (Lane lane : candidates)
556         {
557             if (lane.getWidth(longitudinalPosition).getSI() > widestSeen)
558             {
559                 widestSeen = lane.getWidth(longitudinalPosition).getSI();
560                 bestLane = lane;
561             }
562         }
563         return bestLane;
564     }
565 
566     /** {@inheritDoc} */
567     @Override
568     public final String toString()
569     {
570         return "DirectDefaultSimplePerception";
571     }
572 
573     /**************************************************************************************************************************/
574     /**************************************************** HEADWAY ALGORITHMS **************************************************/
575     /**************************************************************************************************************************/
576 
577     /**
578      * Determine which GTU is in front of this GTU. This method looks in all lanes where this GTU is registered, and not further
579      * than the value of the given maxDistance. The minimum headway is returned of all Lanes where the GTU is registered. When
580      * no GTU is found within the given maxDistance, a HeadwayGTU with <b>null</b> as the gtuId and maxDistance as the distance
581      * is returned. The search will extend into successive lanes if the maxDistance is larger than the remaining length on the
582      * lane. When Lanes (or underlying CrossSectionLinks) diverge, a route planner may be used to determine which kinks and
583      * lanes to take into account and which ones not. When the Lanes (or underlying CrossSectionLinks) converge, "parallel"
584      * traffic is not taken into account in the headway calculation. Instead, gap acceptance algorithms or their equivalent
585      * should guide the merging behavior.<br>
586      * <b>Note:</b> Headway is the net headway and calculated on a front-to-back basis.
587      * @param maxDistance the maximum distance to look for the nearest GTU; positive values search forwards; negative values
588      *            search backwards
589      * @param gtu boolean; look for gtu if true, for an object if false
590      * @return HeadwayGTU; the headway and the GTU information
591      * @throws GTUException when there is an error with the next lanes in the network.
592      * @throws NetworkException when there is a problem with the route planner
593      */
594     private Headway forwardHeadway(final Length maxDistance, final boolean gtu) throws GTUException, NetworkException
595     {
596         LanePathInfo lpi = getLanePathInfo();
597         return forwardHeadway(lpi, maxDistance, gtu);
598     }
599 
600     /**
601      * Determine which GTU is in front of this GTU. This method uses a given lanePathInfo to look forward, but not further than
602      * the value of the given maxDistance. The minimum headway is returned of all Lanes where the GTU is registered. When no GTU
603      * is found within the given maxDistance, a HeadwayGTU with <b>null</b> as the gtuId and maxDistance as the distance is
604      * returned. The search will extend into successive lanes if the maxDistance is larger than the remaining length on the
605      * lane. When Lanes (or underlying CrossSectionLinks) diverge, a route planner may be used to determine which kinks and
606      * lanes to take into account and which ones not. When the Lanes (or underlying CrossSectionLinks) converge, "parallel"
607      * traffic is not taken into account in the headway calculation. Instead, gap acceptance algorithms or their equivalent
608      * should guide the merging behavior.<br>
609      * <b>Note:</b> Headway is the net headway and calculated on a front-to-back basis.
610      * @param lpi LanePathInfo; the lanePathInfo object that informs the headway algorithm in which lanes to look, and from
611      *            which position on the first lane.
612      * @param maxDistance the maximum distance to look for the nearest GTU; positive values search forwards; negative values
613      *            search backwards
614      * @param gtu boolean; look for gtu if true, for an object if false
615      * @return HeadwayGTU; the headway and the GTU information
616      * @throws GTUException when there is an error with the next lanes in the network.
617      * @throws NetworkException when there is a problem with the route planner
618      */
619     private Headway forwardHeadway(final LanePathInfo lpi, final Length maxDistance, final boolean gtu)
620             throws GTUException, NetworkException
621     {
622         Throw.when(maxDistance.le0(), GTUException.class, "forwardHeadway: maxDistance should be positive");
623 
624         int ldIndex = 0;
625         LaneDirection ld = lpi.getReferenceLaneDirection();
626         double gtuPosFrontSI = lpi.getReferencePosition().si;
627         if (lpi.getReferenceLaneDirection().getDirection().isPlus())
628         {
629             gtuPosFrontSI += getGtu().getFront().getDx().si;
630         }
631         else
632         {
633             gtuPosFrontSI -= getGtu().getFront().getDx().si;
634         }
635 
636         // TODO end of lanepath
637 
638         while ((gtuPosFrontSI > ld.getLane().getLength().si || gtuPosFrontSI < 0.0)
639                 && ldIndex < lpi.getLaneDirectionList().size() - 1)
640         {
641             ldIndex++;
642             if (ld.getDirection().isPlus()) // e.g. 1005 on length of lane = 1000
643             {
644                 gtuPosFrontSI -= ld.getLane().getLength().si; // First subtract the length of the lane that the GTU is leaving
645             }
646             else
647             // e.g. -5 on lane of whatever length
648             {
649                 gtuPosFrontSI *= -1.0;
650             }
651             if (lpi.getLaneDirectionList().get(ldIndex).getDirection().isMinus())
652             {
653                 gtuPosFrontSI = lpi.getLaneDirectionList().get(ldIndex).getLane().getLength().si - gtuPosFrontSI;
654             }
655             ld = lpi.getLaneDirectionList().get(ldIndex);
656         }
657 
658         double maxDistanceSI = maxDistance.si;
659         Time time = getGtu().getSimulator().getSimulatorTime();
660 
661         // look forward based on the provided lanePathInfo.
662         Headway closest = headwayLane(ld, gtuPosFrontSI, 0.0, time, gtu);
663         if (closest != null)
664         {
665             if (closest.getDistance().si > maxDistanceSI)
666             {
667                 return new HeadwayDistance(maxDistanceSI);
668             }
669             return closest;
670         }
671         double cumDistSI = ld.getDirection().isPlus() ? ld.getLane().getLength().si - gtuPosFrontSI : gtuPosFrontSI;
672         for (int i = ldIndex + 1; i < lpi.getLaneDirectionList().size(); i++)
673         {
674             ld = lpi.getLaneDirectionList().get(i);
675             closest = headwayLane(ld, ld.getDirection().isPlus() ? 0.0 : ld.getLane().getLength().si, cumDistSI, time, gtu);
676             if (closest != null)
677             {
678                 if (closest.getDistance().si > maxDistanceSI)
679                 {
680                     return new HeadwayDistance(maxDistanceSI);
681                 }
682                 return closest;
683             }
684             cumDistSI += ld.getLane().getLength().si;
685         }
686         return new HeadwayDistance(maxDistanceSI);
687     }
688 
689     /**
690      * Determine the positive headway on a lane, or null if no GTU or blocking object can be found on this lane.
691      * @param laneDirection LaneDirection; the lane and direction to look
692      * @param startPosSI double; the start position to look from in meters
693      * @param cumDistSI double; the cumulative distance that has already been observed on other lanes
694      * @param now Time; the current time to determine the GTU positions on the lane
695      * @return the HeadwayGTU, containing information on a GTU that is ahead of the given start position, or null if no GTU can
696      *         be found on this lane
697      * @param gtu boolean; look for gtu if true, for an object if false
698      * @throws GTUException when the GTUs ahead on the lane cannot be determined
699      */
700     private Headway headwayLane(final LaneDirection laneDirection, final double startPosSI, final double cumDistSI,
701             final Time now, final boolean gtu) throws GTUException
702     {
703         Lane lane = laneDirection.getLane();
704 
705         if (gtu)
706         {
707             LaneBasedGTU laneBasedGTU = lane.getGtuAhead(new Length(startPosSI, LengthUnit.SI), laneDirection.getDirection(),
708                     RelativePosition.REAR, now);
709             if (laneBasedGTU == null)
710             {
711                 return null;
712             }
713             double gtuDistanceSI = Math.abs(laneBasedGTU.position(lane, laneBasedGTU.getRear()).si - startPosSI);
714             return new HeadwayGTUSimple(laneBasedGTU.getId(), laneBasedGTU.getGTUType(),
715                     new Length(cumDistSI + gtuDistanceSI, LengthUnit.SI), laneBasedGTU.getLength(), laneBasedGTU.getWidth(),
716                     laneBasedGTU.getSpeed(), laneBasedGTU.getAcceleration(), null, getGtuStatus(laneBasedGTU));
717         }
718 
719         else
720 
721         {
722             List<LaneBasedObject> laneBasedObjects =
723                     lane.getObjectAhead(new Length(startPosSI, LengthUnit.SI), laneDirection.getDirection());
724             if (laneBasedObjects == null)
725             {
726                 return null;
727             }
728             double objectDistanceSI = Math.abs(laneBasedObjects.get(0).getLongitudinalPosition().si - startPosSI);
729             LaneBasedObject lbo = laneBasedObjects.get(0);
730 
731             // handle the traffic light
732             if (lbo instanceof TrafficLight)
733             {
734                 TrafficLight tl = (TrafficLight) lbo;
735                 if (tl.getTrafficLightColor().isRed())
736                 {
737                     if (cumDistSI + objectDistanceSI > breakingDistance(MAX_RED_DECELERATION, getGtu().getSpeed()).si)
738                     {
739                         return new HeadwayTrafficLight(tl, new Length(cumDistSI + objectDistanceSI, LengthUnit.SI));
740                     }
741                     return new HeadwayTrafficLight(tl, new Length(cumDistSI + objectDistanceSI, LengthUnit.SI));
742                 }
743                 if (tl.getTrafficLightColor().isYellow())
744                 {
745                     // double maxDecel = -MAX_YELLOW_DECELERATION.si; // was 2.09;
746                     // double brakingTime = getGtu().getSpeed().si / maxDecel;
747                     // double brakingDistanceSI =
748                     // getGtu().getSpeed().si * brakingTime - 0.5 * maxDecel * brakingTime * brakingTime;
749                     if (cumDistSI + objectDistanceSI > breakingDistance(MAX_YELLOW_DECELERATION, getGtu().getSpeed()).si)
750                     {
751                         return new HeadwayTrafficLight(tl, new Length(cumDistSI + objectDistanceSI, LengthUnit.SI));
752                     }
753                 }
754                 if (tl.getTrafficLightColor().isRed())
755                 {
756                     System.err.println(
757                             "Not braking for " + tl.getTrafficLightColor() + " because that would require excessive braking");
758                 }
759                 return null;
760             }
761 
762             // other objects are always blocking, we assume
763             return new HeadwayObject(laneBasedObjects.get(0).getId(), new Length(cumDistSI + objectDistanceSI, LengthUnit.SI));
764         }
765     }
766 
767     /**
768      * Determine the braking distance.
769      * @param deceleration Acceleration; the applied deceleration (should have a negative value)
770      * @param initialSpeed Speed; the initial speed
771      * @return double; the breaking distance
772      */
773     private Length breakingDistance(final Acceleration deceleration, final Speed initialSpeed)
774     {
775         double a = -deceleration.si;
776         double brakingTime = initialSpeed.si / a;
777         return new Length(0.5 * a * brakingTime * brakingTime, LengthUnit.SI);
778     }
779 
780     /**
781      * Returns a set of statuses for the GTU.
782      * @param gtu LaneBasedGTU; gtu
783      * @return set of statuses for the GTU
784      */
785     private GTUStatus[] getGtuStatus(final LaneBasedGTU gtu)
786     {
787         if (gtu.getTurnIndicatorStatus().isLeft())
788         {
789             return new GTUStatus[] {GTUStatus.LEFT_TURNINDICATOR};
790         }
791         if (gtu.getTurnIndicatorStatus().isRight())
792         {
793             return new GTUStatus[] {GTUStatus.RIGHT_TURNINDICATOR};
794         }
795         if (gtu.getTurnIndicatorStatus().isHazard())
796         {
797             return new GTUStatus[] {GTUStatus.EMERGENCY_LIGHTS};
798         }
799         return new GTUStatus[0];
800     }
801 
802     /**
803      * Determine which GTU is behind this GTU. This method looks in all lanes where this GTU is registered, and not further back
804      * than the absolute value of the given maxDistance. The minimum net headway is returned of all Lanes where the GTU is
805      * registered. When no GTU is found within the given maxDistance, <b>null</b> is returned. The search will extend into
806      * successive lanes if the maxDistance is larger than the remaining length on the lane. When Lanes (or underlying
807      * CrossSectionLinks) diverge, the headway algorithms have to look at multiple Lanes and return the minimum headway in each
808      * of the Lanes. When the Lanes (or underlying CrossSectionLinks) converge, "parallel" traffic is not taken into account in
809      * the headway calculation. Instead, gap acceptance algorithms or their equivalent should guide the merging behavior.<br>
810      * <b>Note:</b> Headway is the net headway and calculated on a back-to-front basis.
811      * @param maxDistance the maximum distance to look for the nearest GTU; it should have a negative value to search backwards
812      * @return HeadwayGTU; the headway and the GTU information
813      * @throws GTUException when there is an error with the next lanes in the network.
814      * @throws NetworkException when there is a problem with the route planner
815      */
816     private Headway backwardHeadway(final Length maxDistance) throws GTUException, NetworkException
817     {
818         Throw.when(maxDistance.ge0(), GTUException.class, "backwardHeadway: maxDistance should be negative");
819         Time time = getGtu().getSimulator().getSimulatorTime();
820         double maxDistanceSI = maxDistance.si;
821         Headway foundHeadway = new HeadwayDistance(-maxDistanceSI);
822         for (Lane lane : getGtu().positions(getGtu().getRear()).keySet())
823         {
824             Headway closest = headwayRecursiveBackwardSI(lane, getGtu().getDirection(lane),
825                     getGtu().position(lane, getGtu().getRear(), time).getSI(), 0.0, -maxDistanceSI, time);
826             if (closest.getDistance().si < -maxDistanceSI
827                     && closest.getDistance().si < /* NOT - */foundHeadway.getDistance().si)
828             {
829                 foundHeadway = closest;
830             }
831         }
832         if (foundHeadway instanceof AbstractHeadwayGTU)
833         {
834             return new HeadwayGTUSimple(foundHeadway.getId(), ((AbstractHeadwayGTU) foundHeadway).getGtuType(),
835                     foundHeadway.getDistance().neg(), foundHeadway.getLength(), ((AbstractHeadwayGTU) foundHeadway).getWidth(),
836                     foundHeadway.getSpeed(), foundHeadway.getAcceleration(), null);
837         }
838         if (foundHeadway instanceof HeadwayDistance)
839         {
840             return new HeadwayDistance(foundHeadway.getDistance().neg());
841         }
842         // TODO allow observation of other objects as well.
843         throw new GTUException("backwardHeadway not implemented yet for other object types than GTU");
844     }
845 
846     /**
847      * Calculate the minimum headway, possibly on subsequent lanes, in backward direction (so between our back, and the other
848      * GTU's front). Note: this method returns a POSITIVE number.
849      * @param lane Lane; the lane where we are looking right now
850      * @param direction GTUDirectionality; the direction we are driving on that lane
851      * @param lanePositionSI double; from which position on this lane do we start measuring? This is the current position of the
852      *            rear of the GTU when we measure in the lane where the original GTU is positioned, and lane.getLength() for
853      *            each subsequent lane.
854      * @param cumDistanceSI double; the distance we have already covered searching on previous lanes. Note: This is a POSITIVE
855      *            number.
856      * @param maxDistanceSI the maximum distance to look for in SI units; stays the same in subsequent calls. Note: this is a
857      *            POSITIVE number.
858      * @param when Time; the current or future time for which to calculate the headway
859      * @return the headway in SI units when we have found the GTU, or a null GTU with a distance of Double.MAX_VALUE meters when
860      *         no other GTU could not be found within maxDistanceSI meters
861      * @throws GTUException when there is a problem with the geometry of the network
862      */
863     private Headway headwayRecursiveBackwardSI(final Lane lane, final GTUDirectionality direction, final double lanePositionSI,
864             final double cumDistanceSI, final double maxDistanceSI, final Time when) throws GTUException
865     {
866         LaneBasedGTU otherGTU =
867                 lane.getGtuBehind(new Length(lanePositionSI, LengthUnit.SI), direction, RelativePosition.FRONT, when);
868         if (otherGTU != null)
869         {
870             double distanceM = cumDistanceSI + lanePositionSI - otherGTU.position(lane, otherGTU.getFront(), when).getSI();
871             if (distanceM > 0 && distanceM <= maxDistanceSI)
872             {
873                 return new HeadwayGTUSimple(otherGTU.getId(), otherGTU.getGTUType(), new Length(distanceM, LengthUnit.SI),
874                         otherGTU.getLength(), otherGTU.getWidth(), otherGTU.getSpeed(), otherGTU.getAcceleration(), null);
875             }
876             return new HeadwayDistance(Double.MAX_VALUE);
877         }
878 
879         // Continue search on predecessor lanes.
880         if (cumDistanceSI + lanePositionSI < maxDistanceSI)
881         {
882             // is there a predecessor link?
883             if (lane.prevLanes(getGtu().getGTUType()).size() > 0)
884             {
885                 Headway foundMaxGTUDistanceSI = new HeadwayDistance(Double.MAX_VALUE);
886                 for (Lane prevLane : lane.prevLanes(getGtu().getGTUType()).keySet())
887                 {
888                     // What is behind us is INDEPENDENT of the followed route!
889                     double traveledDistanceSI = cumDistanceSI + lanePositionSI;
890                     // WRONG - adapt method to forward perception method!
891                     Headway closest = headwayRecursiveBackwardSI(prevLane, direction, prevLane.getLength().getSI(),
892                             traveledDistanceSI, maxDistanceSI, when);
893                     if (closest.getDistance().si < maxDistanceSI
894                             && closest.getDistance().si < foundMaxGTUDistanceSI.getDistance().si)
895                     {
896                         foundMaxGTUDistanceSI = closest;
897                     }
898                 }
899                 return foundMaxGTUDistanceSI;
900             }
901         }
902 
903         // No other GTU was not on one of the current lanes or their predecessors.
904         return new HeadwayDistance(Double.MAX_VALUE);
905     }
906 
907     /**************************************************************************************************************************/
908     /************************************************ ADJACENT LANE TRAFFIC ***************************************************/
909     /**************************************************************************************************************************/
910 
911     /**
912      * Determine which GTUs are parallel with us on another lane, based on fractional positions. <br>
913      * Note: When the GTU that calls the method is also registered on the given lane, it is excluded from the return set.
914      * @param lane Lane; the lane to look for parallel (partial or full overlapping) GTUs.
915      * @param when Time; the future time for which to calculate the headway
916      * @return the set of GTUs parallel to us on the other lane (partial overlap counts as parallel), based on fractional
917      *         positions, or an empty set when no GTUs were found.
918      * @throws GTUException when the vehicle's route is inconclusive, when vehicles are not registered correctly on their lanes,
919      *             or when the given lane is not parallel to one of the lanes where we are registered.
920      */
921     private Collection<Headway> parallel(final Lane lane, final Time when) throws GTUException
922     {
923         Collection<Headway> headwayCollection = new LinkedHashSet<>();
924         for (Lane l : getGtu().positions(getGtu().getReference()).keySet())
925         {
926             // only take lanes that we can compare based on a shared design line
927             if (l.getParentLink().equals(lane.getParentLink()))
928             {
929                 // compare based on fractional positions.
930                 double posFractionRef = getGtu().fractionalPosition(l, getGtu().getReference(), when);
931                 double posFractionFront = Math.max(0.0, posFractionRef + getGtu().getFront().getDx().si / lane.getLength().si);
932                 double posFractionRear = Math.min(1.0, posFractionRef + getGtu().getRear().getDx().si / lane.getLength().si);
933                 // double posFractionFront = Math.max(0.0, this.gtu.fractionalPosition(l, this.gtu.getFront(), when));
934                 // double posFractionRear = Math.min(1.0, this.gtu.fractionalPosition(l, this.gtu.getRear(), when));
935                 double posMin = Math.min(posFractionFront, posFractionRear);
936                 double posMax = Math.max(posFractionFront, posFractionRear);
937                 for (LaneBasedGTU otherGTU : lane.getGtuList())
938                 {
939                     if (!otherGTU.equals(this)) // TODO
940                     {
941                         /*- cater for: *-----*         *-----*       *-----*       *----------*
942                          *                *-----*    *----*      *------------*       *-----*
943                          * where the GTUs can each drive in two directions (!)
944                          */
945                         double gtuFractionRef = otherGTU.fractionalPosition(lane, otherGTU.getReference(), when);
946                         double gtuFractionFront =
947                                 Math.max(0.0, gtuFractionRef + otherGTU.getFront().getDx().si / lane.getLength().si);
948                         double gtuFractionRear =
949                                 Math.min(1.0, gtuFractionRef + otherGTU.getRear().getDx().si / lane.getLength().si);
950                         double gtuMin = Math.min(gtuFractionFront, gtuFractionRear);
951                         double gtuMax = Math.max(gtuFractionFront, gtuFractionRear);
952                         if ((gtuMin >= posMin && gtuMin <= posMax) || (gtuMax >= posMin && gtuMax <= posMax)
953                                 || (posMin >= gtuMin && posMin <= gtuMax) || (posMax >= gtuMin && posMax <= gtuMax))
954                         {
955                             // TODO calculate real overlaps
956                             Length overlapFront = new Length(1.0, LengthUnit.SI);
957                             Length overlap = new Length(1.0, LengthUnit.SI);
958                             Length overlapRear = new Length(1.0, LengthUnit.SI);
959                             headwayCollection.add(new HeadwayGTUSimple(otherGTU.getId(), otherGTU.getGTUType(), overlapFront,
960                                     overlap, overlapRear, otherGTU.getLength(), otherGTU.getWidth(), otherGTU.getSpeed(),
961                                     otherGTU.getAcceleration(), null, getGtuStatus(otherGTU)));
962                         }
963                     }
964                 }
965             }
966         }
967         return headwayCollection;
968     }
969 
970     /**
971      * Determine which GTUs are parallel with us in a certain lateral direction, based on fractional positions. <br>
972      * Note 1: This method will look to the adjacent lanes of all lanes where the vehicle has been registered.<br>
973      * Note 2: When the GTU that calls the method is also registered on the given lane, it is excluded from the return set.
974      * @param lateralDirection LateralDirectionality; the direction of the adjacent lane(s) to look for parallel (partial or
975      *            full overlapping) GTUs.
976      * @param when Time; the future time for which to calculate the headway
977      * @return the set of GTUs parallel to us on other lane(s) in the given direction (partial overlap counts as parallel),
978      *         based on fractional positions, or an empty set when no GTUs were found.
979      * @throws GTUException when the vehicle's route is inconclusive, when vehicles are not registered correctly on their lanes,
980      *             or when there are no lanes parallel to one of the lanes where we are registered in the given direction.
981      */
982     private Collection<Headway> parallel(final LateralDirectionality lateralDirection, final Time when) throws GTUException
983     {
984         Collection<Headway> gtuSet = new LinkedHashSet<>();
985         for (Lane lane : getGtu().positions(getGtu().getReference()).keySet())
986         {
987             for (Lane adjacentLane : getAccessibleAdjacentLanes(lateralDirection).get(lane))
988             {
989                 gtuSet.addAll(parallel(adjacentLane, when));
990             }
991         }
992         return gtuSet;
993     }
994 
995     /**
996      * Collect relevant traffic in adjacent lanes. Parallel traffic is included with headway equal to Double.NaN.
997      * @param directionality LateralDirectionality; either <cite>LateralDirectionality.LEFT</cite>, or
998      *            <cite>LateralDirectionality.RIGHT</cite>
999      * @param when Time; the (current) time
1000      * @param maximumForwardHeadway Length; the maximum forward search distance
1001      * @param maximumReverseHeadway Length; the maximum reverse search distance
1002      * @return Collection&lt;LaneBasedGTU&gt;;
1003      * @throws NetworkException on network inconsistency
1004      * @throws GTUException on problems with the GTU state (e.g., position)
1005      * @throws ParameterException in case of a parameter problem
1006      */
1007     private Collection<Headway> collectNeighborLaneTraffic(final LateralDirectionality directionality, final Time when,
1008             final Length maximumForwardHeadway, final Length maximumReverseHeadway)
1009             throws NetworkException, GTUException, ParameterException
1010     {
1011         Collection<Headway> result = new HashSet<>();
1012         for (Headway p : parallel(directionality, when))
1013         {
1014             // TODO expand for other types of Headways
1015             // result.add(new HeadwayGTUSimple(p.getId(), ((AbstractHeadwayGTU) p).getGtuType(),
1016             // new Length(Double.NaN, LengthUnit.SI), p.getLength(), p.getSpeed(), p.getAcceleration()));
1017             result.add(p);
1018         }
1019 
1020         // forward
1021         for (Lane adjacentLane : getAccessibleAdjacentLanes(directionality).get(getLanePathInfo().getReferenceLane()))
1022         {
1023             LanePathInfo lpiAdjacent = buildLanePathInfoAdjacent(adjacentLane, directionality, when);
1024             Headway leader = forwardHeadway(lpiAdjacent, maximumForwardHeadway, true);
1025             if (null != leader.getId() && !result.contains(leader))
1026             {
1027                 result.add(leader);
1028             }
1029         }
1030 
1031         // backward
1032         Lane lane = getGtu().getReferencePosition().getLane();
1033         for (Lane adjacentLane : getAccessibleAdjacentLanes(directionality).get(lane))
1034         {
1035             Headway follower = headwayRecursiveBackwardSI(adjacentLane, getGtu().getDirection(lane),
1036                     getGtu().translatedPosition(adjacentLane, getGtu().getRear(), when).getSI(), 0.0,
1037                     -maximumReverseHeadway.getSI(), when);
1038             if (follower instanceof AbstractHeadwayGTU)
1039             {
1040                 boolean found = false;
1041                 for (Headway headway : result)
1042                 {
1043                     if (headway.getId().equals(follower.getId()))
1044                     {
1045                         found = true;
1046                     }
1047                 }
1048                 if (!found)
1049                 {
1050                     result.add(new HeadwayGTUSimple(follower.getId(), ((AbstractHeadwayGTU) follower).getGtuType(),
1051                             follower.getDistance().neg(), follower.getLength(), ((AbstractHeadwayGTU) follower).getWidth(),
1052                             follower.getSpeed(), follower.getAcceleration(), null));
1053                 }
1054             }
1055             else if (follower instanceof HeadwayDistance) // always add for potential lane drop
1056             {
1057                 result.add(new HeadwayDistance(follower.getDistance().neg()));
1058             }
1059             else
1060             {
1061                 throw new GTUException("collectNeighborLaneTraffic not yet suited to observe obstacles on neighboring lanes");
1062             }
1063         }
1064         return result;
1065     }
1066 
1067     /**
1068      * Find a lanePathInfo left or right of the current LanePath.
1069      * @param adjacentLane Lane; the start adjacent lane for which we calculate the LanePathInfo
1070      * @param direction LateralDirectionality; either <cite>LateralDirectionality.LEFT</cite>, or
1071      *            <cite>LateralDirectionality.RIGHT</cite>
1072      * @param when Time; the (current) time
1073      * @return the adjacent LanePathInfo
1074      * @throws GTUException when the GTU was not initialized yet.
1075      * @throws NetworkException when the speed limit for a GTU type cannot be retrieved from the network.
1076      * @throws ParameterException in case of a parameter problem
1077      */
1078     private LanePathInfo buildLanePathInfoAdjacent(final Lane adjacentLane, final LateralDirectionality direction,
1079             final Time when) throws GTUException, NetworkException, ParameterException
1080     {
1081         if (this.lanePathInfo == null || this.lanePathInfo.getTimestamp().ne(when))
1082         {
1083             updateLanePathInfo();
1084         }
1085         LanePathInfo lpi = getLanePathInfo();
1086         List<LaneDirection> laneDirectionList = new ArrayList<>();
1087         laneDirectionList.add(new LaneDirection(adjacentLane, lpi.getReferenceLaneDirection().getDirection()));
1088         Length referencePosition = getGtu().translatedPosition(adjacentLane, getGtu().getReference(), when);
1089         for (int i = 1; i < lpi.getLaneDirectionList().size(); i++)
1090         {
1091             LaneDirection ld = lpi.getLaneDirectionList().get(i);
1092             Set<Lane> accessibleLanes = ld.getLane().accessibleAdjacentLanesLegal(direction, getGtu().getGTUType(),
1093                     lpi.getReferenceLaneDirection().getDirection());
1094             Lane adjLane = null;
1095             for (Lane lane : accessibleLanes)
1096             {
1097                 if (lane.getParentLink().equals(ld.getLane().getParentLink()))
1098                 {
1099                     adjLane = lane;
1100                 }
1101             }
1102             if (adjLane == null)
1103             {
1104                 break;
1105             }
1106             laneDirectionList.add(new LaneDirection(adjLane, ld.getDirection()));
1107         }
1108         return new LanePathInfo(null, laneDirectionList, referencePosition);
1109     }
1110 
1111 }