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 }