1 package org.opentrafficsim.road.gtu.lane;
2
3 import java.awt.geom.AffineTransform;
4 import java.awt.geom.Path2D;
5 import java.awt.geom.Rectangle2D;
6 import java.util.ArrayList;
7 import java.util.HashMap;
8 import java.util.HashSet;
9 import java.util.LinkedHashMap;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Set;
13
14 import javax.media.j3d.Bounds;
15 import javax.vecmath.Point3d;
16
17 import org.djunits.unit.LengthUnit;
18 import org.djunits.value.vdouble.scalar.Duration;
19 import org.djunits.value.vdouble.scalar.Length;
20 import org.djunits.value.vdouble.scalar.Speed;
21 import org.djunits.value.vdouble.scalar.Time;
22 import org.opentrafficsim.core.Throw;
23 import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
24 import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
25 import org.opentrafficsim.core.geometry.OTSGeometryException;
26 import org.opentrafficsim.core.geometry.OTSShape;
27 import org.opentrafficsim.core.gtu.AbstractGTU;
28 import org.opentrafficsim.core.gtu.GTUDirectionality;
29 import org.opentrafficsim.core.gtu.GTUException;
30 import org.opentrafficsim.core.gtu.GTUType;
31 import org.opentrafficsim.core.gtu.RelativePosition;
32 import org.opentrafficsim.core.gtu.behavioralcharacteristics.BehavioralCharacteristics;
33 import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
34 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
35 import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
36 import org.opentrafficsim.core.network.Link;
37 import org.opentrafficsim.core.network.NetworkException;
38 import org.opentrafficsim.core.network.Node;
39 import org.opentrafficsim.core.network.OTSNetwork;
40 import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedTacticalPlanner;
41 import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
42 import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlanner;
43 import org.opentrafficsim.road.network.lane.CrossSectionElement;
44 import org.opentrafficsim.road.network.lane.CrossSectionLink;
45 import org.opentrafficsim.road.network.lane.DirectedLanePosition;
46 import org.opentrafficsim.road.network.lane.Lane;
47
48 import nl.tudelft.simulation.dsol.SimRuntimeException;
49 import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
50 import nl.tudelft.simulation.language.d3.BoundingBox;
51 import nl.tudelft.simulation.language.d3.DirectedPoint;
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 public abstract class AbstractLaneBasedGTU extends AbstractGTU implements LaneBasedGTU
79 {
80
81 private static final long serialVersionUID = 20140822L;
82
83
84
85
86
87
88 private Map<Link, Double> fractionalLinkPositions = new LinkedHashMap<>();
89
90
91
92
93
94
95
96 private final Map<Lane, GTUDirectionality> lanes = new HashMap<>();
97
98
99 private Map<Lane, List<SimEvent<OTSSimTimeDouble>>> pendingTriggers = new HashMap<>();
100
101
102 private Object lock = new Object();
103
104
105 private static final boolean MOVEMENT_LANE_BASED = true;
106
107
108
109
110
111
112
113
114
115 @SuppressWarnings("checkstyle:parameternumber")
116 public AbstractLaneBasedGTU(final String id, final GTUType gtuType, final OTSDEVSSimulatorInterface simulator,
117 final OTSNetwork network) throws GTUException
118 {
119 super(id, gtuType, simulator, network);
120 }
121
122
123
124
125
126
127
128
129
130
131 public final void init(final LaneBasedStrategicalPlanner strategicalPlanner,
132 final Set<DirectedLanePosition> initialLongitudinalPositions, final Speed initialSpeed)
133 throws NetworkException, SimRuntimeException, GTUException, OTSGeometryException
134 {
135 Throw.when(null == initialLongitudinalPositions, GTUException.class, "InitialLongitudinalPositions is null");
136 Throw.when(0 == initialLongitudinalPositions.size(), GTUException.class, "InitialLongitudinalPositions is empty set");
137
138 DirectedPoint lastPoint = null;
139 for (DirectedLanePosition pos : initialLongitudinalPositions)
140 {
141 Throw.when(lastPoint != null && pos.getLocation().distance(lastPoint) > 1E-6, GTUException.class,
142 "initial locations for GTU have distance > 1 mm");
143 lastPoint = pos.getLocation();
144 }
145 DirectedPoint initialLocation = lastPoint;
146
147
148 for (DirectedLanePosition directedLanePosition : initialLongitudinalPositions)
149 {
150 Lane lane = directedLanePosition.getLane();
151 if (!this.fractionalLinkPositions.containsKey(lane.getParentLink()))
152 {
153
154 this.fractionalLinkPositions.put(lane.getParentLink(), lane.fraction(directedLanePosition.getPosition()));
155 }
156 this.lanes.put(lane, directedLanePosition.getGtuDirection());
157 lane.addGTU(this, lane.fraction(directedLanePosition.getPosition()));
158 }
159
160
161 super.init(strategicalPlanner, initialLocation, initialSpeed);
162
163
164 Lane referenceLane = getLanes().keySet().iterator().next();
165 fireTimedEvent(LaneBasedGTU.INIT_EVENT, new Object[] { getId(), initialLocation, getLength(), getWidth(), referenceLane,
166 position(referenceLane, getReference()) }, getSimulator().getSimulatorTime());
167
168 }
169
170
171 @Override
172 public final void enterLane(final Lane lane, final Length position, final GTUDirectionality gtuDirection)
173 throws GTUException
174 {
175 Throw.when(!MOVEMENT_LANE_BASED, GTUException.class, "MOVEMENT_LANE_BASED is true, but enterLane() is called");
176 if (lane == null || gtuDirection == null || position == null)
177 {
178 throw new GTUException("enterLane - one of the arguments is null");
179 }
180 if (this.lanes.containsKey(lane))
181 {
182 System.err.println(this + " is already registered on lane: " + lane + " at fractional position "
183 + this.fractionalPosition(lane, RelativePosition.REFERENCE_POSITION) + " intended position is " + position
184 + " length of lane is " + lane.getLength());
185 return;
186 }
187
188
189
190 if (!this.fractionalLinkPositions.containsKey(lane.getParentLink()))
191 {
192 this.fractionalLinkPositions.put(lane.getParentLink(), lane.fraction(position));
193 }
194 this.lanes.put(lane, gtuDirection);
195 lane.addGTU(this, position);
196 }
197
198
199 @Override
200 public final void leaveLane(final Lane lane) throws GTUException
201 {
202 leaveLane(lane, false);
203 }
204
205
206
207
208
209
210
211 public final void leaveLane(final Lane lane, final boolean beingDestroyed) throws GTUException
212 {
213 Throw.when(!MOVEMENT_LANE_BASED, GTUException.class, "MOVEMENT_LANE_BASED is true, but leaveLane() is called");
214 this.lanes.remove(lane);
215 List<SimEvent<OTSSimTimeDouble>> pending = this.pendingTriggers.get(lane);
216 if (null != pending)
217 {
218 for (SimEvent<OTSSimTimeDouble> event : pending)
219 {
220 if (event.getAbsoluteExecutionTime().get().ge(getSimulator().getSimulatorTime().get()))
221 {
222
223 boolean result = getSimulator().cancelEvent(event);
224 if (!result && event.getAbsoluteExecutionTime().get().ne(getSimulator().getSimulatorTime().get()))
225 {
226 System.err.println("NOTHING REMOVED");
227 }
228 }
229 }
230
231 this.pendingTriggers.remove(lane);
232 }
233
234 boolean found = false;
235 for (Lane l : this.lanes.keySet())
236 {
237 if (l.getParentLink().equals(lane.getParentLink()))
238 {
239 found = true;
240 }
241 }
242 if (!found)
243 {
244 this.fractionalLinkPositions.remove(lane.getParentLink());
245 }
246 lane.removeGTU(this);
247 if (this.lanes.size() == 0 && !beingDestroyed)
248 {
249 System.err.println("lanes.size() = 0 for GTU " + getId());
250 }
251 }
252
253
254 @Override
255 public final Map<Lane, GTUDirectionality> getLanes()
256 {
257 return new HashMap<>(this.lanes);
258 }
259
260
261 @Override
262 protected final void move(final DirectedPoint fromLocation)
263 throws SimRuntimeException, GTUException, OperationalPlanException, NetworkException, ParameterException
264 {
265 if (MOVEMENT_LANE_BASED)
266 {
267
268
269 if (this.lanes.isEmpty())
270 {
271 destroy();
272 return;
273 }
274
275 for (Link link : this.fractionalLinkPositions.keySet())
276 {
277 double d = this.fractionalLinkPositions.get(link);
278
279 double fractionalExcess = d < 0 ? -d : (d - 1);
280 if (fractionalExcess > 0)
281 {
282 double excess = fractionalExcess * link.getLength().si;
283 OperationalPlan op = this.getOperationalPlan();
284 double maxLength = this.getLength().si + op.getTraveledDistanceSI(op.getTotalDuration());
285 if (excess > maxLength)
286
287 {
288 System.err.println(this + " has extreme fractional position on Link " + link + ": " + d + " (" + excess
289 + "m), time is " + this.getSimulator().getSimulatorTime().get());
290 }
291 }
292 }
293
294 Map<Link, Double> newLinkPositions = new HashMap<>();
295
296 for (Lane lane : this.lanes.keySet())
297 {
298 newLinkPositions.put(lane.getParentLink(), lane.fraction(position(lane, getReference())));
299 }
300
301
302 super.move(fromLocation);
303
304
305 Lane referenceLane = getLanes().keySet().iterator().next();
306 fireTimedEvent(LaneBasedGTU.MOVE_EVENT,
307 new Object[] { getId(), fromLocation, getSpeed(), getAcceleration(), getTurnIndicatorStatus(),
308 getOdometer(), referenceLane, position(referenceLane, getReference()) },
309 getSimulator().getSimulatorTime());
310
311 if (getOperationalPlan().getAcceleration(Duration.ZERO).si < -10)
312 {
313 System.err.println("(getOperationalPlan().getAcceleration(Duration.ZERO).si < -10)");
314 }
315
316
317 this.fractionalLinkPositions = newLinkPositions;
318
319
320 scheduleTriggers();
321 }
322
323 else
324
325 {
326 try
327 {
328
329 super.move(fromLocation);
330
331
332 int steps = Math.max(5, (int) (2.0 * getOperationalPlan().getPath().getLengthSI() / getLength().si));
333 DirectedPoint[] points = new DirectedPoint[steps + 1];
334 OTSShape[] rects = new OTSShape[steps + 1];
335 for (int i = 0; i <= steps; i++)
336 {
337 points[i] = getOperationalPlan().getPath().getLocationFraction(1.0 * i / steps);
338 double x = points[i].x;
339 double y = points[i].y;
340 double l = getLength().si;
341 double w = getWidth().si;
342 Rectangle2D rect = new Rectangle2D.Double(-l / 2.0, -w / 2.0, l, w);
343 Path2D.Double path = new Path2D.Double(rect);
344 AffineTransform t = new AffineTransform();
345 t.translate(x, y);
346 t.rotate(points[i].getRotZ());
347 path.transform(t);
348 rects[i] = new OTSShape(path);
349 }
350
351
352 @SuppressWarnings("unchecked")
353 List<Lane>[] laneLists = new ArrayList[steps + 1];
354 Set<Lane> laneSet = new HashSet<>();
355 OTSNetwork network = (OTSNetwork) getPerceivableContext();
356 for (int i = 0; i < rects.length; i++)
357 {
358 laneLists[i] = new ArrayList<>();
359 for (Link link : network.getLinkMap().values())
360 {
361 if (link instanceof CrossSectionLink)
362 {
363 for (Lane lane : ((CrossSectionLink) link).getLanes())
364 {
365 if (lane.getContour().intersects(rects[i]))
366 {
367 laneLists[i].add(lane);
368 laneSet.add(lane);
369 }
370 }
371 }
372 }
373 }
374 System.out.println(this + " will be on lanes: " + laneSet);
375 }
376 catch (OTSGeometryException e)
377 {
378 throw new GTUException(e);
379 }
380 }
381 }
382
383
384 @Override
385 public final Map<Lane, Length> positions(final RelativePosition relativePosition) throws GTUException
386 {
387 return positions(relativePosition, getSimulator().getSimulatorTime().getTime());
388 }
389
390
391 @Override
392 public final Map<Lane, Length> positions(final RelativePosition relativePosition, final Time when) throws GTUException
393 {
394 Map<Lane, Length> positions = new LinkedHashMap<>();
395 for (Lane lane : this.lanes.keySet())
396 {
397 positions.put(lane, position(lane, relativePosition, when));
398 }
399 return positions;
400 }
401
402
403 @Override
404 public final Length position(final Lane lane, final RelativePosition relativePosition) throws GTUException
405 {
406 return position(lane, relativePosition, getSimulator().getSimulatorTime().getTime());
407 }
408
409
410 public final Length projectedPosition(final Lane projectionLane, final RelativePosition relativePosition, final Time when)
411 throws GTUException
412 {
413
414 CrossSectionLink link = projectionLane.getParentLink();
415 for (CrossSectionElement cse : link.getCrossSectionElementList())
416 {
417 if (cse instanceof Lane)
418 {
419 Lane cseLane = (Lane) cse;
420 if (null != this.lanes.get(cseLane))
421 {
422 double fractionalPosition = fractionalPosition(cseLane, RelativePosition.REFERENCE_POSITION, when);
423 Length pos = new Length(projectionLane.getLength().getSI() * fractionalPosition, LengthUnit.SI);
424 if (this.lanes.get(cseLane).isPlus())
425 {
426 return pos.plus(relativePosition.getDx());
427 }
428 return pos.minus(relativePosition.getDx());
429 }
430 }
431 }
432 throw new GTUException(this + " is not on any lane of Link " + link);
433 }
434
435
436 @Override
437 public final Length position(final Lane lane, final RelativePosition relativePosition, final Time when) throws GTUException
438 {
439 if (null == lane)
440 {
441 throw new GTUException("lane is null");
442 }
443 synchronized (this.lock)
444 {
445 if (!this.lanes.containsKey(lane))
446 {
447 throw new GTUException("position() : GTU " + toString() + " is not on lane " + lane);
448 }
449 if (!this.fractionalLinkPositions.containsKey(lane.getParentLink()))
450 {
451
452 throw new GTUException(this + " does not have a fractional position on " + lane.toString());
453 }
454 Length longitudinalPosition = lane.position(this.fractionalLinkPositions.get(lane.getParentLink()));
455 if (longitudinalPosition == null)
456 {
457
458 throw new GTUException("position(): GTU " + toString() + " no position for lane " + lane);
459 }
460 if (getOperationalPlan() == null)
461 {
462
463 return longitudinalPosition.plus(relativePosition.getDx());
464 }
465 Length loc;
466 try
467 {
468 if (this.lanes.get(lane).equals(GTUDirectionality.DIR_PLUS))
469 {
470 loc = longitudinalPosition.plus(getOperationalPlan().getTraveledDistance(when))
471 .plus(relativePosition.getDx());
472 }
473 else
474 {
475 loc = longitudinalPosition.minus(getOperationalPlan().getTraveledDistance(when))
476 .minus(relativePosition.getDx());
477 }
478 }
479 catch (Exception e)
480 {
481 e.printStackTrace();
482 System.err.println(toString());
483 System.err.println(this.lanes);
484 System.err.println(this.fractionalLinkPositions);
485 throw new GTUException(e);
486 }
487 if (Double.isNaN(loc.getSI()))
488 {
489 System.out.println("loc is NaN");
490 }
491 return loc;
492 }
493 }
494
495
496
497
498
499
500
501
502
503 private void scheduleTriggers() throws NetworkException, SimRuntimeException, GTUException
504 {
505
506
507
508
509
510 double timestep = getOperationalPlan().getTotalDuration().si;
511
512 double moveSI = getSpeed().getSI() * timestep + 0.5 * getAcceleration().getSI() * timestep * timestep;
513 Map<Lane, GTUDirectionality> lanesCopy = new LinkedHashMap<>(this.lanes);
514 for (Lane lane : lanesCopy.keySet())
515 {
516
517 double referenceStartSI = this.fractionalLinkPositions.get(lane.getParentLink()) * lane.getLength().getSI();
518 double sign = lanesCopy.get(lane).equals(GTUDirectionality.DIR_PLUS) ? 1.0 : -1.0;
519 if (lanesCopy.get(lane).equals(GTUDirectionality.DIR_PLUS))
520 {
521 lane.scheduleTriggers(this, referenceStartSI, moveSI);
522 }
523 else
524 {
525
526 lane.scheduleTriggers(this, referenceStartSI - moveSI, moveSI);
527 }
528
529
530
531
532
533 double frontPosSI = referenceStartSI + sign * getFront().getDx().getSI();
534 double nextFrontPosSI = frontPosSI + sign * moveSI;
535
536
537 if (lanesCopy.get(lane).equals(GTUDirectionality.DIR_PLUS))
538 {
539 if (frontPosSI <= lane.getLength().si && nextFrontPosSI > lane.getLength().si)
540 {
541 Lane nextLane = determineNextLane(lane);
542 GTUDirectionality direction = lane.nextLanes(getGTUType()).get(nextLane);
543
544
545
546
547
548
549 if (direction.equals(GTUDirectionality.DIR_PLUS))
550 {
551 Length refPosAtLastTimestep =
552 new Length(-(lane.getLength().si - frontPosSI) - getFront().getDx().si, LengthUnit.SI);
553 enterLane(nextLane, refPosAtLastTimestep, direction);
554
555 nextLane.scheduleTriggers(this, refPosAtLastTimestep.getSI(), moveSI);
556 }
557 else if (direction.equals(GTUDirectionality.DIR_MINUS))
558 {
559 Length refPosAtLastTimestep =
560 new Length(nextLane.getLength().si + (lane.getLength().si - frontPosSI) + getFront().getDx().si,
561 LengthUnit.SI);
562 enterLane(nextLane, refPosAtLastTimestep, direction);
563
564
565 nextLane.scheduleTriggers(this, refPosAtLastTimestep.getSI() - moveSI, moveSI);
566 }
567 else
568 {
569 throw new NetworkException("scheduleTriggers DIR_PLUS for GTU " + toString() + ", nextLane " + nextLane
570 + ", direction not DIR_PLUS or DIR_MINUS");
571 }
572 }
573 }
574
575
576 else if (lanesCopy.get(lane).equals(GTUDirectionality.DIR_MINUS))
577 {
578 if (frontPosSI >= 0.0 && nextFrontPosSI < 0.0)
579 {
580 Lane prevLane = determinePrevLane(lane);
581 GTUDirectionality direction = lane.prevLanes(getGTUType()).get(prevLane);
582
583
584
585
586
587
588 if (direction.equals(GTUDirectionality.DIR_MINUS))
589 {
590 Length refPosAtLastTimestep =
591 new Length(prevLane.getLength().si + frontPosSI + getFront().getDx().si, LengthUnit.SI);
592 enterLane(prevLane, refPosAtLastTimestep, direction);
593
594 prevLane.scheduleTriggers(this, refPosAtLastTimestep.getSI() - moveSI, moveSI);
595 }
596 else if (direction.equals(GTUDirectionality.DIR_PLUS))
597 {
598 Length refPosAtLastTimestep = new Length(-frontPosSI - getFront().getDx().si, LengthUnit.SI);
599 enterLane(prevLane, refPosAtLastTimestep, direction);
600
601
602 prevLane.scheduleTriggers(this, refPosAtLastTimestep.getSI(), moveSI);
603 }
604 else
605 {
606 throw new NetworkException("scheduleTriggers DIR_MINUS for GTU " + toString() + ", prevLane " + prevLane
607 + ", direction not DIR_PLUS or DIR_MINUS");
608 }
609 }
610 }
611
612 else
613 {
614 throw new NetworkException(
615 "scheduleTriggers for GTU " + toString() + ", lane " + lane + ", direction not DIR_PLUS or DIR_MINUS");
616 }
617 }
618
619
620 for (Lane lane : this.lanes.keySet())
621 {
622
623
624 double referenceStartSI = this.fractionalLinkPositions.get(lane.getParentLink()) * lane.getLength().getSI();
625 double sign = this.lanes.get(lane).equals(GTUDirectionality.DIR_PLUS) ? 1.0 : -1.0;
626 double rearPosSI = referenceStartSI + sign * getRear().getDx().getSI();
627
628 if (this.lanes.get(lane).equals(GTUDirectionality.DIR_PLUS))
629 {
630 if (rearPosSI <= lane.getLength().si && rearPosSI + moveSI > lane.getLength().si)
631 {
632 double distanceToLeave = lane.getLength().si - rearPosSI;
633 Time exitTime = getOperationalPlan().timeAtDistance(new Length(distanceToLeave, LengthUnit.SI));
634 SimEvent<OTSSimTimeDouble> event = new SimEvent<>(new OTSSimTimeDouble(exitTime), this, this, "leaveLane",
635 new Object[] { lane, new Boolean(false) });
636 getSimulator().scheduleEvent(event);
637 addTrigger(lane, event);
638
639
640
641
642 }
643 }
644 else
645 {
646 if (rearPosSI >= 0.0 && rearPosSI - moveSI < 0.0)
647 {
648 double distanceToLeave = rearPosSI;
649 Time exitTime = getOperationalPlan().timeAtDistance(new Length(distanceToLeave, LengthUnit.SI));
650 SimEvent<OTSSimTimeDouble> event = new SimEvent<>(new OTSSimTimeDouble(exitTime), this, this, "leaveLane",
651 new Object[] { lane, new Boolean(false) });
652 getSimulator().scheduleEvent(event);
653 addTrigger(lane, event);
654
655
656
657 }
658 }
659 }
660 }
661
662
663
664
665
666
667
668
669 private Lane determineNextLane(final Lane lane) throws NetworkException, GTUException
670 {
671 Lane nextLane = null;
672 if (lane.nextLanes(getGTUType()).size() == 0)
673 {
674 throw new NetworkException(this + " - lane " + lane + " does not have a successor");
675 }
676 if (lane.nextLanes(getGTUType()).size() == 1)
677 {
678 nextLane = lane.nextLanes(getGTUType()).keySet().iterator().next();
679 }
680 else
681 {
682 if (!(getStrategicalPlanner() instanceof LaneBasedStrategicalRoutePlanner))
683 {
684 throw new GTUException(this + " reaches branch but has no route navigator");
685 }
686 Node nextNode = ((LaneBasedStrategicalRoutePlanner) getStrategicalPlanner()).nextNode(lane.getParentLink(),
687 GTUDirectionality.DIR_PLUS, getGTUType());
688 if (null == nextNode)
689 {
690 throw new GTUException(this + " reaches branch and the route returns null as nextNodeToVisit");
691 }
692 int continuingLaneCount = 0;
693 for (Lane candidateLane : lane.nextLanes(getGTUType()).keySet())
694 {
695 if (null != this.lanes.get(candidateLane))
696 {
697 continue;
698 }
699
700 if (nextNode == candidateLane.getParentLink().getEndNode()
701 || nextNode == candidateLane.getParentLink().getStartNode())
702 {
703 nextLane = candidateLane;
704 continuingLaneCount++;
705 }
706 }
707 if (continuingLaneCount == 0)
708 {
709 throw new NetworkException(this + " reached branch and the route specifies a nextNodeToVisit (" + nextNode
710 + ") that is not a next node " + "at this branch at (" + lane.getParentLink().getEndNode() + ")");
711 }
712 if (continuingLaneCount > 1)
713 {
714 throw new NetworkException(
715 this + " reached branch and the route specifies multiple lanes to continue on at this branch ("
716 + lane.getParentLink().getEndNode() + "). This is not yet supported");
717 }
718 }
719 return nextLane;
720 }
721
722
723
724
725
726
727
728
729 private Lane determinePrevLane(final Lane lane) throws NetworkException, GTUException
730 {
731 Lane prevLane = null;
732 if (lane.prevLanes(getGTUType()).size() == 0)
733 {
734 throw new NetworkException(this + " - lane " + lane + " does not have a predecessor");
735 }
736 if (lane.prevLanes(getGTUType()).size() == 1)
737 {
738 prevLane = lane.prevLanes(getGTUType()).keySet().iterator().next();
739 }
740 else
741 {
742 if (!(getStrategicalPlanner() instanceof LaneBasedStrategicalRoutePlanner))
743 {
744 throw new GTUException(this + " reaches branch but has no route navigator");
745 }
746 Node prevNode = ((LaneBasedStrategicalRoutePlanner) getStrategicalPlanner()).nextNode(lane.getParentLink(),
747 GTUDirectionality.DIR_MINUS, getGTUType());
748 if (null == prevNode)
749 {
750 throw new GTUException(this + " reaches branch and the route returns null as nextNodeToVisit");
751 }
752 int continuingLaneCount = 0;
753 for (Lane candidateLane : lane.prevLanes(getGTUType()).keySet())
754 {
755 if (null != this.lanes.get(candidateLane))
756 {
757 continue;
758 }
759
760 if (prevNode == candidateLane.getParentLink().getEndNode()
761 || prevNode == candidateLane.getParentLink().getStartNode())
762 {
763 prevLane = candidateLane;
764 continuingLaneCount++;
765 }
766 }
767 if (continuingLaneCount == 0)
768 {
769 throw new NetworkException(this + " reached branch and the route specifies a nextNodeToVisit (" + prevNode
770 + ") that is not a next node " + "at this branch at (" + lane.getParentLink().getStartNode() + ")");
771 }
772 if (continuingLaneCount > 1)
773 {
774 throw new NetworkException(
775 this + " reached branch and the route specifies multiple lanes to continue on at this branch ("
776 + lane.getParentLink().getStartNode() + "). This is not yet supported");
777 }
778 }
779 return prevLane;
780 }
781
782
783 @Override
784 public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition) throws GTUException
785 {
786 return fractionalPositions(relativePosition, getSimulator().getSimulatorTime().getTime());
787 }
788
789
790 @Override
791 public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition, final Time when)
792 throws GTUException
793 {
794 Map<Lane, Double> positions = new LinkedHashMap<>();
795 for (Lane lane : this.lanes.keySet())
796 {
797 positions.put(lane, fractionalPosition(lane, relativePosition, when));
798 }
799 return positions;
800 }
801
802
803 @Override
804 public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition, final Time when)
805 throws GTUException
806 {
807 return position(lane, relativePosition, when).getSI() / lane.getLength().getSI();
808 }
809
810
811 @Override
812 public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition) throws GTUException
813 {
814 return position(lane, relativePosition).getSI() / lane.getLength().getSI();
815 }
816
817
818 @Override
819 public final LaneBasedStrategicalPlanner getStrategicalPlanner()
820 {
821 return (LaneBasedStrategicalPlanner) super.getStrategicalPlanner();
822 }
823
824
825 @Override
826 public final BehavioralCharacteristics getBehavioralCharacteristics()
827 {
828 return getStrategicalPlanner().getBehavioralCharacteristics();
829 }
830
831
832 @Override
833 public final LaneBasedTacticalPlanner getTacticalPlanner()
834 {
835 return (LaneBasedTacticalPlanner) super.getTacticalPlanner();
836 }
837
838
839 public final void addTrigger(final Lane lane, final SimEvent<OTSSimTimeDouble> event)
840 {
841 List<SimEvent<OTSSimTimeDouble>> list = this.pendingTriggers.get(lane);
842 if (null == list)
843 {
844 list = new ArrayList<>();
845 }
846 list.add(event);
847 this.pendingTriggers.put(lane, list);
848 }
849
850
851 @Override
852 @SuppressWarnings("checkstyle:designforextension")
853 public void destroy()
854 {
855
856 Lane referenceLane = getLanes().keySet().iterator().next();
857 if (referenceLane != null)
858 {
859 try
860 {
861 fireTimedEvent(LaneBasedGTU.DESTROY_EVENT, new Object[] { getId(), getLocation(), getOdometer(), referenceLane,
862 position(referenceLane, getReference()) }, getSimulator().getSimulatorTime());
863 }
864 catch (GTUException exception)
865 {
866 referenceLane = null;
867 }
868 }
869
870 if (referenceLane == null)
871 {
872 fireTimedEvent(LaneBasedGTU.DESTROY_EVENT,
873 new Object[] { getId(), getLocation(), getOdometer(), null, Length.ZERO },
874 getSimulator().getSimulatorTime());
875 }
876
877 synchronized (this.lock)
878 {
879 Set<Lane> laneSet = new HashSet<>(this.lanes.keySet());
880 for (Lane lane : laneSet)
881 {
882 try
883 {
884 leaveLane(lane, true);
885 }
886 catch (GTUException e)
887 {
888
889 }
890 }
891 }
892 super.destroy();
893 }
894
895
896 @Override
897 public final Bounds getBounds()
898 {
899 double dx = 0.5 * getLength().doubleValue();
900 double dy = 0.5 * getWidth().doubleValue();
901 return new BoundingBox(new Point3d(-dx, -dy, 0.0), new Point3d(dx, dy, 0.0));
902 }
903
904
905 @SuppressWarnings("checkstyle:designforextension")
906 public String toString()
907 {
908 return String.format("GTU " + getId());
909 }
910 }