View Javadoc
1   package org.opentrafficsim.road.gtu.lane.perception.categories;
2   
3   import java.util.HashMap;
4   import java.util.HashSet;
5   import java.util.Iterator;
6   import java.util.Map;
7   import java.util.Set;
8   import java.util.SortedSet;
9   import java.util.TreeSet;
10  
11  import org.djunits.unit.LengthUnit;
12  import org.djunits.value.vdouble.scalar.Length;
13  import org.djunits.value.vdouble.scalar.Time;
14  import org.opentrafficsim.base.TimeStampedObject;
15  import org.opentrafficsim.core.gtu.GTUDirectionality;
16  import org.opentrafficsim.core.gtu.GTUException;
17  import org.opentrafficsim.core.gtu.RelativePosition;
18  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
19  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
20  import org.opentrafficsim.core.network.LateralDirectionality;
21  import org.opentrafficsim.core.network.NetworkException;
22  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
23  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
24  import org.opentrafficsim.road.gtu.lane.perception.LaneStructureRecord;
25  import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
26  import org.opentrafficsim.road.gtu.lane.perception.headway.Headway;
27  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayDistance;
28  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTU;
29  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTUSimple;
30  import org.opentrafficsim.road.gtu.lane.tactical.AbstractLaneBasedTacticalPlanner;
31  import org.opentrafficsim.road.gtu.lane.tactical.LanePathInfo;
32  import org.opentrafficsim.road.network.lane.Lane;
33  import org.opentrafficsim.road.network.lane.LaneDirection;
34  
35  import nl.tudelft.simulation.language.Throw;
36  
37  /**
38   * Perception of surrounding traffic on the own road, i.e. without crossing traffic.
39   * <p>
40   * Copyright (c) 2013-2017 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
41   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
42   * <p>
43   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jul 22, 2016 <br>
44   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
45   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
46   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
47   */
48  public class DirectNeighborsPerception extends LaneBasedAbstractPerceptionCategory implements NeighborsPerception
49  {
50  
51      /** */
52      private static final long serialVersionUID = 20160811L;
53  
54      /** Set of followers per relative lane. */
55      private final Map<RelativeLane, TimeStampedObject<SortedSet<HeadwayGTU>>> followers = new HashMap<>();
56  
57      /** Set of leaders per relative lane. */
58      private final Map<RelativeLane, TimeStampedObject<SortedSet<HeadwayGTU>>> leaders = new HashMap<>();
59  
60      /** Set of first followers per lane upstream of merge per lateral direction, i.e. in the left or right lane. */
61      private final Map<LateralDirectionality, TimeStampedObject<SortedSet<HeadwayGTU>>> firstFollowers = new HashMap<>();
62  
63      /** Set of first leaders per lane downstream of split per lateral direction, i.e. in the left or right lane. */
64      private final Map<LateralDirectionality, TimeStampedObject<SortedSet<HeadwayGTU>>> firstLeaders = new HashMap<>();
65  
66      /** Whether a GTU is alongside per lateral direction, i.e. in the left or right lane. */
67      private final Map<LateralDirectionality, TimeStampedObject<Boolean>> gtuAlongside = new HashMap<>();
68  
69      /** Headway GTU type that should be used. */
70      private final HeadwayGtuType headwayGtuType;
71  
72      /**
73       * Margin used for neighbor search in some cases to prevent possible deadlock. This does not affect calculated distances to
74       * neighbors, but only whether they are considered a leader or follower.
75       */
76      private static final Length MARGIN = new Length(0.000001, LengthUnit.SI);
77  
78      /**
79       * @param perception perception
80       * @param headwayGtuType type of headway gtu to generate
81       */
82      public DirectNeighborsPerception(final LanePerception perception, final HeadwayGtuType headwayGtuType)
83      {
84          super(perception);
85          this.headwayGtuType = headwayGtuType;
86      }
87  
88      /** {@inheritDoc} */
89      @Override
90      public final void updateAll() throws GTUException, NetworkException, ParameterException
91      {
92          updateLanePathInfo(); // TODO remove this line
93          this.firstLeaders.clear();
94          this.firstFollowers.clear();
95          this.gtuAlongside.clear();
96          if (getPerception().getLaneStructure().getCrossSection().contains(RelativeLane.LEFT))
97          {
98              updateFirstLeaders(LateralDirectionality.LEFT);
99              updateFirstFollowers(LateralDirectionality.LEFT);
100             updateGtuAlongside(LateralDirectionality.LEFT);
101         }
102         if (getPerception().getLaneStructure().getCrossSection().contains(RelativeLane.RIGHT))
103         {
104             updateFirstLeaders(LateralDirectionality.RIGHT);
105             updateFirstFollowers(LateralDirectionality.RIGHT);
106             updateGtuAlongside(LateralDirectionality.RIGHT);
107         }
108         this.leaders.clear();
109         this.followers.clear();
110         for (RelativeLane lane : getPerception().getLaneStructure().getCrossSection())
111         {
112             updateLeaders(lane);
113             updateFollowers(lane);
114         }
115     }
116 
117     /** {@inheritDoc} */
118     @Override
119     public final void updateFirstLeaders(final LateralDirectionality lat)
120             throws ParameterException, GTUException, NetworkException
121     {
122         checkLateralDirectionality(lat);
123         SortedSet<HeadwayGTU> headwaySet = getFirstDownstreamGTUs(lat, RelativePosition.FRONT, RelativePosition.REAR);
124         this.firstLeaders.put(lat, new TimeStampedObject<>(headwaySet, getTimestamp()));
125     }
126 
127     /** {@inheritDoc} */
128     @Override
129     public final void updateFirstFollowers(final LateralDirectionality lat)
130             throws GTUException, ParameterException, NetworkException
131     {
132         checkLateralDirectionality(lat);
133         SortedSet<HeadwayGTU> headwaySet = getFirstUpstreamGTUs(lat, RelativePosition.REAR, RelativePosition.FRONT);
134         this.firstFollowers.put(lat, new TimeStampedObject<>(headwaySet, getTimestamp()));
135     }
136 
137     /** {@inheritDoc} */
138     @Override
139     public final void updateGtuAlongside(final LateralDirectionality lat) throws GTUException, ParameterException
140     {
141 
142         checkLateralDirectionality(lat);
143         // check if any GTU is downstream of the rear, within the vehicle length
144         SortedSet<HeadwayGTU> headwaySet = getFirstDownstreamGTUs(lat, RelativePosition.REAR, RelativePosition.FRONT);
145         if (!headwaySet.isEmpty() && headwaySet.first().getDistance().le0())
146         {
147             this.gtuAlongside.put(lat, new TimeStampedObject<>(true, getTimestamp()));
148             return;
149         }
150         // check if any GTU is upstream of the front, within the vehicle length
151         headwaySet = getFirstUpstreamGTUs(lat, RelativePosition.FRONT, RelativePosition.REAR);
152         if (!headwaySet.isEmpty() && headwaySet.first().getDistance().le0())
153         {
154             this.gtuAlongside.put(lat, new TimeStampedObject<>(true, getTimestamp()));
155             return;
156         }
157         // no such GTU
158         this.gtuAlongside.put(lat, new TimeStampedObject<>(false, getTimestamp()));
159 
160     }
161 
162     /**
163      * Returns a set of first leaders per branch, relative to given relative position. Helper method to find first leaders and
164      * GTU's alongside.
165      * @param lat LEFT or RIGHT
166      * @param egoRelativePosition position of GTU to start search from
167      * @param otherRelativePosition position of other GTU
168      * @return set of first leaders per branch
169      * @throws GTUException if the GTU was not initialized
170      * @throws ParameterException if a parameter was not present or out of bounds
171      */
172     private SortedSet<HeadwayGTU> getFirstDownstreamGTUs(final LateralDirectionality lat,
173             final RelativePosition.TYPE egoRelativePosition, final RelativePosition.TYPE otherRelativePosition)
174             throws GTUException, ParameterException
175     {
176         SortedSet<HeadwayGTU> headwaySet = new TreeSet<>();
177         Set<LaneStructureRecord> currentSet = new HashSet<>();
178         Set<LaneStructureRecord> nextSet = new HashSet<>();
179         LaneStructureRecord record = getPerception().getLaneStructure().getLaneLSR(new RelativeLane(lat, 1));
180         Length dxSearch = getGtu().getRelativePositions().get(egoRelativePosition).getDx();
181         Length dxHeadway = getGtu().getFront().getDx();
182         branchUpstream(record, dxSearch, currentSet);
183         // move downstream over branches as long as no vehicles are found
184         while (!currentSet.isEmpty())
185         {
186             Iterator<LaneStructureRecord> iterator = currentSet.iterator();
187             while (iterator.hasNext())
188             {
189                 record = iterator.next();
190                 /*-
191                  *                                             _ _ _ ______________________ _ _ _
192                  *                                                             _|___    |
193                  * find any vehicle downstream of this point on lane A |      |__o__| A |
194                  *                                             _ _ _ ___________|_______|__ _ _ _ 
195                  *                                                     (--------) negative distance
196                  */
197                 LaneBasedGTU down = record.getLane().getGtuAhead(record.getStartDistance().neg().plus(dxSearch),
198                         record.getDirection(), otherRelativePosition, getTimestamp());
199                 if (down != null)
200                 {
201                     // GTU found, add to set
202                     headwaySet.add(this.headwayGtuType.createHeadwayGtu(down,
203                             record.getStartDistance().plus(down.position(record.getLane(), down.getRear())).minus(dxHeadway)));
204                 }
205                 else
206                 {
207                     // no GTU found, search on next lanes in next loop and maintain cumulative length
208                     for (LaneStructureRecord next : record.getNext())
209                     {
210                         nextSet.add(next);
211                     }
212                 }
213             }
214             currentSet = nextSet;
215             nextSet = new HashSet<>();
216         }
217         return headwaySet;
218     }
219 
220     /**
221      * Returns a set of lanes to start from for a downstream search, upstream of the reference lane if the tail is before this
222      * lane.
223      * @param record start record
224      * @param dx distance between reference point and point to search from
225      * @param set set of lanes that is recursively built up, starting with the reference record
226      */
227     private void branchUpstream(final LaneStructureRecord record, final Length dx, final Set<LaneStructureRecord> set)
228     {
229         Length pos = record.getStartDistance().neg().minus(dx);
230         if (pos.lt0())
231         {
232             for (LaneStructureRecord prev : record.getPrev())
233             {
234                 branchUpstream(prev, dx, set);
235             }
236         }
237         else
238         {
239             set.add(record);
240         }
241     }
242 
243     /**
244      * Returns a set of first followers per branch, relative to given relative position. Helper method to find first followers
245      * and GTU's alongside.
246      * @param lat LEFT or RIGHT
247      * @param egoRelativePosition position of GTU to start search from
248      * @param otherRelativePosition position of other GTU
249      * @return set of first followers per branch
250      * @throws GTUException if the GTU was not initialized
251      * @throws ParameterException if a parameter was not present or out of bounds
252      */
253     private SortedSet<HeadwayGTU> getFirstUpstreamGTUs(final LateralDirectionality lat,
254             final RelativePosition.TYPE egoRelativePosition, final RelativePosition.TYPE otherRelativePosition)
255             throws GTUException, ParameterException
256     {
257         SortedSet<HeadwayGTU> headwaySet = new TreeSet<>();
258         Set<LaneStructureRecord> currentSet = new HashSet<>();
259         Set<LaneStructureRecord> prevSet = new HashSet<>();
260         LaneStructureRecord record = getPerception().getLaneStructure().getLaneLSR(new RelativeLane(lat, 1));
261         Length dxSearch = getGtu().getRelativePositions().get(egoRelativePosition).getDx();
262         Length dxHeadway = getGtu().getRear().getDx();
263         branchDownstream(record, dxSearch, currentSet);
264         // move upstream over branches as long as no vehicles are found
265         while (!currentSet.isEmpty())
266         {
267             Iterator<LaneStructureRecord> iterator = currentSet.iterator();
268             while (iterator.hasNext())
269             {
270                 record = iterator.next();
271                 /*-
272                  * _ _ _ ______________________ _ _ _ 
273                  *         |    ___|_   
274                  *         | A |__o__|      | find any upstream of this point on lane A
275                  * _ _ _ __|_______|___________ _ _ _ 
276                  *         (----------------) distance
277                  */
278                 LaneBasedGTU up = record.getLane().getGtuBehind(record.getStartDistance().neg().plus(dxSearch),
279                         record.getDirection(), otherRelativePosition, getTimestamp());
280                 if (up != null)
281                 {
282                     // GTU found, add to set
283                     headwaySet.add(this.headwayGtuType.createHeadwayGtu(up, record.getStartDistance().neg()
284                             .minus(up.position(record.getLane(), up.getFront())).plus(dxHeadway)));
285                 }
286                 else
287                 {
288                     // no GTU found, search on next lanes in next loop and maintain cumulative length
289                     for (LaneStructureRecord prev : record.getPrev())
290                     {
291                         prevSet.add(prev);
292                     }
293                 }
294             }
295             currentSet = prevSet;
296             prevSet = new HashSet<>();
297         }
298         return headwaySet;
299     }
300 
301     /**
302      * Returns a set of lanes to start from for an upstream search, downstream of the reference lane if the front is after this
303      * lane.
304      * @param record start record
305      * @param dx distance between reference point and point to search from
306      * @param set set of lanes that is recursively built up, starting with the reference record
307      */
308     private void branchDownstream(final LaneStructureRecord record, final Length dx, final Set<LaneStructureRecord> set)
309     {
310         Length pos = record.getStartDistance().neg().plus(dx);
311         if (pos.gt(record.getLane().getLength()))
312         {
313             for (LaneStructureRecord next : record.getNext())
314             {
315                 branchDownstream(next, dx, set);
316             }
317         }
318         else
319         {
320             set.add(record);
321         }
322     }
323 
324     /** {@inheritDoc} */
325     @Override
326     public final void updateLeaders(final RelativeLane lane) throws ParameterException, GTUException, NetworkException
327     {
328         Throw.whenNull(lane, "Lane may not be null.");
329         SortedSet<HeadwayGTU> headwaySet = new TreeSet<>();
330         Set<LaneStructureRecord> currentSet = new HashSet<>();
331         Set<LaneStructureRecord> nextSet = new HashSet<>();
332         LaneStructureRecord initRecord = getPerception().getLaneStructure().getLaneLSR(lane);
333         currentSet.add(initRecord);
334         Length lookahead = getGtu().getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD);
335         Length ds = getGtu().getFront().getDx().minus(getGtu().getReference().getDx());
336         // move downstream over branches
337         while (!currentSet.isEmpty())
338         {
339             for (LaneStructureRecord record : currentSet)
340             {
341                 int first;
342                 Length loc = record.getStartDistance().neg().plus(ds);
343                 if (lane.getLateralDirectionality().isLeft())
344                 {
345                     loc = record.getDirection().isPlus() ? loc.minus(MARGIN) : loc.plus(MARGIN);
346                 }
347                 else if (lane.getLateralDirectionality().isRight())
348                 {
349                     loc = record.getDirection().isPlus() ? loc.plus(MARGIN) : loc.minus(MARGIN);
350                 }
351                 LaneBasedGTU down =
352                         record.getLane().getGtuAhead(loc, record.getDirection(), RelativePosition.FRONT, getTimestamp());
353                 if (down == null)
354                 {
355                     first = record.getLane().getGtuList().size(); // none
356                 }
357                 else
358                 {
359                     first = record.getLane().getGtuList().indexOf(down); // from first downstream till last
360                 }
361                 // loop GTU's and create HeadwayGTU's
362                 for (int i = first; i < record.getLane().getGtuList().size(); i++)
363                 {
364                     LaneBasedGTU gtu = record.getLane().getGtuList().get(i);
365                     if (gtu.position(record.getLane(), gtu.getRear()).lt0() && !record.equals(initRecord))
366                     {
367                         // rear still on previous lane; it is found there
368                         continue;
369                     }
370                     Length distance = record.getStartDistance().plus(gtu.position(record.getLane(), gtu.getRear())).minus(ds);
371                     // only within lookahead
372                     if (distance.le(lookahead) && !gtu.equals(getGtu()))
373                     {
374                         // TODO remove this fix to ignore on-ramp block
375                         if (!gtu.getId().equals("999999"))
376                         {
377                             headwaySet.add(this.headwayGtuType.createHeadwayGtu(gtu, distance));
378                         }
379                     }
380                 }
381                 // add next lanes
382                 for (LaneStructureRecord next : record.getNext())
383                 {
384                     nextSet.add(next);
385                 }
386                 // TODO break search, but how to guarantee that the rear of further GTU's is not within lookahead?
387             }
388             currentSet = nextSet;
389             nextSet = new HashSet<>();
390         }
391         this.leaders.put(lane, new TimeStampedObject<>(headwaySet, getTimestamp()));
392 
393     }
394 
395     /** {@inheritDoc} */
396     @Override
397     public final void updateFollowers(final RelativeLane lane) throws GTUException, NetworkException, ParameterException
398     {
399         Throw.whenNull(lane, "Lane may not be null.");
400         SortedSet<HeadwayGTU> headwaySet = new TreeSet<>();
401         Set<LaneStructureRecord> currentSet = new HashSet<>();
402         Set<LaneStructureRecord> prevSet = new HashSet<>();
403         LaneStructureRecord initRecord = getPerception().getLaneStructure().getLaneLSR(lane);
404         currentSet.add(initRecord);
405         Length lookback = getGtu().getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKBACK);
406         Length dsFront = getGtu().getFront().getDx().minus(getGtu().getReference().getDx());
407         Length dsRear = getGtu().getRear().getDx().minus(getGtu().getReference().getDx());
408         // move upstream over branches
409         while (!currentSet.isEmpty())
410         {
411             for (LaneStructureRecord record : currentSet)
412             {
413                 int first;
414                 Length loc = record.getStartDistance().neg().plus(dsFront);
415                 if (lane.getLateralDirectionality().isLeft())
416                 {
417                     loc = record.getDirection().isPlus() ? loc.plus(MARGIN) : loc.minus(MARGIN);
418                 }
419                 else if (lane.getLateralDirectionality().isRight())
420                 {
421                     loc = record.getDirection().isPlus() ? loc.minus(MARGIN) : loc.plus(MARGIN);
422                 }
423                 LaneBasedGTU up =
424                         record.getLane().getGtuBehind(loc, record.getDirection(), RelativePosition.FRONT, getTimestamp());
425                 if (up == null)
426                 {
427                     first = -1; // none
428                 }
429                 else
430                 {
431                     first = record.getLane().getGtuList().indexOf(up); // from first upstream till last
432                 }
433                 // loop GTU's and create HeadwayGTU's
434                 for (int i = first; i >= 0; i--)
435                 {
436                     LaneBasedGTU gtu = record.getLane().getGtuList().get(i);
437                     Length distance =
438                             record.getStartDistance().neg().minus(gtu.position(record.getLane(), gtu.getFront())).plus(dsRear);
439                     // only within lookback, and ignore self
440                     if (distance.le(lookback) && !gtu.getId().equals(getGtu().getId()))
441                     {
442                         headwaySet.add(this.headwayGtuType.createHeadwayGtu(gtu, distance));
443                     }
444                 }
445                 // add prev lanes
446                 for (LaneStructureRecord prev : record.getPrev())
447                 {
448                     prevSet.add(prev);
449                 }
450                 // TODO break search, but how to guarantee that the front of further GTU's is not within lookback?
451             }
452             currentSet = prevSet;
453             prevSet = new HashSet<>();
454         }
455         this.followers.put(lane, new TimeStampedObject<>(headwaySet, getTimestamp()));
456 
457     }
458 
459     /** {@inheritDoc} */
460     @Override
461     public final SortedSet<HeadwayGTU> getFirstLeaders(final LateralDirectionality lat)
462             throws ParameterException, NullPointerException, IllegalArgumentException
463     {
464         checkLateralDirectionality(lat);
465         return this.firstLeaders.get(lat).getObject();
466     }
467 
468     /** {@inheritDoc} */
469     @Override
470     public final SortedSet<HeadwayGTU> getFirstFollowers(final LateralDirectionality lat)
471             throws ParameterException, NullPointerException, IllegalArgumentException
472     {
473         checkLateralDirectionality(lat);
474         return getObjectOrNull(this.firstFollowers.get(lat));
475     }
476 
477     /** {@inheritDoc} */
478     @Override
479     public final boolean isGtuAlongside(final LateralDirectionality lat)
480             throws ParameterException, NullPointerException, IllegalArgumentException
481     {
482         checkLateralDirectionality(lat);
483         return getObjectOrNull(this.gtuAlongside.get(lat));
484     }
485 
486     /** {@inheritDoc} */
487     @Override
488     public final SortedSet<HeadwayGTU> getLeaders(final RelativeLane lane)
489     {
490         return getObjectOrNull(this.leaders.get(lane));
491     }
492 
493     /** {@inheritDoc} */
494     @Override
495     public final SortedSet<HeadwayGTU> getFollowers(final RelativeLane lane)
496     {
497         return getObjectOrNull(this.followers.get(lane));
498     }
499 
500     /**
501      * Set of leaders on a lane, which is usually 0 or 1, but possibly more in case of a downstream split with no intermediate
502      * GTU. This is shown below. Suppose A needs to go straight. If A considers a lane change to the left, both GTUs B (who's
503      * tail ~ is still on the straight lane) and C need to be considered for whether it's safe to do so. In case of multiple
504      * splits close to one another, the returned set may contain even more than 2 leaders. Leaders are sorted by headway value.
505      * 
506      * <pre>
507      *          | |
508      * _________/B/_____
509      * _ _?_ _ _~_ _C_ _
510      * _ _A_ _ _ _ _ _ _
511      * _________________
512      * </pre>
513      * 
514      * @param lat LEFT or RIGHT
515      * @return list of followers on a lane
516      * @throws ParameterException if parameter is not defined
517      * @throws NullPointerException if {@code lat} is {@code null}
518      * @throws IllegalArgumentException if {@code lat} is {@code NONE}
519      */
520     public final TimeStampedObject<SortedSet<HeadwayGTU>> getTimeStampedFirstLeaders(final LateralDirectionality lat)
521             throws ParameterException, NullPointerException, IllegalArgumentException
522     {
523         checkLateralDirectionality(lat);
524         return this.firstLeaders.get(lat);
525     }
526 
527     /**
528      * Set of followers on a lane, which is usually 0 or 1, but possibly more in case of an upstream merge with no intermediate
529      * GTU. This is shown below. If A considers a lane change to the left, both GTUs B and C need to be considered for whether
530      * it's safe to do so. In case of multiple merges close to one another, the returned set may contain even more than 2
531      * followers. Followers are sorted by tailway value.
532      * 
533      * <pre>
534      *        | |
535      *        |C| 
536      * ________\ \______
537      * _ _B_|_ _ _ _ _?_
538      * _ _ _|_ _ _ _ _A_ 
539      * _____|___________
540      * </pre>
541      * 
542      * @param lat LEFT or RIGHT
543      * @return list of followers on a lane
544      * @throws ParameterException if parameter is not defined
545      * @throws NullPointerException if {@code lat} is {@code null}
546      * @throws IllegalArgumentException if {@code lat} is {@code NONE}
547      */
548     public final TimeStampedObject<SortedSet<HeadwayGTU>> getTimeStampedFirstFollowers(final LateralDirectionality lat)
549             throws ParameterException, NullPointerException, IllegalArgumentException
550     {
551         checkLateralDirectionality(lat);
552         return this.firstFollowers.get(lat);
553     }
554 
555     /**
556      * Whether there is a GTU alongside, i.e. with overlap, in an adjacent lane.
557      * @param lat LEFT or RIGHT
558      * @return whether there is a GTU alongside, i.e. with overlap, in an adjacent lane
559      * @throws ParameterException if parameter is not defined
560      * @throws NullPointerException if {@code lat} is {@code null}
561      * @throws IllegalArgumentException if {@code lat} is {@code NONE}
562      */
563     public final TimeStampedObject<Boolean> isGtuAlongsideTimeStamped(final LateralDirectionality lat)
564             throws ParameterException, NullPointerException, IllegalArgumentException
565     {
566         checkLateralDirectionality(lat);
567         return this.gtuAlongside.get(lat);
568     }
569 
570     /**
571      * Set of leaders on a lane, including adjacent GTU's who's FRONT is ahead of the own vehicle FRONT. Leaders are sorted by
572      * headway value.
573      * @param lane relative lateral lane
574      * @return set of leaders on a lane, including adjacent GTU's who's FRONT is ahead of the own vehicle FRONT
575      */
576     public final TimeStampedObject<SortedSet<HeadwayGTU>> getTimeStampedLeaders(final RelativeLane lane)
577     {
578         return this.leaders.get(lane);
579     }
580 
581     /**
582      * Set of followers on a lane, including adjacent GTU's who's REAR is back of the own vehicle REAR. Follower are are sorted
583      * by tailway value.
584      * @param lane relative lateral lane
585      * @return set of followers on a lane, including adjacent GTU's who's REAR is back of the own vehicle REAR
586      */
587     public final TimeStampedObject<SortedSet<HeadwayGTU>> getTimeStampedFollowers(final RelativeLane lane)
588     {
589         return this.followers.get(lane);
590     }
591 
592     /**
593      * Checks that lateral directionality is either left or right and an existing lane.
594      * @param lat LEFT or RIGHT
595      * @throws ParameterException if parameter is not defined
596      * @throws NullPointerException if {@code lat} is {@code null}
597      * @throws IllegalArgumentException if {@code lat} is {@code NONE}
598      */
599     private void checkLateralDirectionality(final LateralDirectionality lat)
600             throws ParameterException, NullPointerException, IllegalArgumentException
601     {
602         // TODO not use this check when synchronizing or cooperating
603         Throw.whenNull(lat, "Lateral directionality may not be null.");
604         Throw.when(lat.equals(LateralDirectionality.NONE), IllegalArgumentException.class,
605                 "Lateral directionality may not be NONE.");
606         Throw.when(
607                 (lat.equals(LateralDirectionality.LEFT)
608                         && !getPerception().getLaneStructure().getCrossSection().contains(RelativeLane.LEFT))
609                         || (lat.equals(LateralDirectionality.RIGHT)
610                                 && !getPerception().getLaneStructure().getCrossSection().contains(RelativeLane.RIGHT)),
611                 IllegalArgumentException.class, "Lateral directionality may only point to an existing adjacent lane.");
612     }
613 
614     /** {@inheritDoc} */
615     public final String toString()
616     {
617         return "DirectNeighborsPerception";
618     }
619 
620     // TODO remove code below
621     /**************************************************************************************************************************/
622     /****** The code below has been copied from DefaultAlexander and should only be used by a first dummy implementation ******/
623     /**************************************************************************************************************************/
624 
625     /** The lanes and path we expect to take if we do not change lanes. */
626     private TimeStampedObject<LanePathInfo> lanePathInfo;
627 
628     /**
629      * @throws GTUException when the GTU was not initialized yet.
630      * @throws NetworkException when the speed limit for a GTU type cannot be retrieved from the network.
631      * @throws ParameterException in case of not being able to retrieve parameter ParameterTypes.LOOKAHEAD
632      */
633     public final void updateLanePathInfo() throws GTUException, NetworkException, ParameterException
634     {
635         Time timestamp = getTimestamp();
636         this.lanePathInfo = new TimeStampedObject<>(AbstractLaneBasedTacticalPlanner.buildLanePathInfo(getGtu(),
637                 getGtu().getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD)), timestamp);
638     }
639 
640     /**
641      * Retrieve the last perceived lane path info.
642      * @return LanePathInfo
643      */
644     public final LanePathInfo getLanePathInfo()
645     {
646         return this.lanePathInfo.getObject();
647     }
648 
649     /**
650      * Retrieve the time stamped last perceived lane path info.
651      * @return LanePathInfo
652      */
653     public final TimeStampedObject<LanePathInfo> getTimeStampedLanePathInfo()
654     {
655         return this.lanePathInfo;
656     }
657 
658     /**
659      * Determine which GTU is in front of this GTU. This method looks in all lanes where this GTU is registered, and not further
660      * than the value of the given maxDistance. The minimum headway is returned of all Lanes where the GTU is registered. When
661      * no GTU is found within the given maxDistance, a HeadwayGTU with <b>null</b> as the gtuId and maxDistance as the distance
662      * is returned. The search will extend into successive lanes if the maxDistance is larger than the remaining length on the
663      * lane. When Lanes (or underlying CrossSectionLinks) diverge, a route planner may be used to determine which kinks and
664      * lanes to take into account and which ones not. When the Lanes (or underlying CrossSectionLinks) converge, "parallel"
665      * traffic is not taken into account in the headway calculation. Instead, gap acceptance algorithms or their equivalent
666      * should guide the merging behavior.<br>
667      * <b>Note:</b> Headway is the net headway and calculated on a front-to-back basis.
668      * @param maxDistance the maximum distance to look for the nearest GTU; positive values search forwards; negative values
669      *            search backwards
670      * @return HeadwayGTU; the headway and the GTU information
671      * @throws GTUException when there is an error with the next lanes in the network.
672      * @throws NetworkException when there is a problem with the route planner
673      */
674     private Headway forwardHeadway(final Length maxDistance) throws GTUException, NetworkException
675     {
676         LanePathInfo lpi = getLanePathInfo();
677         return forwardHeadway(lpi, maxDistance);
678     }
679 
680     /**
681      * Determine which GTU is in front of this GTU. This method uses a given lanePathInfo to look forward, but not further than
682      * the value of the given maxDistance. The minimum headway is returned of all Lanes where the GTU is registered. When no GTU
683      * is found within the given maxDistance, a HeadwayGTU with <b>null</b> as the gtuId and maxDistance as the distance is
684      * returned. The search will extend into successive lanes if the maxDistance is larger than the remaining length on the
685      * lane. When Lanes (or underlying CrossSectionLinks) diverge, a route planner may be used to determine which kinks and
686      * lanes to take into account and which ones not. When the Lanes (or underlying CrossSectionLinks) converge, "parallel"
687      * traffic is not taken into account in the headway calculation. Instead, gap acceptance algorithms or their equivalent
688      * should guide the merging behavior.<br>
689      * <b>Note:</b> Headway is the net headway and calculated on a front-to-back basis.
690      * @param lpi the lanePathInfo object that informs the headway algorithm in which lanes to look, and from which position on
691      *            the first lane.
692      * @param maxDistance the maximum distance to look for the nearest GTU; positive values search forwards; negative values
693      *            search backwards
694      * @return HeadwayGTU; the headway and the GTU information
695      * @throws GTUException when there is an error with the next lanes in the network.
696      * @throws NetworkException when there is a problem with the route planner
697      */
698     private Headway forwardHeadway(final LanePathInfo lpi, final Length maxDistance) throws GTUException, NetworkException
699     {
700         Throw.when(maxDistance.le(Length.ZERO), GTUException.class, "forwardHeadway: maxDistance should be positive");
701 
702         int ldIndex = 0;
703         LaneDirection ld = lpi.getReferenceLaneDirection();
704         double gtuPosFrontSI = lpi.getReferencePosition().si;
705         if (lpi.getReferenceLaneDirection().getDirection().isPlus())
706         {
707             gtuPosFrontSI += getGtu().getFront().getDx().si;
708         }
709         else
710         {
711             gtuPosFrontSI -= getGtu().getFront().getDx().si;
712         }
713 
714         while ((gtuPosFrontSI > ld.getLane().getLength().si || gtuPosFrontSI < 0.0)
715                 && ldIndex < lpi.getLaneDirectionList().size() - 1)
716         {
717             ldIndex++;
718             if (ld.getDirection().isPlus()) // e.g. 1005 on length of lane = 1000
719             {
720                 if (lpi.getLaneDirectionList().get(ldIndex).getDirection().isPlus())
721                 {
722                     gtuPosFrontSI -= ld.getLane().getLength().si;
723                 }
724                 else
725                 {
726                     gtuPosFrontSI = lpi.getLaneDirectionList().get(ldIndex).getLane().getLength().si - gtuPosFrontSI;
727                 }
728                 ld = lpi.getLaneDirectionList().get(ldIndex);
729             }
730             else
731             // e.g. -5 on lane of whatever length
732             {
733                 if (lpi.getLaneDirectionList().get(ldIndex).getDirection().isPlus())
734                 {
735                     gtuPosFrontSI += ld.getLane().getLength().si;
736                 }
737                 else
738                 {
739                     gtuPosFrontSI += lpi.getLaneDirectionList().get(ldIndex).getLane().getLength().si;
740                 }
741                 ld = lpi.getLaneDirectionList().get(ldIndex);
742             }
743         }
744 
745         Time time = getGtu().getSimulator().getSimulatorTime().getTime();
746 
747         // look forward based on the provided lanePathInfo.
748         return headwayLane(ld, gtuPosFrontSI, 0.0, time);
749 
750     }
751 
752     /**
753      * Determine the positive headway on a lane, or null if no GTU can be found on this lane.
754      * @param laneDirection the lane and direction to look
755      * @param startPosSI the start position to look from in meters
756      * @param cumDistSI the cumulative distance that has already been observed on other lanes
757      * @param now the current time to determine the GTU positions on the lane
758      * @return the HeadwayGTU, containing information on a GTU that is ahead of the given start position, or null if no GTU can
759      *         be found on this lane
760      * @throws GTUException when the GTUs ahead on the lane cannot be determined
761      */
762     private Headway headwayLane(final LaneDirection laneDirection, final double startPosSI, final double cumDistSI,
763             final Time now) throws GTUException
764     {
765         Lane lane = laneDirection.getLane();
766         LaneBasedGTU laneBasedGTU = lane.getGtuAhead(new Length(startPosSI, LengthUnit.SI), laneDirection.getDirection(),
767                 RelativePosition.REAR, now);
768         if (laneBasedGTU == null)
769         {
770             return null;
771         }
772         double distanceSI = Math.abs(laneBasedGTU.position(lane, laneBasedGTU.getRear()).si - startPosSI);
773         return new HeadwayGTUSimple(laneBasedGTU.getId(), laneBasedGTU.getGTUType(),
774                 new Length(cumDistSI + distanceSI, LengthUnit.SI), laneBasedGTU.getLength(), laneBasedGTU.getSpeed(),
775                 laneBasedGTU.getAcceleration());
776     }
777 
778     /**
779      * Determine which GTU is behind this GTU. This method looks in all lanes where this GTU is registered, and not further back
780      * than the absolute value of the given maxDistance. The minimum net headway is returned of all Lanes where the GTU is
781      * registered. When no GTU is found within the given maxDistance, <b>null</b> is returned. The search will extend into
782      * successive lanes if the maxDistance is larger than the remaining length on the lane. When Lanes (or underlying
783      * CrossSectionLinks) diverge, the headway algorithms have to look at multiple Lanes and return the minimum headway in each
784      * of the Lanes. When the Lanes (or underlying CrossSectionLinks) converge, "parallel" traffic is not taken into account in
785      * the headway calculation. Instead, gap acceptance algorithms or their equivalent should guide the merging behavior.<br>
786      * <b>Note:</b> Headway is the net headway and calculated on a back-to-front basis.
787      * @param maxDistance the maximum distance to look for the nearest GTU; it should have a negative value to search backwards
788      * @return HeadwayGTU; the headway and the GTU information
789      * @throws GTUException when there is an error with the next lanes in the network.
790      * @throws NetworkException when there is a problem with the route planner
791      */
792     private Headway backwardHeadway(final Length maxDistance) throws GTUException, NetworkException
793     {
794         Throw.when(maxDistance.ge(Length.ZERO), GTUException.class, "backwardHeadway: maxDistance should be negative");
795         Time time = getGtu().getSimulator().getSimulatorTime().getTime();
796         double maxDistanceSI = maxDistance.si;
797         Headway foundHeadway = new HeadwayDistance(-maxDistanceSI);
798         for (Lane lane : getGtu().positions(getGtu().getRear()).keySet())
799         {
800             Headway closest = headwayRecursiveBackwardSI(lane, getGtu().getDirection(lane),
801                     getGtu().position(lane, getGtu().getRear(), time).getSI(), 0.0, -maxDistanceSI, time);
802             if (closest.getDistance().si < -maxDistanceSI && closest.getDistance().si < -foundHeadway.getDistance().si)
803             {
804                 foundHeadway = closest;
805             }
806         }
807         if (foundHeadway instanceof HeadwayGTU)
808         {
809             return new HeadwayGTUSimple(foundHeadway.getId(), ((HeadwayGTU) foundHeadway).getGtuType(),
810                     foundHeadway.getDistance().neg(), foundHeadway.getLength(), foundHeadway.getSpeed(), null);
811         }
812         return null;
813     }
814 
815     /**
816      * Calculate the minimum headway, possibly on subsequent lanes, in backward direction (so between our back, and the other
817      * GTU's front). Note: this method returns a POSITIVE number.
818      * @param lane the lane where we are looking right now
819      * @param direction the direction we are driving on that lane
820      * @param lanePositionSI from which position on this lane do we start measuring? This is the current position of the rear of
821      *            the GTU when we measure in the lane where the original GTU is positioned, and lane.getLength() for each
822      *            subsequent lane.
823      * @param cumDistanceSI the distance we have already covered searching on previous lanes. Note: This is a POSITIVE number.
824      * @param maxDistanceSI the maximum distance to look for in SI units; stays the same in subsequent calls. Note: this is a
825      *            POSITIVE number.
826      * @param when the current or future time for which to calculate the headway
827      * @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
828      *         no other GTU could not be found within maxDistanceSI meters
829      * @throws GTUException when there is a problem with the geometry of the network
830      */
831     private Headway headwayRecursiveBackwardSI(final Lane lane, final GTUDirectionality direction, final double lanePositionSI,
832             final double cumDistanceSI, final double maxDistanceSI, final Time when) throws GTUException
833     {
834         LaneBasedGTU otherGTU =
835                 lane.getGtuBehind(new Length(lanePositionSI, LengthUnit.SI), direction, RelativePosition.FRONT, when);
836         if (otherGTU != null)
837         {
838             double distanceM = cumDistanceSI + lanePositionSI - otherGTU.position(lane, otherGTU.getFront(), when).getSI();
839             if (distanceM > 0 && distanceM <= maxDistanceSI)
840             {
841                 return new HeadwayGTUSimple(otherGTU.getId(), otherGTU.getGTUType(), new Length(distanceM, LengthUnit.SI),
842                         otherGTU.getLength(), otherGTU.getSpeed(), null);
843             }
844             return new HeadwayDistance(Double.MAX_VALUE);
845         }
846 
847         // Continue search on predecessor lanes.
848         if (cumDistanceSI + lanePositionSI < maxDistanceSI)
849         {
850             // is there a predecessor link?
851             if (lane.prevLanes(getGtu().getGTUType()).size() > 0)
852             {
853                 Headway foundMaxGTUDistanceSI = new HeadwayDistance(Double.MAX_VALUE);
854                 for (Lane prevLane : lane.prevLanes(getGtu().getGTUType()).keySet())
855                 {
856                     // What is behind us is INDEPENDENT of the followed route!
857                     double traveledDistanceSI = cumDistanceSI + lanePositionSI;
858                     // WRONG - adapt method to forward perception method!
859                     Headway closest = headwayRecursiveBackwardSI(prevLane, direction, prevLane.getLength().getSI(),
860                             traveledDistanceSI, maxDistanceSI, when);
861                     if (closest.getDistance().si < maxDistanceSI
862                             && closest.getDistance().si < foundMaxGTUDistanceSI.getDistance().si)
863                     {
864                         foundMaxGTUDistanceSI = closest;
865                     }
866                 }
867                 return foundMaxGTUDistanceSI;
868             }
869         }
870 
871         // No other GTU was not on one of the current lanes or their successors.
872         return new HeadwayDistance(Double.MAX_VALUE);
873     }
874 
875 }