View Javadoc
1   package org.opentrafficsim.road.gtu.lane;
2   
3   import java.util.ArrayList;
4   import java.util.HashMap;
5   import java.util.HashSet;
6   import java.util.Iterator;
7   import java.util.LinkedHashMap;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  
12  import javax.media.j3d.Bounds;
13  import javax.vecmath.Point3d;
14  
15  import org.djunits.unit.LengthUnit;
16  import org.djunits.value.vdouble.scalar.Duration;
17  import org.djunits.value.vdouble.scalar.Length;
18  import org.djunits.value.vdouble.scalar.Speed;
19  import org.djunits.value.vdouble.scalar.Time;
20  import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
21  import org.opentrafficsim.core.dsol.OTSSimTimeDouble;
22  import org.opentrafficsim.core.geometry.OTSGeometryException;
23  import org.opentrafficsim.core.gtu.AbstractGTU;
24  import org.opentrafficsim.core.gtu.GTUDirectionality;
25  import org.opentrafficsim.core.gtu.GTUException;
26  import org.opentrafficsim.core.gtu.GTUType;
27  import org.opentrafficsim.core.gtu.RelativePosition;
28  import org.opentrafficsim.core.gtu.behavioralcharacteristics.BehavioralCharacteristics;
29  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
30  import org.opentrafficsim.core.gtu.plan.operational.OperationalPlanException;
31  import org.opentrafficsim.core.network.LateralDirectionality;
32  import org.opentrafficsim.core.network.Link;
33  import org.opentrafficsim.core.network.NetworkException;
34  import org.opentrafficsim.core.network.Node;
35  import org.opentrafficsim.core.network.OTSNetwork;
36  import org.opentrafficsim.road.gtu.lane.tactical.LaneBasedTacticalPlanner;
37  import org.opentrafficsim.road.gtu.strategical.LaneBasedStrategicalPlanner;
38  import org.opentrafficsim.road.gtu.strategical.route.LaneBasedStrategicalRoutePlanner;
39  import org.opentrafficsim.road.network.lane.CrossSectionElement;
40  import org.opentrafficsim.road.network.lane.CrossSectionLink;
41  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
42  import org.opentrafficsim.road.network.lane.Lane;
43  
44  import nl.tudelft.simulation.dsol.SimRuntimeException;
45  import nl.tudelft.simulation.dsol.formalisms.eventscheduling.SimEvent;
46  import nl.tudelft.simulation.language.Throw;
47  import nl.tudelft.simulation.language.d3.BoundingBox;
48  import nl.tudelft.simulation.language.d3.DirectedPoint;
49  
50  /**
51   * This class contains most of the code that is needed to run a lane based GTU. <br>
52   * The starting point of a LaneBasedTU is that it can be in <b>multiple lanes</b> at the same time. This can be due to a lane
53   * change (lateral), or due to crossing a link (front of the GTU is on another Lane than rear of the GTU). If a Lane is shorter
54   * than the length of the GTU (e.g. when we do node expansion on a crossing, this is very well possible), a GTU could occupy
55   * dozens of Lanes at the same time.
56   * <p>
57   * When calculating a headway, the GTU has to look in successive lanes. When Lanes (or underlying CrossSectionLinks) diverge,
58   * the headway algorithms have to look at multiple Lanes and return the minimum headway in each of the Lanes. When the Lanes (or
59   * underlying CrossSectionLinks) converge, "parallel" traffic is not taken into account in the headway calculation. Instead, gap
60   * acceptance algorithms or their equivalent should guide the merging behavior.
61   * <p>
62   * To decide its movement, an AbstractLaneBasedGTU applies its car following algorithm and lane change algorithm to set the
63   * acceleration and any lane change operation to perform. It then schedules the triggers that will add it to subsequent lanes
64   * and remove it from current lanes as needed during the time step that is has committed to. Finally, it re-schedules its next
65   * movement evaluation with the simulator.
66   * <p>
67   * Copyright (c) 2013-2016 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
68   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
69   * <p>
70   * @version $Revision: 1408 $, $LastChangedDate: 2015-09-24 15:17:25 +0200 (Thu, 24 Sep 2015) $, by $Author: pknoppers $,
71   *          initial version Oct 22, 2014 <br>
72   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
73   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
74   */
75  public abstract class AbstractLaneBasedGTU extends AbstractGTU implements LaneBasedGTU
76  {
77      /** */
78      private static final long serialVersionUID = 20140822L;
79  
80      /**
81       * Fractional longitudinal positions of the reference point of the GTU on one or more links at the start of the current
82       * operational plan. Because the reference point of the GTU might not be on all the links the GTU is registered on, the
83       * fractional longitudinal positions can be more than one, or less than zero.
84       */
85      private Map<Link, Double> fractionalLinkPositions = new LinkedHashMap<>();
86  
87      /**
88       * The lanes the GTU is registered on. Each lane has to have its link registered in the fractionalLinkPositions as well to
89       * keep consistency. Each link from the fractionalLinkPositions can have one or more Lanes on which the vehicle is
90       * registered. This is a list to improve reproducibility: The 'oldest' lanes on which the vehicle is registered are at the
91       * front of the list, the later ones more to the back.
92       */
93      private final Map<Lane, GTUDirectionality> lanesCurrentOperationalPlan = new HashMap<>();
94  
95      /** Pending triggers for each lane. */
96      private Map<Lane, List<SimEvent<OTSSimTimeDouble>>> pendingTriggers = new HashMap<>();
97  
98      /** The object to lock to make the GTU thread safe. */
99      private Object lock = new Object();
100 
101     /**
102      * Construct a Lane Based GTU.
103      * @param id the id of the GTU
104      * @param gtuType the type of GTU, e.g. TruckType, CarType, BusType
105      * @param simulator to initialize the move method and to get the current time
106      * @param network the network that the GTU is initially registered in
107      * @throws GTUException when initial values are not correct
108      */
109     @SuppressWarnings("checkstyle:parameternumber")
110     public AbstractLaneBasedGTU(final String id, final GTUType gtuType, final OTSDEVSSimulatorInterface simulator,
111             final OTSNetwork network) throws GTUException
112     {
113         super(id, gtuType, simulator, network);
114     }
115 
116     /**
117      * @param strategicalPlanner the strategical planner (e.g., route determination) to use
118      * @param initialLongitudinalPositions the initial positions of the car on one or more lanes with their directions
119      * @param initialSpeed the initial speed of the car on the lane
120      * @throws NetworkException when the GTU cannot be placed on the given lane
121      * @throws SimRuntimeException when the move method cannot be scheduled
122      * @throws GTUException when initial values are not correct
123      * @throws OTSGeometryException when the initial path is wrong
124      */
125     public final void init(final LaneBasedStrategicalPlanner strategicalPlanner,
126             final Set<DirectedLanePosition> initialLongitudinalPositions, final Speed initialSpeed)
127             throws NetworkException, SimRuntimeException, GTUException, OTSGeometryException
128     {
129         Throw.when(null == initialLongitudinalPositions, GTUException.class, "InitialLongitudinalPositions is null");
130         Throw.when(0 == initialLongitudinalPositions.size(), GTUException.class, "InitialLongitudinalPositions is empty set");
131 
132         DirectedPoint lastPoint = null;
133         for (DirectedLanePosition pos : initialLongitudinalPositions)
134         {
135             Throw.when(lastPoint != null && pos.getLocation().distance(lastPoint) > 1E-6, GTUException.class,
136                     "initial locations for GTU have distance > 1 mm");
137             lastPoint = pos.getLocation();
138         }
139         DirectedPoint initialLocation = lastPoint;
140 
141         // register the GTU on the lanes
142         for (DirectedLanePosition directedLanePosition : initialLongitudinalPositions)
143         {
144             Lane lane = directedLanePosition.getLane();
145             enterLane(lane, directedLanePosition.getPosition(), directedLanePosition.getGtuDirection());
146         }
147 
148         // initiate the actual move
149         super.init(strategicalPlanner, initialLocation, initialSpeed);
150 
151         Lane referenceLane = getReferencePosition().getLane();
152         fireTimedEvent(LaneBasedGTU.LANEBASED_INIT_EVENT,
153                 new Object[] { getId(), initialLocation, getLength(), getWidth(), getBaseColor(), referenceLane,
154                         position(referenceLane, getReference()), this.lanesCurrentOperationalPlan.get(referenceLane) },
155                 getSimulator().getSimulatorTime());
156     }
157 
158     /** {@inheritDoc} */
159     @Override
160     public final void enterLane(final Lane lane, final Length position, final GTUDirectionality gtuDirection)
161             throws GTUException
162     {
163         if (lane == null || gtuDirection == null || position == null)
164         {
165             throw new GTUException("enterLane - one of the arguments is null");
166         }
167         if (this.lanesCurrentOperationalPlan.containsKey(lane))
168         {
169             System.err.println(this + " is already registered on lane: " + lane + " at fractional position "
170                     + this.fractionalPosition(lane, RelativePosition.REFERENCE_POSITION) + " intended position is " + position
171                     + " length of lane is " + lane.getLength());
172             return;
173         }
174 
175         // if the GTU is already registered on a lane of the same link, do not change its fractional position, as
176         // this might lead to a "jump".
177         if (!this.fractionalLinkPositions.containsKey(lane.getParentLink()))
178         {
179             this.fractionalLinkPositions.put(lane.getParentLink(), lane.fraction(position));
180         }
181         this.lanesCurrentOperationalPlan.put(lane, gtuDirection);
182         lane.addGTU(this, position);
183     }
184 
185     /** {@inheritDoc} */
186     @Override
187     public final void leaveLane(final Lane lane) throws GTUException
188     {
189         leaveLane(lane, false);
190     }
191 
192     /**
193      * Leave a lane but do not complain about having no lanes left when beingDestroyed is true.
194      * @param lane the lane to leave
195      * @param beingDestroyed if true, no complaints about having no lanes left
196      * @throws GTUException in case leaveLane should not be called
197      */
198     public final void leaveLane(final Lane lane, final boolean beingDestroyed) throws GTUException
199     {
200         this.lanesCurrentOperationalPlan.remove(lane);
201         List<SimEvent<OTSSimTimeDouble>> pending = this.pendingTriggers.get(lane);
202         if (null != pending)
203         {
204             for (SimEvent<OTSSimTimeDouble> event : pending)
205             {
206                 if (event.getAbsoluteExecutionTime().get().ge(getSimulator().getSimulatorTime().get()))
207                 {
208                     boolean result = getSimulator().cancelEvent(event);
209                     if (!result && event.getAbsoluteExecutionTime().get().ne(getSimulator().getSimulatorTime().get()))
210                     {
211                         System.err.println("leaveLane, trying to remove event: NOTHING REMOVED");
212                     }
213                 }
214             }
215             this.pendingTriggers.remove(lane);
216         }
217         // check of there are any lanes for this link left. If not, remove the link.
218         boolean found = false;
219         for (Lane l : this.lanesCurrentOperationalPlan.keySet())
220         {
221             if (l.getParentLink().equals(lane.getParentLink()))
222             {
223                 found = true;
224             }
225         }
226         if (!found)
227         {
228             this.fractionalLinkPositions.remove(lane.getParentLink());
229         }
230         lane.removeGTU(this, !found);
231         if (this.lanesCurrentOperationalPlan.size() == 0 && !beingDestroyed)
232         {
233             System.err.println("leaveLane: lanes.size() = 0 for GTU " + getId());
234         }
235     }
236 
237     /** {@inheritDoc} */
238     @Override
239     public final void changeLaneInstantaneously(final LateralDirectionality laneChangeDirection) throws GTUException
240     {
241         // keep a copy of the lanes and directions (!)
242         Map<Lane, GTUDirectionality> lanesCopy = new HashMap<>(this.lanesCurrentOperationalPlan);
243         Set<Lane> lanesToBeRemoved = new HashSet<>(lanesCopy.keySet());
244         Map<Lane, Double> fractionalLanePositions = new HashMap<>();
245 
246         for (Lane lane : lanesCopy.keySet())
247         {
248             fractionalLanePositions.put(lane, fractionalPosition(lane, getReference()));
249             lanesToBeRemoved.add(lane);
250         }
251 
252         for (Lane lane : lanesCopy.keySet())
253         {
254             Set<Lane> laneSet = lane.accessibleAdjacentLanes(laneChangeDirection, getGTUType());
255             if (laneSet.size() > 0)
256             {
257                 Lane adjacentLane = laneSet.iterator().next();
258                 enterLane(adjacentLane, adjacentLane.getLength().multiplyBy(fractionalLanePositions.get(lane)),
259                         lanesCopy.get(lane));
260                 lanesToBeRemoved.remove(adjacentLane); // don't remove lanes we might end up
261             }
262 
263         }
264         for (Lane lane : lanesToBeRemoved)
265         {
266             leaveLane(lane);
267         }
268     }
269 
270     /**
271      * Register on lanes in target lane.
272      * @param laneChangeDirection direction of lane change
273      * @throws GTUException exception
274      */
275     public final void initLaneChange(final LateralDirectionality laneChangeDirection) throws GTUException
276     {
277         Map<Lane, GTUDirectionality> lanesCopy = new HashMap<>(this.lanesCurrentOperationalPlan);
278         Map<Lane, Double> fractionalLanePositions = new HashMap<>();
279         for (Lane lane : lanesCopy.keySet())
280         {
281             fractionalLanePositions.put(lane, fractionalPosition(lane, getReference()));
282         }
283         int numRegistered = 0;
284         for (Lane lane : lanesCopy.keySet())
285         {
286             Set<Lane> laneSet = lane.accessibleAdjacentLanes(laneChangeDirection, getGTUType());
287             if (laneSet.size() > 0)
288             {
289                 numRegistered++;
290                 Lane adjacentLane = laneSet.iterator().next();
291                 enterLane(adjacentLane, adjacentLane.getLength().multiplyBy(fractionalLanePositions.get(lane)),
292                         lanesCopy.get(lane));
293             }
294         }
295         Throw.when(numRegistered == 0, GTUException.class, "Gtu %s starting %s lane change, but no adjacent lane found.",
296                 getId(), laneChangeDirection);
297     }
298 
299     /**
300      * Unregister on lanes in source lane.
301      * @param laneChangeDirection direction of lane change
302      * @param time time to leave lanes
303      * @throws GTUException exception
304      */
305     public final void finalizeLaneChange(final LateralDirectionality laneChangeDirection, final Time time) throws GTUException
306     {
307         try
308         {
309             getSimulator().scheduleEventAbs(time, this, this, "executeLaneChangeFinalization",
310                     new Object[] { laneChangeDirection });
311         }
312         catch (SimRuntimeException exception)
313         {
314             throw new RuntimeException("Error when finalizing lane change.", exception);
315         }
316     }
317 
318     /**
319      * Performs the finalization of a lane change by leaving the from lanes.
320      * @param laneChangeDirection direction of lane change
321      */
322     protected final void executeLaneChangeFinalization(final LateralDirectionality laneChangeDirection)
323     {
324         Map<Lane, GTUDirectionality> lanesCopy = new HashMap<>(this.lanesCurrentOperationalPlan);
325         Set<Lane> lanesToBeRemoved = new HashSet<>();
326         Map<Lane, Double> fractionalLanePositions = new HashMap<>();
327         try
328         {
329             for (Lane lane : lanesCopy.keySet())
330             {
331 
332                 fractionalLanePositions.put(lane, fractionalPosition(lane, getReference()));
333             }
334             for (Lane lane : lanesCopy.keySet())
335             {
336                 Iterator<Lane> iterator = lane.accessibleAdjacentLanes(laneChangeDirection, getGTUType()).iterator();
337                 if (iterator.hasNext() && lanesCopy.keySet().contains(iterator.next()))
338                 {
339                     lanesToBeRemoved.add(lane);
340                 }
341             }
342             for (Lane lane : lanesToBeRemoved)
343             {
344                 leaveLane(lane);
345             }
346         }
347         catch (GTUException exception)
348         {
349             // should not happen, lane was obtained from GTU
350             throw new RuntimeException("fractionalPosition on lane not possible", exception);
351         }
352     }
353 
354     /** {@inheritDoc} */
355     @Override
356     public final GTUDirectionality getDirection(final Lane lane) throws GTUException
357     {
358         Throw.when(!this.lanesCurrentOperationalPlan.containsKey(lane), GTUException.class,
359                 "getDirection: Lanes %s does not contain %s", this.lanesCurrentOperationalPlan.keySet(), lane);
360         return this.lanesCurrentOperationalPlan.get(lane);
361     }
362 
363     /** {@inheritDoc} */
364     @Override
365     protected final void move(final DirectedPoint fromLocation)
366             throws SimRuntimeException, GTUException, OperationalPlanException, NetworkException, ParameterException
367     {
368         // Only carry out move() if we still have lane(s) to drive on.
369         // Note: a (Sink) trigger can have 'destroyed' us between the previous evaluation step and this one.
370         if (this.lanesCurrentOperationalPlan.isEmpty())
371         {
372             destroy();
373             return; // Done; do not re-schedule execution of this move method.
374         }
375 
376         // store the new positions, and sample statistics
377         Map<Link, Double> newLinkPositions = new HashMap<>();
378         for (Lane lane : this.lanesCurrentOperationalPlan.keySet())
379         {
380             newLinkPositions.put(lane.getParentLink(), lane.fraction(position(lane, getReference())));
381         }
382 
383         // generate the next operational plan and carry it out
384         super.move(fromLocation);
385 
386         // update the positions on the lanes we are registered on
387         this.fractionalLinkPositions = newLinkPositions;
388 
389         DirectedLanePosition dlp = getReferencePosition();
390         fireTimedEvent(
391                 LaneBasedGTU.LANEBASED_MOVE_EVENT, new Object[] { getId(), fromLocation, getSpeed(), getAcceleration(),
392                         getTurnIndicatorStatus(), getOdometer(), dlp.getLane(), dlp.getPosition(), dlp.getGtuDirection() },
393                 getSimulator().getSimulatorTime());
394 
395         if (getOperationalPlan().getAcceleration(Duration.ZERO).si < -10
396                 && getOperationalPlan().getSpeed(Duration.ZERO).si > 2.5)
397         {
398             System.err.println("(getOperationalPlan().getAcceleration(Duration.ZERO).si < -10)");
399         }
400 
401         // schedule triggers and determine when to enter lanes with front and leave lanes with rear
402         scheduleEnterLeaveTriggers();
403     }
404 
405     /** {@inheritDoc} */
406     @Override
407     public final Map<Lane, Length> positions(final RelativePosition relativePosition) throws GTUException
408     {
409         return positions(relativePosition, getSimulator().getSimulatorTime().getTime());
410     }
411 
412     /** {@inheritDoc} */
413     @Override
414     public final Map<Lane, Length> positions(final RelativePosition relativePosition, final Time when) throws GTUException
415     {
416         Map<Lane, Length> positions = new LinkedHashMap<>();
417         for (Lane lane : this.lanesCurrentOperationalPlan.keySet())
418         {
419             positions.put(lane, position(lane, relativePosition, when));
420         }
421         return positions;
422     }
423 
424     /** {@inheritDoc} */
425     @Override
426     public final Length position(final Lane lane, final RelativePosition relativePosition) throws GTUException
427     {
428         return position(lane, relativePosition, getSimulator().getSimulatorTime().getTime());
429     }
430 
431     /** {@inheritDoc} */
432     @Override
433     public final Length projectedPosition(final Lane projectionLane, final RelativePosition relativePosition, final Time when)
434             throws GTUException
435     {
436         // do not make a wedge in a curve of the projected position!
437         CrossSectionLink link = projectionLane.getParentLink();
438         for (CrossSectionElement cse : link.getCrossSectionElementList())
439         {
440             if (cse instanceof Lane)
441             {
442                 Lane cseLane = (Lane) cse;
443                 if (null != this.lanesCurrentOperationalPlan.get(cseLane))
444                 {
445                     double fractionalPosition = fractionalPosition(cseLane, RelativePosition.REFERENCE_POSITION, when);
446                     Length pos = new Length(projectionLane.getLength().getSI() * fractionalPosition, LengthUnit.SI);
447                     if (this.lanesCurrentOperationalPlan.get(cseLane).isPlus())
448                     {
449                         return pos.plus(relativePosition.getDx());
450                     }
451                     return pos.minus(relativePosition.getDx());
452                 }
453             }
454         }
455         throw new GTUException(this + " is not on any lane of Link " + link);
456     }
457 
458     /** {@inheritDoc} */
459     @Override
460     public final Length position(final Lane lane, final RelativePosition relativePosition, final Time when) throws GTUException
461     {
462         if (null == lane)
463         {
464             throw new GTUException("lane is null");
465         }
466         synchronized (this.lock)
467         {
468             if (!this.lanesCurrentOperationalPlan.containsKey(lane))
469             {
470                 throw new GTUException("position() : GTU " + toString() + " is not on lane " + lane);
471             }
472             if (!this.fractionalLinkPositions.containsKey(lane.getParentLink()))
473             {
474                 // DO NOT USE toString() here, as it will cause an endless loop...
475                 throw new GTUException(this + " does not have a fractional position on " + lane.toString());
476             }
477             double longitudinalPosition = lane.positionSI(this.fractionalLinkPositions.get(lane.getParentLink()));
478             if (getOperationalPlan() == null)
479             {
480                 // no valid operational plan, e.g. during generation of a new plan
481                 return new Length(longitudinalPosition + relativePosition.getDx().si, LengthUnit.SI);
482             }
483             double loc;
484             try
485             {
486                 if (this.lanesCurrentOperationalPlan.get(lane).isPlus())
487                 {
488                     loc = longitudinalPosition + getOperationalPlan().getTraveledDistanceSI(when) + relativePosition.getDx().si;
489                 }
490                 else
491                 {
492                     loc = longitudinalPosition - getOperationalPlan().getTraveledDistanceSI(when) - relativePosition.getDx().si;
493                 }
494             }
495             catch (Exception e)
496             {
497                 System.err.println(toString());
498                 System.err.println(this.lanesCurrentOperationalPlan);
499                 System.err.println(this.fractionalLinkPositions);
500                 throw new GTUException(e);
501             }
502             if (Double.isNaN(loc))
503             {
504                 System.out.println("loc is NaN");
505             }
506             return new Length(loc, LengthUnit.SI);
507         }
508     }
509 
510     /** {@inheritDoc} */
511     @Override
512     public final DirectedLanePosition getReferencePosition() throws GTUException
513     {
514         for (Lane lane : this.lanesCurrentOperationalPlan.keySet())
515         {
516             double fraction = fractionalPosition(lane, getReference());
517             if (fraction >= 0.0 && fraction <= 1.0)
518             {
519                 // TODO widest lane in case we are registered on more than one lane with the reference point
520                 // TODO lane that leads to our location or not if we are registered on parallel lanes?
521                 return new DirectedLanePosition(lane, position(lane, getReference()), this.getDirection(lane));
522             }
523         }
524         throw new GTUException("The reference point of GTU " + this + " is not on any of the lanes on which it is registered");
525     }
526 
527     /**
528      * Schedule the triggers for this GTU that are going to happen until the next evaluation time. Also schedule the
529      * registration and deregistration of lanes when the vehicle enters or leaves them, at the exact right time. <br>
530      * Note: when the GTU makes a lane change, the vehicle will be registered for both lanes during the entire maneuver.
531      * @throws NetworkException on network inconsistency
532      * @throws SimRuntimeException should never happen
533      * @throws GTUException when a branch is reached where the GTU does not know where to go next
534      */
535     private void scheduleEnterLeaveTriggers() throws NetworkException, SimRuntimeException, GTUException
536     {
537         /*
538          * Move the vehicle into any new lanes with the front, and schedule entrance during this time step and calculate the
539          * current position based on the fractional position, because THE POSITION METHOD DOES NOT WORK FOR THIS. IT CALCULATES
540          * THE POSITION BASED ON THE NEWLY CALCULATED ACCELERATION AND SPEED AND CAN THEREFORE MAKE AN ERROR.
541          */
542         double moveSI = getOperationalPlan().getTotalLength().si;
543         Map<Lane, GTUDirectionality> lanesCopy = new LinkedHashMap<>(this.lanesCurrentOperationalPlan);
544         for (Lane lane : lanesCopy.keySet()) // use a copy because this.lanes can change
545         {
546             // schedule triggers on this lane
547             double referenceStartSI = this.fractionalLinkPositions.get(lane.getParentLink()) * lane.getLength().getSI();
548             double sign = lanesCopy.get(lane).equals(GTUDirectionality.DIR_PLUS) ? 1.0 : -1.0;
549             if (lanesCopy.get(lane).equals(GTUDirectionality.DIR_PLUS))
550             {
551                 lane.scheduleSensorTriggers(this, referenceStartSI, moveSI);
552             }
553             else
554             {
555                 // TODO extra argument for DIR_MINUS driving direction?
556                 lane.scheduleSensorTriggers(this, referenceStartSI - moveSI, moveSI);
557             }
558 
559             // determine when our FRONT will pass the end of this registered lane in case of driving DIR_PLUS or
560             // the start of this registered lane when driving DIR_MINUS.
561             // if the time is earlier than the end of the timestep: schedule the enterLane method.
562             double frontPosSI = referenceStartSI + sign * getFront().getDx().getSI();
563             double nextFrontPosSI = frontPosSI + sign * moveSI;
564 
565             // LANE WE COME FROM IS IN PLUS DIRECTION
566             if (lanesCopy.get(lane).equals(GTUDirectionality.DIR_PLUS))
567             {
568                 while (frontPosSI <= lane.getLength().si && nextFrontPosSI > lane.getLength().si)
569                 {
570                     // TODO PK: this goes very wrong for networks with short lanes with alternating GTUDireationality
571                     Lane nextLane = determineNextLane(lane);
572                     GTUDirectionality direction = lane.nextLanes(getGTUType()).get(nextLane);
573                     /*
574                      * We have to register the position at the previous timestep to keep calculations consistent. And we have to
575                      * correct for the position of the reference point. The idea is that we register the vehicle 'before' the
576                      * entrance of the new lane at the time of the last timestep, so for a DIR_PLUS on a negative position, and
577                      * for a DIR_MINUS on a position beyond the length of the next lane.
578                      */
579                     if (direction.equals(GTUDirectionality.DIR_PLUS))
580                     {
581                         Length refPosAtLastTimestep =
582                                 new Length(-(lane.getLength().si - frontPosSI) - getFront().getDx().si, LengthUnit.SI);
583                         enterLane(nextLane, refPosAtLastTimestep, direction);
584                         // schedule any sensor triggers on this lane for the remainder time
585                         nextLane.scheduleSensorTriggers(this, refPosAtLastTimestep.getSI(), moveSI);
586                     }
587                     else if (direction.equals(GTUDirectionality.DIR_MINUS))
588                     {
589                         Length refPosAtLastTimestep =
590                                 new Length(nextLane.getLength().si + (lane.getLength().si - frontPosSI) + getFront().getDx().si,
591                                         LengthUnit.SI);
592                         enterLane(nextLane, refPosAtLastTimestep, direction);
593                         // schedule any sensor triggers on this lane for the remainder time
594                         // TODO extra argument for DIR_MINUS driving direction?
595                         nextLane.scheduleSensorTriggers(this, refPosAtLastTimestep.getSI() - moveSI, moveSI);
596                     }
597                     else
598                     {
599                         throw new NetworkException("scheduleTriggers DIR_PLUS for GTU " + toString() + ", nextLane " + nextLane
600                                 + ", direction not DIR_PLUS or DIR_MINUS");
601                     }
602 
603                     // TODO this does not account for the GTUDirectionality
604                     // next lane
605                     frontPosSI -= lane.getLength().si;
606                     nextFrontPosSI -= lane.getLength().si;
607                     lane = nextLane;
608                 }
609             }
610 
611             // LANE WE COME FROM IS IN MINUS DIRECTION
612             else if (lanesCopy.get(lane).equals(GTUDirectionality.DIR_MINUS))
613             {
614                 if (frontPosSI >= 0.0 && nextFrontPosSI < 0.0)
615                 {
616                     Lane prevLane = determinePrevLane(lane);
617                     GTUDirectionality direction = lane.prevLanes(getGTUType()).get(prevLane);
618                     /*
619                      * We have to register the position at the previous timestep to keep calculations consistent. And we have to
620                      * correct for the position of the reference point. The idea is that we register the vehicle 'before' the
621                      * entrance of the new lane at the time of the last timestep, so for a DIR_MINUS on a negative position, and
622                      * for a DIR_PLUS on a position beyond the length of the next lane.
623                      */
624                     if (direction.equals(GTUDirectionality.DIR_MINUS))
625                     {
626                         // TODO do this at the right time, scheduled with triggers. 4x
627                         Length refPosAtLastTimestep =
628                                 new Length(prevLane.getLength().si + frontPosSI + getFront().getDx().si, LengthUnit.SI);
629                         enterLane(prevLane, refPosAtLastTimestep, direction);
630                         // schedule any sensor triggers on this lane for the remainder time
631                         prevLane.scheduleSensorTriggers(this, refPosAtLastTimestep.getSI() - moveSI, moveSI);
632                     }
633                     else if (direction.equals(GTUDirectionality.DIR_PLUS))
634                     {
635                         Length refPosAtLastTimestep = new Length(-frontPosSI - getFront().getDx().si, LengthUnit.SI);
636                         enterLane(prevLane, refPosAtLastTimestep, direction);
637                         // schedule any sensor triggers on this lane for the remainder time
638                         // TODO extra argument for DIR_MINUS driving direction?
639                         prevLane.scheduleSensorTriggers(this, refPosAtLastTimestep.getSI(), moveSI);
640                     }
641                     else
642                     {
643                         throw new NetworkException("scheduleTriggers DIR_MINUS for GTU " + toString() + ", prevLane " + prevLane
644                                 + ", direction not DIR_PLUS or DIR_MINUS");
645                     }
646                 }
647             }
648 
649             else
650             {
651                 throw new NetworkException(
652                         "scheduleTriggers for GTU " + toString() + ", lane " + lane + ", direction not DIR_PLUS or DIR_MINUS");
653             }
654         }
655 
656         // move the vehicle out of any lanes with the BACK, and schedule exit during this time step
657         for (Lane lane : this.lanesCurrentOperationalPlan.keySet())
658         {
659             // Determine when our REAR will pass the end of this registered lane.
660             // TODO look if more lanes are exited in one timestep, and continue the algorithm with the remainder of the time...
661             double referenceStartSI = this.fractionalLinkPositions.get(lane.getParentLink()) * lane.getLength().getSI();
662             double sign = this.lanesCurrentOperationalPlan.get(lane).equals(GTUDirectionality.DIR_PLUS) ? 1.0 : -1.0;
663             double rearPosSI = referenceStartSI + sign * getRear().getDx().getSI();
664 
665             if (this.lanesCurrentOperationalPlan.get(lane).equals(GTUDirectionality.DIR_PLUS))
666             {
667                 if (rearPosSI <= lane.getLength().si && rearPosSI + moveSI > lane.getLength().si)
668                 {
669                     double distanceToLeave = lane.getLength().si - rearPosSI;
670                     Time exitTime = getOperationalPlan().timeAtDistance(new Length(distanceToLeave, LengthUnit.SI));
671                     SimEvent<OTSSimTimeDouble> event = new SimEvent<>(new OTSSimTimeDouble(exitTime), this, this, "leaveLane",
672                             new Object[] { lane, new Boolean(false) });
673                     getSimulator().scheduleEvent(event);
674                     addTrigger(lane, event);
675                     // XXXXXXXXXXXXXXXXXX Minus one ULP is not safe if you want to add the current time
676                     // XXXXXXXXXXXXXXXXXX Should compute the time time at which the rear of the GTU exits the lane???
677                     // getSimulator().scheduleEventRel(new Duration(timestep - Math.ulp(timestep), TimeUnit.SI), this, this,
678                     // "leaveLane", new Object[] { lane, new Boolean(true) }); // TODO should be false?
679                 }
680             }
681             else
682             {
683                 if (rearPosSI >= 0.0 && rearPosSI - moveSI < 0.0)
684                 {
685                     double distanceToLeave = rearPosSI;
686                     Time exitTime = getOperationalPlan().timeAtDistance(new Length(distanceToLeave, LengthUnit.SI));
687                     SimEvent<OTSSimTimeDouble> event = new SimEvent<>(new OTSSimTimeDouble(exitTime), this, this, "leaveLane",
688                             new Object[] { lane, new Boolean(false) });
689                     getSimulator().scheduleEvent(event);
690                     addTrigger(lane, event);
691 
692                     // getSimulator().scheduleEventRel(new Duration(timestep - Math.ulp(timestep), TimeUnit.SI), this, this,
693                     // "leaveLane", new Object[] { lane, new Boolean(true) }); // XXX: should be false?
694                 }
695             }
696         }
697     }
698 
699     /**
700      * XXX: direction dependent... <br>
701      * @param lane the lane to find the successor for
702      * @return the next lane for this GTU
703      * @throws NetworkException when no next lane exists or the route branches into multiple next lanes
704      * @throws GTUException when no route could be found or the routeNavigator returns null
705      */
706     private Lane determineNextLane(final Lane lane) throws NetworkException, GTUException
707     {
708         Lane nextLane = null;
709         if (lane.nextLanes(getGTUType()).size() == 0)
710         {
711             throw new NetworkException(this + " - lane " + lane + " does not have a successor");
712         }
713         if (lane.nextLanes(getGTUType()).size() == 1)
714         {
715             nextLane = lane.nextLanes(getGTUType()).keySet().iterator().next();
716         }
717         else
718         {
719             if (!(getStrategicalPlanner() instanceof LaneBasedStrategicalRoutePlanner))
720             {
721                 throw new GTUException(this + " reaches branch but has no route navigator");
722             }
723             Node nextNode = ((LaneBasedStrategicalRoutePlanner) getStrategicalPlanner()).nextNode(lane.getParentLink(),
724                     GTUDirectionality.DIR_PLUS, getGTUType());
725             if (null == nextNode)
726             {
727                 throw new GTUException(this + " reaches branch and the route returns null as nextNodeToVisit");
728             }
729             int continuingLaneCount = 0;
730             for (Lane candidateLane : lane.nextLanes(getGTUType()).keySet())
731             {
732                 if (null != this.lanesCurrentOperationalPlan.get(candidateLane))
733                 {
734                     continue; // Already on this lane
735                 }
736                 // XXX Hack - this should be done more considerate -- fails at loops...
737                 if (nextNode == candidateLane.getParentLink().getEndNode()
738                         || nextNode == candidateLane.getParentLink().getStartNode())
739                 {
740                     nextLane = candidateLane;
741                     continuingLaneCount++;
742                 }
743             }
744             if (continuingLaneCount == 0)
745             {
746                 throw new NetworkException(this + " reached branch and the route specifies a nextNodeToVisit (" + nextNode
747                         + ") that is not a next node " + "at this branch at (" + lane.getParentLink().getEndNode() + ")");
748             }
749             if (continuingLaneCount > 1)
750             {
751                 throw new NetworkException(
752                         this + " reached branch and the route specifies multiple lanes to continue on at this branch ("
753                                 + lane.getParentLink().getEndNode() + "). This is not yet supported");
754             }
755         }
756         return nextLane;
757     }
758 
759     /**
760      * XXX: direction dependent... <br>
761      * @param lane the lane to find the predecessor for
762      * @return the next lane for this GTU
763      * @throws NetworkException when no next lane exists or the route branches into multiple next lanes
764      * @throws GTUException when no route could be found or the routeNavigator returns null
765      */
766     private Lane determinePrevLane(final Lane lane) throws NetworkException, GTUException
767     {
768         Lane prevLane = null;
769         if (lane.prevLanes(getGTUType()).size() == 0)
770         {
771             throw new NetworkException(this + " - lane " + lane + " does not have a predecessor");
772         }
773         if (lane.prevLanes(getGTUType()).size() == 1)
774         {
775             prevLane = lane.prevLanes(getGTUType()).keySet().iterator().next();
776         }
777         else
778         {
779             if (!(getStrategicalPlanner() instanceof LaneBasedStrategicalRoutePlanner))
780             {
781                 throw new GTUException(this + " reaches branch but has no route navigator");
782             }
783             Node prevNode = ((LaneBasedStrategicalRoutePlanner) getStrategicalPlanner()).nextNode(lane.getParentLink(),
784                     GTUDirectionality.DIR_MINUS, getGTUType());
785             if (null == prevNode)
786             {
787                 throw new GTUException(this + " reaches branch and the route returns null as nextNodeToVisit");
788             }
789             int continuingLaneCount = 0;
790             for (Lane candidateLane : lane.prevLanes(getGTUType()).keySet())
791             {
792                 if (null != this.lanesCurrentOperationalPlan.get(candidateLane))
793                 {
794                     continue; // Already on this lane
795                 }
796                 // XXX Hack - this should be done more considerate -- fails at loops...
797                 if (prevNode == candidateLane.getParentLink().getEndNode()
798                         || prevNode == candidateLane.getParentLink().getStartNode())
799                 {
800                     prevLane = candidateLane;
801                     continuingLaneCount++;
802                 }
803             }
804             if (continuingLaneCount == 0)
805             {
806                 throw new NetworkException(this + " reached branch and the route specifies a nextNodeToVisit (" + prevNode
807                         + ") that is not a next node " + "at this branch at (" + lane.getParentLink().getStartNode() + ")");
808             }
809             if (continuingLaneCount > 1)
810             {
811                 throw new NetworkException(
812                         this + " reached branch and the route specifies multiple lanes to continue on at this branch ("
813                                 + lane.getParentLink().getStartNode() + "). This is not yet supported");
814             }
815         }
816         return prevLane;
817     }
818 
819     /** {@inheritDoc} */
820     @Override
821     public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition) throws GTUException
822     {
823         return fractionalPositions(relativePosition, getSimulator().getSimulatorTime().getTime());
824     }
825 
826     /** {@inheritDoc} */
827     @Override
828     public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition, final Time when)
829             throws GTUException
830     {
831         Map<Lane, Double> positions = new LinkedHashMap<>();
832         for (Lane lane : this.lanesCurrentOperationalPlan.keySet())
833         {
834             positions.put(lane, fractionalPosition(lane, relativePosition, when));
835         }
836         return positions;
837     }
838 
839     /** {@inheritDoc} */
840     @Override
841     public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition, final Time when)
842             throws GTUException
843     {
844         return position(lane, relativePosition, when).getSI() / lane.getLength().getSI();
845     }
846 
847     /** {@inheritDoc} */
848     @Override
849     public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition) throws GTUException
850     {
851         return position(lane, relativePosition).getSI() / lane.getLength().getSI();
852     }
853 
854     /** {@inheritDoc} */
855     @Override
856     public final LaneBasedStrategicalPlanner getStrategicalPlanner()
857     {
858         return (LaneBasedStrategicalPlanner) super.getStrategicalPlanner();
859     }
860 
861     /** {@inheritDoc} */
862     @Override
863     public final BehavioralCharacteristics getBehavioralCharacteristics()
864     {
865         return getStrategicalPlanner().getBehavioralCharacteristics();
866     }
867 
868     /** {@inheritDoc} */
869     @Override
870     public final LaneBasedTacticalPlanner getTacticalPlanner()
871     {
872         return (LaneBasedTacticalPlanner) super.getTacticalPlanner();
873     }
874 
875     /** {@inheritDoc} */
876     public final void addTrigger(final Lane lane, final SimEvent<OTSSimTimeDouble> event)
877     {
878         List<SimEvent<OTSSimTimeDouble>> list = this.pendingTriggers.get(lane);
879         if (null == list)
880         {
881             list = new ArrayList<>();
882         }
883         list.add(event);
884         this.pendingTriggers.put(lane, list);
885     }
886 
887     /** {@inheritDoc} */
888     @Override
889     @SuppressWarnings("checkstyle:designforextension")
890     public void destroy()
891     {
892         DirectedLanePosition dlp = null;
893         try
894         {
895             dlp = getReferencePosition();
896         }
897         catch (GTUException e)
898         {
899             // ignore. not important at destroy
900         }
901         DirectedPoint location = this.getOperationalPlan() == null ? new DirectedPoint(0.0, 0.0, 0.0) : getLocation();
902 
903         synchronized (this.lock)
904         {
905             Set<Lane> laneSet = new HashSet<>(this.lanesCurrentOperationalPlan.keySet()); // Operate on a copy of the key set
906             for (Lane lane : laneSet)
907             {
908                 try
909                 {
910                     leaveLane(lane, true);
911                 }
912                 catch (GTUException e)
913                 {
914                     // ignore. not important at destroy
915                 }
916             }
917         }
918 
919         if (dlp != null && dlp.getLane() != null)
920         {
921             Lane referenceLane = dlp.getLane();
922             fireTimedEvent(LaneBasedGTU.LANEBASED_DESTROY_EVENT,
923                     new Object[] { getId(), location, getOdometer(), referenceLane, dlp.getPosition(), dlp.getGtuDirection() },
924                     getSimulator().getSimulatorTime());
925         }
926         else
927         {
928             fireTimedEvent(LaneBasedGTU.LANEBASED_DESTROY_EVENT,
929                     new Object[] { getId(), location, getOdometer(), null, Length.ZERO, null },
930                     getSimulator().getSimulatorTime());
931         }
932 
933         super.destroy();
934     }
935 
936     /** {@inheritDoc} */
937     @Override
938     public final Bounds getBounds()
939     {
940         double dx = 0.5 * getLength().doubleValue();
941         double dy = 0.5 * getWidth().doubleValue();
942         return new BoundingBox(new Point3d(-dx, -dy, 0.0), new Point3d(dx, dy, 0.0));
943     }
944 
945     /** {@inheritDoc} */
946     @SuppressWarnings("checkstyle:designforextension")
947     public String toString()
948     {
949         return String.format("GTU " + getId());
950     }
951 }