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