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