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