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