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