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 }