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