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