1 package org.opentrafficsim.road.gtu.lane;
2
3 import java.util.ArrayList;
4 import java.util.LinkedHashMap;
5 import java.util.LinkedHashSet;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.Map.Entry;
9 import java.util.NavigableMap;
10 import java.util.Optional;
11 import java.util.Set;
12 import java.util.TreeMap;
13
14 import org.djunits.unit.DirectionUnit;
15 import org.djunits.unit.PositionUnit;
16 import org.djunits.value.vdouble.scalar.Acceleration;
17 import org.djunits.value.vdouble.scalar.Direction;
18 import org.djunits.value.vdouble.scalar.Duration;
19 import org.djunits.value.vdouble.scalar.Length;
20 import org.djunits.value.vdouble.scalar.Speed;
21 import org.djunits.value.vdouble.vector.PositionVector;
22 import org.djutils.draw.line.PolyLine2d;
23 import org.djutils.draw.point.DirectedPoint2d;
24 import org.djutils.draw.point.Point2d;
25 import org.djutils.event.EventType;
26 import org.djutils.exceptions.Throw;
27 import org.djutils.exceptions.Try;
28 import org.djutils.metadata.MetaData;
29 import org.djutils.metadata.ObjectDescriptor;
30 import org.opentrafficsim.base.OtsRuntimeException;
31 import org.opentrafficsim.base.geometry.OtsLine2d;
32 import org.opentrafficsim.base.geometry.OtsLine2d.FractionalFallback;
33 import org.opentrafficsim.base.logger.Logger;
34 import org.opentrafficsim.base.parameters.ParameterException;
35 import org.opentrafficsim.core.gtu.Gtu;
36 import org.opentrafficsim.core.gtu.GtuException;
37 import org.opentrafficsim.core.gtu.GtuType;
38 import org.opentrafficsim.core.gtu.RelativePosition;
39 import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
40 import org.opentrafficsim.core.gtu.perception.EgoPerception;
41 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
42 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
43 import org.opentrafficsim.core.gtu.plan.operational.Segments;
44 import org.opentrafficsim.core.network.LateralDirectionality;
45 import org.opentrafficsim.core.network.Link;
46 import org.opentrafficsim.core.network.NetworkException;
47 import org.opentrafficsim.core.network.Node;
48 import org.opentrafficsim.core.network.route.Route;
49 import org.opentrafficsim.core.perception.Historical;
50 import org.opentrafficsim.core.perception.HistoricalValue;
51 import org.opentrafficsim.core.perception.HistoryManager;
52 import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
53 import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
54 import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
55 import org.opentrafficsim.road.gtu.lane.perception.categories.InfrastructurePerception;
56 import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.NeighborsPerception;
57 import org.opentrafficsim.road.gtu.lane.perception.object.PerceivedGtu;
58 import org.opentrafficsim.road.gtu.lane.plan.operational.LaneBasedOperationalPlan;
59 import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedTacticalPlanner;
60 import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
61 import org.opentrafficsim.road.network.RoadNetwork;
62 import org.opentrafficsim.road.network.lane.CrossSectionLink;
63 import org.opentrafficsim.road.network.lane.Lane;
64 import org.opentrafficsim.road.network.lane.LanePosition;
65 import org.opentrafficsim.road.network.lane.object.LaneBasedObject;
66 import org.opentrafficsim.road.network.lane.object.detector.LaneDetector;
67 import org.opentrafficsim.road.network.speed.SpeedLimitInfo;
68 import org.opentrafficsim.road.network.speed.SpeedLimitTypes;
69
70 import nl.tudelft.simulation.dsol.SimRuntimeException;
71 import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97 public class LaneBasedGtu extends Gtu implements LaneBasedObject
98 {
99
100
101
102
103 private static final Length EVENT_MARGIN = Length.ofSI(50.0);
104
105
106 private final HistoricalValue<Lane> lane;
107
108
109 private Duration cachedPositionTime = null;
110
111
112 private LanePosition cachedPosition = null;
113
114
115 private Duration cachedRoamingPositionTime = null;
116
117
118 private LanePosition cachedRoamingPosition = null;
119
120
121 private NavigableMap<Duration, Lane> pendingLanesToEnter = new TreeMap<>();
122
123
124 private Map<Lane, SimEventInterface<Duration>> pendingEnterEvents = new LinkedHashMap<>();
125
126
127 private SimEventInterface<Duration> roamEvent;
128
129
130 private Map<LaneDetector, Length> detectorTriggers = new LinkedHashMap<>();
131
132
133 private Set<SimEventInterface<Duration>> detectorEvents = new LinkedHashSet<>();
134
135
136 private Speed cachedDesiredSpeed;
137
138
139 private Duration desiredSpeedTime;
140
141
142 private Acceleration cachedCarFollowingAcceleration;
143
144
145 private Duration carFollowingAccelerationTime;
146
147
148 private final Historical<TurnIndicatorStatus> turnIndicatorStatus;
149
150
151 private VehicleModel vehicleModel = VehicleModel.MINMAX;
152
153
154 private LaneBookkeeping bookkeeping = LaneBookkeeping.EDGE;
155
156
157 private Length noLaneChangeDistance;
158
159
160 private final Historical<LateralDirectionality> laneChangeDirection;
161
162
163
164
165
166
167
168 public static final EventType LANEBASED_MOVE_EVENT = new EventType("LANEBASEDGTU.MOVE", new MetaData("Lane based GTU moved",
169 "Lane based GTU moved",
170 new ObjectDescriptor[] {new ObjectDescriptor("GTU id", "GTU id", String.class),
171 new ObjectDescriptor("Position", "Position", PositionVector.class),
172 new ObjectDescriptor("Direction", "Direction", Direction.class),
173 new ObjectDescriptor("Speed", "Speed", Speed.class),
174 new ObjectDescriptor("Acceleration", "Acceleration", Acceleration.class),
175 new ObjectDescriptor("TurnIndicatorStatus", "Turn indicator status", String.class),
176 new ObjectDescriptor("Odometer", "Odometer value", Length.class),
177 new ObjectDescriptor("Link id", "Link id", String.class),
178 new ObjectDescriptor("Lane id", "Lane id", String.class),
179 new ObjectDescriptor("Longitudinal position on lane", "Longitudinal position on lane", Length.class)}));
180
181
182
183
184
185
186 public static final EventType LANEBASED_DESTROY_EVENT = new EventType("LANEBASEDGTU.DESTROY", new MetaData(
187 "Lane based GTU destroyed", "Lane based GTU destroyed",
188 new ObjectDescriptor[] {new ObjectDescriptor("GTU id", "GTU id", String.class),
189 new ObjectDescriptor("Position", "Position", PositionVector.class),
190 new ObjectDescriptor("Direction", "Direction", Direction.class),
191 new ObjectDescriptor("Odometer", "Odometer value", Length.class),
192 new ObjectDescriptor("Link id", "Link id", String.class),
193 new ObjectDescriptor("Lane id", "Lane id", String.class),
194 new ObjectDescriptor("Longitudinal position on lane", "Longitudinal position on lane", Length.class)}));
195
196
197
198
199
200 public static final EventType LANE_ENTER_EVENT = new EventType("LANE.ENTER",
201 new MetaData("Lane based GTU entered lane", "Front of lane based GTU entered lane",
202 new ObjectDescriptor[] {new ObjectDescriptor("GTU id", "GTU id", String.class),
203 new ObjectDescriptor("Link id", "Link id", String.class),
204 new ObjectDescriptor("Lane id", "Lane id", String.class)}));
205
206
207
208
209
210 public static final EventType LANE_EXIT_EVENT = new EventType("LANE.EXIT",
211 new MetaData("Lane based GTU exited lane", "Rear of lane based GTU exited lane",
212 new ObjectDescriptor[] {new ObjectDescriptor("GTU id", "GTU id", String.class),
213 new ObjectDescriptor("Link id", "Link id", String.class),
214 new ObjectDescriptor("Lane id", "Lane id", String.class)}));
215
216
217
218
219
220 public static final EventType LANE_CHANGE_EVENT = new EventType("LANE.CHANGE",
221 new MetaData("Lane based GTU changes lane", "Lane based GTU changes lane",
222 new ObjectDescriptor[] {new ObjectDescriptor("GTU id", "GTU id", String.class),
223 new ObjectDescriptor("Lateral direction of lane change", "Lateral direction of lane change",
224 String.class),
225 new ObjectDescriptor("Link id", "Link id", String.class),
226 new ObjectDescriptor("Lane id of exited lane", "Lane id of exited lane", String.class),
227 new ObjectDescriptor("Position along exited lane", "Position along exited lane", Length.class)}));
228
229
230
231
232
233
234
235
236
237
238
239
240 public LaneBasedGtu(final String id, final GtuType gtuType, final Length length, final Length width,
241 final Speed maximumSpeed, final Length front, final RoadNetwork network) throws GtuException
242 {
243 super(id, gtuType, network.getSimulator(), network, length, width, front, maximumSpeed);
244 HistoryManager historyManager = network.getSimulator().getReplication().getHistoryManager(network.getSimulator());
245 this.lane = new HistoricalValue<>(historyManager, this);
246 this.turnIndicatorStatus = new HistoricalValue<>(historyManager, this, TurnIndicatorStatus.NOTPRESENT);
247 this.laneChangeDirection = new HistoricalValue<>(historyManager, this, LateralDirectionality.NONE);
248 }
249
250
251
252
253
254
255
256
257
258
259 @SuppressWarnings("checkstyle:designforextension")
260 public synchronized void init(final LaneBasedStrategicalPlanner strategicalPlanner, final DirectedPoint2d initialLocation,
261 final Speed initialSpeed) throws NetworkException, SimRuntimeException, GtuException
262 {
263 Throw.when(null == initialLocation, GtuException.class, "InitialLongitudinalPositions is null");
264
265
266
267 Duration now = getSimulator().getSimulatorTime();
268 if (initialSpeed.lt(OperationalPlan.DRIFTING_SPEED))
269 {
270 setOperationalPlan(OperationalPlan.standStill(this, initialLocation, now, Duration.ofSI(1E-6)));
271 }
272 else
273 {
274 Point2d p2 = new Point2d(initialLocation.x + 1E-6 * Math.cos(initialLocation.getDirZ()),
275 initialLocation.y + 1E-6 * Math.sin(initialLocation.getDirZ()));
276 OtsLine2d path = new OtsLine2d(initialLocation, p2);
277 setOperationalPlan(new OperationalPlan(this, path, now,
278 Segments.off(initialSpeed, path.getTypedLength().divide(initialSpeed), Acceleration.ZERO)));
279 }
280
281 LanePosition longitudinalPosition = getRoamingPosition(initialLocation);
282 endRoaming(longitudinalPosition);
283 this.cachedPositionTime = null;
284
285
286 super.init(strategicalPlanner, initialLocation, initialSpeed);
287
288 this.cachedPositionTime = null;
289 }
290
291
292
293
294 @Override
295 public synchronized void setParent(final Gtu gtu) throws GtuException
296 {
297 exitLane();
298 super.setParent(gtu);
299 }
300
301
302
303
304 protected synchronized void exitLane()
305 {
306 LanePosition exitLanePosition = getPosition();
307 if (exitLanePosition != null)
308 {
309 exitLanePosition.lane().removeGtu(this, true, exitLanePosition.position());
310 }
311 this.lane.set(null);
312 fireTimedEvent(LaneBasedGtu.LANE_EXIT_EVENT,
313 new Object[] {getId(), exitLanePosition.lane().getLink().getId(), exitLanePosition.lane().getId()},
314 getSimulator().getSimulatorTime());
315 }
316
317
318
319
320
321
322 @SuppressWarnings("hiddenfield")
323 protected synchronized void enterLane(final Lane lane, final double fraction)
324 {
325
326 LanePosition exitLanePosition = getPosition();
327 this.lane.set(lane);
328 Try.execute(() -> lane.addGtu(this, fraction), "Entering lane where the GTU is already at.");
329
330 fireTimedEvent(LaneBasedGtu.LANE_ENTER_EVENT, new Object[] {getId(), lane.getLink().getId(), lane.getId()},
331 getSimulator().getSimulatorTime());
332
333
334
335 if (exitLanePosition != null)
336 {
337 exitLanePosition.lane().removeGtu(this, true, exitLanePosition.position());
338 this.pendingLanesToEnter.values().remove(lane);
339 this.pendingEnterEvents.remove(lane);
340 fireTimedEvent(LaneBasedGtu.LANE_EXIT_EVENT,
341 new Object[] {getId(), exitLanePosition.lane().getLink().getId(), exitLanePosition.lane().getId()},
342 getSimulator().getSimulatorTime());
343 if (exitLanePosition.lane().getLink().equals(lane.getLink()))
344 {
345
346 setLaneChangeDirection(LateralDirectionality.NONE);
347 String direction = lane.equals(exitLanePosition.lane().getLeft(getType()).orElse(null))
348 ? LateralDirectionality.LEFT.name() : LateralDirectionality.RIGHT.name();
349 fireTimedEvent(LaneBasedGtu.LANE_CHANGE_EVENT,
350 new Object[] {getId(), direction, exitLanePosition.lane().getLink().getId(),
351 exitLanePosition.lane().getId(), exitLanePosition.position()},
352 getSimulator().getSimulatorTime());
353 }
354 }
355 }
356
357
358
359
360
361
362 public boolean isRoaming()
363 {
364 return getLane() == null;
365 }
366
367
368
369
370
371
372
373 public boolean isRoaming(final Duration time)
374 {
375 return getLane(time) == null;
376 }
377
378
379
380
381
382 public LanePosition getPositionOrRoaming()
383 {
384 return isRoaming() ? getRoamingPosition() : getPosition();
385 }
386
387 @Override
388 public synchronized Lane getLane()
389 {
390 return this.lane.get();
391 }
392
393
394
395
396
397
398 public synchronized Lane getLane(final Duration time)
399 {
400 return this.pendingLanesToEnter.isEmpty() || this.pendingLanesToEnter.firstKey().ge(time) ? this.lane.get(time)
401 : this.pendingLanesToEnter.floorEntry(time).getValue();
402 }
403
404
405
406
407
408 public synchronized LanePosition getPosition()
409 {
410 if (!getSimulator().getSimulatorTime().equals(this.cachedPositionTime))
411 {
412 this.cachedPositionTime = getSimulator().getSimulatorTime();
413 this.cachedPosition = getPosition(getReference(), this.cachedPositionTime);
414 }
415 return this.cachedPosition;
416 }
417
418
419
420
421
422
423 public synchronized LanePosition getPosition(final Duration time)
424 {
425 return getPosition(getReference(), time);
426 }
427
428
429
430
431
432
433
434 public synchronized LanePosition getPosition(final RelativePosition relativePosition)
435 {
436 LanePosition ref = getPosition();
437 return new LanePosition(ref.lane(), ref.position().plus(relativePosition.dx()));
438 }
439
440
441
442
443
444
445
446
447 public synchronized LanePosition getPosition(final RelativePosition relativePosition, final Duration time)
448 {
449 Lane laneAtTime = getLane(time);
450 if (laneAtTime == null)
451 {
452 return null;
453 }
454 return new LanePosition(laneAtTime, getPosition(laneAtTime, relativePosition, time));
455 }
456
457
458
459
460
461
462
463
464 @SuppressWarnings("hiddenfield")
465 public synchronized Length getPosition(final Lane lane)
466 {
467 return getPosition(lane, getReference(), getSimulator().getSimulatorTime());
468 }
469
470
471
472
473
474
475
476
477
478 @SuppressWarnings("hiddenfield")
479 public synchronized Length getPosition(final Lane lane, final Duration time)
480 {
481 return getPosition(lane, getReference(), time);
482 }
483
484
485
486
487
488
489
490
491
492
493 @SuppressWarnings("hiddenfield")
494 public synchronized Length getPosition(final Lane lane, final RelativePosition relativePosition)
495 {
496 return getPosition(lane, relativePosition, getSimulator().getSimulatorTime());
497 }
498
499 @Override
500 public synchronized Length getLongitudinalPosition()
501 {
502 return getPosition().position();
503 }
504
505
506
507
508
509
510
511
512
513
514
515 @SuppressWarnings("hiddenfield")
516 public synchronized Length getPosition(final Lane lane, final RelativePosition relativePosition, final Duration time)
517 {
518 Throw.when(getLane() == null, IllegalStateException.class, "Requesting position on lane but GTU has no lane.");
519 Throw.when(!lane.getLink().equals(getLane(time).getLink()), IllegalArgumentException.class,
520 "Requesting position on lane on link %s but the GTU is on link %s.", lane.getLink().getId(),
521 getLane().getLink().getId());
522 DirectedPoint2d p = Try.assign(() -> getOperationalPlan(time).getLocation(time, getReference()),
523 "Operational plan at time is not valid at time.");
524 double f = lane.getCenterLine().projectFractional(lane.getLink().getStartNode().getHeading(),
525 lane.getLink().getEndNode().getHeading(), p.x, p.y, FractionalFallback.ORTHOGONAL_EXTENDED);
526 return lane.getLength().times(f).plus(relativePosition.dx());
527 }
528
529
530
531
532
533
534 public synchronized LanePosition getRoamingPosition()
535 {
536 Throw.when(getLane() != null, IllegalStateException.class, "GTU that is on a lane does not have a roaming position.");
537 if (!getSimulator().getSimulatorTime().equals(this.cachedRoamingPositionTime))
538 {
539 this.cachedRoamingPositionTime = getSimulator().getSimulatorTime();
540 this.cachedRoamingPosition = getRoamingPosition(getLocation());
541 }
542 return this.cachedRoamingPosition;
543 }
544
545
546
547
548
549
550
551 protected LanePosition getRoamingPosition(final Point2d location)
552 {
553 Optional<Route> route = getStrategicalPlanner() == null ? Optional.empty() : getStrategicalPlanner().getRoute();
554
555
556 Iterable<Node> nodes = route.isEmpty() ? getNetwork().getNodeMap().values() : route.get().getNodes();
557 List<CrossSectionLink> nearestLinks = new ArrayList<>(2);
558 double minDist = Double.POSITIVE_INFINITY;
559 for (Node node : nodes)
560 {
561 double dist = node.getPoint().distance(location);
562 if (dist < minDist)
563 {
564 nearestLinks.clear();
565 for (Link link : node.getLinks())
566 {
567 if (link instanceof CrossSectionLink cLink && (route.isEmpty() || route.get().containsLink(link)))
568 {
569 nearestLinks.add(cLink);
570 minDist = dist;
571 }
572 }
573 }
574 }
575 Throw.when(nearestLinks.isEmpty(), IllegalStateException.class, "No lane in the route or in the network.");
576 LanePosition roamingPosition = null;
577 minDist = Double.POSITIVE_INFINITY;
578 for (CrossSectionLink nearestLink : nearestLinks)
579 {
580 for (Lane checkLane : nearestLink.getLanesAndShoulders())
581 {
582 double fraction = checkLane.getCenterLine().projectOrthogonalSnap(location.x, location.y);
583 DirectedPoint2d point = checkLane.getCenterLine().getLocationFraction(fraction);
584 double dist = point.distance(location);
585 if (dist < minDist)
586 {
587 roamingPosition =
588 new LanePosition(checkLane, Length.ofSI(checkLane.getCenterLine().getLength() * fraction));
589 minDist = dist;
590 }
591 }
592 }
593 return roamingPosition;
594 }
595
596
597
598
599
600 public synchronized Length getDeviation()
601 {
602 return getDeviation(getLane(), getLocation());
603 }
604
605
606
607
608
609
610 public synchronized Length getDeviation(final Duration time)
611 {
612 return getDeviation(getLane(time), getLocation(time));
613 }
614
615
616
617
618
619
620
621
622 @SuppressWarnings("hiddenfield")
623 protected Length getDeviation(final Lane lane, final Point2d location)
624 {
625 double fraction = lane.getCenterLine().projectFractional(lane.getLink().getStartNode().getHeading(),
626 lane.getLink().getEndNode().getHeading(), location.x, location.y, FractionalFallback.ORTHOGONAL_EXTENDED);
627 DirectedPoint2d a = lane.getCenterLine().getLocationFractionExtended(fraction);
628 Point2d b = new Point2d(a.x + Math.cos(a.dirZ), a.y + Math.sin(a.dirZ));
629 double sign = (b.x - a.x) * (location.y - a.y) - (b.y - a.y) * (location.x - a.x) > 0.0 ? 1.0 : -1.0;
630 return Length.ofSI(sign * lane.getCenterLine().getLocationFractionExtended(fraction).distance(location));
631 }
632
633
634
635
636
637 @SuppressWarnings("hiddenfield")
638 public synchronized void changeLaneInstantaneously(final LateralDirectionality laneChangeDirection)
639 {
640 LanePosition from = getPosition();
641 Set<Lane> adjLanes = from.lane().accessibleAdjacentLanesPhysical(laneChangeDirection, getType());
642 Lane adjLane = adjLanes.iterator().next();
643 Length position = getPosition(adjLane);
644 cancelAllEvents();
645 enterLane(adjLane, position.si / adjLane.getLength().si);
646 this.cachedPositionTime = null;
647 this.cachedPosition = null;
648
649
650 this.fireTimedEvent(
651 LaneBasedGtu.LANE_CHANGE_EVENT, new Object[] {getId(), laneChangeDirection.name(),
652 from.lane().getLink().getId(), from.lane().getId(), from.position()},
653 getSimulator().getSimulatorTime());
654 }
655
656 @Override
657 @SuppressWarnings("checkstyle:designforextension")
658 protected synchronized boolean move(final DirectedPoint2d fromLocation)
659 throws SimRuntimeException, GtuException, NetworkException, ParameterException
660 {
661 if (this.isDestroyed())
662 {
663 return false;
664 }
665 try
666 {
667
668 cancelAllEvents();
669
670
671 try
672 {
673 boolean error = super.move(fromLocation);
674 if (error)
675 {
676 return error;
677 }
678 }
679 catch (Exception exception)
680 {
681 Logger.ots().error(exception);
682 Logger.ots().error(" GTU {} DESTROYED AND REMOVED FROM THE SIMULATION", this);
683 destroy();
684 cancelAllEvents();
685 return true;
686 }
687
688 scheduleLaneEvents();
689 findDetectorTriggers(true);
690
691 LanePosition position = getPosition();
692 String linkId = position != null ? position.lane().getLink().getId() : null;
693 String laneId = position != null ? position.lane().getId() : null;
694 Length pos = position != null ? position.position() : null;
695 fireTimedEvent(LaneBasedGtu.LANEBASED_MOVE_EVENT,
696 new Object[] {getId(),
697 new PositionVector(new double[] {fromLocation.x, fromLocation.y}, PositionUnit.METER),
698 new Direction(fromLocation.getDirZ(), DirectionUnit.EAST_RADIAN), getSpeed(), getAcceleration(),
699 getTurnIndicatorStatus().name(), getOdometer(), linkId, laneId, pos},
700 getSimulator().getSimulatorTime());
701
702 return false;
703
704 }
705 catch (Exception ex)
706 {
707 try
708 {
709 getErrorHandler().handle(this, ex);
710 }
711 catch (Exception exception)
712 {
713 throw new GtuException(exception);
714 }
715 return true;
716 }
717
718 }
719
720
721
722
723 protected void cancelAllEvents()
724 {
725 if (this.roamEvent != null)
726 {
727 getSimulator().cancelEvent(this.roamEvent);
728 this.roamEvent = null;
729 }
730 this.pendingLanesToEnter.clear();
731 this.pendingEnterEvents.values().forEach((event) -> getSimulator().cancelEvent(event));
732 this.pendingEnterEvents.clear();
733
734 this.detectorEvents.forEach((event) -> getSimulator().cancelEvent(event));
735 this.detectorEvents.clear();
736 findDetectorTriggers(false);
737 }
738
739
740
741
742
743 protected void scheduleLaneEvents()
744 {
745
746
747
748
749 Lane laneOnPath = getLane();
750 if (laneOnPath == null)
751 {
752
753 endRoaming(getRoamingPosition());
754 laneOnPath = getLane();
755 }
756
757 Length remain = getOperationalPlan().getTotalLength().plus(EVENT_MARGIN);
758 Length planStartPositionAtLaneOnPath = getLongitudinalPosition();
759 boolean checkLaneChange = getOperationalPlan() instanceof LaneBasedOperationalPlan lbop && lbop.isDeviative()
760 && !this.bookkeeping.equals(LaneBookkeeping.INSTANT);
761 while (true)
762 {
763 Duration enterTime;
764 if (laneOnPath.getLength().minus(planStartPositionAtLaneOnPath).lt(remain))
765 {
766 CrossSectionLink link = laneOnPath.getLink();
767 Link nextLink =
768 Try.assign(() -> getStrategicalPlanner().nextLink(link, getType()), "Network issue during scheduling.");
769 PolyLine2d enterLine = nextLink != null && nextLink instanceof CrossSectionLink
770 ? ((CrossSectionLink) nextLink).getStartLine() : link.getEndLine();
771 enterTime = timeAtLine(enterLine, getReference());
772 }
773 else
774 {
775 enterTime = null;
776 }
777 Duration lastTimeOnLane = enterTime == null ? getOperationalPlan().getEndTime() : enterTime;
778
779
780 if (checkLaneChange && (enterTime == null || !Double.isNaN(enterTime.si)))
781 {
782 Duration firstTimeOnLane = this.pendingLanesToEnter.isEmpty() ? getSimulator().getSimulatorTime()
783 : this.pendingLanesToEnter.lastKey();
784 Length overshoot = laneLateralOvershoot(lastTimeOnLane);
785 if (overshoot.gt0() && laneLateralOvershoot(firstTimeOnLane).le0())
786 {
787 Length deviation = getDeviation(lastTimeOnLane);
788 boolean noAdjacentLane =
789 (deviation.gt0() ? laneOnPath.getLeft(getType()) : laneOnPath.getRight(getType())).isEmpty();
790 boolean willRoam = noAdjacentLane && overshoot.gt(getWidth().times(0.5));
791
792 Duration lateralCrossingTime = getTimeOfLateralCrossing(firstTimeOnLane, lastTimeOnLane, willRoam);
793 if (willRoam)
794 {
795 this.roamEvent =
796 getSimulator().scheduleEventAbs(Duration.ofSI(lateralCrossingTime.si), () -> exitLane());
797 return;
798 }
799 else
800 {
801
802 LateralDirectionality lcDirection = getDeviation(lateralCrossingTime).ge0() ? LateralDirectionality.LEFT
803 : LateralDirectionality.RIGHT;
804 Length distanceTillLaneChange =
805 getPosition(laneOnPath, lateralCrossingTime).minus(planStartPositionAtLaneOnPath);
806 laneOnPath = laneOnPath.getAdjacentLane(lcDirection, getType()).orElse(null);
807 if (laneOnPath != null)
808 {
809 Length positionOnTargetLane = getPosition(laneOnPath, lateralCrossingTime);
810 double fractionOnTargetLane = positionOnTargetLane.si / laneOnPath.getLength().si;
811 planStartPositionAtLaneOnPath = positionOnTargetLane.minus(distanceTillLaneChange);
812 this.pendingLanesToEnter.put(lateralCrossingTime, laneOnPath);
813 Lane finalLane = laneOnPath;
814 this.pendingEnterEvents.put(laneOnPath, getSimulator().scheduleEventAbs(
815 Duration.ofSI(lateralCrossingTime.si), () -> enterLane(finalLane, fractionOnTargetLane)));
816 }
817 else
818 {
819
820 this.roamEvent =
821 getSimulator().scheduleEventAbs(Duration.ofSI(lateralCrossingTime.si), () -> exitLane());
822 return;
823 }
824 }
825 }
826 }
827
828 if (enterTime != null)
829 {
830 planStartPositionAtLaneOnPath = planStartPositionAtLaneOnPath.minus(laneOnPath.getLength());
831 laneOnPath = getNextLaneForRoute(laneOnPath).orElse(null);
832 if (laneOnPath == null && !Double.isNaN(enterTime.si))
833 {
834
835 Duration timeRefLeaving = enterTime;
836 Length distanceRearLeaving = Try.assign(() -> getOperationalPlan().getTraveledDistance(timeRefLeaving),
837 "Time link is left is beyond plan.").minus(getRear().dx());
838 if (distanceRearLeaving.le(getOperationalPlan().getTotalLength()))
839 {
840 Duration timeRearLeaving = Try.assign(() -> getOperationalPlan().getTimeAtDistance(distanceRearLeaving),
841 "Distance till rear leaves link is beyond plan.");
842 this.roamEvent = getSimulator().scheduleEventAbs(Duration.ofSI(timeRearLeaving.si), () -> exitLane());
843 }
844 return;
845 }
846 else
847 {
848 if (Double.isNaN(enterTime.si))
849 {
850
851 enterTime = getSimulator().getSimulatorTime();
852 Logger.ots().error("GTU {} enters lane through hack.", getId());
853 }
854 this.pendingLanesToEnter.put(enterTime, laneOnPath);
855 Lane finalLane = laneOnPath;
856 this.pendingEnterEvents.put(laneOnPath,
857 getSimulator().scheduleEventAbs(Duration.ofSI(enterTime.si), () -> enterLane(finalLane, 0.0)));
858 }
859 }
860 else
861 {
862 return;
863 }
864 }
865 }
866
867
868
869
870
871
872
873
874
875
876 protected Duration getTimeOfLateralCrossing(final Duration fromTime, final Duration toTime, final boolean roam)
877 {
878 try
879 {
880 Length startPosition = getOperationalPlan().getTraveledDistance(fromTime);
881 Length endPosition = getOperationalPlan().getTraveledDistance(toTime);
882 Length lateralMargin = roam ? getWidth().times(0.5) : Length.ZERO;
883 OtsLine2d path = getOperationalPlan().getPath();
884 int low = 0;
885 while (path.size() > low + 1 && path.lengthAtIndex(low + 1) <= startPosition.si)
886 {
887 low++;
888 }
889 int high = path.size() - 1;
890 while (high > 0 && path.lengthAtIndex(high - 1) > endPosition.si)
891 {
892 high--;
893 }
894 int mid = 0;
895 Length position0 = null;
896 Length overshoot0 = null;
897
898 while (low <= high)
899 {
900 mid = (low + high) / 2;
901 position0 = Length.max(startPosition, Length.min(Length.ofSI(path.lengthAtIndex(mid)), endPosition));
902 Duration time0 = getOperationalPlan().getTimeAtDistance(position0);
903 overshoot0 = laneLateralOvershoot(time0).minus(lateralMargin);
904 if (overshoot0.le0())
905 {
906 low = mid + 1;
907 }
908 else
909 {
910 high = mid - 1;
911 }
912 }
913 if (mid == low)
914 {
915 position0 = Length.max(startPosition, Length.min(Length.ofSI(path.lengthAtIndex(low - 1)), endPosition));
916 Duration time0 = getOperationalPlan().getTimeAtDistance(position0);
917 overshoot0 = laneLateralOvershoot(time0).minus(lateralMargin);
918 }
919 Length position1 = Length.min(endPosition, Length.ofSI(path.lengthAtIndex(low)));
920 Duration time1 = getOperationalPlan().getTimeAtDistance(position1);
921 Length overshoot1 = laneLateralOvershoot(time1);
922 double factor = overshoot0.neg().si / (overshoot1.si - overshoot0.si);
923 return getOperationalPlan().getTimeAtDistance(Length.interpolate(position0, position1, factor));
924 }
925 catch (OperationalPlanException ex)
926 {
927 throw new OtsRuntimeException("Lateral crossing time or distance beyond plan.", ex);
928 }
929 }
930
931
932
933
934
935 protected void endRoaming(final LanePosition roamingPosition)
936 {
937 if (roamingPosition.getLocation().distance(
938 getLocation()) < roamingPosition.lane().getWidth(roamingPosition.getFraction()).plus(getWidth()).times(0.5).si)
939 {
940 enterLane(roamingPosition.lane(), roamingPosition.getFraction());
941 }
942 }
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968 protected void findDetectorTriggers(final boolean schedule)
969 {
970 Lane laneOnPath = getLane();
971 if (laneOnPath == null)
972 {
973 return;
974 }
975
976
977 Duration time0 = getSimulator().getSimulatorTime();
978 LanePosition position0 = getPosition();
979 Length searchedDistanceAtFrom = Length.ZERO;
980 while (time0.lt(getOperationalPlan().getEndTime()))
981 {
982
983
984
985
986
987 Duration time1 = this.pendingLanesToEnter.higherKey(time0) == null ? getOperationalPlan().getEndTime()
988 : this.pendingLanesToEnter.higherKey(time0);
989 LanePosition position1 = getPosition(time1);
990 searchedDistanceAtFrom = findDetectorTriggersInEpisode(searchedDistanceAtFrom, position0, position1, schedule);
991 time0 = time1;
992 position0 = position1;
993 }
994
995
996 if (schedule)
997 {
998 for (Entry<LaneDetector, Length> trigger : this.detectorTriggers.entrySet())
999 {
1000 Length toDetector = trigger.getValue().minus(getOdometer());
1001 if (toDetector.le(getOperationalPlan().getTotalLength()))
1002 {
1003 Duration triggerTime = Try.assign(() -> getOperationalPlan().getTimeAtDistance(toDetector),
1004 "Distance to detector beyond plan length.");
1005 this.detectorEvents
1006 .add(getSimulator().scheduleEventAbs(triggerTime, () -> triggerDetector(trigger.getKey())));
1007 }
1008 }
1009 }
1010 }
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021 private Length findDetectorTriggersInEpisode(final Length searchedDistance, final LanePosition position0,
1022 final LanePosition position1, final boolean schedule)
1023 {
1024
1025 Lane searchLane = position0.lane();
1026 Length from = position0.position();
1027 Length to = searchLane.getLength();
1028 Length searchedDistanceAtFrom = searchedDistance;
1029 Length searchedDistanceAtFromOnPendingLink = Length.ZERO;
1030 boolean encounteredPendingLink = false;
1031 Length delta = getFront().dx();
1032 while (searchLane != null)
1033 {
1034
1035 if (!encounteredPendingLink && searchLane.getLink().equals(position1.lane().getLink()))
1036 {
1037 encounteredPendingLink = true;
1038 searchedDistanceAtFromOnPendingLink = searchedDistanceAtFrom;
1039 if (searchLane.equals(position1.lane()))
1040 {
1041
1042 to = position1.position();
1043 }
1044 else
1045 {
1046
1047 Point2d point = position1.getLocation();
1048 double fraction = searchLane.getCenterLine().projectFractional(
1049 searchLane.getLink().getStartNode().getHeading(), searchLane.getLink().getEndNode().getHeading(),
1050 point.x, point.y, FractionalFallback.ENDPOINT);
1051 to = searchLane.getLength().times(fraction);
1052 }
1053 }
1054
1055 for (LaneDetector detector : searchLane.getDetectors(from.plus(delta), to.plus(delta), getType()))
1056 {
1057 if (schedule)
1058 {
1059 Length dxTrigger = getRelativePositions().get(detector.getPositionType()).dx();
1060 Length detectorLocation = detector.getLongitudinalPosition();
1061 Length deltaOdometer = searchedDistanceAtFrom.plus(detectorLocation).minus(from).minus(dxTrigger);
1062 this.detectorTriggers.put(detector, getOdometer().plus(deltaOdometer));
1063 }
1064 else
1065 {
1066 this.detectorTriggers.remove(detector);
1067 }
1068 }
1069
1070 searchedDistanceAtFrom = searchedDistanceAtFrom.plus(to.minus(from));
1071 if (encounteredPendingLink)
1072 {
1073 if (to.plus(getFront().dx()).gt(searchLane.getLength()))
1074 {
1075 to = to.plus(getFront().dx()).minus(searchLane.getLength());
1076 delta = Length.ZERO;
1077 }
1078 else
1079 {
1080 to = to.minus(searchLane.getLength());
1081 }
1082 }
1083 searchLane = to.le0() ? null : getNextLaneForRoute(searchLane).orElse(null);
1084 from = Length.ZERO;
1085 }
1086 return searchedDistanceAtFromOnPendingLink;
1087 }
1088
1089
1090
1091
1092
1093 protected void triggerDetector(final LaneDetector detector)
1094 {
1095 this.detectorTriggers.remove(detector);
1096 detector.trigger(this);
1097 }
1098
1099
1100
1101
1102
1103
1104
1105 protected Length laneLateralOvershoot(final Duration time)
1106 {
1107 Lane laneAtTime = getLane(time);
1108 Point2d location = getLocation(time);
1109 Length deviation = getDeviation(laneAtTime, location);
1110 LanePosition position = getPosition(time);
1111 Length laneWidth = position.lane().getWidth(position.position());
1112 return deviation.abs().minus(laneWidth.times(0.5));
1113 }
1114
1115
1116
1117
1118
1119
1120 @SuppressWarnings("hiddenfield")
1121 public synchronized Optional<Lane> getNextLaneForRoute(final Lane lane)
1122 {
1123
1124 Set<Lane> set = getNextLanesForRoute(lane);
1125 if (set.isEmpty())
1126 {
1127 return Optional.empty();
1128 }
1129 if (set.size() == 1)
1130 {
1131 return Optional.of(set.iterator().next());
1132 }
1133
1134 for (Lane l : set)
1135 {
1136 if (l.getGtuList().contains(this))
1137 {
1138 return Optional.of(l);
1139 }
1140 }
1141
1142 return Optional.of(Try.assign(() -> getTacticalPlanner().chooseLaneAtSplit(lane, set),
1143 "Could not find suitable lane at split after lane %s of link %s for GTU %s.", lane.getId(),
1144 lane.getLink().getId(), getId()));
1145 }
1146
1147
1148
1149
1150
1151
1152 @SuppressWarnings("hiddenfield")
1153 private Set<Lane> getNextLanesForRoute(final Lane lane)
1154 {
1155 Set<Lane> out = new LinkedHashSet<>();
1156 Set<Lane> nextPhysical = lane.nextLanes(null);
1157
1158 Link link = Try.assign(() -> getStrategicalPlanner().nextLink(lane.getLink(), getType()),
1159 "Strategical planner experiences exception on network.");
1160
1161 if (nextPhysical.isEmpty())
1162 {
1163
1164 if (link instanceof CrossSectionLink cLink)
1165 {
1166 double minDistance = Double.POSITIVE_INFINITY;
1167 Lane closest = null;
1168 for (Lane next : cLink.getLanesAndShoulders())
1169 {
1170 double distance = Math.hypot(next.getCenterLine().getFirst().x - lane.getCenterLine().getLast().x,
1171 next.getCenterLine().getFirst().y - lane.getCenterLine().getLast().y);
1172 if (distance < minDistance)
1173 {
1174 closest = next;
1175 minDistance = distance;
1176 }
1177 }
1178 if (closest != null)
1179 {
1180 out.add(closest);
1181 }
1182 }
1183 return out;
1184 }
1185
1186 Set<Lane> next = lane.nextLanes(getType());
1187 if (next.isEmpty())
1188 {
1189 next = nextPhysical;
1190 }
1191 for (Lane l : next)
1192 {
1193 if (l.getLink().equals(link))
1194 {
1195 out.add(l);
1196 }
1197 }
1198 return out;
1199 }
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209 private Duration timeAtLine(final PolyLine2d line, final RelativePosition relativePosition)
1210 {
1211 Throw.when(line.size() != 2, IllegalArgumentException.class, "Line to cross with path should have 2 points.");
1212 OtsLine2d path = getOperationalPlan().getPath();
1213 List<Point2d> points = new ArrayList<>(path.size() + 1);
1214 points.addAll(path.getPointList());
1215 double adjust;
1216 if (relativePosition.dx().gt0())
1217 {
1218
1219 points.add(path.getLocationExtendedSI(path.getLength() + relativePosition.dx().si));
1220 adjust = -relativePosition.dx().si;
1221 }
1222 else if (relativePosition.dx().lt0())
1223 {
1224 points.add(0, path.getLocationExtendedSI(relativePosition.dx().si));
1225 adjust = 0.0;
1226 }
1227 else
1228 {
1229 adjust = 0.0;
1230 }
1231
1232
1233 double cumul = 0.0;
1234 for (int i = 0; i < points.size() - 1; i++)
1235 {
1236 Point2d intersect = Point2d.intersectionOfLineSegments(points.get(i), points.get(i + 1), line.get(0), line.get(1));
1237
1238
1239
1240
1241
1242
1243
1244 if (intersect == null)
1245 {
1246 double projectionFraction = line.projectOrthogonalFractionalExtended(points.get(i));
1247 if (0.0 <= projectionFraction && projectionFraction <= 1.0)
1248 {
1249
1250
1251 Point2d projection = line.getLocationFraction(projectionFraction);
1252 double distance = projection.distance(points.get(i));
1253 if (distance < 1e-6)
1254 {
1255 intersect = projection;
1256 }
1257
1258
1259
1260
1261
1262 }
1263 }
1264
1265 if (intersect != null)
1266 {
1267 cumul += points.get(i).distance(intersect);
1268 cumul += adjust;
1269
1270 if (cumul < 0.0)
1271 {
1272
1273
1274
1275
1276
1277 return Duration.NaN;
1278 }
1279 if (cumul <= getOperationalPlan().getTotalLength().si)
1280 {
1281 return getOperationalPlan().timeAtDistance(Length.ofSI(cumul));
1282 }
1283
1284 return null;
1285 }
1286 else if (i < points.size() - 2)
1287 {
1288 cumul += points.get(i).distance(points.get(i + 1));
1289 }
1290 }
1291
1292 return null;
1293 }
1294
1295
1296
1297
1298
1299 public void setVehicleModel(final VehicleModel vehicleModel)
1300 {
1301 this.vehicleModel = vehicleModel;
1302 }
1303
1304
1305
1306
1307
1308 public VehicleModel getVehicleModel()
1309 {
1310 return this.vehicleModel;
1311 }
1312
1313 @Override
1314 public LaneBasedStrategicalPlanner getStrategicalPlanner()
1315 {
1316 return (LaneBasedStrategicalPlanner) super.getStrategicalPlanner();
1317 }
1318
1319 @Override
1320 public LaneBasedStrategicalPlanner getStrategicalPlanner(final Duration time)
1321 {
1322 return (LaneBasedStrategicalPlanner) super.getStrategicalPlanner(time);
1323 }
1324
1325
1326
1327
1328
1329 public RoadNetwork getNetwork()
1330 {
1331 return (RoadNetwork) super.getPerceivableContext();
1332 }
1333
1334
1335
1336
1337
1338 public synchronized Speed getDesiredSpeed()
1339 {
1340 Duration simTime = getSimulator().getSimulatorTime();
1341 if (this.desiredSpeedTime == null || this.desiredSpeedTime.si < simTime.si)
1342 {
1343 InfrastructurePerception infra =
1344 getTacticalPlanner().getPerception().getPerceptionCategoryOrNull(InfrastructurePerception.class);
1345 SpeedLimitInfo speedInfo;
1346 if (infra == null)
1347 {
1348 speedInfo = new SpeedLimitInfo();
1349 speedInfo.addSpeedInfo(SpeedLimitTypes.MAX_VEHICLE_SPEED, getMaximumSpeed());
1350 }
1351 else
1352 {
1353
1354 speedInfo = infra.getSpeedLimitProspect(RelativeLane.CURRENT).getSpeedLimitInfo(Length.ZERO);
1355 }
1356 this.cachedDesiredSpeed =
1357 Try.assign(() -> getTacticalPlanner().getCarFollowingModel().desiredSpeed(getParameters(), speedInfo),
1358 "Parameter exception while obtaining the desired speed.");
1359 this.desiredSpeedTime = simTime;
1360 }
1361 return this.cachedDesiredSpeed;
1362 }
1363
1364
1365
1366
1367
1368
1369 public synchronized Acceleration getCarFollowingAcceleration()
1370 {
1371 Duration simTime = getSimulator().getSimulatorTime();
1372 if (this.carFollowingAccelerationTime == null || this.carFollowingAccelerationTime.si < simTime.si)
1373 {
1374 LanePerception perception = getTacticalPlanner().getPerception();
1375
1376 EgoPerception<?, ?> ego = perception.getPerceptionCategoryOrNull(EgoPerception.class);
1377 Throw.whenNull(ego, "EgoPerception is required to determine the speed.");
1378 Speed speed = ego.getSpeed();
1379
1380 InfrastructurePerception infra = perception.getPerceptionCategoryOrNull(InfrastructurePerception.class);
1381 Throw.whenNull(infra, "InfrastructurePerception is required to determine the desired speed.");
1382 SpeedLimitInfo speedInfo = infra.getSpeedLimitProspect(RelativeLane.CURRENT).getSpeedLimitInfo(Length.ZERO);
1383
1384 NeighborsPerception neighbors = perception.getPerceptionCategoryOrNull(NeighborsPerception.class);
1385 Throw.whenNull(neighbors, "NeighborsPerception is required to determine the car-following acceleration.");
1386 PerceptionCollectable<PerceivedGtu, LaneBasedGtu> leaders = neighbors.getLeaders(RelativeLane.CURRENT);
1387
1388 this.cachedCarFollowingAcceleration =
1389 Try.assign(() -> getTacticalPlanner().getCarFollowingModel().followingAcceleration(getParameters(), speed,
1390 speedInfo, leaders), "Parameter exception while obtaining the desired speed.");
1391 this.carFollowingAccelerationTime = simTime;
1392 }
1393 return this.cachedCarFollowingAcceleration;
1394 }
1395
1396
1397
1398
1399
1400 public TurnIndicatorStatus getTurnIndicatorStatus()
1401 {
1402 return this.turnIndicatorStatus.get();
1403 }
1404
1405
1406
1407
1408
1409
1410 public TurnIndicatorStatus getTurnIndicatorStatus(final Duration time)
1411 {
1412 return this.turnIndicatorStatus.get(time);
1413 }
1414
1415
1416
1417
1418
1419 public void setTurnIndicatorStatus(final TurnIndicatorStatus turnIndicatorStatus)
1420 {
1421 this.turnIndicatorStatus.set(turnIndicatorStatus);
1422 }
1423
1424 @Override
1425 public Length getHeight()
1426 {
1427 return Length.ZERO;
1428 }
1429
1430 @Override
1431 public String getFullId()
1432 {
1433 return getId();
1434 }
1435
1436
1437
1438
1439
1440 public void setBookkeeping(final LaneBookkeeping bookkeeping)
1441 {
1442 this.bookkeeping = bookkeeping;
1443 }
1444
1445
1446
1447
1448
1449 public LaneBookkeeping getBookkeeping()
1450 {
1451 return this.bookkeeping;
1452 }
1453
1454 @Override
1455 public LaneBasedTacticalPlanner getTacticalPlanner()
1456 {
1457 return getStrategicalPlanner().getTacticalPlanner();
1458 }
1459
1460 @Override
1461 public LaneBasedTacticalPlanner getTacticalPlanner(final Duration time)
1462 {
1463 return getStrategicalPlanner(time).getTacticalPlanner(time);
1464 }
1465
1466
1467
1468
1469
1470 public void setNoLaneChangeDistance(final Length distance)
1471 {
1472 this.noLaneChangeDistance = distance;
1473 }
1474
1475
1476
1477
1478
1479 public boolean laneChangeAllowed()
1480 {
1481 return this.noLaneChangeDistance == null ? true : getOdometer().gt(this.noLaneChangeDistance);
1482 }
1483
1484
1485
1486
1487
1488 public void setLaneChangeDirection(final LateralDirectionality direction)
1489 {
1490 this.laneChangeDirection.set(direction);
1491 }
1492
1493
1494
1495
1496
1497 public LateralDirectionality getLaneChangeDirection()
1498 {
1499 return this.laneChangeDirection.get();
1500 }
1501
1502
1503
1504
1505
1506
1507 public LateralDirectionality getLaneChangeDirection(final Duration time)
1508 {
1509 return this.laneChangeDirection.get(time);
1510 }
1511
1512
1513
1514
1515
1516 public boolean isBrakingLightsOn()
1517 {
1518 return isBrakingLightsOn(getSimulator().getSimulatorTime());
1519 }
1520
1521
1522
1523
1524
1525
1526 public boolean isBrakingLightsOn(final Duration time)
1527 {
1528 return getVehicleModel().isBrakingLightsOn(getSpeed(time), getAcceleration(time));
1529 }
1530
1531
1532
1533
1534
1535
1536 @SuppressWarnings("hiddenfield")
1537 public Length getProjectedLength(final Lane lane)
1538 {
1539 Length front = getPosition(lane, getFront());
1540 Length rear = getPosition(lane, getRear());
1541 return front.minus(rear);
1542 }
1543
1544
1545
1546
1547 public void stop()
1548 {
1549 getSimulator().cancelEvent(getNextMoveEvent());
1550 setOperationalPlan(
1551 OperationalPlan.standStill(this, getLocation(), getSimulator().getSimulatorTime(), Duration.POSITIVE_INFINITY));
1552 }
1553
1554 @Override
1555 @SuppressWarnings("checkstyle:designforextension")
1556 public void destroy()
1557 {
1558 LanePosition dlp = getPosition();
1559 DirectedPoint2d location = this.getOperationalPlan() == null ? new DirectedPoint2d(0.0, 0.0, 0.0) : getLocation();
1560 synchronized (this)
1561 {
1562 if (dlp != null && dlp.lane() != null)
1563 {
1564 dlp.lane().removeGtu(this, true, dlp.position());
1565 }
1566 }
1567 if (dlp != null && dlp.lane() != null)
1568 {
1569 Lane referenceLane = dlp.lane();
1570 fireTimedEvent(LaneBasedGtu.LANEBASED_DESTROY_EVENT,
1571 new Object[] {getId(), new PositionVector(new double[] {location.x, location.y}, PositionUnit.METER),
1572 new Direction(location.getDirZ(), DirectionUnit.EAST_RADIAN), getOdometer(),
1573 referenceLane.getLink().getId(), referenceLane.getId(), dlp.position()},
1574 getSimulator().getSimulatorTime());
1575 }
1576 else
1577 {
1578 fireTimedEvent(LaneBasedGtu.LANEBASED_DESTROY_EVENT,
1579 new Object[] {getId(), new PositionVector(new double[] {location.x, location.y}, PositionUnit.METER),
1580 new Direction(location.getDirZ(), DirectionUnit.EAST_RADIAN), getOdometer(), null, null, null},
1581 getSimulator().getSimulatorTime());
1582 }
1583 cancelAllEvents();
1584
1585 super.destroy();
1586 }
1587
1588 @Override
1589 @SuppressWarnings("checkstyle:designforextension")
1590 public String toString()
1591 {
1592 return "GTU " + getId();
1593 }
1594
1595 }