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 if (enterTime.lt(getSimulator().getSimulatorTime()))
674 {
675 System.err.println(
676 "Time travel? enterTime=" + enterTime + "; simulator time=" + getSimulator().getSimulatorTime());
677 enterTime = getSimulator().getSimulatorTime();
678 }
679 this.pendingEnterTrigger = getSimulator().scheduleEventAbs(enterTime, this, this, "enterCrossSection", null);
680 }
681 }
682 }
683
684
685
686
687
688
689
690 protected synchronized void enterCrossSection() throws GTUException, OperationalPlanException, SimRuntimeException
691 {
692 CrossSection lastCrossSection = this.crossSections.get(this.crossSections.size() - 1);
693 LaneDirection laneDirection =
694 new LaneDirection(lastCrossSection.getLanes().get(this.referenceLaneIndex), lastCrossSection.getDirection());
695 LaneDirection nextLaneDirection = laneDirection.getNextLaneDirection(this);
696 if (nextLaneDirection == null)
697 {
698 forceLaneChangeFinalization();
699 return;
700 }
701 double insertFraction = nextLaneDirection.getDirection().isPlus() ? 0.0 : 1.0;
702 List<Lane> nextLanes = new ArrayList<>();
703 for (int i = 0; i < lastCrossSection.getLanes().size(); i++)
704 {
705 if (i == this.referenceLaneIndex)
706 {
707 nextLanes.add(nextLaneDirection.getLane());
708 }
709 else
710 {
711 Lane lane = lastCrossSection.getLanes().get(i);
712 ImmutableMap<Lane, GTUDirectionality> lanes = lane.downstreamLanes(laneDirection.getDirection(), getGTUType());
713 if (lanes.size() == 1)
714 {
715 Lane nextLane = lanes.keySet().iterator().next();
716 nextLanes.add(nextLane);
717 }
718 else
719 {
720 boolean added = false;
721 for (Lane nextLane : lanes.keySet())
722 {
723 if (nextLane.getParentLink().equals(nextLaneDirection.getLane().getParentLink())
724 && nextLane.accessibleAdjacentLanesPhysical(
725 this.referenceLaneIndex == 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT,
726 getGTUType(), nextLaneDirection.getDirection()).contains(nextLaneDirection.getLane()))
727 {
728 nextLanes.add(nextLane);
729 added = true;
730 break;
731 }
732 }
733 if (!added)
734 {
735 forceLaneChangeFinalization();
736 return;
737 }
738 }
739 }
740 }
741 this.crossSections.add(new CrossSection(nextLanes, nextLaneDirection.getDirection()));
742 for (Lane lane : nextLanes)
743 {
744 lane.addGTU(this, insertFraction);
745 }
746 this.pendingEnterTrigger = null;
747 scheduleEnterEvent();
748 for (Lane lane : nextLanes)
749 {
750 scheduleTriggers(lane, nextLaneDirection.getDirection());
751 }
752 }
753
754
755
756
757
758
759
760
761
762 private void forceLaneChangeFinalization() throws GTUException, OperationalPlanException, SimRuntimeException
763 {
764 if (this.finalizeLaneChangeEvent != null)
765 {
766
767 SimEventInterface<SimTimeDoubleUnit> tmp = this.finalizeLaneChangeEvent;
768 finalizeLaneChange(this.referenceLaneIndex == 0 ? LateralDirectionality.RIGHT : LateralDirectionality.LEFT);
769 getSimulator().cancelEvent(tmp);
770 enterCrossSection();
771 }
772
773 }
774
775
776
777
778
779
780
781
782 protected void scheduleLeaveEvent() throws GTUException, OperationalPlanException, SimRuntimeException
783 {
784 CrossSection firstCrossSection = this.crossSections.get(0);
785
786 boolean possiblyNearNextSection =
787 !getReferencePosition().getLane().equals(firstCrossSection.getLanes().get(this.referenceLaneIndex));
788 if (!possiblyNearNextSection)
789 {
790 Length remain = remainingEventDistance();
791 Lane lane = firstCrossSection.getLanes().get(this.referenceLaneIndex);
792 Length position = position(lane, getRear());
793 possiblyNearNextSection = firstCrossSection.getDirection().isPlus() ? lane.getLength().minus(position).lt(remain)
794 : position.lt(remain);
795 }
796 if (possiblyNearNextSection)
797 {
798 CrossSectionLink link = firstCrossSection.getLanes().get(0).getParentLink();
799 OTSLine3D leaveLine = firstCrossSection.getDirection().isPlus() ? link.getEndLine() : link.getStartLine();
800 Time leaveTime = timeAtLine(leaveLine, getRear());
801 if (leaveTime == null)
802 {
803
804 Lane lane = this.crossSections.get(0).getLanes().get(this.referenceLaneIndex);
805 Length pos = position(lane, getRear());
806 if (pos.gt(lane.getLength()))
807 {
808 pos = position(lane, getRear());
809 this.pendingLeaveTrigger = getSimulator().scheduleEventNow(this, this, "leaveCrossSection", null);
810 SimLogger.always().info("Forcing leave for GTU {} on lane {}", getId(), lane.getFullId());
811 }
812 }
813 if (leaveTime != null)
814 {
815 if (leaveTime.lt(getSimulator().getSimulatorTime()))
816 {
817 System.err.println(
818 "Time travel? leaveTime=" + leaveTime + "; simulator time=" + getSimulator().getSimulatorTime());
819 leaveTime = getSimulator().getSimulatorTime();
820 }
821 this.pendingLeaveTrigger = getSimulator().scheduleEventAbs(leaveTime, this, this, "leaveCrossSection", null);
822 }
823 }
824 }
825
826
827
828
829
830
831
832
833 protected synchronized void leaveCrossSection() throws GTUException, OperationalPlanException, SimRuntimeException
834 {
835 List<Lane> lanes = this.crossSections.get(0).getLanes();
836 for (int i = 0; i < lanes.size(); i++)
837 {
838 Lane lane = lanes.get(i);
839 if (lane != null)
840 {
841 lane.removeGTU(this, i == lanes.size() - 1, position(lane, getReference()));
842 }
843 }
844 this.crossSections.remove(0);
845 this.pendingLeaveTrigger = null;
846 scheduleLeaveEvent();
847 }
848
849
850
851
852
853
854
855
856
857 protected void scheduleTriggers(final Lane lane, final GTUDirectionality direction)
858 throws GTUException, OperationalPlanException, SimRuntimeException
859 {
860 double min;
861 double max;
862 Length remain = remainingEventDistance();
863 if (direction.isPlus())
864 {
865 min = position(lane, getRear()).si;
866 max = min + remain.si + getLength().si;
867 }
868 else
869 {
870 max = position(lane, getRear()).si;
871 min = max - remain.si - getLength().si;
872 }
873 SortedMap<Double, List<SingleSensor>> sensors = lane.getSensorMap(getGTUType(), direction).subMap(min, max);
874 for (List<SingleSensor> list : sensors.values())
875 {
876 for (SingleSensor sensor : list)
877 {
878 RelativePosition pos = this.getRelativePositions().get(sensor.getPositionType());
879 Time time = timeAtLine(sensor.getGeometry(), pos);
880 if (time != null)
881 {
882 this.sensorEvents
883 .add(getSimulator().scheduleEventAbs(time, this, sensor, "trigger", new Object[] { this }));
884 }
885 }
886 }
887 }
888
889
890
891
892
893
894 private Length remainingEventDistance() throws OperationalPlanException
895 {
896 if (getOperationalPlan() instanceof LaneBasedOperationalPlan)
897 {
898 LaneBasedOperationalPlanrg/opentrafficsim/road/gtu/lane/plan/operational/LaneBasedOperationalPlan.html#LaneBasedOperationalPlan">LaneBasedOperationalPlan plan = (LaneBasedOperationalPlan) getOperationalPlan();
899 return plan.getTotalLength().minus(plan.getTraveledDistance(getSimulator().getSimulatorTime())).plus(eventMargin);
900 }
901 return getOperationalPlan().getTotalLength().plus(eventMargin);
902 }
903
904
905
906
907
908
909
910
911
912
913 private Time timeAtLine(final OTSLine3D line, final RelativePosition relativePosition) throws GTUException
914 {
915 Throw.when(line.size() != 2, IllegalArgumentException.class, "Line to cross with path should have 2 points.");
916 OTSLine3D path = getOperationalPlan().getPath();
917 OTSPoint3D[] points;
918 double adjust;
919 if (relativePosition.getDx().gt0())
920 {
921
922 points = new OTSPoint3D[path.size() + 1];
923 System.arraycopy(path.getPoints(), 0, points, 0, path.size());
924 points[path.size()] = new OTSPoint3D(path.getLocationExtendedSI(path.getLengthSI() + relativePosition.getDx().si));
925 adjust = -relativePosition.getDx().si;
926 }
927 else if (relativePosition.getDx().lt0())
928 {
929 points = new OTSPoint3D[path.size() + 1];
930 System.arraycopy(path.getPoints(), 0, points, 1, path.size());
931 points[0] = new OTSPoint3D(path.getLocationExtendedSI(relativePosition.getDx().si));
932 adjust = 0.0;
933 }
934 else
935 {
936 points = path.getPoints();
937 adjust = 0.0;
938 }
939
940
941 double cumul = 0.0;
942 for (int i = 0; i < points.length - 1; i++)
943 {
944 OTSPoint3D intersect;
945 try
946 {
947 intersect = OTSPoint3D.intersectionOfLineSegments(points[i], points[i + 1], line.get(0), line.get(1));
948 }
949 catch (OTSGeometryException exception)
950 {
951
952 throw new RuntimeException("Unexpected exception while obtaining points from line to cross.", exception);
953 }
954 if (intersect != null)
955 {
956 cumul += points[i].distanceSI(intersect);
957 cumul += adjust;
958
959 if (cumul < 0.0)
960 {
961 return getSimulator().getSimulatorTime();
962 }
963 if (cumul <= getOperationalPlan().getTotalLength().si)
964 {
965 return getOperationalPlan().timeAtDistance(Length.instantiateSI(cumul));
966 }
967
968 return null;
969 }
970 else if (i < points.length - 2)
971 {
972 cumul += points[i].distanceSI(points[i + 1]);
973 }
974 }
975
976 return null;
977 }
978
979
980 @Override
981 public final Map<Lane, Length> positions(final RelativePosition relativePosition) throws GTUException
982 {
983 return positions(relativePosition, getSimulator().getSimulatorTime());
984 }
985
986
987 @Override
988 public final Map<Lane, Length> positions(final RelativePosition relativePosition, final Time when) throws GTUException
989 {
990 Map<Lane, Length> positions = new LinkedHashMap<>();
991 for (CrossSection crossSection : this.crossSections.get(when))
992 {
993 for (Lane lane : crossSection.getLanes())
994 {
995 positions.put(lane, position(lane, relativePosition, when));
996 }
997 }
998 return positions;
999 }
1000
1001
1002 @Override
1003 public final Length position(final Lane lane, final RelativePosition relativePosition) throws GTUException
1004 {
1005 return position(lane, relativePosition, getSimulator().getSimulatorTime());
1006 }
1007
1008
1009 private double cachePositionsTime = Double.NaN;
1010
1011
1012 private Map<Integer, Length> cachedPositions = new LinkedHashMap<>();
1013
1014
1015 @Override
1016 @SuppressWarnings("checkstyle:designforextension")
1017 public Length position(final Lane lane, final RelativePosition relativePosition, final Time when) throws GTUException
1018 {
1019 int cacheIndex = 0;
1020 if (CACHING)
1021 {
1022 cacheIndex = 17 * lane.hashCode() + relativePosition.hashCode();
1023 Length l;
1024 if (when.si == this.cachePositionsTime && (l = this.cachedPositions.get(cacheIndex)) != null)
1025 {
1026
1027
1028
1029
1030
1031
1032
1033 CACHED_POSITION++;
1034 return l;
1035 }
1036 if (when.si != this.cachePositionsTime)
1037 {
1038 this.cachedPositions.clear();
1039 this.cachePositionsTime = when.si;
1040 }
1041 }
1042 NON_CACHED_POSITION++;
1043
1044 synchronized (this.lock)
1045 {
1046 List<CrossSection> whenCrossSections = this.crossSections.get(when);
1047 double loc = Double.NaN;
1048
1049 try
1050 {
1051 int crossSectionIndex = -1;
1052 int lateralIndex = -1;
1053 for (int i = 0; i < whenCrossSections.size(); i++)
1054 {
1055 if (whenCrossSections.get(i).getLanes().contains(lane))
1056 {
1057 crossSectionIndex = i;
1058 lateralIndex = whenCrossSections.get(i).getLanes().indexOf(lane);
1059 break;
1060 }
1061 }
1062 Throw.when(lateralIndex == -1, GTUException.class, "GTU %s is not on lane %s.", this, lane);
1063
1064 OperationalPlan plan = getOperationalPlan(when);
1065 DirectedPoint p = plan.getLocation(when, relativePosition);
1066 double f = lane.getCenterLine().projectFractional(null, null, p.x, p.y, FractionalFallback.NaN);
1067 if (!Double.isNaN(f))
1068 {
1069 loc = f * lane.getLength().si;
1070 }
1071 else
1072 {
1073
1074
1075 double distance = 0.0;
1076 for (int i = crossSectionIndex - 1; i >= 0; i--)
1077 {
1078 Lane tryLane = whenCrossSections.get(i).getLanes().get(lateralIndex);
1079 f = tryLane.getCenterLine().projectFractional(null, null, p.x, p.y, FractionalFallback.NaN);
1080 if (!Double.isNaN(f))
1081 {
1082 f = whenCrossSections.get(i).getDirection() == GTUDirectionality.DIR_PLUS ? 1 - f : f;
1083 loc = distance - f * tryLane.getLength().si;
1084 break;
1085 }
1086 distance -= tryLane.getLength().si;
1087 }
1088
1089 if (Double.isNaN(loc))
1090 {
1091 distance = lane.getLength().si;
1092 for (int i = crossSectionIndex + 1; i < whenCrossSections.size(); i++)
1093 {
1094 Lane tryLane = whenCrossSections.get(i).getLanes().get(lateralIndex);
1095 f = tryLane.getCenterLine().projectFractional(null, null, p.x, p.y, FractionalFallback.NaN);
1096 if (!Double.isNaN(f))
1097 {
1098 f = whenCrossSections.get(i).getDirection() == GTUDirectionality.DIR_PLUS ? f : 1 - f;
1099 loc = distance + f * tryLane.getLength().si;
1100 break;
1101 }
1102 distance += tryLane.getLength().si;
1103 }
1104 }
1105
1106 }
1107
1108 if (Double.isNaN(loc))
1109 {
1110
1111
1112
1113 f = lane.getCenterLine().projectFractional(null, null, p.x, p.y, FractionalFallback.ENDPOINT);
1114 loc = lane.getLength().si * f;
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130 }
1131 }
1132 catch (Exception e)
1133 {
1134 System.err.println(toString());
1135 throw new GTUException(e);
1136 }
1137
1138 Length length = Length.instantiateSI(loc);
1139 if (CACHING)
1140 {
1141 this.cachedPositions.put(cacheIndex, length);
1142 }
1143 return length;
1144 }
1145 }
1146
1147
1148 @Override
1149 @SuppressWarnings("checkstyle:designforextension")
1150 public DirectedLanePosition getReferencePosition() throws GTUException
1151 {
1152 if (this.referencePositionTime == getSimulator().getSimulatorTime().si)
1153 {
1154 return this.cachedReferencePosition;
1155 }
1156 Lane refLane = null;
1157 for (CrossSection crossSection : this.crossSections)
1158 {
1159 Lane lane = crossSection.getLanes().get(this.referenceLaneIndex);
1160 double fraction = fractionalPosition(lane, getReference());
1161 if (fraction >= 0.0 && fraction <= 1.0)
1162 {
1163 refLane = lane;
1164 break;
1165 }
1166 }
1167 if (refLane != null)
1168 {
1169 this.cachedReferencePosition =
1170 new DirectedLanePosition(refLane, position(refLane, getReference()), this.getDirection(refLane));
1171 this.referencePositionTime = getSimulator().getSimulatorTime().si;
1172 return this.cachedReferencePosition;
1173 }
1174 throw new GTUException("The reference point of GTU " + this + " is not on any of the lanes on which it is registered");
1175 }
1176
1177
1178 @Override
1179 public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition) throws GTUException
1180 {
1181 return fractionalPositions(relativePosition, getSimulator().getSimulatorTime());
1182 }
1183
1184
1185 @Override
1186 public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition, final Time when)
1187 throws GTUException
1188 {
1189 Map<Lane, Double> positions = new LinkedHashMap<>();
1190 for (CrossSection crossSection : this.crossSections)
1191 {
1192 for (Lane lane : crossSection.getLanes())
1193 {
1194 positions.put(lane, fractionalPosition(lane, relativePosition, when));
1195 }
1196 }
1197 return positions;
1198 }
1199
1200
1201 @Override
1202 public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition, final Time when)
1203 throws GTUException
1204 {
1205 return position(lane, relativePosition, when).getSI() / lane.getLength().getSI();
1206 }
1207
1208
1209 @Override
1210 public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition) throws GTUException
1211 {
1212 return position(lane, relativePosition).getSI() / lane.getLength().getSI();
1213 }
1214
1215
1216 @Override
1217 public final void addTrigger(final Lane lane, final SimEventInterface<SimTimeDoubleUnit> event)
1218 {
1219 throw new UnsupportedOperationException("Method addTrigger is not supported.");
1220 }
1221
1222
1223
1224
1225
1226 public void setVehicleModel(final VehicleModel vehicleModel)
1227 {
1228 this.vehicleModel = vehicleModel;
1229 }
1230
1231
1232 @Override
1233 public VehicleModel getVehicleModel()
1234 {
1235 return this.vehicleModel;
1236 }
1237
1238
1239 @Override
1240 @SuppressWarnings("checkstyle:designforextension")
1241 public void destroy()
1242 {
1243 DirectedLanePosition dlp = null;
1244 try
1245 {
1246 dlp = getReferencePosition();
1247 }
1248 catch (GTUException e)
1249 {
1250
1251 }
1252 DirectedPoint location = this.getOperationalPlan() == null ? new DirectedPoint(0.0, 0.0, 0.0) : getLocation();
1253 synchronized (this.lock)
1254 {
1255 for (CrossSection crossSection : this.crossSections)
1256 {
1257 boolean removeFromParentLink = true;
1258 for (Lane lane : crossSection.getLanes())
1259 {
1260 Length position;
1261 try
1262 {
1263 position = position(lane, getReference());
1264 }
1265 catch (GTUException exception)
1266 {
1267
1268
1269 throw new RuntimeException(exception);
1270 }
1271 lane.removeGTU(this, removeFromParentLink, position);
1272 removeFromParentLink = false;
1273 }
1274 }
1275 }
1276 if (dlp != null && dlp.getLane() != null)
1277 {
1278 Lane referenceLane = dlp.getLane();
1279 fireTimedEvent(LaneBasedGTU.LANEBASED_DESTROY_EVENT,
1280 new Object[] { getId(), location, getOdometer(), referenceLane, dlp.getPosition(), dlp.getGtuDirection() },
1281 getSimulator().getSimulatorTime());
1282 }
1283 else
1284 {
1285 fireTimedEvent(LaneBasedGTU.LANEBASED_DESTROY_EVENT,
1286 new Object[] { getId(), location, getOdometer(), null, Length.ZERO, null },
1287 getSimulator().getSimulatorTime());
1288 }
1289 cancelAllEvents();
1290
1291 super.destroy();
1292 }
1293
1294
1295 @Override
1296 public final Bounds getBounds()
1297 {
1298 double dx = 0.5 * getLength().doubleValue();
1299 double dy = 0.5 * getWidth().doubleValue();
1300 return new BoundingBox(new Point3d(-dx, -dy, 0.0), new Point3d(dx, dy, 0.0));
1301 }
1302
1303
1304 @Override
1305 public final LaneBasedStrategicalPlanner getStrategicalPlanner()
1306 {
1307 return (LaneBasedStrategicalPlanner) super.getStrategicalPlanner();
1308 }
1309
1310
1311 @Override
1312 public final LaneBasedStrategicalPlanner getStrategicalPlanner(final Time time)
1313 {
1314 return (LaneBasedStrategicalPlanner) super.getStrategicalPlanner(time);
1315 }
1316
1317
1318 @Override
1319 public RoadNetwork getNetwork()
1320 {
1321 return (RoadNetwork) super.getPerceivableContext();
1322 }
1323
1324
1325 @Override
1326 public Speed getDesiredSpeed()
1327 {
1328 Time simTime = getSimulator().getSimulatorTime();
1329 if (this.desiredSpeedTime == null || this.desiredSpeedTime.si < simTime.si)
1330 {
1331 InfrastructurePerception infra =
1332 getTacticalPlanner().getPerception().getPerceptionCategoryOrNull(InfrastructurePerception.class);
1333 SpeedLimitInfo speedInfo;
1334 if (infra == null)
1335 {
1336 speedInfo = new SpeedLimitInfo();
1337 speedInfo.addSpeedInfo(SpeedLimitTypes.MAX_VEHICLE_SPEED, getMaximumSpeed());
1338 }
1339 else
1340 {
1341
1342 speedInfo = infra.getSpeedLimitProspect(RelativeLane.CURRENT).getSpeedLimitInfo(Length.ZERO);
1343 }
1344 this.cachedDesiredSpeed =
1345 Try.assign(() -> getTacticalPlanner().getCarFollowingModel().desiredSpeed(getParameters(), speedInfo),
1346 "Parameter exception while obtaining the desired speed.");
1347 this.desiredSpeedTime = simTime;
1348 }
1349 return this.cachedDesiredSpeed;
1350 }
1351
1352
1353 @Override
1354 public Acceleration getCarFollowingAcceleration()
1355 {
1356 Time simTime = getSimulator().getSimulatorTime();
1357 if (this.carFollowingAccelerationTime == null || this.carFollowingAccelerationTime.si < simTime.si)
1358 {
1359 LanePerception perception = getTacticalPlanner().getPerception();
1360
1361 EgoPerception<?, ?> ego = perception.getPerceptionCategoryOrNull(EgoPerception.class);
1362 Throw.whenNull(ego, "EgoPerception is required to determine the speed.");
1363 Speed speed = ego.getSpeed();
1364
1365 InfrastructurePerception infra = perception.getPerceptionCategoryOrNull(InfrastructurePerception.class);
1366 Throw.whenNull(infra, "InfrastructurePerception is required to determine the desired speed.");
1367 SpeedLimitInfo speedInfo = infra.getSpeedLimitProspect(RelativeLane.CURRENT).getSpeedLimitInfo(Length.ZERO);
1368
1369 NeighborsPerception neighbors = perception.getPerceptionCategoryOrNull(NeighborsPerception.class);
1370 Throw.whenNull(neighbors, "NeighborsPerception is required to determine the car-following acceleration.");
1371 PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders = neighbors.getLeaders(RelativeLane.CURRENT);
1372
1373 this.cachedCarFollowingAcceleration =
1374 Try.assign(() -> getTacticalPlanner().getCarFollowingModel().followingAcceleration(getParameters(), speed,
1375 speedInfo, leaders), "Parameter exception while obtaining the desired speed.");
1376 this.carFollowingAccelerationTime = simTime;
1377 }
1378 return this.cachedCarFollowingAcceleration;
1379 }
1380
1381
1382 @Override
1383 public final TurnIndicatorStatus getTurnIndicatorStatus()
1384 {
1385 return this.turnIndicatorStatus.get();
1386 }
1387
1388
1389 @Override
1390 public final TurnIndicatorStatus getTurnIndicatorStatus(final Time time)
1391 {
1392 return this.turnIndicatorStatus.get(time);
1393 }
1394
1395
1396 @Override
1397 public final void setTurnIndicatorStatus(final TurnIndicatorStatus turnIndicatorStatus)
1398 {
1399 this.turnIndicatorStatus.set(turnIndicatorStatus);
1400 }
1401
1402
1403 @Override
1404 public Length getLateralPosition(final Lane lane) throws GTUException
1405 {
1406 OperationalPlan plan = getOperationalPlan();
1407 if (plan instanceof LaneBasedOperationalPlanntrafficsim/road/gtu/lane/plan/operational/LaneBasedOperationalPlan.html#LaneBasedOperationalPlan">LaneBasedOperationalPlan && !((LaneBasedOperationalPlan) plan).isDeviative())
1408 {
1409 return Length.ZERO;
1410 }
1411 DirectedLanePosition ref = getReferencePosition();
1412 int latIndex = -1;
1413 int longIndex = -1;
1414 for (int i = 0; i < this.crossSections.size(); i++)
1415 {
1416 List<Lane> lanes = this.crossSections.get(i).getLanes();
1417 if (lanes.contains(lane))
1418 {
1419 latIndex = lanes.indexOf(lane);
1420 }
1421 if (lanes.contains(ref.getLane()))
1422 {
1423 longIndex = i;
1424 }
1425 }
1426 Throw.when(latIndex == -1 || longIndex == -1, GTUException.class, "GTU %s is not on %s", getId(), lane);
1427 Lane refCrossSectionLane = this.crossSections.get(longIndex).getLanes().get(latIndex);
1428 DirectedPoint loc = getLocation();
1429 double f = refCrossSectionLane.getCenterLine().projectOrthogonal(loc.x, loc.y);
1430 DirectedPoint p = Try.assign(() -> refCrossSectionLane.getCenterLine().getLocationFraction(f), GTUException.class,
1431 "GTU %s is not orthogonal to the reference lane.", getId());
1432 double d = p.distance(loc);
1433 d = ref.getGtuDirection().isPlus() ? d : -d;
1434 if (this.crossSections.get(0).getLanes().size() > 1)
1435 {
1436 return Length.instantiateSI(latIndex == 0 ? -d : d);
1437 }
1438 double x2 = p.x + Math.cos(p.getRotZ());
1439 double y2 = p.y + Math.sin(p.getRotZ());
1440 double det = (loc.x - p.x) * (y2 - p.y) - (loc.y - p.y) * (x2 - p.x);
1441 return Length.instantiateSI(det < 0.0 ? -d : d);
1442 }
1443
1444
1445 @Override
1446 public void setInstantaneousLaneChange(final boolean instantaneous)
1447 {
1448 this.instantaneousLaneChange = instantaneous;
1449 }
1450
1451
1452 @Override
1453 public boolean isInstantaneousLaneChange()
1454 {
1455 return this.instantaneousLaneChange;
1456 }
1457
1458
1459 @Override
1460 @SuppressWarnings("checkstyle:designforextension")
1461 public String toString()
1462 {
1463 return String.format("GTU " + getId());
1464 }
1465
1466
1467 private static class CrossSection
1468 {
1469
1470
1471 private final List<Lane> lanes;
1472
1473
1474 private final GTUDirectionality direction;
1475
1476
1477
1478
1479
1480 protected CrossSection(final List<Lane> lanes, final GTUDirectionality direction)
1481 {
1482 this.lanes = lanes;
1483 this.direction = direction;
1484 }
1485
1486
1487
1488
1489 protected List<Lane> getLanes()
1490 {
1491 return this.lanes;
1492 }
1493
1494
1495
1496
1497 protected GTUDirectionality getDirection()
1498 {
1499 return this.direction;
1500 }
1501
1502 }
1503
1504 }