View Javadoc
1   package org.opentrafficsim.road.network;
2   
3   import java.util.ArrayList;
4   import java.util.Collections;
5   import java.util.LinkedHashMap;
6   import java.util.LinkedHashSet;
7   import java.util.List;
8   import java.util.Map;
9   import java.util.NavigableMap;
10  import java.util.Optional;
11  import java.util.Set;
12  import java.util.SortedMap;
13  import java.util.TreeMap;
14  
15  import org.djunits.unit.LengthUnit;
16  import org.djunits.value.vdouble.scalar.Duration;
17  import org.djunits.value.vdouble.scalar.Length;
18  import org.djutils.event.EventType;
19  import org.djutils.exceptions.Throw;
20  import org.djutils.immutablecollections.Immutable;
21  import org.djutils.immutablecollections.ImmutableArrayList;
22  import org.djutils.immutablecollections.ImmutableList;
23  import org.djutils.metadata.MetaData;
24  import org.djutils.metadata.ObjectDescriptor;
25  import org.djutils.multikeymap.MultiKeyMap;
26  import org.opentrafficsim.base.HierarchicallyTyped;
27  import org.opentrafficsim.core.gtu.GtuException;
28  import org.opentrafficsim.core.gtu.GtuType;
29  import org.opentrafficsim.core.gtu.RelativePosition;
30  import org.opentrafficsim.core.network.LateralDirectionality;
31  import org.opentrafficsim.core.network.Link;
32  import org.opentrafficsim.core.network.NetworkException;
33  import org.opentrafficsim.core.object.Detector;
34  import org.opentrafficsim.core.perception.HistoryManager;
35  import org.opentrafficsim.core.perception.collections.HistoricalArrayList;
36  import org.opentrafficsim.core.perception.collections.HistoricalList;
37  import org.opentrafficsim.road.gtu.LaneBasedGtu;
38  import org.opentrafficsim.road.network.object.LaneBasedObject;
39  import org.opentrafficsim.road.network.object.detector.LaneDetector;
40  import org.opentrafficsim.road.network.speed.LaneSpeedLimits;
41  import org.opentrafficsim.road.network.speed.SpeedLimit;
42  import org.opentrafficsim.road.network.speed.SpeedLimits;
43  
44  import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
45  
46  /**
47   * The Lane is the CrossSectionElement of a CrossSectionLink on which GTUs can drive. The Lane stores several important
48   * properties, such as the successor lane(s), predecessor lane(s), and adjacent lane(s), all separated per GTU type. It can, for
49   * instance, be that a truck is not allowed to move into an adjacent lane, while a car is allowed to do so. Furthermore, the
50   * lane contains detectors that can be triggered by passing GTUs. The Lane class also contains methods to determine to trigger
51   * the detectors at exactly calculated and scheduled times, given the movement of the GTUs. <br>
52   * Finally, the Lane stores the GTUs on the lane, and contains several access methods to determine successor and predecessor
53   * GTUs, as well as methods to add a GTU to a lane (either at the start or in the middle when changing lanes), and remove a GTU
54   * from the lane (either at the end, or in the middle when changing onto another lane). The GTU is only booked with its
55   * reference point on the lane, and is -- unless during a lane change -- only booked on one lane at a time.
56   * <p>
57   * Copyright (c) 2013-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
58   * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
59   * </p>
60   * @author Alexander Verbraeck
61   * @author Peter Knoppers
62   */
63  public class Lane extends CrossSectionElement implements HierarchicallyTyped<LaneType, Lane>
64  {
65      /** Type of lane to deduce compatibility with GTU types. */
66      private final LaneType laneType;
67  
68      /** Speed limit information. */
69      private final LaneSpeedLimits speedLimits;
70  
71      /**
72       * Detectors on the lane to trigger behavior of the GTU, sorted by longitudinal position. The triggering of detectors is
73       * done per GTU type, so different GTUs can trigger different detectors.
74       */
75      private final SortedMap<Double, List<LaneDetector>> detectors = new TreeMap<>();
76  
77      /**
78       * Objects on the lane can be observed by the GTU. Examples are signs, speed signs, blocks, and traffic lights. They are
79       * sorted by longitudinal position.
80       */
81      private final SortedMap<Double, List<LaneBasedObject>> laneBasedObjects = new TreeMap<>();
82  
83      /** GTUs ordered by increasing longitudinal position; increasing in the direction of the center line. */
84      private final HistoricalList<LaneBasedGtu> gtuList;
85  
86      /** Last returned past GTU list. */
87      private List<LaneBasedGtu> gtuListAtTime = null;
88  
89      /** Time of last returned GTU list. */
90      private Duration gtuListTime = null;
91  
92      /**
93       * Adjacent left lanes that some GTU types can change onto. Left is defined relative to the direction of the design line of
94       * the link (and the direction of the center line of the lane). In terms of offsets, 'left' lanes always have a more
95       * positive offset than the current lane. Initially empty so we can calculate and cache the first time the method is called.
96       */
97      private final MultiKeyMap<Set<Lane>> leftNeighbours = new MultiKeyMap<>(GtuType.class, Boolean.class);
98  
99      /**
100      * Adjacent right lanes that some GTU types can change onto. Right is defined relative to the direction of the design line
101      * of the link (and the direction of the center line of the lane). In terms of offsets, 'right' lanes always have a more
102      * negative offset than the current lane. Initially empty so we can calculate and cache the first time the method is called.
103      */
104     private final MultiKeyMap<Set<Lane>> rightNeighbours = new MultiKeyMap<>(GtuType.class, Boolean.class);
105 
106     /**
107      * Next lane(s) following this lane that some GTU types can drive onto. Next is defined in the direction of the design line.
108      * Initially empty so we can calculate and cache the first time the method is called.
109      */
110     private final Map<GtuType, Set<Lane>> nextLanes = new LinkedHashMap<>(1);
111 
112     /**
113      * Previous lane(s) preceding this lane that some GTU types can drive from. Previous is defined relative to the direction of
114      * the design line. Initially empty so we can calculate and cache the first time the method is called.
115      */
116     private final Map<GtuType, Set<Lane>> prevLanes = new LinkedHashMap<>(1);
117 
118     /**
119      * The <b>timed</b> event type for pub/sub indicating the addition of a GTU to the lane. <br>
120      * Payload: Object[] {String gtuId, int count_after_addition, String laneId, String linkId}
121      */
122     public static final EventType GTU_ADD_EVENT = new EventType("LANE.GTU.ADD",
123             new MetaData("Lane GTU add", "GTU id, number of GTUs after addition, lane id, link id",
124                     new ObjectDescriptor("GTU id", "Id of GTU", String.class),
125                     new ObjectDescriptor("GTU count", "New number of GTUs on lane", Integer.class),
126                     new ObjectDescriptor("Lane id", "Id of the lane", String.class),
127                     new ObjectDescriptor("Link id", "Id of the link", String.class)));
128 
129     /**
130      * The <b>timed</b> event type for pub/sub indicating the removal of a GTU from the lane. <br>
131      * Payload: Object[] {String gtuId, LaneBasedGtu gtu, int count_after_removal, Length position, String laneId, String
132      * linkId}
133      */
134     public static final EventType GTU_REMOVE_EVENT = new EventType("LANE.GTU.REMOVE",
135             new MetaData("Lane GTU remove", "GTU id, gtu, number of GTUs after removal, position, lane id, link id",
136                     new ObjectDescriptor("GTU id", "Id of GTU", String.class),
137                     new ObjectDescriptor("GTU", "The GTU itself", LaneBasedGtu.class),
138                     new ObjectDescriptor("GTU count", "New number of GTUs on lane", Integer.class),
139                     new ObjectDescriptor("Position", "Last position of GTU on the lane", Length.class),
140                     new ObjectDescriptor("Lane id", "Id of the lane", String.class),
141                     new ObjectDescriptor("Link id", "Id of the link", String.class)));
142 
143     /**
144      * The <b>timed</b> event type for pub/sub indicating the addition of a Detector to the lane. <br>
145      * Payload: Object[] {String detectorId, Detector detector}
146      */
147     public static final EventType DETECTOR_ADD_EVENT = new EventType("LANE.DETECTOR.ADD",
148             new MetaData("Lane detector add", "Detector id, detector",
149                     new ObjectDescriptor("detector id", "id of detector", String.class),
150                     new ObjectDescriptor("detector", "detector itself", Detector.class)));
151 
152     /**
153      * The <b>timed</b> event type for pub/sub indicating the removal of a Detector from the lane. <br>
154      * Payload: Object[] {String detectorId, Detector detector}
155      */
156     public static final EventType DETECTOR_REMOVE_EVENT = new EventType("LANE.DETECTOR.REMOVE",
157             new MetaData("Lane detector remove", "Detector id, detector",
158                     new ObjectDescriptor("detector id", "id of detector", String.class),
159                     new ObjectDescriptor("detector", "detector itself", Detector.class)));
160 
161     /**
162      * The event type for pub/sub indicating the addition of a LaneBasedObject to the lane. <br>
163      * Payload: Object[] {LaneBasedObject laneBasedObject}
164      */
165     public static final EventType OBJECT_ADD_EVENT = new EventType("LANE.OBJECT.ADD", new MetaData("Lane object add", "Object",
166             new ObjectDescriptor("GTU", "The lane-based GTU", LaneBasedObject.class)));
167 
168     /**
169      * The event type for pub/sub indicating the removal of a LaneBasedObject from the lane. <br>
170      * Payload: Object[] {LaneBasedObject laneBasedObject}
171      */
172     public static final EventType OBJECT_REMOVE_EVENT = new EventType("LANE.OBJECT.REMOVE", new MetaData("Lane object remove",
173             "Object", new ObjectDescriptor("GTU", "The lane-based GTU", LaneBasedObject.class)));
174 
175     /**
176      * Constructor specifying geometry.
177      * @param link link
178      * @param id the id of this lane within the link; should be unique within the link
179      * @param geometry geometry
180      * @param laneType lane type
181      * @param laneSpeedLimits speed limits
182      */
183     public Lane(final CrossSectionLink link, final String id, final CrossSectionGeometry geometry, final LaneType laneType,
184             final LaneSpeedLimits laneSpeedLimits)
185     {
186         super(link, id, geometry);
187         this.speedLimits = laneSpeedLimits;
188         this.laneType = laneType;
189         this.gtuList = new HistoricalArrayList<>(getManager(link), this);
190     }
191 
192     /**
193      * Obtains the history manager from the parent link.
194      * @param parentLink parent link
195      * @return history manager
196      */
197     private HistoryManager getManager(final CrossSectionLink parentLink)
198     {
199         return parentLink.getSimulator().getReplication().getHistoryManager(parentLink.getSimulator());
200     }
201 
202     // TODO constructor calls with this(...)
203 
204     /**
205      * Retrieve one of the sets of neighboring Lanes that is accessible for the given type of GTU. A defensive copy of the
206      * internal data structure is returned.
207      * @param direction either LEFT or RIGHT, relative to the DESIGN LINE of the link (and the direction of the center line of
208      *            the lane). In terms of offsets, 'left' lanes always have a more positive offset than the current lane, and
209      *            'right' lanes a more negative offset.
210      * @param gtuType the GTU type to check the accessibility for
211      * @param legal whether to check legal possibility
212      * @return the indicated set of neighboring Lanes
213      */
214     private Set<Lane> neighbors(final LateralDirectionality direction, final GtuType gtuType, final boolean legal)
215     {
216         MultiKeyMap<Set<Lane>> cache = direction.isLeft() ? this.leftNeighbours : this.rightNeighbours;
217         return cache.get(() ->
218         {
219             Set<Lane> lanes = new LinkedHashSet<>(1);
220             for (CrossSectionElement cse : this.link.getCrossSectionElementList())
221             {
222                 if (cse instanceof Lane && !cse.equals(this))
223                 {
224                     Lane lane = (Lane) cse;
225                     if (laterallyAdjacentAndAccessible(lane, direction, gtuType, legal))
226                     {
227                         lanes.add(lane);
228                     }
229                 }
230             }
231             return lanes;
232         }, gtuType, legal);
233     }
234 
235     /** Lateral alignment margin for longitudinally connected Lanes. */
236     static final Length ADJACENT_MARGIN = new Length(0.2, LengthUnit.METER);
237 
238     /**
239      * Determine whether another lane is adjacent to this lane (dependent on distance) and accessible (dependent on stripes) for
240      * a certain GTU type (dependent on usability of the adjacent lane for that GTU type). This method assumes that when there
241      * is NO stripe between two adjacent lanes that are accessible for the GTU type, the GTU can enter that lane. <br>
242      * @param lane the other lane to evaluate
243      * @param direction the direction to look at, relative to the DESIGN LINE of the link. This is a very important aspect to
244      *            note: all information is stored relative to the direction of the design line, and not in a driving direction,
245      *            which can vary for lanes that can be driven in two directions (e.g. at overtaking).
246      * @param gtuType the GTU type to check the accessibility for
247      * @param legal whether to check legal possibility
248      * @return true if the other lane is adjacent to this lane and accessible for the given GTU type; false otherwise
249      */
250     private boolean laterallyAdjacentAndAccessible(final Lane lane, final LateralDirectionality direction,
251             final GtuType gtuType, final boolean legal)
252     {
253         if (legal && !lane.getType().isCompatible(gtuType))
254         {
255             // not accessible for the given GTU type
256             return false;
257         }
258         if (direction.equals(LateralDirectionality.LEFT))
259         {
260             // TODO take the cross section slices into account...
261             if (lane.getOffsetAtBegin().si + ADJACENT_MARGIN.si > getOffsetAtBegin().si
262                     && lane.getOffsetAtEnd().si + ADJACENT_MARGIN.si > getOffsetAtEnd().si
263                     && (lane.getOffsetAtBegin().si - lane.getBeginWidth().si / 2.0)
264                             - (getOffsetAtBegin().si + getBeginWidth().si / 2.0) < ADJACENT_MARGIN.si
265                     && (lane.getOffsetAtEnd().si - lane.getEndWidth().si / 2.0)
266                             - (getOffsetAtEnd().si + getEndWidth().si / 2.0) < ADJACENT_MARGIN.si)
267             {
268                 // look at stripes between the two lanes
269                 if (!(this instanceof Shoulder) && legal) // may always leave shoulder
270                 {
271                     for (CrossSectionElement cse : this.link.getCrossSectionElementList())
272                     {
273                         if (cse instanceof Stripe)
274                         {
275                             Stripe stripe = (Stripe) cse;
276                             // TODO take the cross section slices into account...
277                             if ((getOffsetAtBegin().si < stripe.getOffsetAtBegin().si
278                                     && stripe.getOffsetAtBegin().si < lane.getOffsetAtBegin().si)
279                                     || (getOffsetAtEnd().si < stripe.getOffsetAtEnd().si
280                                             && stripe.getOffsetAtEnd().si < lane.getOffsetAtEnd().si))
281                             {
282                                 if (!stripe.isPermeable(gtuType, LateralDirectionality.LEFT))
283                                 {
284                                     // there is a stripe forbidding to cross to the adjacent lane
285                                     return false;
286                                 }
287                             }
288                         }
289                     }
290                 }
291                 // the lanes are adjacent, and there is no stripe forbidding us to enter that lane
292                 // or there is no stripe at all
293                 return true;
294             }
295         }
296 
297         else
298         // direction.equals(LateralDirectionality.RIGHT)
299         {
300             // TODO take the cross section slices into account...
301             if (lane.getOffsetAtBegin().si < getOffsetAtBegin().si + ADJACENT_MARGIN.si
302                     && lane.getOffsetAtEnd().si < getOffsetAtEnd().si + ADJACENT_MARGIN.si
303                     && (getOffsetAtBegin().si - getBeginWidth().si / 2.0)
304                             - (lane.getOffsetAtBegin().si + lane.getBeginWidth().si / 2.0) < ADJACENT_MARGIN.si
305                     && (getOffsetAtEnd().si - getEndWidth().si / 2.0)
306                             - (lane.getOffsetAtEnd().si + lane.getEndWidth().si / 2.0) < ADJACENT_MARGIN.si)
307             {
308                 // look at stripes between the two lanes
309                 if (!(this instanceof Shoulder) && legal) // may always leave shoulder
310                 {
311                     for (CrossSectionElement cse : this.link.getCrossSectionElementList())
312                     {
313                         if (cse instanceof Stripe)
314                         {
315                             Stripe stripe = (Stripe) cse;
316                             // TODO take the cross section slices into account...
317                             if ((getOffsetAtBegin().si > stripe.getOffsetAtBegin().si
318                                     && stripe.getOffsetAtBegin().si > lane.getOffsetAtBegin().si)
319                                     || (getOffsetAtEnd().si > stripe.getOffsetAtEnd().si
320                                             && stripe.getOffsetAtEnd().si > lane.getOffsetAtEnd().si))
321                             {
322                                 if (!stripe.isPermeable(gtuType, LateralDirectionality.RIGHT))
323                                 {
324                                     // there is a stripe forbidding to cross to the adjacent lane
325                                     return false;
326                                 }
327                             }
328                         }
329                     }
330                 }
331                 // the lanes are adjacent, and there is no stripe forbidding us to enter that lane
332                 // or there is no stripe at all
333                 return true;
334             }
335         }
336 
337         // no lanes were found that are close enough laterally.
338         return false;
339     }
340 
341     /**
342      * Insert a detector at the right place in the detector list of this Lane.
343      * @param detector the detector to add
344      * @throws NetworkException when the position of the detector is beyond (or before) the range of this Lane
345      */
346     public void addDetector(final LaneDetector detector) throws NetworkException
347     {
348         double position = detector.getLongitudinalPosition().si;
349         if (position < 0 || position > getLength().getSI())
350         {
351             throw new NetworkException(
352                     "Illegal position for detector " + position + " valid range is 0.." + getLength().getSI());
353         }
354         if (this.link.getNetwork().containsObject(detector.getFullId()))
355         {
356             throw new NetworkException("Network already contains an object with the name " + detector.getFullId());
357         }
358         List<LaneDetector> detectorList = this.detectors.get(position);
359         if (null == detectorList)
360         {
361             detectorList = new ArrayList<>(1);
362             this.detectors.put(position, detectorList);
363         }
364         detectorList.add(detector);
365         this.link.getNetwork().addObject(detector);
366         fireTimedEvent(Lane.DETECTOR_ADD_EVENT, new Object[] {detector.getId(), detector},
367                 detector.getSimulator().getSimulatorTime());
368     }
369 
370     /**
371      * Remove a detector from the detector list of this Lane.
372      * @param detector the detector to remove.
373      * @throws NetworkException when the detector was not found on this Lane
374      */
375     public void removeDetector(final LaneDetector detector) throws NetworkException
376     {
377         fireTimedEvent(Lane.DETECTOR_REMOVE_EVENT, new Object[] {detector.getId(), detector},
378                 detector.getSimulator().getSimulatorTime());
379         List<LaneDetector> detectorList = this.detectors.get(detector.getLongitudinalPosition().si);
380         if (null == detectorList)
381         {
382             throw new NetworkException("No detector at " + detector.getLongitudinalPosition().si);
383         }
384         detectorList.remove(detector);
385         if (detectorList.size() == 0)
386         {
387             this.detectors.remove(detector.getLongitudinalPosition().si);
388         }
389         this.link.getNetwork().removeObject(detector);
390     }
391 
392     /**
393      * Retrieve the list of Detectors of this Lane in the specified distance range for the given GtuType. The resulting list is
394      * a defensive copy.
395      * @param minimumPosition the minimum distance on the Lane (inclusive)
396      * @param maximumPosition the maximum distance on the Lane (inclusive)
397      * @param gtuType the GTU type to provide the detectors for
398      * @return list of the detectors in the specified range. This is a defensive copy.
399      */
400     public List<LaneDetector> getDetectors(final Length minimumPosition, final Length maximumPosition, final GtuType gtuType)
401     {
402         List<LaneDetector> detectorList = new ArrayList<>(1);
403         for (List<LaneDetector> dets : this.detectors.values())
404         {
405             for (LaneDetector detector : dets)
406             {
407                 if (detector.isCompatible(gtuType) && detector.getLongitudinalPosition().ge(minimumPosition)
408                         && detector.getLongitudinalPosition().le(maximumPosition))
409                 {
410                     detectorList.add(detector);
411                 }
412             }
413         }
414         return detectorList;
415     }
416 
417     /**
418      * Retrieve the list of Detectors of this Lane that are triggered by the given GtuType. The resulting list is a defensive
419      * copy.
420      * @param gtuType the GTU type to provide the detectors for
421      * @return list of the detectors, in ascending order for the location on the Lane
422      */
423     public List<LaneDetector> getDetectors(final GtuType gtuType)
424     {
425         List<LaneDetector> detectorList = new ArrayList<>(1);
426         for (List<LaneDetector> dets : this.detectors.values())
427         {
428             for (LaneDetector detector : dets)
429             {
430                 if (detector.isCompatible(gtuType))
431                 {
432                     detectorList.add(detector);
433                 }
434             }
435         }
436         return detectorList;
437     }
438 
439     /**
440      * Retrieve the list of all Detectors of this Lane. The resulting list is a defensive copy.
441      * @return list of the detectors, in ascending order for the location on the Lane
442      */
443     public List<LaneDetector> getDetectors()
444     {
445         if (this.detectors == null)
446         {
447             return new ArrayList<>();
448         }
449         List<LaneDetector> detectorList = new ArrayList<>(1);
450         for (List<LaneDetector> dets : this.detectors.values())
451         {
452             for (LaneDetector detector : dets)
453             {
454                 detectorList.add(detector);
455             }
456         }
457         return detectorList;
458     }
459 
460     /**
461      * Retrieve the list of Detectors of this Lane for the given GtuType. The resulting Map is a defensive copy.
462      * @param gtuType the GTU type to provide the detectors for
463      * @return all detectors on this lane for the given GtuType as a map per distance
464      */
465     public SortedMap<Double, List<LaneDetector>> getDetectorMap(final GtuType gtuType)
466     {
467         SortedMap<Double, List<LaneDetector>> detectorMap = new TreeMap<>();
468         for (double d : this.detectors.keySet())
469         {
470             List<LaneDetector> detectorList = new ArrayList<>(1);
471             for (List<LaneDetector> dets : this.detectors.values())
472             {
473                 for (LaneDetector detector : dets)
474                 {
475                     if (detector.getLongitudinalPosition().si == d && detector.isCompatible(gtuType))
476                     {
477                         detectorList.add(detector);
478                     }
479                 }
480             }
481             if (detectorList.size() > 0)
482             {
483                 detectorMap.put(d, detectorList);
484             }
485         }
486         return detectorMap;
487     }
488 
489     /**
490      * Insert a laneBasedObject at the right place in the laneBasedObject list of this Lane. Register it in the network WITH the
491      * Lane id.
492      * @param laneBasedObject the laneBasedObject to add
493      * @throws NetworkException when the position of the laneBasedObject is beyond (or before) the range of this Lane
494      */
495     public synchronized void addLaneBasedObject(final LaneBasedObject laneBasedObject) throws NetworkException
496     {
497         double position = laneBasedObject.getLongitudinalPosition().si;
498         if (position < 0 || position > getLength().getSI())
499         {
500             throw new NetworkException(
501                     "Illegal position for laneBasedObject " + position + " valid range is 0.." + getLength().getSI());
502         }
503         if (this.link.getNetwork().containsObject(laneBasedObject.getFullId()))
504         {
505             throw new NetworkException("Network already contains an object with the name " + laneBasedObject.getFullId());
506         }
507         List<LaneBasedObject> laneBasedObjectList = this.laneBasedObjects.get(position);
508         if (null == laneBasedObjectList)
509         {
510             laneBasedObjectList = new ArrayList<>(1);
511             this.laneBasedObjects.put(position, laneBasedObjectList);
512         }
513         laneBasedObjectList.add(laneBasedObject);
514         this.link.getNetwork().addObject(laneBasedObject);
515         fireTimedEvent(Lane.OBJECT_ADD_EVENT, new Object[] {laneBasedObject}, getLink().getSimulator().getSimulatorTime());
516     }
517 
518     /**
519      * Remove a laneBasedObject from the laneBasedObject list of this Lane.
520      * @param laneBasedObject the laneBasedObject to remove.
521      * @throws NetworkException when the laneBasedObject was not found on this Lane
522      */
523     public synchronized void removeLaneBasedObject(final LaneBasedObject laneBasedObject) throws NetworkException
524     {
525         fireTimedEvent(Lane.OBJECT_REMOVE_EVENT, new Object[] {laneBasedObject}, getLink().getSimulator().getSimulatorTime());
526         List<LaneBasedObject> laneBasedObjectList =
527                 this.laneBasedObjects.get(laneBasedObject.getLongitudinalPosition().getSI());
528         if (null == laneBasedObjectList)
529         {
530             throw new NetworkException("No laneBasedObject at " + laneBasedObject.getLongitudinalPosition().si);
531         }
532         laneBasedObjectList.remove(laneBasedObject);
533         if (laneBasedObjectList.isEmpty())
534         {
535             this.laneBasedObjects.remove(laneBasedObject.getLongitudinalPosition().doubleValue());
536         }
537         this.link.getNetwork().removeObject(laneBasedObject);
538     }
539 
540     /**
541      * Retrieve the list of LaneBasedObjects of this Lane in the specified distance range. The resulting list is a defensive
542      * copy.
543      * @param minimumPosition the minimum distance on the Lane (inclusive)
544      * @param maximumPosition the maximum distance on the Lane (inclusive)
545      * @return list of the laneBasedObject in the specified range. This is a defensive copy.
546      */
547     public List<LaneBasedObject> getLaneBasedObjects(final Length minimumPosition, final Length maximumPosition)
548     {
549         List<LaneBasedObject> laneBasedObjectList = new ArrayList<>(1);
550         for (List<LaneBasedObject> lbol : this.laneBasedObjects.values())
551         {
552             for (LaneBasedObject lbo : lbol)
553             {
554                 if (lbo.getLongitudinalPosition().ge(minimumPosition) && lbo.getLongitudinalPosition().le(maximumPosition))
555                 {
556                     laneBasedObjectList.add(lbo);
557                 }
558             }
559         }
560         return laneBasedObjectList;
561     }
562 
563     /**
564      * Retrieve the list of all LaneBasedObjects of this Lane. The resulting list is a defensive copy.
565      * @return list of the laneBasedObjects, in ascending order for the location on the Lane
566      */
567     public List<LaneBasedObject> getLaneBasedObjects()
568     {
569         if (this.laneBasedObjects == null)
570         {
571             return new ArrayList<>();
572         }
573         List<LaneBasedObject> laneBasedObjectList = new ArrayList<>(1);
574         for (List<LaneBasedObject> lbol : this.laneBasedObjects.values())
575         {
576             for (LaneBasedObject lbo : lbol)
577             {
578                 laneBasedObjectList.add(lbo);
579             }
580         }
581         return laneBasedObjectList;
582     }
583 
584     /**
585      * Retrieve the list of LaneBasedObjects of this Lane. The resulting Map is a defensive copy.
586      * @return all laneBasedObjects on this lane
587      */
588     public SortedMap<Double, List<LaneBasedObject>> getLaneBasedObjectMap()
589     {
590         SortedMap<Double, List<LaneBasedObject>> laneBasedObjectMap = new TreeMap<>();
591         for (double d : this.laneBasedObjects.keySet())
592         {
593             List<LaneBasedObject> laneBasedObjectList = new ArrayList<>(1);
594             for (LaneBasedObject lbo : this.laneBasedObjects.get(d))
595             {
596                 laneBasedObjectList.add(lbo);
597             }
598             laneBasedObjectMap.put(d, laneBasedObjectList);
599         }
600         return laneBasedObjectMap;
601     }
602 
603     /**
604      * Transform a fraction on the lane to a relative length (can be less than zero or larger than the lane length).
605      * @param fraction fraction relative to the lane length.
606      * @return the longitudinal length corresponding to the fraction.
607      */
608     public Length position(final double fraction)
609     {
610         if (getLength().getDisplayUnit().isBaseSIUnit())
611         {
612             return new Length(getLength().si * fraction, LengthUnit.SI);
613         }
614         return new Length(getLength().getInUnit() * fraction, getLength().getDisplayUnit());
615     }
616 
617     /**
618      * Transform a fraction on the lane to a relative length in SI units (can be less than zero or larger than the lane length).
619      * @param fraction fraction relative to the lane length.
620      * @return length corresponding to the fraction, in SI units.
621      */
622     public double positionSI(final double fraction)
623     {
624         return getLength().si * fraction;
625     }
626 
627     /**
628      * Transform a position on the lane (can be less than zero or larger than the lane length) to a fraction.
629      * @param position relative length on the lane (may be less than zero or larger than the lane length).
630      * @return fraction relative to the lane length.
631      */
632     public double fraction(final Length position)
633     {
634         return position.si / getLength().si;
635     }
636 
637     /**
638      * Transform a position on the lane in SI units (can be less than zero or larger than the lane length) to a fraction.
639      * @param positionSI relative length on the lane in SI units (may be less than zero or larger than the lane length).
640      * @return fraction relative to the lane length.
641      */
642     public double fractionSI(final double positionSI)
643     {
644         return positionSI / getLength().si;
645     }
646 
647     /**
648      * Add a LaneBasedGtu to the list of this Lane.
649      * @param gtu the GTU to add
650      * @param fractionalPosition the fractional position that the newly added GTU will have on this Lane
651      * @return the rank that the newly added GTU has on this Lane (should be 0, except when the GTU enters this Lane due to a
652      *         lane change operation)
653      * @throws GtuException when the GTU is already registered on this Lane
654      */
655     // @docs/02-model-structure/djutils.md#event-producers-and-listeners
656     public int addGtu(final LaneBasedGtu gtu, final double fractionalPosition) throws GtuException
657     {
658         int index;
659         // figure out the rank for the new GTU
660         for (index = 0; index < this.gtuList.size(); index++)
661         {
662             LaneBasedGtu otherGTU = this.gtuList.get(index);
663             if (gtu == otherGTU)
664             {
665                 throw new GtuException(gtu + " already registered on Lane " + this + ", location: "
666                         + gtu.getLongitudinalPosition() + " time: " + gtu.getSimulator().getSimulatorTime());
667             }
668             if (otherGTU.getPosition().getFraction() >= fractionalPosition)
669             {
670                 break;
671             }
672         }
673         this.gtuList.add(index, gtu);
674         getLink().getSimulator().scheduleEventNow((short) (SimEventInterface.MIN_PRIORITY + 1), () ->
675         {
676             // @docs/02-model-structure/djutils.md#event-producers-and-listeners
677             fireTimedEvent(Lane.GTU_ADD_EVENT, new Object[] {gtu.getId(), this.gtuList.size(), getId(), getLink().getId()},
678                     gtu.getSimulator().getSimulatorTime());
679             // @end
680         });
681         getLink().addGTU(gtu);
682         return index;
683     }
684 
685     /**
686      * Add a LaneBasedGtu to the list of this Lane.
687      * @param gtu the GTU to add
688      * @param longitudinalPosition the longitudinal position that the newly added GTU will have on this Lane
689      * @return the rank that the newly added GTU has on this Lane (should be 0, except when the GTU enters this Lane due to a
690      *         lane change operation)
691      * @throws GtuException when longitudinalPosition is negative or exceeds the length of this Lane
692      */
693     public int addGtu(final LaneBasedGtu gtu, final Length longitudinalPosition) throws GtuException
694     {
695         return addGtu(gtu, longitudinalPosition.getSI() / getLength().getSI());
696     }
697 
698     /**
699      * Remove a GTU from the GTU list of this lane.
700      * @param gtu the GTU to remove.
701      * @param removeFromParentLink when the GTU leaves the last lane of the parentLink of this Lane
702      * @param position last position of the GTU
703      */
704     // @docs/02-model-structure/djutils.md#event-producers-and-listeners
705     public void removeGtu(final LaneBasedGtu gtu, final boolean removeFromParentLink, final Length position)
706     {
707         boolean contained = this.gtuList.remove(gtu);
708         if (contained)
709         {
710             getLink().getSimulator().scheduleEventNow(SimEventInterface.MIN_PRIORITY, () ->
711             {
712                 // @docs/02-model-structure/djutils.md#event-producers-and-listeners
713                 fireTimedEvent(Lane.GTU_REMOVE_EVENT,
714                         new Object[] {gtu.getId(), gtu, this.gtuList.size(), position, getId(), getLink().getId()},
715                         gtu.getSimulator().getSimulatorTime());
716                 // @end
717             });
718         }
719         if (removeFromParentLink)
720         {
721             this.link.removeGTU(gtu);
722         }
723     }
724 
725     /**
726      * Get the last GTU on the lane, relative to a driving direction on this lane.
727      * @return the last GTU on this lane in the given direction, empty if no GTU could be found.
728      * @throws GtuException when there is a problem with the position of the GTUs on the lane.
729      */
730     public Optional<LaneBasedGtu> getLastGtu() throws GtuException
731     {
732         if (this.gtuList.size() == 0)
733         {
734             return Optional.empty();
735         }
736         return Optional.of(this.gtuList.get(this.gtuList.size() - 1));
737     }
738 
739     /**
740      * Get the first GTU on the lane, relative to a driving direction on this lane.
741      * @return the first GTU on this lane in the given direction, empty if no GTU could be found.
742      * @throws GtuException when there is a problem with the position of the GTUs on the lane.
743      */
744     public Optional<LaneBasedGtu> getFirstGtu() throws GtuException
745     {
746         if (this.gtuList.size() == 0)
747         {
748             return Optional.empty();
749         }
750         return Optional.of(this.gtuList.get(0));
751     }
752 
753     /**
754      * Get the first GTU where the relativePosition is in front of another GTU on the lane, in a driving direction on this lane,
755      * compared to the DESIGN LINE.
756      * @param position the position before which the relative position of a GTU will be searched.
757      * @param relativePosition RelativePosition.TYPE; the relative position we want to compare against
758      * @param when the simulation time for which to evaluate the positions.
759      * @return the first GTU before a position on this lane in the given direction, empty if no GTU could be found.
760      */
761     public Optional<LaneBasedGtu> getGtuAhead(final Length position, final RelativePosition.Type relativePosition,
762             final Duration when)
763     {
764         List<LaneBasedGtu> list = this.gtuList.get(when);
765         if (list.isEmpty())
766         {
767             return Optional.empty();
768         }
769         int[] search = lineSearch((final int index) ->
770         {
771             LaneBasedGtu gtu = list.get(index);
772             return gtu.getPosition(gtu.getRelativePositions().get(relativePosition), when).position().si;
773         }, list.size(), position.si);
774         if (search[1] < list.size())
775         {
776             return Optional.of(list.get(search[1]));
777         }
778         return Optional.empty();
779     }
780 
781     /**
782      * Get the first GTU where the relativePosition is behind a certain position on the lane, in a driving direction on this
783      * lane, compared to the DESIGN LINE.
784      * @param position the position before which the relative position of a GTU will be searched.
785      * @param relativePosition RelativePosition.TYPE; the relative position of the GTU we are looking for.
786      * @param when the time for which to evaluate the positions.
787      * @return the first GTU after a position on this lane in the given direction, empty if no GTU could be found.
788      */
789     public Optional<LaneBasedGtu> getGtuBehind(final Length position, final RelativePosition.Type relativePosition,
790             final Duration when)
791     {
792         List<LaneBasedGtu> list = this.gtuList.get(when);
793         if (list.isEmpty())
794         {
795             return Optional.empty();
796         }
797         int[] search = lineSearch((final int index) ->
798         {
799             LaneBasedGtu gtu = list.get(index);
800             return gtu.getPosition(gtu.getRelativePositions().get(relativePosition), when).position().si;
801         }, list.size(), position.si);
802         if (search[0] >= 0)
803         {
804             return Optional.of(list.get(search[0]));
805         }
806         return Optional.empty();
807     }
808 
809     /**
810      * Searches for objects just before and after a given position.
811      * @param positions functional interface returning positions at indices
812      * @param listSize number of objects in the underlying list
813      * @param position position
814      * @return int[2]; Where int[0] is the index of the object with lower position, and int[1] with higher. In case an object is
815      *         exactly at the position int[1] - int[0] = 2. If all objects have a higher position int[0] = -1, if all objects
816      *         have a lower position int[1] = listSize.
817      */
818     private int[] lineSearch(final Positions positions, final int listSize, final double position)
819     {
820         int[] out = new int[2];
821         // line search only works if the position is within the original domain, first catch 4 outside situations
822         double pos0 = positions.get(0);
823         double posEnd;
824         if (position < pos0)
825         {
826             out[0] = -1;
827             out[1] = 0;
828         }
829         else if (position == pos0)
830         {
831             out[0] = -1;
832             out[1] = 1;
833         }
834         else if (position > (posEnd = positions.get(listSize - 1)))
835         {
836             out[0] = listSize - 1;
837             out[1] = listSize;
838         }
839         else if (position == posEnd)
840         {
841             out[0] = listSize - 2;
842             out[1] = listSize;
843         }
844         else
845         {
846             int low = 0;
847             int mid = (int) ((listSize - 1) * position / getLength().si);
848             mid = mid < 0 ? 0 : mid >= listSize ? listSize - 1 : mid;
849             int high = listSize - 1;
850             while (high - low > 1)
851             {
852                 double midPos = positions.get(mid);
853                 if (midPos < position)
854                 {
855                     low = mid;
856                 }
857                 else if (midPos > position)
858                 {
859                     high = mid;
860                 }
861                 else
862                 {
863                     low = mid - 1;
864                     high = mid + 1;
865                     break;
866                 }
867                 mid = (low + high) / 2;
868             }
869             out[0] = low;
870             out[1] = high;
871         }
872         return out;
873     }
874 
875     /**
876      * Get the first object where the relativePosition is in front of a certain position on the lane, in a driving direction on
877      * this lane, compared to the DESIGN LINE. Perception should iterate over results from this method to see what is most
878      * limiting.
879      * @param position the position after which the relative position of an object will be searched.
880      * @return the first object(s) before a position on this lane in the given direction, empty if no object could be found.
881      */
882     public List<LaneBasedObject> getObjectAhead(final Length position)
883     {
884         for (double distance : this.laneBasedObjects.keySet())
885         {
886             if (distance > position.si)
887             {
888                 return new ArrayList<>(this.laneBasedObjects.get(distance));
889             }
890         }
891         return Collections.emptyList();
892     }
893 
894     /**
895      * Get the first object where the relativePosition is behind of a certain position on the lane, in a driving direction on
896      * this lane, compared to the DESIGN LINE. Perception should iterate over results from this method to see what is most
897      * limiting.
898      * @param position the position after which the relative position of an object will be searched.
899      * @return the first object(s) after a position on this lane in the given direction, empty if no object could be found.
900      */
901     public List<LaneBasedObject> getObjectBehind(final Length position)
902     {
903         NavigableMap<Double, List<LaneBasedObject>> reverseLBO =
904                 (NavigableMap<Double, List<LaneBasedObject>>) this.laneBasedObjects;
905         for (double distance : reverseLBO.descendingKeySet())
906         {
907             if (distance < position.si)
908             {
909                 return new ArrayList<>(this.laneBasedObjects.get(distance));
910             }
911         }
912         return Collections.emptyList();
913     }
914 
915     /*
916      * TODO only center position? Or also width? What is a good cutoff? Base on average width of the GTU type that can drive on
917      * this Lane? E.g., for a Tram or Train, a 5 cm deviation is a problem; for a Car or a Bicycle, more deviation is
918      * acceptable.
919      */
920     /** Lateral alignment margin for longitudinally connected Lanes. */
921     public static final Length MARGIN = new Length(0.5, LengthUnit.METER);
922 
923     /**
924      * NextLanes returns the successor lane(s) in the design line direction, if any exist.<br>
925      * The next lane(s) are cached, as it is too expensive to make the calculation every time. There are several possibilities:
926      * (1) Returning an empty set when there is no successor lane in the design direction or there is no longitudinal transfer
927      * possible to a successor lane in the design direction. (2) Returning a set with just one lane if the lateral position of
928      * the successor lane matches the lateral position of this lane (based on an overlap of the lateral positions of the two
929      * joining lanes of more than a certain percentage). (3) Multiple lanes in case the Node where the underlying Link for this
930      * Lane has multiple "outgoing" Links, and there are multiple lanes that match the lateral position of this lane.<br>
931      * The next lanes can differ per GTU type. For instance, a lane where cars and buses are allowed can have a next lane where
932      * only buses are allowed, forcing the cars to leave that lane.
933      * @param gtuType the GTU type for which we return the next lanes, use {@code null} to return all next lanes and their
934      *            design direction
935      * @return set of Lanes following this lane for the given GTU type.
936      */
937     // TODO this should return something immutable
938     public Set<Lane> nextLanes(final GtuType gtuType)
939     {
940         if (!this.nextLanes.containsKey(gtuType))
941         {
942             // TODO determine if this should synchronize on this.nextLanes
943             Set<Lane> laneSet = new LinkedHashSet<>(1);
944             this.nextLanes.put(gtuType, laneSet);
945             if (gtuType == null)
946             {
947                 // Construct (and cache) the result.
948                 for (Link link : getLink().getEndNode().getLinks())
949                 {
950                     if (!(link.equals(this.getLink())) && link instanceof CrossSectionLink)
951                     {
952                         for (CrossSectionElement cse : ((CrossSectionLink) link).getCrossSectionElementList())
953                         {
954                             if (cse instanceof Lane)
955                             {
956                                 Lane lane = (Lane) cse;
957                                 double jumpToStart = this.getCenterLine().getLast().distance(lane.getCenterLine().getFirst());
958                                 double jumpToEnd = this.getCenterLine().getLast().distance(lane.getCenterLine().getLast());
959                                 if (jumpToStart < MARGIN.si && jumpToStart < jumpToEnd
960                                         && link.getStartNode().equals(getLink().getEndNode()))
961                                 {
962                                     // TODO And is it aligned with its next lane?
963                                     laneSet.add(lane);
964                                 }
965                                 // else: not a "connected" lane
966                             }
967                         }
968                     }
969                 }
970             }
971             else
972             {
973                 nextLanes(null).stream().filter((lane) -> lane.getType().isCompatible(gtuType))
974                         .forEach((lane) -> laneSet.add(lane));
975             }
976         }
977         return this.nextLanes.get(gtuType);
978     }
979 
980     /**
981      * Forces the next lanes to be as specified. For specific GTU types, a subset of these lanes is taken.
982      * @param lanes lanes to set as next lanes.
983      */
984     public void forceNextLanes(final Set<Lane> lanes)
985     {
986         Throw.whenNull(lanes, "Lanes should not be null. Use an empty set instead.");
987         this.nextLanes.clear();
988         this.nextLanes.put(null, lanes);
989     }
990 
991     /**
992      * PrevLanes returns the predecessor lane(s) relative to the design line direction, if any exist.<br>
993      * The previous lane(s) are cached, as it is too expensive to make the calculation every time. There are several
994      * possibilities: (1) Returning an empty set when there is no predecessor lane relative to the design direction or there is
995      * no longitudinal transfer possible to a predecessor lane relative to the design direction. (2) Returning a set with just
996      * one lane if the lateral position of the predecessor lane matches the lateral position of this lane (based on an overlap
997      * of the lateral positions of the two joining lanes of more than a certain percentage). (3) Multiple lanes in case the Node
998      * where the underlying Link for this Lane has multiple "incoming" Links, and there are multiple lanes that match the
999      * lateral position of this lane.<br>
1000      * The previous lanes can differ per GTU type. For instance, a lane where cars and buses are allowed can be preceded by a
1001      * lane where only buses are allowed.
1002      * @param gtuType the GTU type for which we return the next lanes, use {@code null} to return all prev lanes and their
1003      *            design direction
1004      * @return set of Lanes following this lane for the given GTU type.
1005      */
1006     // TODO this should return something immutable
1007     public Set<Lane> prevLanes(final GtuType gtuType)
1008     {
1009         if (!this.prevLanes.containsKey(gtuType))
1010         {
1011             Set<Lane> laneSet = new LinkedHashSet<>(1);
1012             this.prevLanes.put(gtuType, laneSet);
1013             // Construct (and cache) the result.
1014             if (gtuType == null)
1015             {
1016                 for (Link link : getLink().getStartNode().getLinks())
1017                 {
1018                     if (!(link.equals(this.getLink())) && link instanceof CrossSectionLink)
1019                     {
1020                         for (CrossSectionElement cse : ((CrossSectionLink) link).getCrossSectionElementList())
1021                         {
1022                             if (cse instanceof Lane)
1023                             {
1024                                 Lane lane = (Lane) cse;
1025                                 double jumpToStart = this.getCenterLine().getFirst().distance(lane.getCenterLine().getFirst());
1026                                 double jumpToEnd = this.getCenterLine().getFirst().distance(lane.getCenterLine().getLast());
1027                                 if (jumpToEnd < MARGIN.si && jumpToEnd < jumpToStart
1028                                         && link.getEndNode().equals(getLink().getStartNode()))
1029                                 {
1030                                     // TODO And is it aligned with its next lane?
1031                                     laneSet.add(lane);
1032                                 }
1033                                 // else: not a "connected" lane
1034                             }
1035                         }
1036                     }
1037                 }
1038             }
1039             else
1040             {
1041                 prevLanes(null).stream().filter((lane) -> lane.getType().isCompatible(gtuType))
1042                         .forEach((lane) -> laneSet.add(lane));
1043             }
1044         }
1045         return this.prevLanes.get(gtuType);
1046     }
1047 
1048     /**
1049      * Forces the previous lanes to be as specified. For specific GTU types, a subset of these lanes is taken.
1050      * @param lanes lanes to set as previous lanes.
1051      */
1052     public void forcePrevLanes(final Set<Lane> lanes)
1053     {
1054         Throw.whenNull(lanes, "Lanes should not be null. Use an empty set instead.");
1055         this.prevLanes.clear();
1056         this.prevLanes.put(null, lanes);
1057     }
1058 
1059     /**
1060      * Determine the set of lanes to the left or to the right of this lane, which are accessible from this lane, or an empty set
1061      * if no lane could be found. The method ignores all legal restrictions such as allowable directions and stripes.<br>
1062      * A lane is called adjacent to another lane if the lateral edges are not more than a delta distance apart. This means that
1063      * a lane that <i>overlaps</i> with another lane is <b>not</b> returned as an adjacent lane. <br>
1064      * <b>Note:</b> LEFT and RIGHT are seen from the direction of the GTU, in its forward driving direction. <br>
1065      * @param lateralDirection LEFT or RIGHT.
1066      * @param gtuType the type of GTU for which to return the adjacent lanes.
1067      * @return the set of lanes that are accessible, empty if there is no lane that is accessible with a matching driving
1068      *         direction.
1069      */
1070     public Set<Lane> accessibleAdjacentLanesPhysical(final LateralDirectionality lateralDirection, final GtuType gtuType)
1071     {
1072         return neighbors(lateralDirection, gtuType, false);
1073     }
1074 
1075     /**
1076      * Determine the set of lanes to the left or to the right of this lane, which are accessible from this lane, or an empty set
1077      * if no lane could be found. The method takes the LongitidinalDirectionality of the lane into account. In other words, if
1078      * we drive in the DIR_PLUS direction and look for a lane on the LEFT, and there is a lane but the Directionality of that
1079      * lane is not DIR_PLUS or DIR_BOTH, it will not be included.<br>
1080      * A lane is called adjacent to another lane if the lateral edges are not more than a delta distance apart. This means that
1081      * a lane that <i>overlaps</i> with another lane is <b>not</b> returned as an adjacent lane. <br>
1082      * <b>Note:</b> LEFT and RIGHT are seen from the direction of the GTU, in its forward driving direction. <br>
1083      * @param lateralDirection LEFT or RIGHT.
1084      * @param gtuType the type of GTU for which to return the adjacent lanes.
1085      * @return the set of lanes that are accessible, empty if there is no lane that is accessible with a matching driving
1086      *         direction.
1087      */
1088     public Set<Lane> accessibleAdjacentLanesLegal(final LateralDirectionality lateralDirection, final GtuType gtuType)
1089     {
1090         return neighbors(lateralDirection, gtuType, true);
1091     }
1092 
1093     /**
1094      * Returns the left lane for given GTU type, regardless of legality.
1095      * @param gtuType GTU type
1096      * @return left lane for given GTU type, empty if none
1097      */
1098     public Optional<Lane> getLeft(final GtuType gtuType)
1099     {
1100         Set<Lane> set = neighbors(LateralDirectionality.LEFT, gtuType, false);
1101         if (set.isEmpty())
1102         {
1103             return Optional.empty();
1104         }
1105         return Optional.of(set.iterator().next());
1106     }
1107 
1108     /**
1109      * Returns the right lane for given GTU type, regardless of legality.
1110      * @param gtuType GTU type
1111      * @return right lane for given GTU type, empty if none
1112      */
1113     public Optional<Lane> getRight(final GtuType gtuType)
1114     {
1115         Set<Lane> set = neighbors(LateralDirectionality.RIGHT, gtuType, false);
1116         if (set.isEmpty())
1117         {
1118             return Optional.empty();
1119         }
1120         return Optional.of(set.iterator().next());
1121     }
1122 
1123     /**
1124      * Returns one adjacent lane, regardless of legality.
1125      * @param laneChangeDirection lane change direction
1126      * @param gtuType GTU type
1127      * @return adjacent lane, empty if none
1128      */
1129     public Optional<Lane> getAdjacentLane(final LateralDirectionality laneChangeDirection, final GtuType gtuType)
1130     {
1131         Throw.whenNull(laneChangeDirection, "laneChangeDirection");
1132         Throw.when(laneChangeDirection.isNone(), IllegalArgumentException.class, "Lane change direction should not be null.");
1133         return laneChangeDirection.isLeft() ? getLeft(gtuType) : getRight(gtuType);
1134     }
1135 
1136     /**
1137      * Get the speed limits of this lane at the current time-of-day, which can differ per GTU type. E.g., a vehicle type speed
1138      * limit may apply to trucks.
1139      * @param gtuType the GTU type to provide the speed limits for
1140      * @return speed limits
1141      */
1142     public SpeedLimits getSpeedLimits(final GtuType gtuType)
1143     {
1144         return this.speedLimits.getSpeedLimits(gtuType, getLink().getSimulator().getTimeOfDay());
1145     }
1146 
1147     /**
1148      * Get the speed limits of this lane, which can differ per GTU type. E.g., a vehicle type speed limit may apply to trucks.
1149      * @param gtuType the GTU type to provide the speed limits for
1150      * @param timeOfDay time-of-day
1151      * @return speed limits
1152      */
1153     public SpeedLimits getSpeedLimits(final GtuType gtuType, final Duration timeOfDay)
1154     {
1155         return this.speedLimits.getSpeedLimits(gtuType, timeOfDay);
1156     }
1157 
1158     /**
1159      * Returns the speed limit at the current time-of-day.
1160      * @return speed limit, empty if no speed limit given
1161      */
1162     public Optional<SpeedLimit> getSpeedLimit()
1163     {
1164         return this.speedLimits.getSpeedLimit(getLink().getSimulator().getTimeOfDay());
1165     }
1166 
1167     /**
1168      * Returns the speed limit.
1169      * @param timeOfDay time-of-day
1170      * @return speed limit, empty if no speed limit given
1171      */
1172     public Optional<SpeedLimit> getSpeedLimit(final Duration timeOfDay)
1173     {
1174         return this.speedLimits.getSpeedLimit(timeOfDay);
1175     }
1176 
1177     @Override
1178     public LaneType getType()
1179     {
1180         return this.laneType;
1181     }
1182 
1183     /**
1184      * Returns GTU list.
1185      * @return gtuList.
1186      */
1187     public ImmutableList<LaneBasedGtu> getGtuList()
1188     {
1189         // TODO let HistoricalArrayList return an Immutable (WRAP) of itself
1190         return this.gtuList == null ? new ImmutableArrayList<>(new ArrayList<>())
1191                 : new ImmutableArrayList<>(this.gtuList, Immutable.COPY);
1192     }
1193 
1194     /**
1195      * Returns the list of GTU's at the specified time.
1196      * @param time simulation time
1197      * @return list of GTU's at the specified times
1198      */
1199     public List<LaneBasedGtu> getGtuList(final Duration time)
1200     {
1201         if (time.equals(this.gtuListTime))
1202         {
1203             return this.gtuListAtTime;
1204         }
1205         this.gtuListTime = time;
1206         this.gtuListAtTime = this.gtuList == null ? new ArrayList<>() : this.gtuList.get(time);
1207         return this.gtuListAtTime;
1208     }
1209 
1210     /**
1211      * Returns the number of GTU's.
1212      * @return number of GTU's.
1213      */
1214     public int numberOfGtus()
1215     {
1216         return this.gtuList.size();
1217     }
1218 
1219     /**
1220      * Returns the number of GTU's at specified time.
1221      * @param time simulation time
1222      * @return number of GTU's.
1223      */
1224     public int numberOfGtus(final Duration time)
1225     {
1226         return getGtuList(time).size();
1227     }
1228 
1229     /**
1230      * Returns the index of the given GTU, or -1 if not present.
1231      * @param gtu gtu to get the index of
1232      * @return index of the given GTU, or -1 if not present
1233      */
1234     public int indexOfGtu(final LaneBasedGtu gtu)
1235     {
1236         return Collections.binarySearch(this.gtuList, gtu, (gtu1, gtu2) ->
1237         {
1238             return gtu1.getPosition().position().compareTo(gtu2.getPosition().position());
1239         });
1240     }
1241 
1242     /**
1243      * Returns the index'th GTU.
1244      * @param index index of the GTU
1245      * @return the index'th GTU
1246      */
1247     public LaneBasedGtu getGtu(final int index)
1248     {
1249         return this.gtuList.get(index);
1250     }
1251 
1252     /**
1253      * Returns the index'th GTU at specified time.
1254      * @param index index of the GTU
1255      * @param time simulation time
1256      * @return the index'th GTU
1257      */
1258     public LaneBasedGtu getGtu(final int index, final Duration time)
1259     {
1260         return getGtuList(time).get(index);
1261     }
1262 
1263     /**
1264      * Returns the covered distance driven to the given fractional position.
1265      * @param fraction fractional position
1266      * @return covered distance driven to the given fractional position
1267      */
1268     public Length coveredDistance(final double fraction)
1269     {
1270         return getLength().times(fraction);
1271     }
1272 
1273     /**
1274      * Returns the remaining distance to be driven from the given fractional position.
1275      * @param fraction fractional position
1276      * @return remaining distance to be driven from the given fractional position
1277      */
1278     public Length remainingDistance(final double fraction)
1279     {
1280         return getLength().times(1.0 - fraction);
1281     }
1282 
1283     /**
1284      * Returns the fraction along the design line for having covered the given distance.
1285      * @param distance covered distance
1286      * @return fraction along the design line for having covered the given distance
1287      */
1288     @Deprecated
1289     public double fractionAtCoveredDistance(final Length distance)
1290     {
1291         return fraction(distance);
1292     }
1293 
1294     @Override
1295     public String toString()
1296     {
1297         CrossSectionLink link = getLink();
1298         return String.format("Lane %s of %s", getId(), link.getId());
1299     }
1300 
1301     /** Cache of the hashCode. */
1302     private Integer cachedHashCode = null;
1303 
1304     @SuppressWarnings("checkstyle:designforextension")
1305     @Override
1306     public int hashCode()
1307     {
1308         if (this.cachedHashCode == null)
1309         {
1310             final int prime = 31;
1311             int result = super.hashCode();
1312             result = prime * result + ((this.laneType == null) ? 0 : this.laneType.hashCode());
1313             this.cachedHashCode = result;
1314         }
1315         return this.cachedHashCode;
1316     }
1317 
1318     @SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
1319     @Override
1320     public boolean equals(final Object obj)
1321     {
1322         if (this == obj)
1323             return true;
1324         if (!super.equals(obj))
1325             return false;
1326         if (getClass() != obj.getClass())
1327             return false;
1328         Lane other = (Lane) obj;
1329         if (this.laneType == null)
1330         {
1331             if (other.laneType != null)
1332                 return false;
1333         }
1334         else if (!this.laneType.equals(other.laneType))
1335             return false;
1336         return true;
1337     }
1338 
1339     /**
1340      * Functional interface that can be used for line searches of objects on the lane.
1341      * <p>
1342      * Copyright (c) 2013-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
1343      * <br>
1344      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
1345      * </p>
1346      * @author Alexander Verbraeck
1347      * @author Peter Knoppers
1348      * @author Wouter Schakel
1349      */
1350     private interface Positions
1351     {
1352         /**
1353          * Returns the position of the index'th element.
1354          * @param index index
1355          * @return position of the index'th element
1356          */
1357         double get(int index);
1358     }
1359 
1360 }