1   package org.opentrafficsim.road.gtu.lane;
2   
3   import java.util.ArrayList;
4   import java.util.Collections;
5   import java.util.HashMap;
6   import java.util.HashSet;
7   import java.util.Iterator;
8   import java.util.LinkedHashMap;
9   import java.util.LinkedHashSet;
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.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.opentrafficsim.base.parameters.ParameterException;
27  import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
28  import org.opentrafficsim.core.geometry.OTSGeometryException;
29  import org.opentrafficsim.core.geometry.OTSLine3D;
30  import org.opentrafficsim.core.geometry.OTSLine3D.FractionalFallback;
31  import org.opentrafficsim.core.geometry.OTSPoint3D;
32  import org.opentrafficsim.core.gtu.AbstractGTU;
33  import org.opentrafficsim.core.gtu.GTU;
34  import org.opentrafficsim.core.gtu.GTUDirectionality;
35  import org.opentrafficsim.core.gtu.GTUException;
36  import org.opentrafficsim.core.gtu.GTUType;
37  import org.opentrafficsim.core.gtu.RelativePosition;
38  import org.opentrafficsim.core.gtu.TurnIndicatorStatus;
39  import org.opentrafficsim.core.gtu.perception.EgoPerception;
40  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlan;
41  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanBuilder;
42  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
43  import org.opentrafficsim.core.network.LateralDirectionality;
44  import org.opentrafficsim.core.network.Link;
45  import org.opentrafficsim.core.network.NetworkException;
46  import org.opentrafficsim.core.network.OTSNetwork;
47  import org.opentrafficsim.core.perception.Historical;
48  import org.opentrafficsim.core.perception.HistoricalValue;
49  import org.opentrafficsim.core.perception.HistoryManager;
50  import org.opentrafficsim.core.perception.collections.HistoricalLinkedHashMap;
51  import org.opentrafficsim.core.perception.collections.HistoricalMap;
52  import org.opentrafficsim.road.gtu.lane.perception.LanePerception;
53  import org.opentrafficsim.road.gtu.lane.perception.PerceptionCollectable;
54  import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
55  import org.opentrafficsim.road.gtu.lane.perception.categories.DefaultSimplePerception;
56  import org.opentrafficsim.road.gtu.lane.perception.categories.InfrastructurePerception;
57  import org.opentrafficsim.road.gtu.lane.perception.categories.neighbors.NeighborsPerception;
58  import org.opentrafficsim.road.gtu.lane.perception.headway.HeadwayGTU;
59  import org.opentrafficsim.road.gtu.lane.plan.operational.LaneBasedOperationalPlan;
60  import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
61  import org.opentrafficsim.road.network.lane.CrossSectionElement;
62  import org.opentrafficsim.road.network.lane.CrossSectionLink;
63  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
64  import org.opentrafficsim.road.network.lane.Lane;
65  import org.opentrafficsim.road.network.lane.LaneDirection;
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.SimEvent;
71  import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEventInterface;
72  import nl.tudelft.simulation.dsol.logger.SimLogger;
73  import nl.tudelft.simulation.dsol.simtime.SimTimeDoubleUnit;
74  import nl.tudelft.simulation.language.d3.BoundingBox;
75  import nl.tudelft.simulation.language.d3.DirectedPoint;
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 
102 public abstract class AbstractLaneBasedGTU extends AbstractGTU implements LaneBasedGTU
103 {
104     
105     private static final long serialVersionUID = 20140822L;
106 
107     
108 
109 
110 
111 
112     private HistoricalMap<Link, Double> fractionalLinkPositions;
113 
114     
115 
116 
117 
118 
119 
120     private final HistoricalMap<Lane, GTUDirectionality> currentLanes;
121 
122     
123     private Map<Lane, List<SimEventInterface<SimTimeDoubleUnit>>> pendingLeaveTriggers = new HashMap<>();
124 
125     
126     private Map<Lane, List<SimEventInterface<SimTimeDoubleUnit>>> pendingEnterTriggers = new HashMap<>();
127 
128     
129     private SimEventInterface<SimTimeDoubleUnit> finalizeLaneChangeEvent = null;
130 
131     
132     private Speed cachedDesiredSpeed;
133 
134     
135     private Time desiredSpeedTime;
136 
137     
138     private Acceleration cachedCarFollowingAcceleration;
139 
140     
141     private Time carFollowingAccelerationTime;
142 
143     
144     private Object lock = new Object();
145 
146     
147     @SuppressWarnings("checkstyle:visibilitymodifier")
148     public static Length initialLocationThresholdDifference = new Length(1.0, LengthUnit.MILLIMETER);
149 
150     
151     private final Historical<TurnIndicatorStatus> turnIndicatorStatus;
152 
153     
154     
155     public static boolean CACHING = true;
156 
157     
158     
159     public static int CACHED_POSITION = 0;
160 
161     
162     
163     public static int NON_CACHED_POSITION = 0;
164 
165     
166     private VehicleModel vehicleModel = VehicleModel.MINMAX;
167 
168     
169 
170 
171 
172 
173 
174 
175 
176     public AbstractLaneBasedGTU(final String id, final GTUType gtuType, final OTSSimulatorInterface simulator,
177             final OTSNetwork network) throws GTUException
178     {
179         super(id, gtuType, simulator, network);
180         HistoryManager historyManager = simulator.getReplication().getHistoryManager(simulator);
181         this.fractionalLinkPositions = new HistoricalLinkedHashMap<>(historyManager);
182         this.currentLanes = new HistoricalLinkedHashMap<>(historyManager);
183         this.turnIndicatorStatus = new HistoricalValue<>(historyManager, TurnIndicatorStatus.NOTPRESENT);
184     }
185 
186     
187 
188 
189 
190 
191 
192 
193 
194 
195 
196     @SuppressWarnings("checkstyle:designforextension")
197     public void init(final LaneBasedStrategicalPlanner strategicalPlanner,
198             final Set<DirectedLanePosition> initialLongitudinalPositions, final Speed initialSpeed)
199             throws NetworkException, SimRuntimeException, GTUException, OTSGeometryException
200     {
201         Throw.when(null == initialLongitudinalPositions, GTUException.class, "InitialLongitudinalPositions is null");
202         Throw.when(0 == initialLongitudinalPositions.size(), GTUException.class, "InitialLongitudinalPositions is empty set");
203 
204         DirectedPoint lastPoint = null;
205         for (DirectedLanePosition pos : initialLongitudinalPositions)
206         {
207             
208             
209             lastPoint = pos.getLocation();
210         }
211         DirectedPoint initialLocation = lastPoint;
212 
213         
214         Time now = getSimulator().getSimulatorTime();
215         try
216         {
217             if (initialSpeed.si < OperationalPlan.DRIFTING_SPEED_SI)
218             {
219                 this.operationalPlan
220                         .set(new OperationalPlan(this, initialLocation, now, new Duration(1E-6, DurationUnit.SECOND)));
221             }
222             else
223             {
224                 OTSPoint3D p2 = new OTSPoint3D(initialLocation.x + 1E-6 * Math.cos(initialLocation.getRotZ()),
225                         initialLocation.y + 1E-6 * Math.sin(initialLocation.getRotZ()), initialLocation.z);
226                 OTSLine3D path = new OTSLine3D(new OTSPoint3D(initialLocation), p2);
227                 this.operationalPlan.set(OperationalPlanBuilder.buildConstantSpeedPlan(this, path, now, initialSpeed));
228             }
229         }
230         catch (OperationalPlanException e)
231         {
232             throw new RuntimeException("Initial operational plan could not be created.", e);
233         }
234 
235         
236         for (DirectedLanePosition directedLanePosition : initialLongitudinalPositions)
237         {
238             Lane lane = directedLanePosition.getLane();
239             addLaneToGtu(lane, directedLanePosition.getPosition(), directedLanePosition.getGtuDirection()); 
240         }
241 
242         
243         DirectedLanePosition referencePosition = getReferencePosition();
244         fireTimedEvent(LaneBasedGTU.LANEBASED_INIT_EVENT,
245                 new Object[] { getId(), initialLocation, getLength(), getWidth(), referencePosition.getLane(),
246                         referencePosition.getPosition(), referencePosition.getGtuDirection(), getGTUType() },
247                 getSimulator().getSimulatorTime());
248 
249         
250         for (DirectedLanePosition directedLanePosition : initialLongitudinalPositions)
251         {
252             Lane lane = directedLanePosition.getLane();
253             lane.addGTU(this, directedLanePosition.getPosition()); 
254         }
255 
256         
257         super.init(strategicalPlanner, initialLocation, initialSpeed);
258 
259         this.referencePositionTime = Double.NaN; 
260 
261     }
262 
263     
264 
265 
266     @Override
267     public void setParent(final GTU gtu) throws GTUException
268     {
269         for (Lane lane : new HashSet<>(this.currentLanes.keySet())) 
270         {
271             leaveLane(lane);
272         }
273         super.setParent(gtu);
274     }
275 
276     
277 
278 
279 
280 
281 
282 
283 
284     public void reinit(final Set<DirectedLanePosition> initialLongitudinalPositions)
285             throws NetworkException, SimRuntimeException, GTUException, OTSGeometryException
286     {
287         init(getStrategicalPlanner(), initialLongitudinalPositions, Speed.ZERO);
288     }
289 
290     
291 
292 
293 
294 
295     public final boolean isSafeToChange() throws GTUException
296     {
297         return this.fractionalLinkPositions.get(getReferencePosition().getLane().getParentLink()) > 0.0;
298     }
299 
300     
301     @Override
302     @SuppressWarnings("checkstyle:designforextension")
303     public void enterLane(final Lane lane, final Length position, final GTUDirectionality gtuDirection) throws GTUException
304     {
305         if (lane == null || gtuDirection == null || position == null)
306         {
307             throw new GTUException("enterLane - one of the arguments is null");
308         }
309         addLaneToGtu(lane, position, gtuDirection);
310         addGtuToLane(lane, position);
311     }
312 
313     
314 
315 
316 
317 
318 
319 
320 
321     private void addLaneToGtu(final Lane lane, final Length position, final GTUDirectionality gtuDirection) throws GTUException
322     {
323         if (this.currentLanes.containsKey(lane))
324         {
325             System.err.println(this + " is already registered on lane: " + lane + " at fractional position "
326                     + this.fractionalPosition(lane, RelativePosition.REFERENCE_POSITION) + " intended position is " + position
327                     + " length of lane is " + lane.getLength());
328             return;
329         }
330         
331         
332         if (!this.fractionalLinkPositions.containsKey(lane.getParentLink()))
333         {
334             this.fractionalLinkPositions.put(lane.getParentLink(), lane.fraction(position));
335         }
336         this.currentLanes.put(lane, gtuDirection);
337     }
338 
339     
340 
341 
342 
343 
344 
345     protected void addGtuToLane(final Lane lane, final Length position) throws GTUException
346     {
347         List<SimEventInterface<SimTimeDoubleUnit>> pending = this.pendingEnterTriggers.get(lane);
348         if (null != pending)
349         {
350             for (SimEventInterface<SimTimeDoubleUnit> event : pending)
351             {
352                 if (event.getAbsoluteExecutionTime().get().ge(getSimulator().getSimulatorTime()))
353                 {
354                     boolean result = getSimulator().cancelEvent(event);
355                     if (!result && event.getAbsoluteExecutionTime().get().ne(getSimulator().getSimulatorTime()))
356                     {
357                         System.err.println("addLaneToGtu, trying to remove event: NOTHING REMOVED -- result=" + result
358                                 + ", simTime=" + getSimulator().getSimulatorTime() + ", eventTime="
359                                 + event.getAbsoluteExecutionTime().get());
360                     }
361                 }
362             }
363             this.pendingEnterTriggers.remove(lane);
364         }
365         lane.addGTU(this, position);
366     }
367 
368     
369     @Override
370     @SuppressWarnings("checkstyle:designforextension")
371     public void leaveLane(final Lane lane) throws GTUException
372     {
373         leaveLane(lane, false);
374     }
375 
376     
377 
378 
379 
380 
381 
382     @SuppressWarnings("checkstyle:designforextension")
383     public void leaveLane(final Lane lane, final boolean beingDestroyed) throws GTUException
384     {
385         Length position = position(lane, getReference());
386         this.currentLanes.remove(lane);
387         removePendingEvents(lane, this.pendingLeaveTriggers);
388         removePendingEvents(lane, this.pendingEnterTriggers);
389         
390         boolean found = false;
391         for (Lane l : this.currentLanes.keySet())
392         {
393             if (l.getParentLink().equals(lane.getParentLink()))
394             {
395                 found = true;
396             }
397         }
398         if (!found)
399         {
400             this.fractionalLinkPositions.remove(lane.getParentLink());
401         }
402         lane.removeGTU(this, !found, position);
403         if (this.currentLanes.size() == 0 && !beingDestroyed)
404         {
405             System.err.println("leaveLane: lanes.size() = 0 for GTU " + getId());
406         }
407     }
408 
409     
410 
411 
412 
413 
414     private void removePendingEvents(final Lane lane, final Map<Lane, List<SimEventInterface<SimTimeDoubleUnit>>> triggers)
415     {
416         List<SimEventInterface<SimTimeDoubleUnit>> pending = triggers.get(lane);
417         if (null != pending)
418         {
419             for (SimEventInterface<SimTimeDoubleUnit> event : pending)
420             {
421                 if (event.getAbsoluteExecutionTime().get().ge(getSimulator().getSimulatorTime()))
422                 {
423                     boolean result = getSimulator().cancelEvent(event);
424                     if (!result && event.getAbsoluteExecutionTime().get().ne(getSimulator().getSimulatorTime()))
425                     {
426                         System.err.println("leaveLane, trying to remove event: NOTHING REMOVED -- result=" + result
427                                 + ", simTime=" + getSimulator().getSimulatorTime() + ", eventTime="
428                                 + event.getAbsoluteExecutionTime().get());
429                     }
430                 }
431             }
432             triggers.remove(lane);
433         }
434     }
435 
436     
437     @Override
438     public void changeLaneInstantaneously(final LateralDirectionality laneChangeDirection) throws GTUException
439     {
440 
441         
442         DirectedLanePosition from = getReferencePosition();
443 
444         
445         Set<Lane> lanesToBeRemoved = new LinkedHashSet<>(this.currentLanes.keySet());
446 
447         
448         
449         
450         Map<Link, Double> newLinkPositionsLC = new LinkedHashMap<>(this.fractionalLinkPositions);
451 
452         
453         Set<Lane> adjLanes = from.getLane().accessibleAdjacentLanesPhysical(laneChangeDirection, getGTUType(),
454                 this.currentLanes.get(from.getLane()));
455         Lane adjLane = adjLanes.iterator().next();
456         Length position = adjLane.position(from.getLane().fraction(from.getPosition()));
457         GTUDirectionality direction = getDirection(from.getLane());
458         Length planLength = Try.assign(() -> getOperationalPlan().getTraveledDistance(getSimulator().getSimulatorTime()),
459                 "Exception while determining plan length.");
460         enterLaneRecursive(new LaneDirection(adjLane, direction), position, newLinkPositionsLC, planLength, lanesToBeRemoved,
461                 0);
462 
463         
464         this.fractionalLinkPositions.clear();
465         this.fractionalLinkPositions.putAll(newLinkPositionsLC);
466 
467         
468         for (Lane lane : lanesToBeRemoved)
469         {
470             leaveLane(lane);
471         }
472 
473         
474         this.referencePositionTime = Double.NaN;
475         this.cachedPositions.clear();
476 
477         
478         this.fireTimedEvent(LaneBasedGTU.LANE_CHANGE_EVENT, new Object[] { getId(), laneChangeDirection, from },
479                 getSimulator().getSimulatorTime());
480 
481     }
482 
483     
484 
485 
486 
487 
488 
489 
490 
491 
492 
493 
494     private void enterLaneRecursive(final LaneDirection lane, final Length position, final Map<Link, Double> newLinkPositionsLC,
495             final Length planLength, final Set<Lane> lanesToBeRemoved, final int dir) throws GTUException
496     {
497         enterLane(lane.getLane(), position, lane.getDirection());
498         lanesToBeRemoved.remove(lane);
499         Length adjusted = lane.getDirection().isPlus() ? position.minus(planLength) : position.plus(planLength);
500         newLinkPositionsLC.put(lane.getLane().getParentLink(), adjusted.si / lane.getLength().si);
501 
502         
503         if (dir < 1)
504         {
505             Length rear = lane.getDirection().isPlus() ? position.plus(getRear().getDx()) : position.minus(getRear().getDx());
506             Length before = null;
507             if (lane.getDirection().isPlus() && rear.si < 0.0)
508             {
509                 before = rear.neg();
510             }
511             else if (lane.getDirection().isMinus() && rear.si > lane.getLength().si)
512             {
513                 before = rear.minus(lane.getLength());
514             }
515             if (before != null)
516             {
517                 GTUDirectionality upDir = lane.getDirection();
518                 Map<Lane, GTUDirectionality> upstream = lane.getLane().upstreamLanes(upDir, getGTUType());
519                 if (!upstream.isEmpty())
520                 {
521                     Lane upLane = null;
522                     for (Lane nextUp : upstream.keySet())
523                     {
524                         if (newLinkPositionsLC.containsKey(nextUp.getParentLink()))
525                         {
526                             
527                             
528                             upLane = nextUp;
529                             break;
530                         }
531                     }
532                     if (upLane == null)
533                     {
534                         
535                         
536                         
537                         upLane = upstream.keySet().iterator().next();
538                     }
539                     upDir = upstream.get(upLane);
540                     LaneDirection next = new LaneDirection(upLane, upDir);
541                     Length nextPos = upDir.isPlus() ? next.getLength().minus(before).minus(getRear().getDx())
542                             : before.plus(getRear().getDx());
543                     enterLaneRecursive(next, nextPos, newLinkPositionsLC, planLength, lanesToBeRemoved, -1);
544                 }
545             }
546         }
547 
548         
549         if (dir > -1)
550         {
551             Length front =
552                     lane.getDirection().isPlus() ? position.plus(getFront().getDx()) : position.minus(getFront().getDx());
553             Length passed = null;
554             if (lane.getDirection().isPlus() && front.si > lane.getLength().si)
555             {
556                 passed = front.minus(lane.getLength());
557             }
558             else if (lane.getDirection().isMinus() && front.si < 0.0)
559             {
560                 passed = front.neg();
561             }
562             if (passed != null)
563             {
564                 LaneDirection next = lane.getNextLaneDirection(this);
565                 Length nextPos = next.getDirection().isPlus() ? passed.minus(getFront().getDx())
566                         : next.getLength().minus(passed).plus(getFront().getDx());
567                 enterLaneRecursive(next, nextPos, newLinkPositionsLC, planLength, lanesToBeRemoved, 1);
568             }
569         }
570     }
571 
572     
573 
574 
575 
576 
577     @SuppressWarnings("checkstyle:designforextension")
578     public void initLaneChange(final LateralDirectionality laneChangeDirection) throws GTUException
579     {
580         Map<Lane, GTUDirectionality> lanesCopy = new LinkedHashMap<>(this.currentLanes);
581         Map<Lane, Double> fractionalLanePositions = new HashMap<>();
582         for (Lane lane : lanesCopy.keySet())
583         {
584             fractionalLanePositions.put(lane, fractionalPosition(lane, getReference()));
585         }
586         int numRegistered = 0;
587         for (Lane lane : lanesCopy.keySet())
588         {
589             Set<Lane> laneSet = lane.accessibleAdjacentLanesLegal(laneChangeDirection, getGTUType(), getDirection(lane));
590             if (laneSet.size() > 0)
591             {
592                 numRegistered++;
593                 Lane adjacentLane = laneSet.iterator().next();
594                 enterLane(adjacentLane, adjacentLane.getLength().multiplyBy(fractionalLanePositions.get(lane)),
595                         lanesCopy.get(lane));
596             }
597         }
598         Throw.when(numRegistered == 0, GTUException.class, "Gtu %s starting %s lane change, but no adjacent lane found.",
599                 getId(), laneChangeDirection);
600     }
601 
602     
603 
604 
605 
606     @SuppressWarnings("checkstyle:designforextension")
607     protected void finalizeLaneChange(final LateralDirectionality laneChangeDirection)
608     {
609         Map<Lane, GTUDirectionality> lanesCopy = new LinkedHashMap<>(this.currentLanes);
610         Set<Lane> lanesToBeRemoved = new LinkedHashSet<>();
611         Lane fromLane = null;
612         Length fromPosition = null;
613         GTUDirectionality fromDirection = null;
614         try
615         {
616             
617             for (Lane lane : lanesCopy.keySet())
618             {
619                 Iterator<Lane> iterator =
620                         lane.accessibleAdjacentLanesPhysical(laneChangeDirection, getGTUType(), getDirection(lane)).iterator();
621                 if (iterator.hasNext() && lanesCopy.keySet().contains(iterator.next()))
622                 {
623                     lanesToBeRemoved.add(lane);
624                 }
625             }
626             
627             boolean added = true;
628             while (added)
629             {
630                 added = false;
631                 Set<Lane> lanesToAlsoBeRemoved = new LinkedHashSet<>();
632                 for (Lane lane : lanesToBeRemoved)
633                 {
634                     GTUDirectionality direction = getDirection(lane);
635                     for (Lane nextLane : direction.isPlus() ? lane.nextLanes(getGTUType()).keySet()
636                             : lane.prevLanes(getGTUType()).keySet())
637                     {
638                         if (lanesCopy.containsKey(nextLane) && !lanesToBeRemoved.contains(nextLane))
639                         {
640                             added = true;
641                             lanesToAlsoBeRemoved.add(nextLane);
642                         }
643                     }
644                 }
645                 lanesToBeRemoved.addAll(lanesToAlsoBeRemoved);
646             }
647             double nearest = Double.POSITIVE_INFINITY;
648             for (Lane lane : lanesToBeRemoved)
649             {
650                 Length pos = position(lane, RelativePosition.REFERENCE_POSITION);
651                 if (0.0 <= pos.si && pos.si <= lane.getLength().si)
652                 {
653                     fromLane = lane;
654                     fromPosition = pos;
655                     fromDirection = getDirection(lane);
656                 }
657                 else if (fromLane == null && (getDirection(lane).isPlus() ? pos.si > lane.getLength().si : pos.le0()))
658                 {
659                     
660                     double distance = getDirection(lane).isPlus() ? pos.si - lane.getLength().si : -pos.si;
661                     if (distance < nearest)
662                     {
663                         nearest = distance;
664                         fromLane = lane;
665                         fromPosition = pos;
666                         fromDirection = getDirection(lane);
667                     }
668                 }
669                 leaveLane(lane);
670             }
671             this.referencePositionTime = Double.NaN;
672             this.finalizeLaneChangeEvent = null;
673         }
674         catch (GTUException exception)
675         {
676             
677             throw new RuntimeException("position on lane not possible", exception);
678         }
679         Throw.when(fromLane == null, RuntimeException.class, "No from lane for lane change event.");
680         DirectedLanePosition from;
681         try
682         {
683             from = new DirectedLanePosition(fromLane, fromPosition, fromDirection);
684         }
685         catch (GTUException exception)
686         {
687             throw new RuntimeException(exception);
688         }
689         this.fireTimedEvent(LaneBasedGTU.LANE_CHANGE_EVENT, new Object[] { getId(), laneChangeDirection, from },
690                 getSimulator().getSimulatorTime());
691     }
692 
693     
694     @Override
695     public void setFinalizeLaneChangeEvent(final SimEventInterface<SimTimeDoubleUnit> event)
696     {
697         this.finalizeLaneChangeEvent = event;
698     }
699 
700     
701     @Override
702     public final GTUDirectionality getDirection(final Lane lane) throws GTUException
703     {
704         Throw.when(!this.currentLanes.containsKey(lane), GTUException.class, "getDirection: Lanes %s does not contain %s",
705                 this.currentLanes.keySet(), lane);
706         return this.currentLanes.get(lane);
707     }
708 
709     
710     @Override
711     @SuppressWarnings("checkstyle:designforextension")
712     protected void move(final DirectedPoint fromLocation)
713             throws SimRuntimeException, GTUException, OperationalPlanException, NetworkException, ParameterException
714     {
715         
716         
717         
718         if (this.currentLanes.isEmpty())
719         {
720             destroy();
721             return; 
722         }
723 
724         
725         
726         
727         
728         
729         
730         
731         
732         
733         
734         
735         
736         
737         
738 
739         
740         Length covered;
741         if (getOperationalPlan() instanceof LaneBasedOperationalPlan
742                 && ((LaneBasedOperationalPlan) getOperationalPlan()).isDeviative())
743         {
744             
745             
746             LaneBasedOperationalPlan plan = (LaneBasedOperationalPlan) getOperationalPlan();
747             DirectedLanePosition ref = getReferencePosition();
748             covered = ref.getGtuDirection().isPlus()
749                     ? position(ref.getLane(), getReference())
750                             .minus(position(ref.getLane(), getReference(), plan.getStartTime()))
751                     : position(ref.getLane(), getReference(), plan.getStartTime())
752                             .minus(position(ref.getLane(), getReference()));
753             
754             
755         }
756         else
757         {
758             covered = getOperationalPlan().getTraveledDistance(getSimulator().getSimulatorTime());
759         }
760 
761         
762         
763         super.move(fromLocation);
764 
765         
766         
767         
768         
769         Map<Link, Double> newLinkFractions = new LinkedHashMap<>(this.fractionalLinkPositions);
770         Set<Link> done = new LinkedHashSet<>();
771         
772         updateLinkFraction(getReferencePosition().getLane(), newLinkFractions, done, false, covered, true);
773         updateLinkFraction(getReferencePosition().getLane(), newLinkFractions, done, true, covered, true);
774         this.fractionalLinkPositions.clear();
775         this.fractionalLinkPositions.putAll(newLinkFractions);
776 
777         DirectedLanePosition dlp = getReferencePosition();
778         fireTimedEvent(
779                 LaneBasedGTU.LANEBASED_MOVE_EVENT, new Object[] { getId(), fromLocation, getSpeed(), getAcceleration(),
780                         getTurnIndicatorStatus(), getOdometer(), dlp.getLane(), dlp.getPosition(), dlp.getGtuDirection() },
781                 getSimulator().getSimulatorTime());
782 
783         if (getOperationalPlan().getAcceleration(Duration.ZERO).si < -10
784                 && getOperationalPlan().getSpeed(Duration.ZERO).si > 2.5)
785         {
786             System.err.println("GTU: " + getId() + " - getOperationalPlan().getAcceleration(Duration.ZERO).si < -10)");
787             System.err.println("Lanes in current plan: " + this.currentLanes.keySet());
788             if (getTacticalPlanner().getPerception().contains(DefaultSimplePerception.class))
789             {
790                 DefaultSimplePerception p =
791                         getTacticalPlanner().getPerception().getPerceptionCategory(DefaultSimplePerception.class);
792                 System.err.println("HeadwayGTU: " + p.getForwardHeadwayGTU());
793                 System.err.println("HeadwayObject: " + p.getForwardHeadwayObject());
794             }
795         }
796         
797         
798         
799         
800         
801         
802         scheduleEnterLeaveTriggers();
803     }
804 
805     
806 
807 
808 
809 
810 
811 
812 
813 
814     private void updateLinkFraction(final Lane lane, final Map<Link, Double> newLinkFractions, final Set<Link> done,
815             final boolean prevs, final Length covered, final boolean isReferenceLane)
816     {
817         if (!prevs || !isReferenceLane)
818         {
819             if (done.contains(lane.getParentLink()) || !this.currentLanes.containsKey(lane))
820             {
821                 return;
822             }
823             double sign;
824             try
825             {
826                 sign = getDirection(lane).isPlus() ? 1.0 : -1.0;
827             }
828             catch (GTUException exception)
829             {
830                 
831                 throw new RuntimeException("Unexpected exception: trying to obtain direction on lane.", exception);
832             }
833             newLinkFractions.put(lane.getParentLink(),
834                     this.fractionalLinkPositions.get(lane.getParentLink()) + sign * covered.si / lane.getLength().si);
835             done.add(lane.getParentLink());
836         }
837         for (Lane nextLane : (prevs ? lane.prevLanes(getGTUType()) : lane.nextLanes(getGTUType())).keySet())
838         {
839             updateLinkFraction(nextLane, newLinkFractions, done, prevs, covered, false);
840         }
841     }
842 
843     
844     @Override
845     public final Map<Lane, Length> positions(final RelativePosition relativePosition) throws GTUException
846     {
847         return positions(relativePosition, getSimulator().getSimulatorTime());
848     }
849 
850     
851     @Override
852     public final Map<Lane, Length> positions(final RelativePosition relativePosition, final Time when) throws GTUException
853     {
854         Map<Lane, Length> positions = new LinkedHashMap<>();
855         for (Lane lane : this.currentLanes.keySet())
856         {
857             positions.put(lane, position(lane, relativePosition, when));
858         }
859         return positions;
860     }
861 
862     
863     @Override
864     public final Length position(final Lane lane, final RelativePosition relativePosition) throws GTUException
865     {
866         return position(lane, relativePosition, getSimulator().getSimulatorTime());
867     }
868 
869     
870     @Override
871     @SuppressWarnings("checkstyle:designforextension")
872     public Length translatedPosition(final Lane projectionLane, final RelativePosition relativePosition, final Time when)
873             throws GTUException
874     {
875         CrossSectionLink link = projectionLane.getParentLink();
876         for (CrossSectionElement cse : link.getCrossSectionElementList())
877         {
878             if (cse instanceof Lane)
879             {
880                 Lane cseLane = (Lane) cse;
881                 if (null != this.currentLanes.get(cseLane))
882                 {
883                     double fractionalPosition = fractionalPosition(cseLane, RelativePosition.REFERENCE_POSITION, when);
884                     Length pos = new Length(projectionLane.getLength().getSI() * fractionalPosition, LengthUnit.SI);
885                     if (this.currentLanes.get(cseLane).isPlus())
886                     {
887                         return pos.plus(relativePosition.getDx());
888                     }
889                     return pos.minus(relativePosition.getDx());
890                 }
891             }
892         }
893         throw new GTUException(this + " is not on any lane of Link " + link);
894     }
895 
896     
897     @Override
898     @SuppressWarnings("checkstyle:designforextension")
899     public Length projectedPosition(final Lane projectionLane, final RelativePosition relativePosition, final Time when)
900             throws GTUException
901     {
902         CrossSectionLink link = projectionLane.getParentLink();
903         for (CrossSectionElement cse : link.getCrossSectionElementList())
904         {
905             if (cse instanceof Lane)
906             {
907                 Lane cseLane = (Lane) cse;
908                 if (null != this.currentLanes.get(cseLane))
909                 {
910                     double fractionalPosition = fractionalPosition(cseLane, relativePosition, when);
911                     return new Length(projectionLane.getLength().getSI() * fractionalPosition, LengthUnit.SI);
912                 }
913             }
914         }
915         throw new GTUException(this + " is not on any lane of Link " + link);
916     }
917 
918     
919     private double cachePositionsTime = Double.NaN;
920 
921     
922     private Map<Integer, Length> cachedPositions = new HashMap<>();
923 
924     
925     @Override
926     @SuppressWarnings("checkstyle:designforextension")
927     public Length position(final Lane lane, final RelativePosition relativePosition, final Time when) throws GTUException
928     {
929         int cacheIndex = 0;
930         if (CACHING)
931         {
932             cacheIndex = 17 * lane.hashCode() + relativePosition.hashCode();
933             Length l;
934             if (when.si == this.cachePositionsTime && (l = this.cachedPositions.get(cacheIndex)) != null)
935             {
936                 
937                 
938                 
939                 
940                 
941                 
942                 
943                 CACHED_POSITION++;
944                 return l;
945             }
946             if (when.si != this.cachePositionsTime)
947             {
948                 this.cachedPositions.clear();
949                 this.cachePositionsTime = when.si;
950             }
951         }
952         NON_CACHED_POSITION++;
953 
954         synchronized (this.lock)
955         {
956             double longitudinalPosition;
957             try
958             {
959                 longitudinalPosition = lane.positionSI(this.fractionalLinkPositions.get(when).get(lane.getParentLink()));
960             }
961             catch (NullPointerException exception)
962             {
963                 throw exception;
964             }
965             double loc = Double.NaN;
966             try
967             {
968                 OperationalPlan plan = getOperationalPlan(when);
969                 if (!(plan instanceof LaneBasedOperationalPlan) || !((LaneBasedOperationalPlan) plan).isDeviative())
970                 {
971                     if (this.currentLanes.get(when).get(lane).isPlus())
972                     {
973                         loc = longitudinalPosition + plan.getTraveledDistanceSI(when) + relativePosition.getDx().si;
974                     }
975                     else
976                     {
977                         loc = longitudinalPosition - plan.getTraveledDistanceSI(when) - relativePosition.getDx().si;
978                     }
979                 }
980                 else
981                 {
982                     
983                     DirectedPoint p = plan.getLocation(when, relativePosition);
984                     double f = lane.getCenterLine().projectFractional(null, null, p.x, p.y, FractionalFallback.NaN);
985                     if (!Double.isNaN(f))
986                     {
987                         loc = f * lane.getLength().si;
988                     }
989                     else
990                     {
991                         
992 
993                         
994                         boolean upstream = this.fractionalLinkPositions.get(lane.getParentLink()) < 0.0 ? true : false;
995 
996                         
997                         int i = 0;
998                         while (true)
999                         {
1000                             Set<Lane> otherLanesToConsider = new LinkedHashSet<>();
1001                             otherLanesToConsider.addAll(this.currentLanes.keySet());
1002                             double distance = getDistanceAtOtherLane(lane, when, upstream, 0.0, p, otherLanesToConsider);
1003                             
1004                             if (!Double.isNaN(distance))
1005                             {
1006                                 if (i == 1 && !Double.isNaN(loc))
1007                                 {
1008                                     
1009                                     double loc2 = upstream ? -distance : distance + lane.getLength().si;
1010                                     double d1 = loc < 0.0 ? -loc : loc - lane.getLength().si;
1011                                     double d2 = loc2 < 0.0 ? -loc2 : loc2 - lane.getLength().si;
1012                                     loc = d1 < d2 ? loc : loc2;
1013                                     break;
1014                                 }
1015                                 else
1016                                 {
1017                                     
1018                                     loc = upstream ? -distance : distance + lane.getLength().si;
1019                                 }
1020                             }
1021                             else if (!Double.isNaN(loc))
1022                             {
1023                                 
1024                                 break;
1025                             }
1026                             else if (i == 1)
1027                             {
1028                                 
1029                                 
1030                                 
1031                                 
1032                                 
1033                                 loc = lane.getLength().si * lane.getCenterLine().projectFractional(null, null, p.x, p.y,
1034                                         FractionalFallback.ENDPOINT);
1035                                 break;
1036                             }
1037                             
1038                             i++;
1039                             upstream = !upstream;
1040                         }
1041                     }
1042                 }
1043             }
1044             catch (NullPointerException e)
1045             {
1046                 throw new GTUException("lanesCurrentOperationalPlan or fractionalLinkPositions is null", e);
1047             }
1048             catch (Exception e)
1049             {
1050                 System.err.println(toString());
1051                 System.err.println(this.currentLanes.get(when));
1052                 System.err.println(this.fractionalLinkPositions.get(when));
1053                 throw new GTUException(e);
1054             }
1055             if (Double.isNaN(loc))
1056             {
1057                 System.out.println("loc is NaN");
1058             }
1059             Length length = Length.createSI(loc);
1060             if (CACHING)
1061             {
1062                 this.cachedPositions.put(cacheIndex, length);
1063             }
1064             return length;
1065         }
1066     }
1067 
1068     
1069     
1070 
1071     
1072 
1073 
1074 
1075 
1076 
1077 
1078 
1079 
1080 
1081 
1082 
1083 
1084     private double getDistanceAtOtherLane(final Lane lane, final Time when, final boolean upstream, final double distance,
1085             final DirectedPoint point, final Set<Lane> otherLanesToConsider) throws GTUException
1086     {
1087         Set<Lane> nextLanes = new HashSet<>(upstream == getDirection(lane).isPlus() ? lane.prevLanes(getGTUType()).keySet()
1088                 : lane.nextLanes(getGTUType()).keySet()); 
1089         nextLanes.retainAll(otherLanesToConsider); 
1090         if (!upstream && nextLanes.size() > 1)
1091         {
1092             LaneDirection laneDir = new LaneDirection(lane, getDirection(lane)).getNextLaneDirection(this);
1093             if (nextLanes.contains(laneDir.getLane()))
1094             {
1095                 nextLanes.clear();
1096                 nextLanes.add(laneDir.getLane());
1097             }
1098             else
1099             {
1100                 SimLogger.always().error("Distance on downstream lane could not be determined.");
1101             }
1102         }
1103         
1104         
1105         
1106         
1107         
1108         
1109         
1110         
1111         
1112         
1113         if (nextLanes.size() == 0)
1114         {
1115             return Double.NaN; 
1116         }
1117         Throw.when(nextLanes.size() > 1, IllegalStateException.class,
1118                 "A position (%s) of GTU %s is not on any of the current registered lanes.", point, this.getId());
1119         Lane nextLane = nextLanes.iterator().next();
1120         otherLanesToConsider.remove(lane);
1121         double f = nextLane.getCenterLine().projectFractional(null, null, point.x, point.y, FractionalFallback.NaN);
1122         if (Double.isNaN(f))
1123         {
1124             return getDistanceAtOtherLane(nextLane, when, upstream, distance + nextLane.getLength().si, point,
1125                     otherLanesToConsider);
1126         }
1127         return distance + (upstream == this.currentLanes.get(nextLane).isPlus() ? 1.0 - f : f) * nextLane.getLength().si;
1128     }
1129 
1130     
1131     private double referencePositionTime = Double.NaN;
1132 
1133     
1134     private DirectedLanePosition cachedReferencePosition = null;
1135 
1136     
1137     @Override
1138     @SuppressWarnings("checkstyle:designforextension")
1139     public DirectedLanePosition getReferencePosition() throws GTUException
1140     {
1141         if (this.referencePositionTime == getSimulator().getSimulatorTime().si)
1142         {
1143             return this.cachedReferencePosition;
1144         }
1145         boolean anyOnLink = false;
1146         Lane refLane = null;
1147         double closest = Double.POSITIVE_INFINITY;
1148         double minEps = Double.POSITIVE_INFINITY;
1149         for (Lane lane : this.currentLanes.keySet())
1150         {
1151             double fraction = fractionalPosition(lane, getReference());
1152             if (fraction >= 0.0 && fraction <= 1.0)
1153             {
1154                 anyOnLink = true;
1155                 
1156                 
1157                 if (refLane == null)
1158                 {
1159                     refLane = lane;
1160                 }
1161                 else
1162                 {
1163                     DirectedPoint loc = getLocation();
1164                     double f = lane.getCenterLine().projectFractional(null, null, loc.x, loc.y, FractionalFallback.ENDPOINT);
1165                     double distance = loc.distance(lane.getCenterLine().getLocationFractionExtended(f));
1166                     if (refLane != null && Double.isInfinite(closest))
1167                     {
1168                         f = refLane.getCenterLine().projectFractional(null, null, loc.x, loc.y, FractionalFallback.ENDPOINT);
1169                         closest = loc.distance(refLane.getCenterLine().getLocationFractionExtended(f));
1170                     }
1171                     if (distance < closest)
1172                     {
1173                         refLane = lane;
1174                         closest = distance;
1175                     }
1176                 }
1177             }
1178             else if (!anyOnLink && Double.isInfinite(closest) && getOperationalPlan() instanceof LaneBasedOperationalPlan
1179                     && ((LaneBasedOperationalPlan) getOperationalPlan()).isDeviative())
1180             {
1181                 double eps = (fraction > 1.0 ? lane.getCenterLine().getLast() : lane.getCenterLine().getFirst())
1182                         .distanceSI(new OTSPoint3D(getLocation()));
1183                 if (eps < minEps)
1184                 {
1185                     minEps = eps;
1186                     refLane = lane;
1187                 }
1188             }
1189         }
1190         if (refLane != null)
1191         {
1192             this.cachedReferencePosition =
1193                     new DirectedLanePosition(refLane, position(refLane, getReference()), this.getDirection(refLane));
1194             this.referencePositionTime = getSimulator().getSimulatorTime().si;
1195             return this.cachedReferencePosition;
1196         }
1197         
1198         
1199         
1200         
1201         
1202         
1203         
1204         throw new GTUException("The reference point of GTU " + this + " is not on any of the lanes on which it is registered");
1205     }
1206 
1207     
1208 
1209 
1210 
1211 
1212 
1213 
1214 
1215     @SuppressWarnings("checkstyle:designforextension")
1216     protected void scheduleEnterLeaveTriggers() throws NetworkException, SimRuntimeException, GTUException
1217     {
1218         LaneBasedOperationalPlan plan = null;
1219         double moveSI;
1220         if (getOperationalPlan() instanceof LaneBasedOperationalPlan)
1221         {
1222             plan = (LaneBasedOperationalPlan) getOperationalPlan();
1223             moveSI = plan.getTotalLengthAlongLane(this).si;
1224         }
1225         else
1226         {
1227             moveSI = getOperationalPlan().getTotalLength().si;
1228         }
1229 
1230         
1231         Map<Lane, GTUDirectionality> lanesCopy = new LinkedHashMap<>(this.currentLanes);
1232         Iterator<Lane> it = lanesCopy.keySet().iterator();
1233         Lane enteredLane = null;
1234         while (it.hasNext() || enteredLane != null) 
1235         {
1236             
1237             Lane lane;
1238             GTUDirectionality laneDir;
1239             if (enteredLane == null)
1240             {
1241                 lane = it.next();
1242                 laneDir = lanesCopy.get(lane);
1243             }
1244             else
1245             {
1246                 lane = enteredLane;
1247                 laneDir = this.currentLanes.get(lane);
1248             }
1249             double sign = laneDir.isPlus() ? 1.0 : -1.0;
1250             enteredLane = null;
1251 
1252             
1253             if (!Collections.disjoint(this.currentLanes.keySet(), lane.downstreamLanes(laneDir, getGTUType()).keySet()))
1254             {
1255                 continue;
1256             }
1257 
1258             
1259             double referenceStartSI = this.fractionalLinkPositions.get(lane.getParentLink()) * lane.getLength().getSI();
1260             
1261             if (laneDir.isPlus())
1262             {
1263                 lane.scheduleSensorTriggers(this, referenceStartSI, moveSI);
1264             }
1265             else
1266             {
1267                 lane.scheduleSensorTriggers(this, referenceStartSI - moveSI, moveSI);
1268             }
1269 
1270             double nextFrontPosSI = referenceStartSI + sign * (moveSI + getFront().getDx().si);
1271             Lane nextLane = null;
1272             GTUDirectionality nextDirection = null;
1273             Length refPosAtLastTimestep = null;
1274             DirectedPoint end = null;
1275             if (laneDir.isPlus() ? nextFrontPosSI > lane.getLength().si : nextFrontPosSI < 0.0)
1276             {
1277                 LaneDirection next = new LaneDirection(lane, laneDir).getNextLaneDirection(this);
1278                 nextLane = next.getLane();
1279                 nextDirection = next.getDirection();
1280                 double endPos = laneDir.isPlus() ? lane.getLength().si - getFront().getDx().si : getFront().getDx().si;
1281                 Lane endLane = lane;
1282                 GTUDirectionality endLaneDir = laneDir;
1283                 while (endLaneDir.isPlus() ? endPos < 0.0 : endPos > endLane.getLength().si)
1284                 {
1285                     
1286                     Map<Lane, GTUDirectionality> map = endLane.upstreamLanes(endLaneDir, getGTUType());
1287                     map.keySet().retainAll(this.currentLanes.keySet());
1288                     double remain = endLaneDir.isPlus() ? -endPos : endPos - endLane.getLength().si;
1289                     endLane = map.keySet().iterator().next();
1290                     endLaneDir = map.get(endLane);
1291                     endPos = endLaneDir.isPlus() ? endLane.getLength().si - remain : remain;
1292                 }
1293                 end = endLane.getCenterLine().getLocationExtendedSI(endPos);
1294                 if (laneDir.isPlus())
1295                 {
1296                     refPosAtLastTimestep = nextDirection.isPlus() ? Length.createSI(referenceStartSI - lane.getLength().si)
1297                             : Length.createSI(nextLane.getLength().si - referenceStartSI + lane.getLength().si);
1298                 }
1299                 else
1300                 {
1301                     refPosAtLastTimestep = nextDirection.isPlus() ? Length.createSI(-referenceStartSI)
1302                             : Length.createSI(nextLane.getLength().si + referenceStartSI);
1303                 }
1304             }
1305 
1306             if (end != null)
1307             {
1308                 Time enterTime = getOperationalPlan().timeAtPoint(end, false);
1309                 if (enterTime != null)
1310                 {
1311                     addLaneToGtu(nextLane, refPosAtLastTimestep, nextDirection);
1312                     enteredLane = nextLane;
1313                     Length coveredDistance;
1314                     if (plan == null || !plan.isDeviative())
1315                     {
1316                         try
1317                         {
1318                             coveredDistance = getOperationalPlan().getTraveledDistance(enterTime);
1319                         }
1320                         catch (OperationalPlanException exception)
1321                         {
1322                             throw new RuntimeException("Enter time of lane beyond plan.", exception);
1323                         }
1324                     }
1325                     else
1326                     {
1327                         coveredDistance = plan.getDistanceAlongLane(this, end);
1328                     }
1329                     SimEventInterface<SimTimeDoubleUnit> event = getSimulator().scheduleEventAbs(enterTime, this, this,
1330                             "addGtuToLane", new Object[] { nextLane, refPosAtLastTimestep.plus(coveredDistance) });
1331                     addEnterTrigger(nextLane, event);
1332                 }
1333             }
1334         }
1335 
1336         
1337         for (Lane lane : this.currentLanes.keySet())
1338         {
1339             double referenceStartSI = this.fractionalLinkPositions.get(lane.getParentLink()) * lane.getLength().getSI();
1340             Time exitTime = null;
1341 
1342             GTUDirectionality laneDir = getDirection(lane);
1343             double sign = laneDir.isPlus() ? 1.0 : -1.0;
1344 
1345             double nextRearPosSI = referenceStartSI + sign * (getRear().getDx().si + moveSI);
1346             if (plan == null || !plan.isDeviative())
1347             {
1348                 if (laneDir.isPlus() ? nextRearPosSI > lane.getLength().si : nextRearPosSI < 0.0)
1349                 {
1350                     exitTime = getOperationalPlan().timeAtDistance(
1351                             Length.createSI((laneDir.isPlus() ? lane.getLength().si - referenceStartSI : referenceStartSI)
1352                                     - getRear().getDx().si));
1353                 }
1354             }
1355             else
1356             {
1357                 DirectedPoint end = null;
1358                 if (laneDir.isPlus() ? nextRearPosSI > lane.getLength().si : nextRearPosSI < 0.0)
1359                 {
1360                     double endPos = laneDir.isPlus() ? lane.getLength().si - getRear().getDx().si : getRear().getDx().si;
1361                     Lane endLane = lane;
1362                     GTUDirectionality endLaneDir = laneDir;
1363                     while (endLaneDir.isPlus() ? endPos > endLane.getLength().si : endPos < 0.0)
1364                     {
1365                         Map<Lane, GTUDirectionality> map = endLane.downstreamLanes(endLaneDir, getGTUType());
1366                         map.keySet().retainAll(this.currentLanes.keySet());
1367                         double remain = endLaneDir.isPlus() ? endPos - endLane.getLength().si : -endPos;
1368                         endLane = map.keySet().iterator().next();
1369                         endLaneDir = map.get(endLane);
1370                         endPos = endLaneDir.isPlus() ? remain : endLane.getLength().si - remain;
1371                     }
1372                     end = endLane.getCenterLine().getLocationExtendedSI(endPos);
1373                 }
1374                 if (end != null)
1375                 {
1376                     exitTime = getOperationalPlan().timeAtPoint(end, false);
1377                 }
1378             }
1379 
1380             if (exitTime != null && !Double.isNaN(exitTime.si))
1381             {
1382                 SimEvent<SimTimeDoubleUnit> event = new SimEvent<>(new SimTimeDoubleUnit(exitTime), this, this, "leaveLane",
1383                         new Object[] { lane, new Boolean(false) });
1384                 getSimulator().scheduleEvent(event);
1385                 addTrigger(lane, event);
1386             }
1387         }
1388     }
1389 
1390     
1391     @Override
1392     public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition) throws GTUException
1393     {
1394         return fractionalPositions(relativePosition, getSimulator().getSimulatorTime());
1395     }
1396 
1397     
1398     @Override
1399     public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition, final Time when)
1400             throws GTUException
1401     {
1402         Map<Lane, Double> positions = new LinkedHashMap<>();
1403         for (Lane lane : this.currentLanes.keySet())
1404         {
1405             positions.put(lane, fractionalPosition(lane, relativePosition, when));
1406         }
1407         return positions;
1408     }
1409 
1410     
1411     @Override
1412     public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition, final Time when)
1413             throws GTUException
1414     {
1415         return position(lane, relativePosition, when).getSI() / lane.getLength().getSI();
1416     }
1417 
1418     
1419     @Override
1420     public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition) throws GTUException
1421     {
1422         return position(lane, relativePosition).getSI() / lane.getLength().getSI();
1423     }
1424 
1425     
1426     @Override
1427     public final void addTrigger(final Lane lane, final SimEventInterface<SimTimeDoubleUnit> event)
1428     {
1429         List<SimEventInterface<SimTimeDoubleUnit>> list = this.pendingLeaveTriggers.get(lane);
1430         if (null == list)
1431         {
1432             list = new ArrayList<>();
1433         }
1434         list.add(event);
1435         this.pendingLeaveTriggers.put(lane, list);
1436     }
1437 
1438     
1439 
1440 
1441 
1442 
1443     private void addEnterTrigger(final Lane lane, final SimEventInterface<SimTimeDoubleUnit> event)
1444     {
1445         List<SimEventInterface<SimTimeDoubleUnit>> list = this.pendingEnterTriggers.get(lane);
1446         if (null == list)
1447         {
1448             list = new ArrayList<>();
1449         }
1450         list.add(event);
1451         this.pendingEnterTriggers.put(lane, list);
1452     }
1453 
1454     
1455 
1456 
1457 
1458     public void setVehicleModel(final VehicleModel vehicleModel)
1459     {
1460         this.vehicleModel = vehicleModel;
1461     }
1462 
1463     
1464     @Override
1465     public VehicleModel getVehicleModel()
1466     {
1467         return this.vehicleModel;
1468     }
1469 
1470     
1471     @Override
1472     @SuppressWarnings("checkstyle:designforextension")
1473     public void destroy()
1474     {
1475         DirectedLanePosition dlp = null;
1476         try
1477         {
1478             dlp = getReferencePosition();
1479         }
1480         catch (GTUException e)
1481         {
1482             
1483         }
1484         DirectedPoint location = this.getOperationalPlan() == null ? new DirectedPoint(0.0, 0.0, 0.0) : getLocation();
1485 
1486         synchronized (this.lock)
1487         {
1488             Set<Lane> laneSet = new LinkedHashSet<>(this.currentLanes.keySet()); 
1489                                                                                  
1490             for (Lane lane : laneSet)
1491             {
1492                 try
1493                 {
1494                     leaveLane(lane, true);
1495                 }
1496                 catch (GTUException e)
1497                 {
1498                     
1499                 }
1500             }
1501         }
1502 
1503         if (dlp != null && dlp.getLane() != null)
1504         {
1505             Lane referenceLane = dlp.getLane();
1506             fireTimedEvent(LaneBasedGTU.LANEBASED_DESTROY_EVENT,
1507                     new Object[] { getId(), location, getOdometer(), referenceLane, dlp.getPosition(), dlp.getGtuDirection() },
1508                     getSimulator().getSimulatorTime());
1509         }
1510         else
1511         {
1512             fireTimedEvent(LaneBasedGTU.LANEBASED_DESTROY_EVENT,
1513                     new Object[] { getId(), location, getOdometer(), null, Length.ZERO, null },
1514                     getSimulator().getSimulatorTime());
1515         }
1516         if (this.finalizeLaneChangeEvent != null)
1517         {
1518             getSimulator().cancelEvent(this.finalizeLaneChangeEvent);
1519         }
1520 
1521         super.destroy();
1522     }
1523 
1524     
1525     @Override
1526     public final Bounds getBounds()
1527     {
1528         double dx = 0.5 * getLength().doubleValue();
1529         double dy = 0.5 * getWidth().doubleValue();
1530         return new BoundingBox(new Point3d(-dx, -dy, 0.0), new Point3d(dx, dy, 0.0));
1531     }
1532 
1533     
1534     @Override
1535     public final LaneBasedStrategicalPlanner getStrategicalPlanner()
1536     {
1537         return (LaneBasedStrategicalPlanner) super.getStrategicalPlanner();
1538     }
1539 
1540     
1541     @Override
1542     public final LaneBasedStrategicalPlanner getStrategicalPlanner(final Time time)
1543     {
1544         return (LaneBasedStrategicalPlanner) super.getStrategicalPlanner(time);
1545     }
1546 
1547     
1548     @Override
1549     public Speed getDesiredSpeed()
1550     {
1551         Time simTime = getSimulator().getSimulatorTime();
1552         if (this.desiredSpeedTime == null || this.desiredSpeedTime.si < simTime.si)
1553         {
1554             InfrastructurePerception infra =
1555                     getTacticalPlanner().getPerception().getPerceptionCategoryOrNull(InfrastructurePerception.class);
1556             SpeedLimitInfo speedInfo;
1557             if (infra == null)
1558             {
1559                 speedInfo = new SpeedLimitInfo();
1560                 speedInfo.addSpeedInfo(SpeedLimitTypes.MAX_VEHICLE_SPEED, getMaximumSpeed());
1561             }
1562             else
1563             {
1564                 
1565                 speedInfo = infra.getSpeedLimitProspect(RelativeLane.CURRENT).getSpeedLimitInfo(Length.ZERO);
1566             }
1567             this.cachedDesiredSpeed =
1568                     Try.assign(() -> getTacticalPlanner().getCarFollowingModel().desiredSpeed(getParameters(), speedInfo),
1569                             "Parameter exception while obtaining the desired speed.");
1570             this.desiredSpeedTime = simTime;
1571         }
1572         return this.cachedDesiredSpeed;
1573     }
1574 
1575     
1576     @Override
1577     public Acceleration getCarFollowingAcceleration()
1578     {
1579         Time simTime = getSimulator().getSimulatorTime();
1580         if (this.carFollowingAccelerationTime == null || this.carFollowingAccelerationTime.si < simTime.si)
1581         {
1582             LanePerception perception = getTacticalPlanner().getPerception();
1583             
1584             EgoPerception<?, ?> ego = perception.getPerceptionCategoryOrNull(EgoPerception.class);
1585             Throw.whenNull(ego, "EgoPerception is required to determine the speed.");
1586             Speed speed = ego.getSpeed();
1587             
1588             InfrastructurePerception infra = perception.getPerceptionCategoryOrNull(InfrastructurePerception.class);
1589             Throw.whenNull(infra, "InfrastructurePerception is required to determine the desired speed.");
1590             SpeedLimitInfo speedInfo = infra.getSpeedLimitProspect(RelativeLane.CURRENT).getSpeedLimitInfo(Length.ZERO);
1591             
1592             NeighborsPerception neighbors = perception.getPerceptionCategoryOrNull(NeighborsPerception.class);
1593             Throw.whenNull(neighbors, "NeighborsPerception is required to determine the car-following acceleration.");
1594             PerceptionCollectable<HeadwayGTU, LaneBasedGTU> leaders = neighbors.getLeaders(RelativeLane.CURRENT);
1595             
1596             this.cachedCarFollowingAcceleration =
1597                     Try.assign(() -> getTacticalPlanner().getCarFollowingModel().followingAcceleration(getParameters(), speed,
1598                             speedInfo, leaders), "Parameter exception while obtaining the desired speed.");
1599             this.carFollowingAccelerationTime = simTime;
1600         }
1601         return this.cachedCarFollowingAcceleration;
1602     }
1603 
1604     
1605     @Override
1606     public final TurnIndicatorStatus getTurnIndicatorStatus()
1607     {
1608         return this.turnIndicatorStatus.get();
1609     }
1610 
1611     
1612     @Override
1613     public final TurnIndicatorStatus getTurnIndicatorStatus(final Time time)
1614     {
1615         return this.turnIndicatorStatus.get(time);
1616     }
1617 
1618     
1619     @Override
1620     public final void setTurnIndicatorStatus(final TurnIndicatorStatus turnIndicatorStatus)
1621     {
1622         this.turnIndicatorStatus.set(turnIndicatorStatus);
1623     }
1624 
1625     
1626     @Override
1627     @SuppressWarnings("checkstyle:designforextension")
1628     public String toString()
1629     {
1630         return String.format("GTU " + getId());
1631     }
1632 
1633 }