1 package org.opentrafficsim.road.gtu.lane;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.Comparator;
6 import java.util.LinkedHashMap;
7 import java.util.LinkedHashSet;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Map.Entry;
11 import java.util.Set;
12 import java.util.SortedMap;
13
14 import org.djunits.unit.DirectionUnit;
15 import org.djunits.unit.DurationUnit;
16 import org.djunits.unit.LengthUnit;
17 import org.djunits.unit.PositionUnit;
18 import org.djunits.value.vdouble.scalar.Acceleration;
19 import org.djunits.value.vdouble.scalar.Duration;
20 import org.djunits.value.vdouble.scalar.Length;
21 import org.djunits.value.vdouble.scalar.Speed;
22 import org.djunits.value.vdouble.scalar.Time;
23 import org.djutils.draw.point.Point3d;
24 import org.djutils.exceptions.Throw;
25 import org.djutils.exceptions.Try;
26 import org.djutils.immutablecollections.ImmutableMap;
27 import org.djutils.logger.CategoryLogger;
28 import org.djutils.multikeymap.MultiKeyMap;
29 import org.opentrafficsim.base.parameters.ParameterException;
30 import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
31 import org.opentrafficsim.core.geometry.Bounds;
32 import org.opentrafficsim.core.geometry.DirectedPoint;
33 import org.opentrafficsim.core.geometry.OTSGeometryException;
34 import org.opentrafficsim.core.geometry.OTSLine3D;
35 import org.opentrafficsim.core.geometry.OTSLine3D.FractionalFallback;
36 import org.opentrafficsim.core.geometry.OTSPoint3D;
37 import org.opentrafficsim.core.gtu.AbstractGTU;
38 import org.opentrafficsim.core.gtu.GTU;
39 import org.opentrafficsim.core.gtu.GTUDirectionality;
40 import org.opentrafficsim.core.gtu.GTUException;
41 import org.opentrafficsim.core.gtu.GTUType;
42 import org.opentrafficsim.core.gtu.RelativePosition;
43 import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
44 import org.opentrafficsim.core.gtu.perception.EgoPerception;
45 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
46 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanBuilder;
47 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
48 import org.opentrafficsim.core.network.LateralDirectionality;
49 import org.opentrafficsim.core.network.NetworkException;
50 import org.opentrafficsim.core.perception.Historical;
51 import org.opentrafficsim.core.perception.HistoricalValue;
52 import org.opentrafficsim.core.perception.HistoryManager;
53 import org.opentrafficsim.core.perception.collections.HistoricalArrayList;
54 import org.opentrafficsim.core.perception.collections.HistoricalList;
55 import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
56 import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
57 import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
58 import org.opentrafficsim.road.gtu.lane.perception.categories.InfrastructurePerception;
59 import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.NeighborsPerception;
60 import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTU;
61 import org.opentrafficsim.road.gtu.lane.plan.operational.LaneBasedOperationalPlan;
62 import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
63 import org.opentrafficsim.road.network.OTSRoadNetwork;
64 import org.opentrafficsim.road.network.RoadNetwork;
65 import org.opentrafficsim.road.network.lane.CrossSectionLink;
66 import org.opentrafficsim.road.network.lane.DirectedLanePosition;
67 import org.opentrafficsim.road.network.lane.Lane;
68 import org.opentrafficsim.road.network.lane.LaneDirection;
69 import org.opentrafficsim.road.network.lane.object.sensor.SingleSensor;
70 import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
71 import org.opentrafficsim.road.network.speed.SpeedLimitTypes;
72
73 import nl.tudelft.simulation.dsol.SimRuntimeException;
74 import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101 public abstract class AbstractLaneBasedGTU2 extends AbstractGTU implements LaneBasedGTU
102 {
103
104 private static final long serialVersionUID = 20140822L;
105
106
107 private final HistoricalList<CrossSection> crossSections;
108
109
110 private int referenceLaneIndex = 0;
111
112
113 private double referencePositionTime = Double.NaN;
114
115
116 private DirectedLanePosition cachedReferencePosition = null;
117
118
119 private SimEventInterface<Duration> pendingLeaveTrigger;
120
121
122 private SimEventInterface<Duration> pendingEnterTrigger;
123
124
125 private SimEventInterface<Duration> finalizeLaneChangeEvent;
126
127
128 private Set<SimEventInterface<Duration>> sensorEvents = new LinkedHashSet<>();
129
130
131 private Speed cachedDesiredSpeed;
132
133
134 private Time desiredSpeedTime;
135
136
137 private Acceleration cachedCarFollowingAcceleration;
138
139
140 private Time carFollowingAccelerationTime;
141
142
143 private Object lock = new Object();
144
145
146 @SuppressWarnings("checkstyle:visibilitymodifier")
147 public static Length initialLocationThresholdDifference = new Length(1.0, LengthUnit.MILLIMETER);
148
149
150 public static Length eventMargin = Length.instantiateSI(50.0);
151
152
153 private final Historical<TurnIndicatorStatus> turnIndicatorStatus;
154
155
156
157 public static boolean CACHING = true;
158
159
160
161 public static int CACHED_POSITION = 0;
162
163
164
165 public static int NON_CACHED_POSITION = 0;
166
167
168 private VehicleModel vehicleModel = VehicleModel.MINMAX;
169
170
171 private boolean instantaneousLaneChange = false;
172
173
174
175
176
177
178
179
180 public AbstractLaneBasedGTU2(final String id, final GTUType gtuType, final OTSRoadNetwork network) throws GTUException
181 {
182 super(id, gtuType, network.getSimulator(), network);
183 OTSSimulatorInterface simulator = network.getSimulator();
184 HistoryManager historyManager = simulator.getReplication().getHistoryManager(simulator);
185 this.crossSections = new HistoricalArrayList<>(historyManager);
186 this.turnIndicatorStatus = new HistoricalValue<>(historyManager, TurnIndicatorStatus.NOTPRESENT);
187 }
188
189
190
191
192
193
194
195
196
197
198
199 @SuppressWarnings("checkstyle:designforextension")
200 public void init(final LaneBasedStrategicalPlanner strategicalPlanner,
201 final Set<DirectedLanePosition> initialLongitudinalPositions, final Speed initialSpeed)
202 throws NetworkException, SimRuntimeException, GTUException, OTSGeometryException
203 {
204 Throw.when(null == initialLongitudinalPositions, GTUException.class, "InitialLongitudinalPositions is null");
205 Throw.when(0 == initialLongitudinalPositions.size(), GTUException.class, "InitialLongitudinalPositions is empty set");
206
207 for (DirectedLanePosition pos : new LinkedHashSet<DirectedLanePosition>(initialLongitudinalPositions))
208 {
209 double fracFront = (pos.getPosition().si + getFront().getDx().si) / pos.getLane().getLength().si;
210 if (fracFront > 1.0)
211 {
212 System.err.println("GTU " + toString() + " has been destroyed at init since it occupied multiple lanes");
213 this.destroy();
214 return;
215 }
216 double fracRear = (pos.getPosition().si - getRear().getDx().si) / pos.getLane().getLength().si;
217 if (fracRear < 0.0)
218 {
219 System.err.println("GTU " + toString() + " has been destroyed at init since it occupied multiple lanes");
220 this.destroy();
221 return;
222 }
223
224 }
225
226 DirectedPoint lastPoint = null;
227 for (DirectedLanePosition pos : initialLongitudinalPositions)
228 {
229
230
231 lastPoint = pos.getLocation();
232 }
233 DirectedPoint initialLocation = lastPoint;
234
235
236 Time now = getSimulator().getSimulatorAbsTime();
237 try
238 {
239 if (initialSpeed.si < OperationalPlan.DRIFTING_SPEED_SI)
240 {
241 this.operationalPlan
242 .set(new OperationalPlan(this, initialLocation, now, new Duration(1E-6, DurationUnit.SECOND)));
243 }
244 else
245 {
246 OTSPoint3D p2 = new OTSPoint3D(initialLocation.x + 1E-6 * Math.cos(initialLocation.getRotZ()),
247 initialLocation.y + 1E-6 * Math.sin(initialLocation.getRotZ()), initialLocation.z);
248 OTSLine3D path = new OTSLine3D(new OTSPoint3D(initialLocation), p2);
249 this.operationalPlan.set(OperationalPlanBuilder.buildConstantSpeedPlan(this, path, now, initialSpeed));
250 }
251 }
252 catch (OperationalPlanException e)
253 {
254 throw new RuntimeException("Initial operational plan could not be created.", e);
255 }
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329 List<DirectedLanePosition> inits = new ArrayList<>();
330 inits.addAll(initialLongitudinalPositions);
331 Collections.sort(inits, new Comparator<DirectedLanePosition>()
332 {
333 @Override
334 public int compare(final DirectedLanePosition o1, final DirectedLanePosition o2)
335 {
336 Length length1 =
337 o1.getGtuDirection().isPlus() ? o1.getPosition() : o1.getLane().getLength().minus(o1.getPosition());
338 Length length2 =
339 o2.getGtuDirection().isPlus() ? o2.getPosition() : o2.getLane().getLength().minus(o2.getPosition());
340 return length1.compareTo(length2);
341 }
342 });
343 for (DirectedLanePosition directedLanePosition : inits)
344 {
345 List<Lane> lanes = new ArrayList<>();
346 lanes.add(directedLanePosition.getLane());
347 this.crossSections.add(new CrossSection(lanes, directedLanePosition.getGtuDirection()));
348 }
349
350
351 DirectedLanePosition referencePosition = getReferencePosition();
352 fireTimedEvent(LaneBasedGTU.LANEBASED_INIT_EVENT,
353 new Object[] {getId(), new OTSPoint3D(initialLocation).doubleVector(PositionUnit.METER),
354 OTSPoint3D.direction(initialLocation, DirectionUnit.EAST_RADIAN), getLength(), getWidth(),
355 referencePosition.getLane().getParentLink().getId(), referencePosition.getLane().getId(),
356 referencePosition.getPosition(), referencePosition.getGtuDirection().name(), getGTUType().getId()},
357 getSimulator().getSimulatorTime());
358
359
360 for (DirectedLanePosition directedLanePosition : initialLongitudinalPositions)
361 {
362 Lane lane = directedLanePosition.getLane();
363 lane.addGTU(this, directedLanePosition.getPosition());
364 }
365
366
367 super.init(strategicalPlanner, initialLocation, initialSpeed);
368
369 this.referencePositionTime = Double.NaN;
370 }
371
372
373
374
375 @Override
376 public synchronized void setParent(final GTU gtu) throws GTUException
377 {
378 leaveAllLanes();
379 super.setParent(gtu);
380 }
381
382
383
384
385 private void leaveAllLanes()
386 {
387 for (CrossSection crossSection : this.crossSections)
388 {
389 boolean removeFromParentLink = true;
390 for (Lane lane : crossSection.getLanes())
391 {
392
393 Length pos = Try.assign(() -> position(lane, getReference()), "Unexpected exception.");
394 lane.removeGTU(this, removeFromParentLink, pos);
395 removeFromParentLink = false;
396 }
397 }
398 this.crossSections.clear();
399 }
400
401
402
403
404
405
406
407
408
409 public void reinit(final Set<DirectedLanePosition> initialLongitudinalPositions)
410 throws NetworkException, SimRuntimeException, GTUException, OTSGeometryException
411 {
412 init(getStrategicalPlanner(), initialLongitudinalPositions, Speed.ZERO);
413 }
414
415
416 @Override
417 public synchronized void changeLaneInstantaneously(final LateralDirectionality laneChangeDirection) throws GTUException
418 {
419
420
421 DirectedLanePosition from = getReferencePosition();
422
423
424 GTUDirectionality direction = getDirection(from.getLane());
425 Set<Lane> adjLanes = from.getLane().accessibleAdjacentLanesPhysical(laneChangeDirection, getGTUType(), direction);
426 Lane adjLane = adjLanes.iterator().next();
427 Length position = adjLane.position(from.getLane().fraction(from.getPosition()));
428 leaveAllLanes();
429 enterLaneRecursive(new LaneDirection(adjLane, direction), position, 0);
430
431
432 this.referencePositionTime = Double.NaN;
433 this.cachedPositions.clear();
434
435
436 this.fireTimedEvent(
437 LaneBasedGTU.LANE_CHANGE_EVENT, new Object[] {getId(), laneChangeDirection.name(),
438 from.getLane().getParentLink().getId(), from.getLane().getId(), from.getPosition()},
439 getSimulator().getSimulatorTime());
440
441 }
442
443
444
445
446
447
448
449
450 private void enterLaneRecursive(final LaneDirection lane, final Length position, final int dir) throws GTUException
451 {
452 List<Lane> lanes = new ArrayList<>();
453 lanes.add(lane.getLane());
454 int index = dir > 0 ? this.crossSections.size() : 0;
455 this.crossSections.add(index, new CrossSection(lanes, lane.getDirection()));
456 lane.getLane().addGTU(this, position);
457
458
459 if (dir < 1)
460 {
461 Length rear = lane.getDirection().isPlus() ? position.plus(getRear().getDx()) : position.minus(getRear().getDx());
462 Length before = null;
463 if (lane.getDirection().isPlus() && rear.si < 0.0)
464 {
465 before = rear.neg();
466 }
467 else if (lane.getDirection().isMinus() && rear.si > lane.getLength().si)
468 {
469 before = rear.minus(lane.getLength());
470 }
471 if (before != null)
472 {
473 GTUDirectionality upDir = lane.getDirection();
474 ImmutableMap<Lane, GTUDirectionality> upstream = lane.getLane().upstreamLanes(upDir, getGTUType());
475 if (!upstream.isEmpty())
476 {
477 Lane upLane = null;
478 for (Lane nextUp : upstream.keySet())
479 {
480 for (CrossSection crossSection : this.crossSections)
481 {
482 if (crossSection.getLanes().contains(nextUp))
483 {
484
485
486 upLane = nextUp;
487 break;
488 }
489 }
490 }
491 if (upLane == null)
492 {
493
494
495 upLane = upstream.keySet().iterator().next();
496 }
497 upDir = upstream.get(upLane);
498 LaneDirection next = new LaneDirection(upLane, upDir);
499 Length nextPos = upDir.isPlus() ? next.getLength().minus(before).minus(getRear().getDx())
500 : before.plus(getRear().getDx());
501 enterLaneRecursive(next, nextPos, -1);
502 }
503 }
504 }
505
506
507 if (dir > -1)
508 {
509 Length front =
510 lane.getDirection().isPlus() ? position.plus(getFront().getDx()) : position.minus(getFront().getDx());
511 Length passed = null;
512 if (lane.getDirection().isPlus() && front.si > lane.getLength().si)
513 {
514 passed = front.minus(lane.getLength());
515 }
516 else if (lane.getDirection().isMinus() && front.si < 0.0)
517 {
518 passed = front.neg();
519 }
520 if (passed != null)
521 {
522 LaneDirection next = lane.getNextLaneDirection(this);
523 Length nextPos = next.getDirection().isPlus() ? passed.minus(getFront().getDx())
524 : next.getLength().minus(passed).plus(getFront().getDx());
525 enterLaneRecursive(next, nextPos, 1);
526 }
527 }
528 }
529
530
531
532
533
534
535 @Override
536 @SuppressWarnings("checkstyle:designforextension")
537 public synchronized void initLaneChange(final LateralDirectionality laneChangeDirection) throws GTUException
538 {
539 List<CrossSection> newLanes = new ArrayList<>();
540 int index = laneChangeDirection.isLeft() ? 0 : 1;
541 int numRegistered = 0;
542 DirectedPoint point = getLocation();
543 Map<Lane, Double> addToLanes = new LinkedHashMap<>();
544 for (CrossSection crossSection : this.crossSections)
545 {
546 List<Lane> resultingLanes = new ArrayList<>();
547 Lane lane = crossSection.getLanes().get(0);
548 resultingLanes.add(lane);
549 Set<Lane> laneSet = lane.accessibleAdjacentLanesLegal(laneChangeDirection, getGTUType(), getDirection(lane));
550 if (laneSet.size() > 0)
551 {
552 numRegistered++;
553 Lane adjacentLane = laneSet.iterator().next();
554 double f = adjacentLane.getCenterLine().projectFractional(null, null, point.x, point.y, FractionalFallback.NaN);
555 if (Double.isNaN(f))
556 {
557
558
559
560 Length pos = position(lane, getReference());
561 addToLanes.put(adjacentLane, pos.si < lane.getLength().si / 2 ? 0.0 : 1.0);
562 }
563 else
564 {
565 f = crossSection.getDirection().isPlus() ? f : 1.0 - f;
566 addToLanes.put(adjacentLane, adjacentLane.getLength().times(f).si / adjacentLane.getLength().si);
567 }
568 resultingLanes.add(index, adjacentLane);
569 }
570 newLanes.add(new CrossSection(resultingLanes, crossSection.getDirection()));
571 }
572 Throw.when(numRegistered == 0, GTUException.class, "Gtu %s starting %s lane change, but no adjacent lane found.",
573 getId(), laneChangeDirection);
574 this.crossSections.clear();
575 this.crossSections.addAll(newLanes);
576 for (Entry<Lane, Double> entry : addToLanes.entrySet())
577 {
578 entry.getKey().addGTU(this, entry.getValue());
579 }
580 this.referenceLaneIndex = 1 - index;
581 }
582
583
584
585
586
587
588 @SuppressWarnings("checkstyle:designforextension")
589 protected synchronized void finalizeLaneChange(final LateralDirectionality laneChangeDirection) throws GTUException
590 {
591 List<CrossSection> newLanes = new ArrayList<>();
592 Lane fromLane = null;
593 Length fromPosition = null;
594 GTUDirectionality fromDirection = null;
595 for (CrossSection crossSection : this.crossSections)
596 {
597 Lane lane = crossSection.getLanes().get(this.referenceLaneIndex);
598 if (lane != null)
599 {
600 Length pos = position(lane, RelativePosition.REFERENCE_POSITION);
601 if (0.0 <= pos.si && pos.si <= lane.getLength().si)
602 {
603 fromLane = lane;
604 fromPosition = pos;
605 fromDirection = getDirection(lane);
606 }
607 lane.removeGTU(this, false, pos);
608 }
609 List<Lane> remainingLane = new ArrayList<>();
610 remainingLane.add(crossSection.getLanes().get(1 - this.referenceLaneIndex));
611 newLanes.add(new CrossSection(remainingLane, crossSection.getDirection()));
612 }
613 this.crossSections.clear();
614 this.crossSections.addAll(newLanes);
615 this.referenceLaneIndex = 0;
616
617 Throw.when(fromLane == null, RuntimeException.class, "No from lane for lane change event.");
618 DirectedLanePosition from;
619 try
620 {
621 from = new DirectedLanePosition(fromLane, fromPosition, fromDirection);
622 }
623 catch (GTUException exception)
624 {
625 throw new RuntimeException(exception);
626 }
627
628
629
630 this.fireTimedEvent(
631 LaneBasedGTU.LANE_CHANGE_EVENT, new Object[] {getId(), laneChangeDirection.name(),
632 from.getLane().getParentLink().getId(), from.getLane().getId(), from.getPosition()},
633 getSimulator().getSimulatorTime());
634
635 this.finalizeLaneChangeEvent = null;
636 }
637
638
639 @Override
640 public void setFinalizeLaneChangeEvent(final SimEventInterface<Duration> event)
641 {
642 this.finalizeLaneChangeEvent = event;
643 }
644
645
646 @Override
647 public final synchronized GTUDirectionality getDirection(final Lane lane) throws GTUException
648 {
649 for (CrossSection crossSection : this.crossSections)
650 {
651 if (crossSection.getLanes().contains(lane))
652 {
653 return crossSection.getDirection();
654 }
655 }
656 throw new GTUException("getDirection: GTU does not contain " + lane);
657 }
658
659
660 @Override
661 @SuppressWarnings("checkstyle:designforextension")
662 protected synchronized boolean move(final DirectedPoint fromLocation)
663 throws SimRuntimeException, GTUException, OperationalPlanException, NetworkException, ParameterException
664 {
665 if (this.isDestroyed())
666 {
667 return false;
668 }
669 try
670 {
671 if (this.crossSections.isEmpty())
672 {
673 destroy();
674 return false;
675 }
676
677
678
679
680 cancelAllEvents();
681
682
683
684 try
685 {
686 boolean error = super.move(fromLocation);
687 if (error)
688 {
689 return error;
690 }
691 }
692 catch (Exception exception)
693 {
694 System.err.println(exception.getMessage());
695 System.err.println(" GTU " + this + " DESTROYED AND REMOVED FROM THE SIMULATION");
696 this.destroy();
697 this.cancelAllEvents();
698 return true;
699 }
700
701 DirectedLanePosition dlp = getReferencePosition();
702
703 scheduleEnterEvent();
704 scheduleLeaveEvent();
705
706
707 for (CrossSection crossSection : this.crossSections)
708 {
709 for (Lane lane : crossSection.getLanes())
710 {
711 scheduleTriggers(lane, crossSection.getDirection());
712 }
713 }
714
715 fireTimedEvent(LaneBasedGTU.LANEBASED_MOVE_EVENT,
716 new Object[] {getId(), new OTSPoint3D(fromLocation).doubleVector(PositionUnit.METER),
717 OTSPoint3D.direction(fromLocation, DirectionUnit.EAST_RADIAN), getSpeed(), getAcceleration(),
718 getTurnIndicatorStatus().name(), getOdometer(), dlp.getLane().getParentLink().getId(),
719 dlp.getLane().getId(), dlp.getPosition(), dlp.getGtuDirection().name()},
720 getSimulator().getSimulatorTime());
721
722 return false;
723
724 }
725 catch (Exception ex)
726 {
727 try
728 {
729 getErrorHandler().handle(this, ex);
730 }
731 catch (Exception exception)
732 {
733 throw new GTUException(exception);
734 }
735 return true;
736 }
737
738 }
739
740
741
742
743 private void cancelAllEvents()
744 {
745 if (this.pendingEnterTrigger != null)
746 {
747 getSimulator().cancelEvent(this.pendingEnterTrigger);
748 }
749 if (this.pendingLeaveTrigger != null)
750 {
751 getSimulator().cancelEvent(this.pendingLeaveTrigger);
752 }
753 if (this.finalizeLaneChangeEvent != null)
754 {
755 getSimulator().cancelEvent(this.finalizeLaneChangeEvent);
756 }
757 for (SimEventInterface<Duration> event : this.sensorEvents)
758 {
759 if (event.getAbsoluteExecutionTime().gt(getSimulator().getSimulatorTime()))
760 {
761 getSimulator().cancelEvent(event);
762 }
763 }
764 this.sensorEvents.clear();
765 }
766
767
768
769
770
771
772
773
774 protected void scheduleEnterEvent() throws GTUException, OperationalPlanException, SimRuntimeException
775 {
776 CrossSection lastCrossSection = this.crossSections.get(this.crossSections.size() - 1);
777
778 Length remain = remainingEventDistance();
779 Lane lane = lastCrossSection.getLanes().get(this.referenceLaneIndex);
780 Length position = position(lane, getFront());
781 boolean possiblyNearNextSection =
782 lastCrossSection.getDirection().isPlus() ? lane.getLength().minus(position).lt(remain) : position.lt(remain);
783 if (possiblyNearNextSection)
784 {
785 CrossSectionLink link = lastCrossSection.getLanes().get(0).getParentLink();
786 OTSLine3D enterLine = lastCrossSection.getDirection().isPlus() ? link.getEndLine() : link.getStartLine();
787 Time enterTime = timeAtLine(enterLine, getFront());
788 if (enterTime != null)
789 {
790 if (enterTime.lt(getSimulator().getSimulatorAbsTime()))
791 {
792 System.err.println(
793 "Time travel? enterTime=" + enterTime + "; simulator time=" + getSimulator().getSimulatorAbsTime());
794 enterTime = getSimulator().getSimulatorAbsTime();
795 }
796 this.pendingEnterTrigger =
797 getSimulator().scheduleEventAbsTime(enterTime, this, this, "enterCrossSection", null);
798 }
799 }
800 }
801
802
803
804
805
806
807
808 protected synchronized void enterCrossSection() throws GTUException, OperationalPlanException, SimRuntimeException
809 {
810 CrossSection lastCrossSection = this.crossSections.get(this.crossSections.size() - 1);
811 LaneDirection laneDirection =
812 new LaneDirection(lastCrossSection.getLanes().get(this.referenceLaneIndex), lastCrossSection.getDirection());
813 LaneDirection nextLaneDirection = laneDirection.getNextLaneDirection(this);
814 if (nextLaneDirection == null)
815 {
816 forceLaneChangeFinalization();
817 return;
818 }
819 double insertFraction = nextLaneDirection.getDirection().isPlus() ? 0.0 : 1.0;
820 List<Lane> nextLanes = new ArrayList<>();
821 for (int i = 0; i < lastCrossSection.getLanes().size(); i++)
822 {
823 if (i == this.referenceLaneIndex)
824 {
825 nextLanes.add(nextLaneDirection.getLane());
826 }
827 else
828 {
829 Lane lane = lastCrossSection.getLanes().get(i);
830 ImmutableMap<Lane, GTUDirectionality> lanes = lane.downstreamLanes(laneDirection.getDirection(), getGTUType());
831 if (lanes.size() == 1)
832 {
833 Lane nextLane = lanes.keySet().iterator().next();
834 nextLanes.add(nextLane);
835 }
836 else
837 {
838 boolean added = false;
839 for (Lane nextLane : lanes.keySet())
840 {
841 if (nextLane.getParentLink().equals(nextLaneDirection.getLane().getParentLink())
842 && nextLane.accessibleAdjacentLanesPhysical(
843 this.referenceLaneIndex == 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT,
844 getGTUType(), nextLaneDirection.getDirection()).contains(nextLaneDirection.getLane()))
845 {
846 nextLanes.add(nextLane);
847 added = true;
848 break;
849 }
850 }
851 if (!added)
852 {
853 forceLaneChangeFinalization();
854 return;
855 }
856 }
857 }
858 }
859 this.crossSections.add(new CrossSection(nextLanes, nextLaneDirection.getDirection()));
860 for (Lane lane : nextLanes)
861 {
862 lane.addGTU(this, insertFraction);
863 }
864 this.pendingEnterTrigger = null;
865 scheduleEnterEvent();
866 for (Lane lane : nextLanes)
867 {
868 scheduleTriggers(lane, nextLaneDirection.getDirection());
869 }
870 }
871
872
873
874
875
876
877
878
879
880 private void forceLaneChangeFinalization() throws GTUException, OperationalPlanException, SimRuntimeException
881 {
882 if (this.finalizeLaneChangeEvent != null)
883 {
884
885 SimEventInterface<Duration> tmp = this.finalizeLaneChangeEvent;
886 finalizeLaneChange(this.referenceLaneIndex == 0 ? LateralDirectionality.RIGHT : LateralDirectionality.LEFT);
887 getSimulator().cancelEvent(tmp);
888 enterCrossSection();
889 }
890
891 }
892
893
894
895
896
897
898
899
900 protected void scheduleLeaveEvent() throws GTUException, OperationalPlanException, SimRuntimeException
901 {
902 if (this.crossSections.isEmpty())
903 {
904 CategoryLogger.always().error("GTU {} has empty crossSections", this);
905 return;
906 }
907 CrossSection firstCrossSection = this.crossSections.get(0);
908
909 boolean possiblyNearNextSection =
910 !getReferencePosition().getLane().equals(firstCrossSection.getLanes().get(this.referenceLaneIndex));
911 if (!possiblyNearNextSection)
912 {
913 Length remain = remainingEventDistance();
914 Lane lane = firstCrossSection.getLanes().get(this.referenceLaneIndex);
915 Length position = position(lane, getRear());
916 possiblyNearNextSection = firstCrossSection.getDirection().isPlus() ? lane.getLength().minus(position).lt(remain)
917 : position.lt(remain);
918 }
919 if (possiblyNearNextSection)
920 {
921 CrossSectionLink link = firstCrossSection.getLanes().get(0).getParentLink();
922 OTSLine3D leaveLine = firstCrossSection.getDirection().isPlus() ? link.getEndLine() : link.getStartLine();
923 Time leaveTime = timeAtLine(leaveLine, getRear());
924 if (leaveTime == null)
925 {
926
927 Lane lane = this.crossSections.get(0).getLanes().get(this.referenceLaneIndex);
928 Length pos = position(lane, getRear());
929 if (pos.gt(lane.getLength()))
930 {
931 pos = position(lane, getRear());
932 this.pendingLeaveTrigger = getSimulator().scheduleEventNow(this, this, "leaveCrossSection", null);
933 getSimulator().getLogger().always().info("Forcing leave for GTU {} on lane {}", getId(), lane.getFullId());
934 }
935 }
936 if (leaveTime != null)
937 {
938 if (leaveTime.lt(getSimulator().getSimulatorAbsTime()))
939 {
940 System.err.println(
941 "Time travel? leaveTime=" + leaveTime + "; simulator time=" + getSimulator().getSimulatorAbsTime());
942 leaveTime = getSimulator().getSimulatorAbsTime();
943 }
944 this.pendingLeaveTrigger =
945 getSimulator().scheduleEventAbsTime(leaveTime, this, this, "leaveCrossSection", null);
946 }
947 }
948 }
949
950
951
952
953
954
955
956
957 protected synchronized void leaveCrossSection() throws GTUException, OperationalPlanException, SimRuntimeException
958 {
959
960 List<Lane> lanes = this.crossSections.get(0).getLanes();
961 for (int i = 0; i < lanes.size(); i++)
962 {
963 Lane lane = lanes.get(i);
964 if (lane != null)
965 {
966 lane.removeGTU(this, i == lanes.size() - 1, position(lane, getReference()));
967 }
968 }
969 this.crossSections.remove(0);
970 this.pendingLeaveTrigger = null;
971 scheduleLeaveEvent();
972 }
973
974
975
976
977
978
979
980
981
982 protected void scheduleTriggers(final Lane lane, final GTUDirectionality direction)
983 throws GTUException, OperationalPlanException, SimRuntimeException
984 {
985 double min;
986 double max;
987 Length remain = remainingEventDistance();
988 if (direction.isPlus())
989 {
990 min = position(lane, getRear()).si;
991 max = min + remain.si + getLength().si;
992 }
993 else
994 {
995 max = position(lane, getRear()).si;
996 min = max - remain.si - getLength().si;
997 }
998 SortedMap<Double, List<SingleSensor>> sensors = lane.getSensorMap(getGTUType(), direction).subMap(min, max);
999 for (List<SingleSensor> list : sensors.values())
1000 {
1001 for (SingleSensor sensor : list)
1002 {
1003 RelativePosition pos = this.getRelativePositions().get(sensor.getPositionType());
1004 Time time = timeAtLine(sensor.getGeometry(), pos);
1005 if (time != null)
1006 {
1007 this.sensorEvents
1008 .add(getSimulator().scheduleEventAbsTime(time, this, sensor, "trigger", new Object[] {this}));
1009 }
1010 }
1011 }
1012 }
1013
1014
1015
1016
1017
1018
1019 private Length remainingEventDistance() throws OperationalPlanException
1020 {
1021 if (getOperationalPlan() instanceof LaneBasedOperationalPlan)
1022 {
1023 LaneBasedOperationalPlan plan = (LaneBasedOperationalPlan) getOperationalPlan();
1024 return plan.getTotalLength().minus(plan.getTraveledDistance(getSimulator().getSimulatorAbsTime()))
1025 .plus(eventMargin);
1026 }
1027 return getOperationalPlan().getTotalLength().plus(eventMargin);
1028 }
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039 private Time timeAtLine(final OTSLine3D line, final RelativePosition relativePosition) throws GTUException
1040 {
1041 Throw.when(line.size() != 2, IllegalArgumentException.class, "Line to cross with path should have 2 points.");
1042 OTSLine3D path = getOperationalPlan().getPath();
1043 OTSPoint3D[] points;
1044 double adjust;
1045 if (relativePosition.getDx().gt0())
1046 {
1047
1048 points = new OTSPoint3D[path.size() + 1];
1049 System.arraycopy(path.getPoints(), 0, points, 0, path.size());
1050 points[path.size()] = new OTSPoint3D(path.getLocationExtendedSI(path.getLengthSI() + relativePosition.getDx().si));
1051 adjust = -relativePosition.getDx().si;
1052 }
1053 else if (relativePosition.getDx().lt0())
1054 {
1055 points = new OTSPoint3D[path.size() + 1];
1056 System.arraycopy(path.getPoints(), 0, points, 1, path.size());
1057 points[0] = new OTSPoint3D(path.getLocationExtendedSI(relativePosition.getDx().si));
1058 adjust = 0.0;
1059 }
1060 else
1061 {
1062 points = path.getPoints();
1063 adjust = 0.0;
1064 }
1065
1066
1067 double cumul = 0.0;
1068 for (int i = 0; i < points.length - 1; i++)
1069 {
1070 OTSPoint3D intersect;
1071 try
1072 {
1073 intersect = OTSPoint3D.intersectionOfLineSegments(points[i], points[i + 1], line.get(0), line.get(1));
1074 }
1075 catch (OTSGeometryException exception)
1076 {
1077
1078 throw new RuntimeException("Unexpected exception while obtaining points from line to cross.", exception);
1079 }
1080 if (intersect != null)
1081 {
1082 cumul += points[i].distanceSI(intersect);
1083 cumul += adjust;
1084
1085 if (cumul < 0.0)
1086 {
1087
1088
1089 return null;
1090 }
1091 if (cumul <= getOperationalPlan().getTotalLength().si)
1092 {
1093 return getOperationalPlan().timeAtDistance(Length.instantiateSI(cumul));
1094 }
1095
1096 return null;
1097 }
1098 else if (i < points.length - 2)
1099 {
1100 cumul += points[i].distanceSI(points[i + 1]);
1101 }
1102 }
1103
1104 return null;
1105 }
1106
1107
1108 @Override
1109 public final Map<Lane, Length> positions(final RelativePosition relativePosition) throws GTUException
1110 {
1111 return positions(relativePosition, getSimulator().getSimulatorAbsTime());
1112 }
1113
1114
1115 @Override
1116 public final Map<Lane, Length> positions(final RelativePosition relativePosition, final Time when) throws GTUException
1117 {
1118 Map<Lane, Length> positions = new LinkedHashMap<>();
1119 for (CrossSection crossSection : this.crossSections.get(when))
1120 {
1121 for (Lane lane : crossSection.getLanes())
1122 {
1123 positions.put(lane, position(lane, relativePosition, when));
1124 }
1125 }
1126 return positions;
1127 }
1128
1129
1130 @Override
1131 public final Length position(final Lane lane, final RelativePosition relativePosition) throws GTUException
1132 {
1133 return position(lane, relativePosition, getSimulator().getSimulatorAbsTime());
1134 }
1135
1136
1137 private double cachePositionsTime = Double.NaN;
1138
1139
1140 private OperationalPlan cacheOperationalPlan = null;
1141
1142
1143 private MultiKeyMap<Length> cachedPositions = new MultiKeyMap<>(Lane.class, RelativePosition.class);
1144
1145
1146 @Override
1147 @SuppressWarnings("checkstyle:designforextension")
1148 public Length position(final Lane lane, final RelativePosition relativePosition, final Time when) throws GTUException
1149 {
1150 synchronized (this)
1151 {
1152 OperationalPlan plan = getOperationalPlan(when);
1153 if (CACHING)
1154 {
1155 if (when.si == this.cachePositionsTime && plan == this.cacheOperationalPlan)
1156 {
1157 Length l = this.cachedPositions.get(lane, relativePosition);
1158 if (l != null && (!Double.isNaN(l.si)))
1159 {
1160 CACHED_POSITION++;
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173 return l;
1174 }
1175 }
1176 if (when.si != this.cachePositionsTime || plan != this.cacheOperationalPlan)
1177 {
1178 this.cachePositionsTime = Double.NaN;
1179 this.cacheOperationalPlan = null;
1180 this.cachedPositions.clear();
1181 }
1182 }
1183 NON_CACHED_POSITION++;
1184
1185 synchronized (this.lock)
1186 {
1187 List<CrossSection> whenCrossSections = this.crossSections.get(when);
1188 double loc = Double.NaN;
1189
1190 try
1191 {
1192 int crossSectionIndex = -1;
1193 int lateralIndex = -1;
1194 for (int i = 0; i < whenCrossSections.size(); i++)
1195 {
1196 if (whenCrossSections.get(i).getLanes().contains(lane))
1197 {
1198 crossSectionIndex = i;
1199 lateralIndex = whenCrossSections.get(i).getLanes().indexOf(lane);
1200 break;
1201 }
1202 }
1203 Throw.when(lateralIndex == -1, GTUException.class, "GTU %s is not on lane %s.", this, lane);
1204
1205 DirectedPoint p = plan.getLocation(when, relativePosition);
1206 double f = lane.getCenterLine().projectFractional(null, null, p.x, p.y, FractionalFallback.NaN);
1207 if (!Double.isNaN(f))
1208 {
1209 loc = f * lane.getLength().si;
1210 }
1211 else
1212 {
1213
1214
1215 double distance = 0.0;
1216 for (int i = crossSectionIndex - 1; i >= 0; i--)
1217 {
1218 Lane tryLane = whenCrossSections.get(i).getLanes().get(lateralIndex);
1219 f = tryLane.getCenterLine().projectFractional(null, null, p.x, p.y, FractionalFallback.NaN);
1220 if (!Double.isNaN(f))
1221 {
1222 f = whenCrossSections.get(i).getDirection() == GTUDirectionality.DIR_PLUS ? 1 - f : f;
1223 loc = distance - f * tryLane.getLength().si;
1224 break;
1225 }
1226 distance -= tryLane.getLength().si;
1227 }
1228
1229 if (Double.isNaN(loc))
1230 {
1231 distance = lane.getLength().si;
1232 for (int i = crossSectionIndex + 1; i < whenCrossSections.size(); i++)
1233 {
1234 Lane tryLane = whenCrossSections.get(i).getLanes().get(lateralIndex);
1235 f = tryLane.getCenterLine().projectFractional(null, null, p.x, p.y, FractionalFallback.NaN);
1236 if (!Double.isNaN(f))
1237 {
1238 f = whenCrossSections.get(i).getDirection() == GTUDirectionality.DIR_PLUS ? f : 1 - f;
1239 loc = distance + f * tryLane.getLength().si;
1240 break;
1241 }
1242 distance += tryLane.getLength().si;
1243 }
1244 }
1245
1246 }
1247
1248 if (Double.isNaN(loc))
1249 {
1250
1251
1252
1253 f = lane.getCenterLine().projectFractional(null, null, p.x, p.y, FractionalFallback.ENDPOINT);
1254 if (Double.isNaN(f))
1255 {
1256 CategoryLogger.always().error("GTU {} at location {} cannot project itself onto {}; p is {}", this,
1257 getLocation(), lane.getCenterLine(), p);
1258 plan.getLocation(when, relativePosition);
1259 }
1260 loc = lane.getLength().si * f;
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276 }
1277 }
1278 catch (Exception e)
1279 {
1280
1281 throw new GTUException(e);
1282 }
1283
1284 Length length = Length.instantiateSI(loc);
1285 if (CACHING)
1286 {
1287 this.cachedPositions.put(length, lane, relativePosition);
1288 this.cachePositionsTime = when.si;
1289 this.cacheOperationalPlan = plan;
1290 }
1291 return length;
1292 }
1293 }
1294 }
1295
1296
1297 @Override
1298 @SuppressWarnings("checkstyle:designforextension")
1299 public DirectedLanePosition getReferencePosition() throws GTUException
1300 {
1301 synchronized (this)
1302 {
1303 if (this.referencePositionTime == getSimulator().getSimulatorAbsTime().si)
1304 {
1305 return this.cachedReferencePosition;
1306 }
1307 Lane refLane = null;
1308 for (CrossSection crossSection : this.crossSections)
1309 {
1310 Lane lane = crossSection.getLanes().get(this.referenceLaneIndex);
1311 double fraction = fractionalPosition(lane, getReference());
1312 if (fraction >= 0.0 && fraction <= 1.0)
1313 {
1314 refLane = lane;
1315 break;
1316 }
1317 }
1318 if (refLane != null)
1319 {
1320 this.cachedReferencePosition =
1321 new DirectedLanePosition(refLane, position(refLane, getReference()), this.getDirection(refLane));
1322 this.referencePositionTime = getSimulator().getSimulatorAbsTime().si;
1323 return this.cachedReferencePosition;
1324 }
1325 CategoryLogger.always().error("The reference point of GTU {} is not on any of the lanes on which it is registered",
1326 this);
1327 for (CrossSection crossSection : this.crossSections)
1328 {
1329 Lane lane = crossSection.getLanes().get(this.referenceLaneIndex);
1330 double fraction = fractionalPosition(lane, getReference());
1331 CategoryLogger.always().error("\tGTU is on lane \"{}\" at fraction {}", lane, fraction);
1332 }
1333 throw new GTUException(
1334 "The reference point of GTU " + this + " is not on any of the lanes on which it is registered");
1335 }
1336 }
1337
1338
1339 @Override
1340 public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition) throws GTUException
1341 {
1342 return fractionalPositions(relativePosition, getSimulator().getSimulatorAbsTime());
1343 }
1344
1345
1346 @Override
1347 public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition, final Time when)
1348 throws GTUException
1349 {
1350 Map<Lane, Double> positions = new LinkedHashMap<>();
1351 for (CrossSection crossSection : this.crossSections)
1352 {
1353 for (Lane lane : crossSection.getLanes())
1354 {
1355 positions.put(lane, fractionalPosition(lane, relativePosition, when));
1356 }
1357 }
1358 return positions;
1359 }
1360
1361
1362 @Override
1363 public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition, final Time when)
1364 throws GTUException
1365 {
1366 return position(lane, relativePosition, when).getSI() / lane.getLength().getSI();
1367 }
1368
1369
1370 @Override
1371 public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition) throws GTUException
1372 {
1373 return position(lane, relativePosition).getSI() / lane.getLength().getSI();
1374 }
1375
1376
1377 @Override
1378 public final void addTrigger(final Lane lane, final SimEventInterface<Duration> event)
1379 {
1380 throw new UnsupportedOperationException("Method addTrigger is not supported.");
1381 }
1382
1383
1384
1385
1386
1387 public void setVehicleModel(final VehicleModel vehicleModel)
1388 {
1389 this.vehicleModel = vehicleModel;
1390 }
1391
1392
1393 @Override
1394 public VehicleModel getVehicleModel()
1395 {
1396 return this.vehicleModel;
1397 }
1398
1399
1400 @Override
1401 @SuppressWarnings("checkstyle:designforextension")
1402 public void destroy()
1403 {
1404 DirectedLanePosition dlp = null;
1405 try
1406 {
1407 dlp = getReferencePosition();
1408 }
1409 catch (GTUException e)
1410 {
1411
1412 }
1413 DirectedPoint location = this.getOperationalPlan() == null ? new DirectedPoint(0.0, 0.0, 0.0) : getLocation();
1414 synchronized (this.lock)
1415 {
1416 for (CrossSection crossSection : this.crossSections)
1417 {
1418 boolean removeFromParentLink = true;
1419 for (Lane lane : crossSection.getLanes())
1420 {
1421 Length position;
1422 try
1423 {
1424 position = position(lane, getReference());
1425 }
1426 catch (GTUException exception)
1427 {
1428
1429
1430 throw new RuntimeException(exception);
1431 }
1432 lane.removeGTU(this, removeFromParentLink, position);
1433 removeFromParentLink = false;
1434 }
1435 }
1436 }
1437 if (dlp != null && dlp.getLane() != null)
1438 {
1439 Lane referenceLane = dlp.getLane();
1440 fireTimedEvent(LaneBasedGTU.LANEBASED_DESTROY_EVENT,
1441 new Object[] {getId(), new OTSPoint3D(location).doubleVector(PositionUnit.METER),
1442 OTSPoint3D.direction(location, DirectionUnit.EAST_RADIAN), getOdometer(),
1443 referenceLane.getParentLink().getId(), referenceLane.getId(), dlp.getPosition(),
1444 dlp.getGtuDirection().name()},
1445 getSimulator().getSimulatorTime());
1446 }
1447 else
1448 {
1449 fireTimedEvent(LaneBasedGTU.LANEBASED_DESTROY_EVENT,
1450 new Object[] {getId(), new OTSPoint3D(location).doubleVector(PositionUnit.METER),
1451 OTSPoint3D.direction(location, DirectionUnit.EAST_RADIAN), getOdometer(), null, null, null, null},
1452 getSimulator().getSimulatorTime());
1453 }
1454 cancelAllEvents();
1455
1456 super.destroy();
1457 }
1458
1459
1460 @Override
1461 public final Bounds getBounds()
1462 {
1463 double dx = 0.5 * getLength().doubleValue();
1464 double dy = 0.5 * getWidth().doubleValue();
1465 return new Bounds(new Point3d(-dx, -dy, 0.0), new Point3d(dx, dy, 0.0));
1466 }
1467
1468
1469 @Override
1470 public final LaneBasedStrategicalPlanner getStrategicalPlanner()
1471 {
1472 return (LaneBasedStrategicalPlanner) super.getStrategicalPlanner();
1473 }
1474
1475
1476 @Override
1477 public final LaneBasedStrategicalPlanner getStrategicalPlanner(final Time time)
1478 {
1479 return (LaneBasedStrategicalPlanner) super.getStrategicalPlanner(time);
1480 }
1481
1482
1483 @Override
1484 public RoadNetwork getNetwork()
1485 {
1486 return (RoadNetwork) super.getPerceivableContext();
1487 }
1488
1489
1490 @Override
1491 public Speed getDesiredSpeed()
1492 {
1493 synchronized (this)
1494 {
1495 Time simTime = getSimulator().getSimulatorAbsTime();
1496 if (this.desiredSpeedTime == null || this.desiredSpeedTime.si < simTime.si)
1497 {
1498 InfrastructurePerception infra =
1499 getTacticalPlanner().getPerception().getPerceptionCategoryOrNull(InfrastructurePerception.class);
1500 SpeedLimitInfo speedInfo;
1501 if (infra == null)
1502 {
1503 speedInfo = new SpeedLimitInfo();
1504 speedInfo.addSpeedInfo(SpeedLimitTypes.MAX_VEHICLE_SPEED, getMaximumSpeed());
1505 }
1506 else
1507 {
1508
1509 speedInfo = infra.getSpeedLimitProspect(RelativeLane.CURRENT).getSpeedLimitInfo(Length.ZERO);
1510 }
1511 this.cachedDesiredSpeed =
1512 Try.assign(() -> getTacticalPlanner().getCarFollowingModel().desiredSpeed(getParameters(), speedInfo),
1513 "Parameter exception while obtaining the desired speed.");
1514 this.desiredSpeedTime = simTime;
1515 }
1516 return this.cachedDesiredSpeed;
1517 }
1518 }
1519
1520
1521 @Override
1522 public Acceleration getCarFollowingAcceleration()
1523 {
1524 synchronized (this)
1525 {
1526 Time simTime = getSimulator().getSimulatorAbsTime();
1527 if (this.carFollowingAccelerationTime == null || this.carFollowingAccelerationTime.si < simTime.si)
1528 {
1529 LanePerception perception = getTacticalPlanner().getPerception();
1530
1531 EgoPerception<?, ?> ego = perception.getPerceptionCategoryOrNull(EgoPerception.class);
1532 Throw.whenNull(ego, "EgoPerception is required to determine the speed.");
1533 Speed speed = ego.getSpeed();
1534
1535 InfrastructurePerception infra = perception.getPerceptionCategoryOrNull(InfrastructurePerception.class);
1536 Throw.whenNull(infra, "InfrastructurePerception is required to determine the desired speed.");
1537 SpeedLimitInfo speedInfo = infra.getSpeedLimitProspect(RelativeLane.CURRENT).getSpeedLimitInfo(Length.ZERO);
1538
1539 NeighborsPerception neighbors = perception.getPerceptionCategoryOrNull(NeighborsPerception.class);
1540 Throw.whenNull(neighbors, "NeighborsPerception is required to determine the car-following acceleration.");
1541 PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders = neighbors.getLeaders(RelativeLane.CURRENT);
1542
1543 this.cachedCarFollowingAcceleration =
1544 Try.assign(() -> getTacticalPlanner().getCarFollowingModel().followingAcceleration(getParameters(),
1545 speed, speedInfo, leaders), "Parameter exception while obtaining the desired speed.");
1546 this.carFollowingAccelerationTime = simTime;
1547 }
1548 return this.cachedCarFollowingAcceleration;
1549 }
1550 }
1551
1552
1553 @Override
1554 public final TurnIndicatorStatus getTurnIndicatorStatus()
1555 {
1556 return this.turnIndicatorStatus.get();
1557 }
1558
1559
1560 @Override
1561 public final TurnIndicatorStatus getTurnIndicatorStatus(final Time time)
1562 {
1563 return this.turnIndicatorStatus.get(time);
1564 }
1565
1566
1567 @Override
1568 public final void setTurnIndicatorStatus(final TurnIndicatorStatus turnIndicatorStatus)
1569 {
1570 this.turnIndicatorStatus.set(turnIndicatorStatus);
1571 }
1572
1573
1574 @Override
1575 public Length getLateralPosition(final Lane lane) throws GTUException
1576 {
1577 OperationalPlan plan = getOperationalPlan();
1578 if (plan instanceof LaneBasedOperationalPlan && !((LaneBasedOperationalPlan) plan).isDeviative())
1579 {
1580 return Length.ZERO;
1581 }
1582 DirectedLanePosition ref = getReferencePosition();
1583 int latIndex = -1;
1584 int longIndex = -1;
1585 for (int i = 0; i < this.crossSections.size(); i++)
1586 {
1587 List<Lane> lanes = this.crossSections.get(i).getLanes();
1588 if (lanes.contains(lane))
1589 {
1590 latIndex = lanes.indexOf(lane);
1591 }
1592 if (lanes.contains(ref.getLane()))
1593 {
1594 longIndex = i;
1595 }
1596 }
1597 Throw.when(latIndex == -1 || longIndex == -1, GTUException.class, "GTU %s is not on %s", getId(), lane);
1598 Lane refCrossSectionLane = this.crossSections.get(longIndex).getLanes().get(latIndex);
1599 DirectedPoint loc = getLocation();
1600 double f = refCrossSectionLane.getCenterLine().projectOrthogonal(loc.x, loc.y);
1601 DirectedPoint p = Try.assign(() -> refCrossSectionLane.getCenterLine().getLocationFraction(f), GTUException.class,
1602 "GTU %s is not orthogonal to the reference lane.", getId());
1603 double d = p.distance(loc);
1604 d = ref.getGtuDirection().isPlus() ? d : -d;
1605 if (this.crossSections.get(0).getLanes().size() > 1)
1606 {
1607 return Length.instantiateSI(latIndex == 0 ? -d : d);
1608 }
1609 double x2 = p.x + Math.cos(p.getRotZ());
1610 double y2 = p.y + Math.sin(p.getRotZ());
1611 double det = (loc.x - p.x) * (y2 - p.y) - (loc.y - p.y) * (x2 - p.x);
1612 return Length.instantiateSI(det < 0.0 ? -d : d);
1613 }
1614
1615
1616 @Override
1617 public void setInstantaneousLaneChange(final boolean instantaneous)
1618 {
1619 this.instantaneousLaneChange = instantaneous;
1620 }
1621
1622
1623 @Override
1624 public boolean isInstantaneousLaneChange()
1625 {
1626 return this.instantaneousLaneChange;
1627 }
1628
1629
1630 @Override
1631 @SuppressWarnings("checkstyle:designforextension")
1632 public String toString()
1633 {
1634 return String.format("GTU " + getId());
1635 }
1636
1637
1638 private static class CrossSection
1639 {
1640
1641
1642 private final List<Lane> lanes;
1643
1644
1645 private final GTUDirectionality direction;
1646
1647
1648
1649
1650
1651 protected CrossSection(final List<Lane> lanes, final GTUDirectionality direction)
1652 {
1653 this.lanes = lanes;
1654 this.direction = direction;
1655 }
1656
1657
1658
1659
1660 protected List<Lane> getLanes()
1661 {
1662 return this.lanes;
1663 }
1664
1665
1666
1667
1668 protected GTUDirectionality getDirection()
1669 {
1670 return this.direction;
1671 }
1672
1673 }
1674
1675 }