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