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.SpatialObject;
31 import org.opentrafficsim.core.geometry.OtsGeometryException;
32 import org.opentrafficsim.core.geometry.OtsShape;
33 import org.opentrafficsim.core.gtu.GtuException;
34 import org.opentrafficsim.core.gtu.GtuType;
35 import org.opentrafficsim.core.gtu.RelativePosition;
36 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
37 import org.opentrafficsim.core.network.LateralDirectionality;
38 import org.opentrafficsim.core.network.Link;
39 import org.opentrafficsim.core.network.NetworkException;
40 import org.opentrafficsim.core.perception.HistoryManager;
41 import org.opentrafficsim.core.perception.collections.HistoricalArrayList;
42 import org.opentrafficsim.core.perception.collections.HistoricalList;
43 import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
44 import org.opentrafficsim.road.network.lane.object.LaneBasedObject;
45 import org.opentrafficsim.road.network.lane.object.detector.DestinationDetector;
46 import org.opentrafficsim.road.network.lane.object.detector.Detector;
47 import org.opentrafficsim.road.network.lane.object.detector.LaneDetector;
48 import org.opentrafficsim.road.network.lane.object.detector.SinkDetector;
49
50 import nl.tudelft.simulation.dsol.SimRuntimeException;
51 import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 public class Lane extends CrossSectionElement implements HierarchicallyTyped<LaneType, Lane>, SpatialObject, Serializable
71 {
72
73 private static final long serialVersionUID = 20150826L;
74
75
76 private final LaneType laneType;
77
78
79 private OtsShape shape = null;
80
81
82
83
84
85 private final Map<GtuType, Speed> speedLimitMap = new LinkedHashMap<>();
86
87
88 private final Map<GtuType, Speed> cachedSpeedLimits = new LinkedHashMap<>();
89
90
91
92
93
94 private final SortedMap<Double, List<LaneDetector>> detectors = new TreeMap<>();
95
96
97
98
99
100 private final SortedMap<Double, List<LaneBasedObject>> laneBasedObjects = new TreeMap<>();
101
102
103 private final HistoricalList<LaneBasedGtu> gtuList;
104
105
106 private List<LaneBasedGtu> gtuListAtTime = null;
107
108
109 private Time gtuListTime = null;
110
111
112
113
114
115
116 private final MultiKeyMap<Set<Lane>> leftNeighbours = new MultiKeyMap<>(GtuType.class, Boolean.class);
117
118
119
120
121
122
123 private final MultiKeyMap<Set<Lane>> rightNeighbours = new MultiKeyMap<>(GtuType.class, Boolean.class);
124
125
126
127
128
129 private final Map<GtuType, Set<Lane>> nextLanes = new LinkedHashMap<>(1);
130
131
132
133
134
135 private final Map<GtuType, Set<Lane>> prevLanes = new LinkedHashMap<>(1);
136
137
138
139
140
141 public static final EventType GTU_ADD_EVENT = new EventType("LANE.GTU.ADD",
142 new MetaData("Lane GTU add", "GTU id, number of GTUs after addition, lane id, link id",
143 new ObjectDescriptor("GTU id", "Id of GTU", String.class),
144 new ObjectDescriptor("GTU count", "New number of GTUs on lane", Integer.class),
145 new ObjectDescriptor("Lane id", "Id of the lane", String.class),
146 new ObjectDescriptor("Link id", "Id of the link", String.class)));
147
148
149
150
151
152
153 public static final EventType GTU_REMOVE_EVENT = new EventType("LANE.GTU.REMOVE",
154 new MetaData("Lane GTU remove", "GTU id, gtu, number of GTUs after removal, position, lane id, link id",
155 new ObjectDescriptor("GTU id", "Id of GTU", String.class),
156 new ObjectDescriptor("GTU", "The GTU itself", LaneBasedGtu.class),
157 new ObjectDescriptor("GTU count", "New number of GTUs on lane", Integer.class),
158 new ObjectDescriptor("Position", "Last position of GTU on the lane", Length.class),
159 new ObjectDescriptor("Lane id", "Id of the lane", String.class),
160 new ObjectDescriptor("Link id", "Id of the link", String.class)));
161
162
163
164
165
166 public static final EventType DETECTOR_ADD_EVENT = new EventType("LANE.DETECTOR.ADD",
167 new MetaData("Lane detector add", "Detector id, detector",
168 new ObjectDescriptor("detector id", "id of detector", String.class),
169 new ObjectDescriptor("detector", "detector itself", Detector.class)));
170
171
172
173
174
175 public static final EventType DETECTOR_REMOVE_EVENT = new EventType("LANE.DETECTOR.REMOVE",
176 new MetaData("Lane detector remove", "Detector id, detector",
177 new ObjectDescriptor("detector id", "id of detector", String.class),
178 new ObjectDescriptor("detector", "detector itself", Detector.class)));
179
180
181
182
183
184 public static final EventType OBJECT_ADD_EVENT = new EventType("LANE.OBJECT.ADD", new MetaData("Lane object add", "Object",
185 new ObjectDescriptor("GTU", "The lane-based GTU", LaneBasedObject.class)));
186
187
188
189
190
191 public static final EventType OBJECT_REMOVE_EVENT = new EventType("LANE.OBJECT.REMOVE", new MetaData("Lane object remove",
192 "Object", new ObjectDescriptor("GTU", "The lane-based GTU", LaneBasedObject.class)));
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210 @SuppressWarnings("checkstyle:parameternumber")
211 public Lane(final CrossSectionLink parentLink, final String id, final Length lateralOffsetAtStart,
212 final Length lateralOffsetAtEnd, final Length beginWidth, final Length endWidth, final LaneType laneType,
213 final Map<GtuType, Speed> speedLimitMap, final boolean fixGradualLateralOffset)
214 throws OtsGeometryException, NetworkException
215 {
216 super(parentLink, id, lateralOffsetAtStart, lateralOffsetAtEnd, beginWidth, endWidth, fixGradualLateralOffset);
217 this.laneType = laneType;
218 this.speedLimitMap.putAll(speedLimitMap);
219 this.gtuList = new HistoricalArrayList<>(getManager(parentLink));
220 }
221
222
223
224
225
226
227
228
229
230
231
232
233
234 @SuppressWarnings("checkstyle:parameternumber")
235 public Lane(final CrossSectionLink parentLink, final String id, final Length lateralOffset, final Length width,
236 final LaneType laneType, final Map<GtuType, Speed> speedLimitMap) throws OtsGeometryException, NetworkException
237 {
238 super(parentLink, id, lateralOffset, width);
239 this.laneType = laneType;
240 this.speedLimitMap.putAll(speedLimitMap);
241 this.gtuList = new HistoricalArrayList<>(getManager(parentLink));
242 }
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257 @SuppressWarnings("checkstyle:parameternumber")
258 public Lane(final CrossSectionLink parentLink, final String id, final List<CrossSectionSlice> crossSectionSlices,
259 final LaneType laneType, final Map<GtuType, Speed> speedLimitMap) throws OtsGeometryException, NetworkException
260 {
261 super(parentLink, id, crossSectionSlices);
262 this.laneType = laneType;
263 this.speedLimitMap.putAll(speedLimitMap);
264 this.gtuList = new HistoricalArrayList<>(getManager(parentLink));
265 }
266
267
268
269
270
271
272 private HistoryManager getManager(final CrossSectionLink parentLink)
273 {
274 return parentLink.getSimulator().getReplication().getHistoryManager(parentLink.getSimulator());
275 }
276
277
278
279
280
281
282
283
284
285
286
287
288
289 private Set<Lane> neighbors(final LateralDirectionality direction, final GtuType gtuType, final boolean legal)
290 {
291 MultiKeyMap<Set<Lane>> cache = direction.isLeft() ? this.leftNeighbours : this.rightNeighbours;
292 return cache.get(() ->
293 {
294 Set<Lane> lanes = new LinkedHashSet<>(1);
295 for (CrossSectionElement cse : this.parentLink.getCrossSectionElementList())
296 {
297 if (cse instanceof Lane && !cse.equals(this))
298 {
299 Lane lane = (Lane) cse;
300 if (laterallyAdjacentAndAccessible(lane, direction, gtuType, legal))
301 {
302 lanes.add(lane);
303 }
304 }
305 }
306 return lanes;
307 }, gtuType, legal);
308 }
309
310
311 static final Length ADJACENT_MARGIN = new Length(0.2, LengthUnit.METER);
312
313
314
315
316
317
318
319
320
321
322
323
324
325 private boolean laterallyAdjacentAndAccessible(final Lane lane, final LateralDirectionality direction,
326 final GtuType gtuType, final boolean legal)
327 {
328 if (!lane.getType().isCompatible(gtuType))
329 {
330
331 return false;
332 }
333 if (direction.equals(LateralDirectionality.LEFT))
334 {
335
336 if (lane.getDesignLineOffsetAtBegin().si + ADJACENT_MARGIN.si > getDesignLineOffsetAtBegin().si
337 && lane.getDesignLineOffsetAtEnd().si + ADJACENT_MARGIN.si > getDesignLineOffsetAtEnd().si
338 && (lane.getDesignLineOffsetAtBegin().si - lane.getBeginWidth().si / 2.0)
339 - (getDesignLineOffsetAtBegin().si + getBeginWidth().si / 2.0) < ADJACENT_MARGIN.si
340 && (lane.getDesignLineOffsetAtEnd().si - lane.getEndWidth().si / 2.0)
341 - (getDesignLineOffsetAtEnd().si + getEndWidth().si / 2.0) < ADJACENT_MARGIN.si)
342 {
343
344 if (legal)
345 {
346 for (CrossSectionElement cse : this.parentLink.getCrossSectionElementList())
347 {
348 if (cse instanceof Stripe)
349 {
350 Stripe stripe = (Stripe) cse;
351
352 if ((getDesignLineOffsetAtBegin().si < stripe.getDesignLineOffsetAtBegin().si
353 && stripe.getDesignLineOffsetAtBegin().si < lane.getDesignLineOffsetAtBegin().si)
354 || (getDesignLineOffsetAtEnd().si < stripe.getDesignLineOffsetAtEnd().si
355 && stripe.getDesignLineOffsetAtEnd().si < lane.getDesignLineOffsetAtEnd().si))
356 {
357 if (!stripe.isPermeable(gtuType, LateralDirectionality.LEFT))
358 {
359
360 return false;
361 }
362 }
363 }
364 }
365 }
366
367
368 return true;
369 }
370 }
371
372 else
373
374 {
375
376 if (lane.getDesignLineOffsetAtBegin().si < getDesignLineOffsetAtBegin().si + ADJACENT_MARGIN.si
377 && lane.getDesignLineOffsetAtEnd().si < getDesignLineOffsetAtEnd().si + ADJACENT_MARGIN.si
378 && (getDesignLineOffsetAtBegin().si - getBeginWidth().si / 2.0)
379 - (lane.getDesignLineOffsetAtBegin().si + lane.getBeginWidth().si / 2.0) < ADJACENT_MARGIN.si
380 && (getDesignLineOffsetAtEnd().si - getEndWidth().si / 2.0)
381 - (lane.getDesignLineOffsetAtEnd().si + lane.getEndWidth().si / 2.0) < ADJACENT_MARGIN.si)
382 {
383
384 if (legal)
385 {
386 for (CrossSectionElement cse : this.parentLink.getCrossSectionElementList())
387 {
388 if (cse instanceof Stripe)
389 {
390 Stripe stripe = (Stripe) cse;
391
392 if ((getDesignLineOffsetAtBegin().si > stripe.getDesignLineOffsetAtBegin().si
393 && stripe.getDesignLineOffsetAtBegin().si > lane.getDesignLineOffsetAtBegin().si)
394 || (getDesignLineOffsetAtEnd().si > stripe.getDesignLineOffsetAtEnd().si
395 && stripe.getDesignLineOffsetAtEnd().si > lane.getDesignLineOffsetAtEnd().si))
396 {
397 if (!stripe.isPermeable(gtuType, LateralDirectionality.RIGHT))
398 {
399
400 return false;
401 }
402 }
403 }
404 }
405 }
406
407
408 return true;
409 }
410 }
411
412
413 return false;
414 }
415
416
417
418
419
420
421 public final void addDetector(final LaneDetector detector) throws NetworkException
422 {
423 double position = detector.getLongitudinalPosition().si;
424 if (position < 0 || position > getLength().getSI())
425 {
426 throw new NetworkException(
427 "Illegal position for detector " + position + " valid range is 0.." + getLength().getSI());
428 }
429 if (this.parentLink.getNetwork().containsObject(detector.getFullId()))
430 {
431 throw new NetworkException("Network already contains an object with the name " + detector.getFullId());
432 }
433 List<LaneDetector> detectorList = this.detectors.get(position);
434 if (null == detectorList)
435 {
436 detectorList = new ArrayList<>(1);
437 this.detectors.put(position, detectorList);
438 }
439 detectorList.add(detector);
440 this.parentLink.getNetwork().addObject(detector);
441 fireTimedEvent(Lane.DETECTOR_ADD_EVENT, new Object[] {detector.getId(), detector},
442 detector.getSimulator().getSimulatorTime());
443 }
444
445
446
447
448
449
450 public final void removeDetector(final LaneDetector detector) throws NetworkException
451 {
452 fireTimedEvent(Lane.DETECTOR_REMOVE_EVENT, new Object[] {detector.getId(), detector},
453 detector.getSimulator().getSimulatorTime());
454 List<LaneDetector> detectorList = this.detectors.get(detector.getLongitudinalPosition().si);
455 if (null == detectorList)
456 {
457 throw new NetworkException("No detector at " + detector.getLongitudinalPosition().si);
458 }
459 detectorList.remove(detector);
460 if (detectorList.size() == 0)
461 {
462 this.detectors.remove(detector.getLongitudinalPosition().si);
463 }
464 this.parentLink.getNetwork().removeObject(detector);
465 }
466
467
468
469
470
471
472
473
474
475 public final List<LaneDetector> getDetectors(final Length minimumPosition, final Length maximumPosition,
476 final GtuType gtuType)
477 {
478 List<LaneDetector> detectorList = new ArrayList<>(1);
479 for (List<LaneDetector> dets : this.detectors.values())
480 {
481 for (LaneDetector detector : dets)
482 {
483 if (detector.isCompatible(gtuType) && detector.getLongitudinalPosition().ge(minimumPosition)
484 && detector.getLongitudinalPosition().le(maximumPosition))
485 {
486 detectorList.add(detector);
487 }
488 }
489 }
490 return detectorList;
491 }
492
493
494
495
496
497
498
499 public final List<LaneDetector> getDetectors(final GtuType gtuType)
500 {
501 List<LaneDetector> detectorList = new ArrayList<>(1);
502 for (List<LaneDetector> dets : this.detectors.values())
503 {
504 for (LaneDetector detector : dets)
505 {
506 if (detector.isCompatible(gtuType))
507 {
508 detectorList.add(detector);
509 }
510 }
511 }
512 return detectorList;
513 }
514
515
516
517
518
519 public final List<LaneDetector> getDetectors()
520 {
521 if (this.detectors == null)
522 {
523 return new ArrayList<>();
524 }
525 List<LaneDetector> detectorList = new ArrayList<>(1);
526 for (List<LaneDetector> dets : this.detectors.values())
527 {
528 for (LaneDetector detector : dets)
529 {
530 detectorList.add(detector);
531 }
532 }
533 return detectorList;
534 }
535
536
537
538
539
540
541
542 public final SortedMap<Double, List<LaneDetector>> getDetectorMap(final GtuType gtuType)
543 {
544 SortedMap<Double, List<LaneDetector>> detectorMap = new TreeMap<>();
545 for (double d : this.detectors.keySet())
546 {
547 List<LaneDetector> detectorList = new ArrayList<>(1);
548 for (List<LaneDetector> dets : this.detectors.values())
549 {
550 for (LaneDetector detector : dets)
551 {
552 if (detector.getLongitudinalPosition().si == d && detector.isCompatible(gtuType))
553 {
554 detectorList.add(detector);
555 }
556 }
557 }
558 if (detectorList.size() > 0)
559 {
560 detectorMap.put(d, detectorList);
561 }
562 }
563 return detectorMap;
564 }
565
566
567
568
569
570
571
572
573
574 public final void scheduleDetectorrTriggers(final LaneBasedGtu gtu, final double referenceStartSI,
575 final double referenceMoveSI) throws NetworkException, SimRuntimeException
576 {
577 double minPos = referenceStartSI + gtu.getRear().getDx().si;
578 double maxPos = referenceStartSI + gtu.getFront().getDx().si + referenceMoveSI;
579 Map<Double, List<LaneDetector>> map = this.detectors.subMap(minPos, maxPos);
580 for (double pos : map.keySet())
581 {
582 for (LaneDetector detector : map.get(pos))
583 {
584 if (detector.isCompatible(gtu.getType()))
585 {
586 double dx = gtu.getRelativePositions().get(detector.getPositionType()).getDx().si;
587 minPos = referenceStartSI + dx;
588 maxPos = minPos + referenceMoveSI;
589 if (minPos <= detector.getLongitudinalPosition().si && maxPos > detector.getLongitudinalPosition().si)
590 {
591 double d = detector.getLongitudinalPosition().si - minPos;
592 if (d < 0)
593 {
594 throw new NetworkException("scheduleTriggers for gtu: " + gtu + ", d<0 d=" + d);
595 }
596 OperationalPlan oPlan = gtu.getOperationalPlan();
597 Time triggerTime = oPlan.timeAtDistance(Length.instantiateSI(d));
598 if (triggerTime.gt(oPlan.getEndTime()))
599 {
600 System.err.println("Time=" + gtu.getSimulator().getSimulatorTime().getSI()
601 + " - Scheduling trigger at " + triggerTime.getSI() + "s. > " + oPlan.getEndTime().getSI()
602 + "s. (nextEvalTime) for detector " + detector + " , gtu " + gtu);
603 System.err.println(" v=" + gtu.getSpeed() + ", a=" + gtu.getAcceleration() + ", lane=" + toString()
604 + ", refStartSI=" + referenceStartSI + ", moveSI=" + referenceMoveSI);
605 triggerTime = new Time(oPlan.getEndTime().getSI() - Math.ulp(oPlan.getEndTime().getSI()),
606 TimeUnit.DEFAULT);
607 }
608 SimEvent<Duration> event =
609 new SimEvent<>(new Duration(triggerTime.minus(gtu.getSimulator().getStartTimeAbs())), detector,
610 "trigger", new Object[] {gtu});
611 gtu.getSimulator().scheduleEvent(event);
612 gtu.addTrigger(this, event);
613 }
614 else if (detector.getLongitudinalPosition().si < minPos
615 && (detector instanceof SinkDetector || detector instanceof DestinationDetector))
616 {
617
618
619 SimEvent<Duration> event = new SimEvent<>(new Duration(gtu.getSimulator().getSimulatorTime()), detector,
620 "trigger", new Object[] {gtu});
621 gtu.getSimulator().scheduleEvent(event);
622 gtu.addTrigger(this, event);
623 }
624 }
625 }
626 }
627 }
628
629
630
631
632
633
634
635 public final synchronized void addLaneBasedObject(final LaneBasedObject laneBasedObject) throws NetworkException
636 {
637 double position = laneBasedObject.getLongitudinalPosition().si;
638 if (position < 0 || position > getLength().getSI())
639 {
640 throw new NetworkException(
641 "Illegal position for laneBasedObject " + position + " valid range is 0.." + getLength().getSI());
642 }
643 if (this.parentLink.getNetwork().containsObject(laneBasedObject.getFullId()))
644 {
645 throw new NetworkException("Network already contains an object with the name " + laneBasedObject.getFullId());
646 }
647 List<LaneBasedObject> laneBasedObjectList = this.laneBasedObjects.get(position);
648 if (null == laneBasedObjectList)
649 {
650 laneBasedObjectList = new ArrayList<>(1);
651 this.laneBasedObjects.put(position, laneBasedObjectList);
652 }
653 laneBasedObjectList.add(laneBasedObject);
654 this.parentLink.getNetwork().addObject(laneBasedObject);
655 fireTimedEvent(Lane.OBJECT_ADD_EVENT, new Object[] {laneBasedObject},
656 getParentLink().getSimulator().getSimulatorTime());
657 }
658
659
660
661
662
663
664 public final synchronized void removeLaneBasedObject(final LaneBasedObject laneBasedObject) throws NetworkException
665 {
666 fireTimedEvent(Lane.OBJECT_REMOVE_EVENT, new Object[] {laneBasedObject},
667 getParentLink().getSimulator().getSimulatorTime());
668 List<LaneBasedObject> laneBasedObjectList =
669 this.laneBasedObjects.get(laneBasedObject.getLongitudinalPosition().getSI());
670 if (null == laneBasedObjectList)
671 {
672 throw new NetworkException("No laneBasedObject at " + laneBasedObject.getLongitudinalPosition().si);
673 }
674 laneBasedObjectList.remove(laneBasedObject);
675 if (laneBasedObjectList.isEmpty())
676 {
677 this.laneBasedObjects.remove(laneBasedObject.getLongitudinalPosition().doubleValue());
678 }
679 this.parentLink.getNetwork().removeObject(laneBasedObject);
680 }
681
682
683
684
685
686
687
688
689 public final List<LaneBasedObject> getLaneBasedObjects(final Length minimumPosition, final Length maximumPosition)
690 {
691 List<LaneBasedObject> laneBasedObjectList = new ArrayList<>(1);
692 for (List<LaneBasedObject> lbol : this.laneBasedObjects.values())
693 {
694 for (LaneBasedObject lbo : lbol)
695 {
696 if (lbo.getLongitudinalPosition().ge(minimumPosition) && lbo.getLongitudinalPosition().le(maximumPosition))
697 {
698 laneBasedObjectList.add(lbo);
699 }
700 }
701 }
702 return laneBasedObjectList;
703 }
704
705
706
707
708
709 public final List<LaneBasedObject> getLaneBasedObjects()
710 {
711 if (this.laneBasedObjects == null)
712 {
713 return new ArrayList<>();
714 }
715 List<LaneBasedObject> laneBasedObjectList = new ArrayList<>(1);
716 for (List<LaneBasedObject> lbol : this.laneBasedObjects.values())
717 {
718 for (LaneBasedObject lbo : lbol)
719 {
720 laneBasedObjectList.add(lbo);
721 }
722 }
723 return laneBasedObjectList;
724 }
725
726
727
728
729
730 public final SortedMap<Double, List<LaneBasedObject>> getLaneBasedObjectMap()
731 {
732 SortedMap<Double, List<LaneBasedObject>> laneBasedObjectMap = new TreeMap<>();
733 for (double d : this.laneBasedObjects.keySet())
734 {
735 List<LaneBasedObject> laneBasedObjectList = new ArrayList<>(1);
736 for (LaneBasedObject lbo : this.laneBasedObjects.get(d))
737 {
738 laneBasedObjectList.add(lbo);
739 }
740 laneBasedObjectMap.put(d, laneBasedObjectList);
741 }
742 return laneBasedObjectMap;
743 }
744
745
746
747
748
749
750 public final Length position(final double fraction)
751 {
752 if (this.length.getDisplayUnit().isBaseSIUnit())
753 {
754 return new Length(this.length.si * fraction, LengthUnit.SI);
755 }
756 return new Length(this.length.getInUnit() * fraction, this.length.getDisplayUnit());
757 }
758
759
760
761
762
763
764 public final double positionSI(final double fraction)
765 {
766 return this.length.si * fraction;
767 }
768
769
770
771
772
773
774 public final double fraction(final Length position)
775 {
776 return position.si / this.length.si;
777 }
778
779
780
781
782
783
784 public final double fractionSI(final double positionSI)
785 {
786 return positionSI / this.length.si;
787 }
788
789
790 @Override
791 public OtsShape getShape()
792 {
793 return getContour();
794 }
795
796
797
798
799
800
801
802
803
804
805 public final int addGtu(final LaneBasedGtu gtu, final double fractionalPosition) throws GtuException
806 {
807 int index;
808
809 if (this.gtuList.size() == 0)
810 {
811 this.gtuList.add(gtu);
812 index = 0;
813 }
814 else
815 {
816
817 for (index = 0; index < this.gtuList.size(); index++)
818 {
819 LaneBasedGtu otherGTU = this.gtuList.get(index);
820 if (gtu == otherGTU)
821 {
822 throw new GtuException(gtu + " already registered on Lane " + this + " [registered lanes: "
823 + gtu.positions(gtu.getFront()).keySet() + "] locations: " + gtu.positions(gtu.getFront()).values()
824 + " time: " + gtu.getSimulator().getSimulatorTime());
825 }
826 if (otherGTU.fractionalPosition(this, otherGTU.getFront()) >= fractionalPosition)
827 {
828 break;
829 }
830 }
831 this.gtuList.add(index, gtu);
832 }
833
834 fireTimedEvent(Lane.GTU_ADD_EVENT, new Object[] {gtu.getId(), this.gtuList.size(), getId(), getParentLink().getId()},
835 gtu.getSimulator().getSimulatorTime());
836
837 getParentLink().addGTU(gtu);
838 return index;
839 }
840
841
842
843
844
845
846
847
848
849 public final int addGtu(final LaneBasedGtu gtu, final Length longitudinalPosition) throws GtuException
850 {
851 return addGtu(gtu, longitudinalPosition.getSI() / getLength().getSI());
852 }
853
854
855
856
857
858
859
860
861 public final void removeGtu(final LaneBasedGtu gtu, final boolean removeFromParentLink, final Length position)
862 {
863 boolean contained = this.gtuList.remove(gtu);
864 if (contained)
865 {
866
867 fireTimedEvent(Lane.GTU_REMOVE_EVENT,
868 new Object[] {gtu.getId(), gtu, this.gtuList.size(), position, getId(), getParentLink().getId()},
869 gtu.getSimulator().getSimulatorTime());
870
871 }
872 if (removeFromParentLink)
873 {
874 this.parentLink.removeGTU(gtu);
875 }
876 }
877
878
879
880
881
882
883 public final LaneBasedGtu getLastGtu() throws GtuException
884 {
885 if (this.gtuList.size() == 0)
886 {
887 return null;
888 }
889 return this.gtuList.get(this.gtuList.size() - 1);
890 }
891
892
893
894
895
896
897 public final LaneBasedGtu getFirstGtu() throws GtuException
898 {
899 if (this.gtuList.size() == 0)
900 {
901 return null;
902 }
903 return this.gtuList.get(0);
904 }
905
906
907
908
909
910
911
912
913
914
915
916 public final LaneBasedGtu getGtuAhead(final Length position, final RelativePosition.TYPE relativePosition, final Time when)
917 throws GtuException
918 {
919 List<LaneBasedGtu> list = this.gtuList.get(when);
920 if (list.isEmpty())
921 {
922 return null;
923 }
924 int[] search = lineSearch((final int index) ->
925 {
926 LaneBasedGtu gtu = list.get(index);
927 return gtu.position(this, gtu.getRelativePositions().get(relativePosition), when).si;
928 }, list.size(), position.si);
929 if (search[1] < list.size())
930 {
931 return list.get(search[1]);
932 }
933 return null;
934 }
935
936
937
938
939
940
941
942
943
944
945
946 public final LaneBasedGtu getGtuBehind(final Length position, final RelativePosition.TYPE relativePosition, final Time when)
947 throws GtuException
948 {
949 List<LaneBasedGtu> list = this.gtuList.get(when);
950 if (list.isEmpty())
951 {
952 return null;
953 }
954 int[] search = lineSearch((final int index) ->
955 {
956 LaneBasedGtu gtu = list.get(index);
957 return gtu.position(this, gtu.getRelativePositions().get(relativePosition), when).si;
958 }, list.size(), position.si);
959 if (search[0] >= 0)
960 {
961 return list.get(search[0]);
962 }
963 return null;
964 }
965
966
967
968
969
970
971
972
973
974
975
976 private int[] lineSearch(final Positions positions, final int listSize, final double position) throws GtuException
977 {
978 int[] out = new int[2];
979
980 double pos0 = positions.get(0);
981 double posEnd;
982 if (position < pos0)
983 {
984 out[0] = -1;
985 out[1] = 0;
986 }
987 else if (position == pos0)
988 {
989 out[0] = -1;
990 out[1] = 1;
991 }
992 else if (position > (posEnd = positions.get(listSize - 1)))
993 {
994 out[0] = listSize - 1;
995 out[1] = listSize;
996 }
997 else if (position == posEnd)
998 {
999 out[0] = listSize - 2;
1000 out[1] = listSize;
1001 }
1002 else
1003 {
1004 int low = 0;
1005 int mid = (int) ((listSize - 1) * position / this.length.si);
1006 mid = mid < 0 ? 0 : mid >= listSize ? listSize - 1 : mid;
1007 int high = listSize - 1;
1008 while (high - low > 1)
1009 {
1010 double midPos = positions.get(mid);
1011 if (midPos < position)
1012 {
1013 low = mid;
1014 }
1015 else if (midPos > position)
1016 {
1017 high = mid;
1018 }
1019 else
1020 {
1021 low = mid - 1;
1022 high = mid + 1;
1023 break;
1024 }
1025 mid = (low + high) / 2;
1026 }
1027 out[0] = low;
1028 out[1] = high;
1029 }
1030 return out;
1031 }
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041 public final List<LaneBasedObject> getObjectAhead(final Length position)
1042 {
1043 for (double distance : this.laneBasedObjects.keySet())
1044 {
1045 if (distance > position.si)
1046 {
1047 return new ArrayList<>(this.laneBasedObjects.get(distance));
1048 }
1049 }
1050 return null;
1051 }
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061 public final List<LaneBasedObject> getObjectBehind(final Length position)
1062 {
1063 NavigableMap<Double, List<LaneBasedObject>> reverseLBO =
1064 (NavigableMap<Double, List<LaneBasedObject>>) this.laneBasedObjects;
1065 for (double distance : reverseLBO.descendingKeySet())
1066 {
1067 if (distance < position.si)
1068 {
1069 return new ArrayList<>(this.laneBasedObjects.get(distance));
1070 }
1071 }
1072 return null;
1073 }
1074
1075
1076
1077
1078
1079
1080
1081 public static final Length MARGIN = new Length(0.5, LengthUnit.METER);
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098 public final Set<Lane> nextLanes(final GtuType gtuType)
1099 {
1100 if (!this.nextLanes.containsKey(gtuType))
1101 {
1102
1103 Set<Lane> laneSet = new LinkedHashSet<>(1);
1104 this.nextLanes.put(gtuType, laneSet);
1105 if (gtuType == null)
1106 {
1107
1108 for (Link link : getParentLink().getEndNode().getLinks())
1109 {
1110 if (!(link.equals(this.getParentLink())) && link instanceof CrossSectionLink)
1111 {
1112 for (CrossSectionElement cse : ((CrossSectionLink) link).getCrossSectionElementList())
1113 {
1114 if (cse instanceof Lane)
1115 {
1116 Lane lane = (Lane) cse;
1117 Length jumpToStart = this.getCenterLine().getLast().distance(lane.getCenterLine().getFirst());
1118 Length jumpToEnd = this.getCenterLine().getLast().distance(lane.getCenterLine().getLast());
1119 if (jumpToStart.lt(MARGIN) && jumpToStart.lt(jumpToEnd)
1120 && link.getStartNode().equals(getParentLink().getEndNode()))
1121 {
1122
1123 laneSet.add(lane);
1124 }
1125
1126 }
1127 }
1128 }
1129 }
1130 }
1131 else
1132 {
1133 nextLanes(null).stream().filter((lane) -> lane.getType().isCompatible(gtuType))
1134 .forEach((lane) -> laneSet.add(lane));
1135 }
1136 }
1137 return this.nextLanes.get(gtuType);
1138 }
1139
1140
1141
1142
1143
1144 public void forceNextLanes(final Set<Lane> lanes)
1145 {
1146 Throw.whenNull(lanes, "Lanes should not be null. Use an empty set instead.");
1147 this.nextLanes.clear();
1148 this.nextLanes.put(null, lanes);
1149 }
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167 public final Set<Lane> prevLanes(final GtuType gtuType)
1168 {
1169 if (!this.prevLanes.containsKey(gtuType))
1170 {
1171 Set<Lane> laneSet = new LinkedHashSet<>(1);
1172 this.prevLanes.put(gtuType, laneSet);
1173
1174 if (gtuType == null)
1175 {
1176 for (Link link : getParentLink().getStartNode().getLinks())
1177 {
1178 if (!(link.equals(this.getParentLink())) && link instanceof CrossSectionLink)
1179 {
1180 for (CrossSectionElement cse : ((CrossSectionLink) link).getCrossSectionElementList())
1181 {
1182 if (cse instanceof Lane)
1183 {
1184 Lane lane = (Lane) cse;
1185 Length jumpToStart = this.getCenterLine().getFirst().distance(lane.getCenterLine().getFirst());
1186 Length jumpToEnd = this.getCenterLine().getFirst().distance(lane.getCenterLine().getLast());
1187 if (jumpToEnd.lt(MARGIN) && jumpToEnd.lt(jumpToStart)
1188 && link.getEndNode().equals(getParentLink().getStartNode()))
1189 {
1190
1191 laneSet.add(lane);
1192 }
1193
1194 }
1195 }
1196 }
1197 }
1198 }
1199 else
1200 {
1201 prevLanes(null).stream().filter((lane) -> lane.getType().isCompatible(gtuType))
1202 .forEach((lane) -> laneSet.add(lane));
1203 }
1204 }
1205 return this.prevLanes.get(gtuType);
1206 }
1207
1208
1209
1210
1211
1212 public void forcePrevLanes(final Set<Lane> lanes)
1213 {
1214 Throw.whenNull(lanes, "Lanes should not be null. Use an empty set instead.");
1215 this.prevLanes.clear();
1216 this.prevLanes.put(null, lanes);
1217 }
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230 public final Set<Lane> accessibleAdjacentLanesPhysical(final LateralDirectionality lateralDirection, final GtuType gtuType)
1231 {
1232 return neighbors(lateralDirection, gtuType, false);
1233 }
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248 public final Set<Lane> accessibleAdjacentLanesLegal(final LateralDirectionality lateralDirection, final GtuType gtuType)
1249 {
1250 Set<Lane> candidates = new LinkedHashSet<>(1);
1251 for (Lane lane : neighbors(lateralDirection, gtuType, true))
1252 {
1253 if (lane.getType().isCompatible(gtuType))
1254 {
1255 candidates.add(lane);
1256 }
1257 }
1258 return candidates;
1259 }
1260
1261
1262
1263
1264
1265
1266
1267 public final Lane getAdjacentLane(final LateralDirectionality laneChangeDirection, final LaneBasedGtu gtu)
1268 {
1269 Set<Lane> adjLanes = accessibleAdjacentLanesLegal(laneChangeDirection, gtu.getType());
1270 if (!adjLanes.isEmpty())
1271 {
1272 return adjLanes.iterator().next();
1273 }
1274 return null;
1275 }
1276
1277
1278
1279
1280
1281
1282
1283
1284 public Speed getSpeedLimit(final GtuType gtuType) throws NetworkException
1285 {
1286 Speed speedLimit = this.cachedSpeedLimits.get(gtuType);
1287 if (speedLimit == null)
1288 {
1289 if (this.speedLimitMap.containsKey(gtuType))
1290 {
1291 speedLimit = this.speedLimitMap.get(gtuType);
1292 }
1293 else if (gtuType.getParent() != null)
1294 {
1295 speedLimit = getSpeedLimit(gtuType.getParent());
1296 }
1297 else
1298 {
1299 throw new NetworkException("No speed limit set for GtuType " + gtuType + " on lane " + toString());
1300 }
1301 this.cachedSpeedLimits.put(gtuType, speedLimit);
1302 }
1303 return speedLimit;
1304 }
1305
1306
1307
1308
1309
1310
1311 public final Speed getLowestSpeedLimit() throws NetworkException
1312 {
1313 Throw.when(this.speedLimitMap.isEmpty(), NetworkException.class, "Lane %s has no speed limits set.", toString());
1314 Speed out = Speed.POSITIVE_INFINITY;
1315 for (GtuType gtuType : this.speedLimitMap.keySet())
1316 {
1317 out = Speed.min(out, this.speedLimitMap.get(gtuType));
1318 }
1319 return out;
1320 }
1321
1322
1323
1324
1325
1326
1327 public final Speed getHighestSpeedLimit() throws NetworkException
1328 {
1329 Throw.when(this.speedLimitMap.isEmpty(), NetworkException.class, "Lane %s has no speed limits set.", toString());
1330 Speed out = Speed.ZERO;
1331 for (GtuType gtuType : this.speedLimitMap.keySet())
1332 {
1333 out = Speed.max(out, this.speedLimitMap.get(gtuType));
1334 }
1335 return out;
1336 }
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352 public final void setSpeedLimit(final GtuType gtuType, final Speed speedLimit)
1353 {
1354 this.speedLimitMap.put(gtuType, speedLimit);
1355 this.cachedSpeedLimits.clear();
1356 }
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366 public final void removeSpeedLimit(final GtuType gtuType)
1367 {
1368 this.speedLimitMap.remove(gtuType);
1369 this.cachedSpeedLimits.clear();
1370 }
1371
1372
1373 @Override
1374 public final LaneType getType()
1375 {
1376 return this.laneType;
1377 }
1378
1379
1380
1381
1382 public final ImmutableList<LaneBasedGtu> getGtuList()
1383 {
1384
1385 return this.gtuList == null ? new ImmutableArrayList<>(new ArrayList<>())
1386 : new ImmutableArrayList<>(this.gtuList, Immutable.COPY);
1387 }
1388
1389
1390
1391
1392
1393
1394 public final List<LaneBasedGtu> getGtuList(final Time time)
1395 {
1396 if (time.equals(this.gtuListTime))
1397 {
1398 return this.gtuListAtTime;
1399 }
1400 this.gtuListTime = time;
1401 this.gtuListAtTime = this.gtuList == null ? new ArrayList<>() : this.gtuList.get(time);
1402 return this.gtuListAtTime;
1403 }
1404
1405
1406
1407
1408
1409 public final int numberOfGtus()
1410 {
1411 return this.gtuList.size();
1412 }
1413
1414
1415
1416
1417
1418
1419 public final int numberOfGtus(final Time time)
1420 {
1421 return getGtuList(time).size();
1422 }
1423
1424
1425
1426
1427
1428
1429 public final int indexOfGtu(final LaneBasedGtu gtu)
1430 {
1431 return Collections.binarySearch(this.gtuList, gtu, (gtu1, gtu2) ->
1432 {
1433 try
1434 {
1435 return gtu1.position(this, gtu1.getReference()).compareTo(gtu2.position(this, gtu2.getReference()));
1436 }
1437 catch (GtuException exception)
1438 {
1439 throw new RuntimeException(exception);
1440 }
1441 });
1442 }
1443
1444
1445
1446
1447
1448
1449
1450 public final int indexOfGtu(final LaneBasedGtu gtu, final Time time)
1451 {
1452 return Collections.binarySearch(getGtuList(time), gtu, (gtu1, gtu2) ->
1453 {
1454 try
1455 {
1456 return Double.compare(gtu1.fractionalPosition(this, gtu1.getReference(), time),
1457 gtu2.fractionalPosition(this, gtu2.getReference(), time));
1458 }
1459 catch (GtuException exception)
1460 {
1461 throw new RuntimeException(exception);
1462 }
1463 });
1464 }
1465
1466
1467
1468
1469
1470
1471 public final LaneBasedGtu getGtu(final int index)
1472 {
1473 return this.gtuList.get(index);
1474 }
1475
1476
1477
1478
1479
1480
1481
1482 public final LaneBasedGtu getGtu(final int index, final Time time)
1483 {
1484 return getGtuList(time).get(index);
1485 }
1486
1487
1488
1489
1490
1491
1492 public final Length coveredDistance(final double fraction)
1493 {
1494 return getLength().times(fraction);
1495 }
1496
1497
1498
1499
1500
1501
1502 public final Length remainingDistance(final double fraction)
1503 {
1504 return getLength().times(1.0 - fraction);
1505 }
1506
1507
1508
1509
1510
1511
1512 @Deprecated
1513 public final double fractionAtCoveredDistance(final Length distance)
1514 {
1515 return fraction(distance);
1516 }
1517
1518
1519 @Override
1520 @SuppressWarnings("checkstyle:designforextension")
1521 public double getZ()
1522 {
1523 return -0.0003;
1524 }
1525
1526
1527 @Override
1528 public final String toString()
1529 {
1530 CrossSectionLink link = getParentLink();
1531 return String.format("Lane %s of %s", getId(), link.getId());
1532 }
1533
1534
1535 private Integer cachedHashCode = null;
1536
1537
1538 @SuppressWarnings("checkstyle:designforextension")
1539 @Override
1540 public int hashCode()
1541 {
1542 if (this.cachedHashCode == null)
1543 {
1544 final int prime = 31;
1545 int result = super.hashCode();
1546 result = prime * result + ((this.laneType == null) ? 0 : this.laneType.hashCode());
1547 this.cachedHashCode = result;
1548 }
1549 return this.cachedHashCode;
1550 }
1551
1552
1553 @SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"})
1554 @Override
1555 public boolean equals(final Object obj)
1556 {
1557 if (this == obj)
1558 return true;
1559 if (!super.equals(obj))
1560 return false;
1561 if (getClass() != obj.getClass())
1562 return false;
1563 Lane other = (Lane) obj;
1564 if (this.laneType == null)
1565 {
1566 if (other.laneType != null)
1567 return false;
1568 }
1569 else if (!this.laneType.equals(other.laneType))
1570 return false;
1571 return true;
1572 }
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585 private interface Positions
1586 {
1587
1588
1589
1590
1591
1592
1593 double get(int index) throws GtuException;
1594 }
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611 @SuppressWarnings("checkstyle:parameternumber")
1612 public static Lane noTrafficLane(final CrossSectionLink parentLink, final String id, final Length lateralOffsetAtStart,
1613 final Length lateralOffsetAtEnd, final Length beginWidth, final Length endWidth,
1614 final boolean fixGradualLateralOffset) throws OtsGeometryException, NetworkException
1615 {
1616 return new Lane(parentLink, id, lateralOffsetAtStart, lateralOffsetAtEnd, beginWidth, endWidth,
1617 new LaneType("NO_TRAFFIC"), new LinkedHashMap<>(), fixGradualLateralOffset)
1618 {
1619
1620 private static final long serialVersionUID = 20230116L;
1621
1622
1623 @Override
1624 public double getZ()
1625 {
1626 return -0.00005;
1627 }
1628
1629
1630 @Override
1631 public Speed getSpeedLimit(final GtuType gtuType)
1632 {
1633 return Speed.ZERO;
1634 }
1635 };
1636 }
1637
1638 }