View Javadoc
1   package org.opentrafficsim.core.gtu.lane;
2   
3   import java.rmi.RemoteException;
4   import java.util.ArrayList;
5   import java.util.Collection;
6   import java.util.HashMap;
7   import java.util.HashSet;
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.naming.NamingException;
14  import javax.vecmath.Point3d;
15  
16  import nl.tudelft.simulation.dsol.SimRuntimeException;
17  import nl.tudelft.simulation.language.d3.BoundingBox;
18  import nl.tudelft.simulation.language.d3.DirectedPoint;
19  
20  import org.opentrafficsim.core.dsol.OTSDEVSSimulatorInterface;
21  import org.opentrafficsim.core.gtu.AbstractGTU;
22  import org.opentrafficsim.core.gtu.GTUException;
23  import org.opentrafficsim.core.gtu.GTUType;
24  import org.opentrafficsim.core.gtu.RelativePosition;
25  import org.opentrafficsim.core.gtu.following.AccelerationStep;
26  import org.opentrafficsim.core.gtu.following.GTUFollowingModel;
27  import org.opentrafficsim.core.gtu.following.HeadwayGTU;
28  import org.opentrafficsim.core.gtu.lane.changing.LaneChangeModel;
29  import org.opentrafficsim.core.gtu.lane.changing.LaneMovementStep;
30  import org.opentrafficsim.core.network.LateralDirectionality;
31  import org.opentrafficsim.core.network.Link;
32  import org.opentrafficsim.core.network.NetworkException;
33  import org.opentrafficsim.core.network.lane.CrossSectionElement;
34  import org.opentrafficsim.core.network.lane.CrossSectionLink;
35  import org.opentrafficsim.core.network.lane.Lane;
36  import org.opentrafficsim.core.unit.AccelerationUnit;
37  import org.opentrafficsim.core.unit.LengthUnit;
38  import org.opentrafficsim.core.unit.SpeedUnit;
39  import org.opentrafficsim.core.unit.TimeUnit;
40  import org.opentrafficsim.core.value.conversions.Calc;
41  import org.opentrafficsim.core.value.vdouble.scalar.DoubleScalar;
42  import org.opentrafficsim.core.value.vdouble.scalar.DoubleScalar.Rel;
43  
44  import com.vividsolutions.jts.geom.Coordinate;
45  import com.vividsolutions.jts.geom.LineString;
46  import com.vividsolutions.jts.linearref.LengthIndexedLine;
47  
48  /**
49   * <p>
50   * Copyright (c) 2013-2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
51   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
52   * <p>
53   * @version Oct 22, 2014 <br>
54   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
55   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
56   * @param <ID> The type of ID, e.g., String or Integer
57   */
58  public abstract class AbstractLaneBasedGTU<ID> extends AbstractGTU<ID> implements LaneBasedGTU<ID>
59  {
60      /** */
61      private static final long serialVersionUID = 20140822L;
62  
63      /** Time of last evaluation. */
64      private DoubleScalar.Abs<TimeUnit> lastEvaluationTime;
65  
66      /** Time of next evaluation. */
67      private DoubleScalar.Abs<TimeUnit> nextEvaluationTime;
68  
69      /**
70       * Fractional longitudinal positions of the reference point of the GTU on one or more links at the lastEvaluationTime.
71       * Because the reference point of the GTU might not be on all the links the GTU is registered on, the fractional
72       * longitudinal positions can be more than one, or less than zero.
73       */
74      private final Map<Link<?, ?>, Double> fractionalLinkPositions = new HashMap<>();
75  
76      /**
77       * The lanes the GTU is registered on. Each lane has to have its link registered in the fractionalLinkPositions as well to
78       * keep consistency. Each link from the fractionalLinkPositions can have one or more Lanes on which the vehicle is
79       * registered. This is a list to improve reproducibility: The 'oldest' lanes on which the vehicle is registered are at the
80       * front of the list, the later ones more to the back.
81       */
82      private final List<Lane> lanes = new ArrayList<>();
83  
84      /**
85       * @return lanes.
86       */
87      public final List<Lane> getLanes()
88      {
89          return this.lanes;
90      }
91  
92      /** Speed at lastEvaluationTime. */
93      private DoubleScalar.Abs<SpeedUnit> speed;
94  
95      /** lateral velocity at lastEvaluationTime. */
96      private DoubleScalar.Abs<SpeedUnit> lateralVelocity;
97  
98      /** acceleration (negative values indicate deceleration) at the lastEvaluationTime. */
99      private DoubleScalar.Abs<AccelerationUnit> acceleration = new DoubleScalar.Abs<AccelerationUnit>(0,
100         AccelerationUnit.METER_PER_SECOND_2);
101 
102     /** CarFollowingModel used by this GTU. */
103     private final GTUFollowingModel gtuFollowingModel;
104 
105     /** LaneChangeModel used by this GTU. */
106     private final LaneChangeModel laneChangeModel;
107 
108     /**
109      * Construct a Lane Based GTU.
110      * @param id the id of the GTU, could be String or Integer
111      * @param gtuType the type of GTU, e.g. TruckType, CarType, BusType
112      * @param gtuFollowingModel the following model, including a reference to the simulator.
113      * @param laneChangeModel LaneChangeModel; the lane change model
114      * @param initialLongitudinalPositions the initial positions of the car on one or more lanes
115      * @param initialSpeed the initial speed of the car on the lane
116      * @param simulator to initialize the move method and to get the current time
117      * @throws RemoteException when the simulator cannot be reached
118      * @throws NetworkException when the GTU cannot be placed on the given lane
119      * @throws SimRuntimeException when the move method cannot be scheduled
120      */
121     public AbstractLaneBasedGTU(final ID id, final GTUType<?> gtuType, final GTUFollowingModel gtuFollowingModel,
122         final LaneChangeModel laneChangeModel, final Map<Lane, DoubleScalar.Rel<LengthUnit>> initialLongitudinalPositions,
123         final DoubleScalar.Abs<SpeedUnit> initialSpeed, final OTSDEVSSimulatorInterface simulator) throws RemoteException,
124         NetworkException, SimRuntimeException
125     {
126         super(id, gtuType);
127         this.gtuFollowingModel = gtuFollowingModel;
128         this.laneChangeModel = laneChangeModel;
129         this.lateralVelocity = new DoubleScalar.Abs<SpeedUnit>(0.0, SpeedUnit.METER_PER_SECOND);
130 
131         // register the GTU on the lanes
132         for (Lane lane : initialLongitudinalPositions.keySet())
133         {
134             this.lanes.add(lane);
135             this.fractionalLinkPositions.put(lane.getParentLink(), lane.fraction(initialLongitudinalPositions.get(lane)));
136             lane.addGTU(this, initialLongitudinalPositions.get(lane));
137         }
138 
139         // Duplicate the other arguments as these are modified in this class and may be re-used by the caller
140         this.lastEvaluationTime = new DoubleScalar.Abs<TimeUnit>(simulator.getSimulatorTime().get());
141         this.speed = new DoubleScalar.Abs<SpeedUnit>(initialSpeed);
142         this.nextEvaluationTime = new DoubleScalar.Abs<TimeUnit>(simulator.getSimulatorTime().get());
143 
144         // start the movement of the GTU
145         simulator.scheduleEventNow(this, this, "move", null);
146     }
147 
148     /** {@inheritDoc} */
149     @Override
150     public final DoubleScalar.Abs<SpeedUnit> getLongitudinalVelocity(final DoubleScalar.Abs<TimeUnit> when)
151     {
152         DoubleScalar.Rel<TimeUnit> dT = DoubleScalar.minus(when, this.lastEvaluationTime).immutable();
153         return DoubleScalar.plus(this.speed, Calc.accelerationTimesTime(this.getAcceleration(when), dT)).immutable();
154     }
155 
156     /** {@inheritDoc} */
157     @Override
158     public final DoubleScalar.Abs<SpeedUnit> getLongitudinalVelocity() throws RemoteException
159     {
160         return getLongitudinalVelocity(getSimulator().getSimulatorTime().get());
161     }
162 
163     /** {@inheritDoc} */
164     @Override
165     public final DoubleScalar.Abs<TimeUnit> getLastEvaluationTime()
166     {
167         return new DoubleScalar.Abs<TimeUnit>(this.lastEvaluationTime);
168     }
169 
170     /** {@inheritDoc} */
171     @Override
172     public final DoubleScalar.Abs<TimeUnit> getNextEvaluationTime()
173     {
174         return this.nextEvaluationTime;
175     }
176 
177     /** {@inheritDoc} */
178     @Override
179     public final DoubleScalar.Abs<AccelerationUnit> getAcceleration(final DoubleScalar.Abs<TimeUnit> when)
180     {
181         // Currently the acceleration is independent of when; it is constant during the evaluation interval
182         return new DoubleScalar.Abs<AccelerationUnit>(this.acceleration);
183     }
184 
185     /** {@inheritDoc} */
186     @Override
187     public final DoubleScalar.Abs<AccelerationUnit> getAcceleration() throws RemoteException
188     {
189         return getAcceleration(getSimulator().getSimulatorTime().get());
190     }
191 
192     /** {@inheritDoc} */
193     @Override
194     public final DoubleScalar.Abs<SpeedUnit> getLateralVelocity()
195     {
196         return new DoubleScalar.Abs<SpeedUnit>(this.lateralVelocity);
197     }
198 
199     @Override
200     public final void addFrontToSubsequentLane(final Lane lane) throws RemoteException, NetworkException
201     {
202         Lane prevLane = this.lanes.get(0); // TODO exceptions en zo.
203         double positionPrevTimeStepSI = position(prevLane, getReference(), this.lastEvaluationTime).getSI();
204         double positionNowSI = position(prevLane, getReference()).getSI();
205         DoubleScalar.Rel<LengthUnit> position =
206             new DoubleScalar.Rel<>(positionPrevTimeStepSI - positionNowSI
207                 - (getFront().getDx().getSI() - getReference().getDx().getSI()), LengthUnit.SI);
208         addLane(lane, position);
209     }
210 
211     /** {@inheritDoc} */
212     @Override
213     public final void addLane(final Lane lane, final DoubleScalar.Rel<LengthUnit> position) throws NetworkException
214     {
215         if (this.lanes.contains(lane))
216         {
217             throw new NetworkException("GTU " + toString() + " is already registered on this lane: " + lane);
218         }
219         // if the GTU is already registered on a lane of the same link, do not change its fractional position, as this
220         // might lead to a "jump".
221         if (!this.fractionalLinkPositions.containsKey(lane.getParentLink()))
222         {
223             this.fractionalLinkPositions.put(lane.getParentLink(), lane.fraction(position));
224         }
225         this.lanes.add(lane);
226     }
227 
228     /** {@inheritDoc} */
229     @Override
230     public final void removeLane(final Lane lane)
231     {
232         this.lanes.remove(lane);
233         // check of there are any lanes for this link left. If not, remove the link.
234         boolean found = false;
235         for (Lane l : this.lanes)
236         {
237             if (l.getParentLink().equals(lane.getParentLink()))
238             {
239                 found = true;
240             }
241         }
242         if (!found)
243         {
244             this.fractionalLinkPositions.remove(lane.getParentLink());
245         }
246     }
247 
248     /**
249      * Set the new state.
250      * @param cfmr AccelerationStep; the new state of this GTU
251      * @throws RemoteException when simulator time could not be retrieved or sensor trigger scheduling fails.
252      * @throws NetworkException when the vehicle is not on the given lane.
253      * @throws SimRuntimeException when sensor trigger(s) cannot be scheduled on the simulator.
254      */
255     private void setState(final AccelerationStep cfmr) throws RemoteException, NetworkException, SimRuntimeException
256     {
257         if (cfmr.getAcceleration().getSI() < -9999)
258         {
259             System.out.println("Problem");
260         }
261         // GTUs move based on their fractional position to stay aligned when registered in parallel lanes.
262         // The "oldest" lane of parallel lanes takes preference when updating the fractional position.
263         // So we work from back to front.
264         for (int i = this.lanes.size() - 1; i >= 0; i--)
265         {
266             Lane lane = this.lanes.get(i);
267             this.fractionalLinkPositions.put(lane.getParentLink(), lane.fraction(position(lane, getReference(),
268                 this.nextEvaluationTime)));
269         }
270         // Compute and set the current speed using the "old" nextEvaluationTime and acceleration
271         this.speed = getLongitudinalVelocity(this.nextEvaluationTime);
272         // Update lastEvaluationTime and then set the new nextEvaluationTime
273         this.lastEvaluationTime = this.nextEvaluationTime;
274         this.nextEvaluationTime = cfmr.getValidUntil();
275         this.acceleration = cfmr.getAcceleration();
276 
277         // // Does our front reference point enter new lane(s) during the next time step? If so, add to our lane list!
278         // // Note: the trigger at the start of the lane will add the vehicle to that lane at the exact right time.
279         // List<Lane> lanesToCheck = new ArrayList<Lane>(this.lanes);
280         // while (!lanesToCheck.isEmpty())
281         // {
282         // Lane lane = lanesToCheck.remove(0);
283         // double frontPosSI = position(lane, getFront(), this.lastEvaluationTime).getSI();
284         // // TODO speed this up by using SI units, caching, etc.
285         // if (lane.fractionSI(frontPosSI) <= 1.0
286         // && lane.fraction(position(lane, getFront(), this.nextEvaluationTime)) > 1.0)
287         // {
288         // for (Lane nextLane : lane.nextLanes())
289         // {
290         // // Only follow links on the Route if there is a Route, and we haven't added this lane already
291         // if (!this.lanes.contains(nextLane)
292         // && (this.getRoute() == null || this.getRoute() != null
293         // && this.getRoute().containsLink(nextLane.getParentLink())))
294         // {
295         // this.lanes.add(nextLane);
296         // if (!this.fractionalLinkPositions.containsKey(nextLane.getParentLink()))
297         // {
298         // double positionSI = frontPosSI - lane.getLength().getSI() - getFront().getDx().getSI();
299         // this.fractionalLinkPositions.put(nextLane.getParentLink(), nextLane.fractionSI(positionSI));
300         // }
301         // lanesToCheck.add(nextLane);
302         // }
303         // }
304         // }
305         // }
306         // Execute all samplers
307         for (Lane lane : this.lanes)
308         {
309             lane.sample(this);
310         }
311         // System.out.println("setState: " + cfmr + " " + this + " next evaluation is " + cfmr.getValidUntil());
312     }
313 
314     /**
315      * @throws RemoteException RemoteException
316      * @throws NamingException on ???
317      * @throws NetworkException on network inconsistency
318      * @throws SimRuntimeException on not being able to reschedule the move() method.
319      */
320     protected final void move() throws RemoteException, NamingException, NetworkException, GTUException, SimRuntimeException
321     {
322 //         if (getId().toString().equals("41")) // && getSimulator().getSimulatorTime().get().getSI() > 7.9)
323 //         {
324 //         System.out.println("Debug me: " + this);
325 //         }
326 
327         // Sanity check
328         if (getSimulator().getSimulatorTime().get().getSI() != getNextEvaluationTime().getSI())
329         {
330             throw new Error("move called at wrong time: expected time " + getNextEvaluationTime() + " simulator time is : "
331                 + getSimulator().getSimulatorTime().get());
332         }
333         // only carry out move() if we still have lane(s) to drive on.
334         // Note: a (Sink) trigger can have 'destroyed' us between the previous evaluation step and this one.
335         if (this.lanes.isEmpty())
336         {
337             return;
338         }
339         DoubleScalar.Rel<LengthUnit> maximumForwardHeadway = new DoubleScalar.Rel<LengthUnit>(500.0, LengthUnit.METER);
340         // TODO 500?
341         DoubleScalar.Rel<LengthUnit> maximumReverseHeadway = new DoubleScalar.Rel<LengthUnit>(-200.0, LengthUnit.METER);
342         // TODO 200?
343         DoubleScalar.Abs<SpeedUnit> speedLimit = new DoubleScalar.Abs<SpeedUnit>(100.0, SpeedUnit.KM_PER_HOUR);
344         // TODO should be the local speed limit and based on the maximum lane speed and the maximum GTU speed
345         if (null != this.laneChangeModel)
346         {
347             Collection<HeadwayGTU> sameLaneTraffic = new ArrayList<HeadwayGTU>();
348             HeadwayGTU leader = headway(maximumForwardHeadway);
349             if (null != leader.getOtherGTU())
350             {
351                 sameLaneTraffic.add(leader);
352             }
353             HeadwayGTU follower = headway(maximumReverseHeadway);
354             if (null != follower.getOtherGTU())
355             {
356                 sameLaneTraffic.add(new HeadwayGTU(follower.getOtherGTU(), -follower.getDistanceSI()));
357             }
358             DoubleScalar.Abs<TimeUnit> now = getSimulator().getSimulatorTime().get();
359             Collection<HeadwayGTU> leftLaneTraffic =
360                 collectNeighborLaneTraffic(LateralDirectionality.LEFT, now, maximumForwardHeadway, maximumReverseHeadway);
361             Collection<HeadwayGTU> rightLaneTraffic =
362                 collectNeighborLaneTraffic(LateralDirectionality.RIGHT, now, maximumForwardHeadway, maximumReverseHeadway);
363             LaneMovementStep lcmr =
364                 this.laneChangeModel.computeLaneChangeAndAcceleration(this, sameLaneTraffic, rightLaneTraffic,
365                     leftLaneTraffic, speedLimit, new DoubleScalar.Rel<AccelerationUnit>(0.3,
366                         AccelerationUnit.METER_PER_SECOND_2), new DoubleScalar.Rel<AccelerationUnit>(0.1,
367                         AccelerationUnit.METER_PER_SECOND_2), new DoubleScalar.Rel<AccelerationUnit>(-0.3,
368                         AccelerationUnit.METER_PER_SECOND_2));
369             // First move this GTU forward (to its current location)
370             setState(lcmr.getGfmr());
371 
372             // Then change onto laterally adjacent lane(s) if the LaneMovementStep indicates a lane change
373 
374             if (lcmr.getLaneChange() != null)
375             {
376                 synchronized (this.lanes)
377                 {
378                     // TODO: make lane changes gradual (not instantaneous; like now)
379                     Collection<Lane> oldLaneSet = new ArrayList<Lane>(this.lanes);
380                     Collection<Lane> newLaneSet = adjacentLanes(lcmr.getLaneChange());
381                     // Remove this GTU from all of the Lanes that it is on and remember the fractional position on each one
382                     Map<Lane, Double> oldFractionalPositions = new HashMap<Lane, Double>();
383                     for (Lane l : this.lanes)
384                     {
385                         oldFractionalPositions.put(l, fractionalPosition(l, getReference(), getLastEvaluationTime()));
386                         this.fractionalLinkPositions.remove(l.getParentLink());
387                     }
388                     for (Lane l : oldFractionalPositions.keySet())
389                     {
390                         l.removeGTU(this);
391                         removeLane(l);
392                     }
393                     ArrayList<Lane> replacementLanes = new ArrayList<Lane>(); // for DEBUG
394                     // Add this GTU to the lanes in newLaneSet
395                     // This could be rewritten to be more efficient.
396                     for (Lane newLane : newLaneSet)
397                     {
398                         Double fractionalPosition = null;
399                         // find ONE lane in oldLaneSet that has l as neighbor
400                         for (Lane oldLane : oldLaneSet)
401                         {
402                             if (oldLane.accessibleAdjacentLanes(lcmr.getLaneChange(), getGTUType()).contains(newLane))
403                             {
404                                 fractionalPosition = oldFractionalPositions.get(oldLane);
405                                 break;
406                             }
407                         }
408                         if (null == fractionalPosition)
409                         {
410                             throw new Error("Program error: Cannot find an oldLane that has newLane " + newLane + " as "
411                                 + lcmr.getLaneChange() + " neighbor");
412                         }
413                         newLane.addGTU(this, fractionalPosition);
414                         addLane(newLane, (Rel<LengthUnit>) newLane.getLength().mutable().multiply(fractionalPosition)
415                             .immutable());
416                         replacementLanes.add(newLane);
417                     }
418                     System.out.println("GTU " + this + " changed lanes from: " + oldLaneSet + " to " + replacementLanes);
419                     checkConsistency();
420                 }
421             }
422             // }
423             else
424             {
425                 // throw new GTUException("All LaneBasedGTUs should have a LaneChangeModel");
426                 /*- TODO the rest of this method should disappear; all GTUs should have a LaneChangeModel
427                 HeadwayGTU leader = headway(maximumForwardHeadway);
428                 AccelerationStep as =
429                     null != leader.getOtherGTU() ? getGTUFollowingModel().computeAcceleration(this, leader.getOtherGTU().getLateralVelocity(),
430                         leader.getDistance(), speedLimit) : new AccelerationStep(new DoubleScalar.Abs<AccelerationUnit>(0,
431                         AccelerationUnit.SI), DoubleScalar.plus(getSimulator().getSimulatorTime().get(),
432                         getGTUFollowingModel().getStepSize()).immutable());
433                 setState(as);
434                  */
435             }
436             // Schedule all sensor triggers that are going to happen until the next evaluation time.
437             scheduleTriggers();
438             getSimulator().scheduleEventAbs(this.getNextEvaluationTime(), this, this, "move", null);
439         }
440     }
441 
442     /**
443      * Verify that all the lanes registered in this GTU have this GTU registered as well and vice versa.
444      */
445     private void checkConsistency()
446     {
447         for (Lane l : this.lanes)
448         {
449             if (!this.fractionalLinkPositions.containsKey(l.getParentLink()))
450             {
451                 System.err.println("GTU " + this + "XXXXXXXX");
452             }
453         }
454         for (Link<?, ?> csl : this.fractionalLinkPositions.keySet())
455         {
456             boolean found = false;
457             for (Lane l : this.lanes)
458             {
459                 if (l.getParentLink().equals(csl))
460                 {
461                     found = true;
462                     break;
463                 }
464             }
465             if (!found)
466             {
467                 System.err.println("GTU " + this + "YYYYYYYYY");
468             }
469         }
470     }
471 
472     /**
473      * Schedule the triggers for this GTU during the new time period.
474      * @throws NetworkException on network inconsistency
475      * @throws RemoteException on communications failure
476      * @throws SimRuntimeException should never happen
477      */
478     private void scheduleTriggers() throws NetworkException, RemoteException, SimRuntimeException
479     {
480         // Does our front reference point enter new lane(s) during the next time step?
481         // Note: the trigger at the start of the lane will add the vehicle to that lane at the exact right time.
482         for (Lane lane : this.lanes)
483         {
484             double frontPosSI = position(lane, getFront(), this.lastEvaluationTime).getSI();
485             double frontPosAtNextEvalSI = position(lane, getFront(), this.nextEvaluationTime).getSI();
486             double remainingDistanceSI = lane.getLength().getSI() - frontPosSI;
487             //double excessNextLaneSI = frontPosAtNextEvalSI - lane.getLength().getSI();
488             double moveSI = frontPosAtNextEvalSI - frontPosSI;
489             if (lane.fractionSI(frontPosSI) <= 1.0 && lane.fractionSI(frontPosAtNextEvalSI) > 1.0)
490             {
491                 for (Lane nextLane : lane.nextLanes())
492                 {
493                     // Only follow links on the Route if there is a Route, and we haven't added this lane already
494                     if (!this.lanes.contains(nextLane)
495                         && (this.getRoute() == null || this.getRoute() != null
496                             && this.getRoute().containsLink(nextLane.getParentLink())))
497                     {
498                         nextLane.scheduleTriggers(this, -remainingDistanceSI - getFront().getDx().getSI(), moveSI);
499                     }
500                 }
501             }
502             // also schedule any triggers for the current lane(s)
503             lane.scheduleTriggers(this, lane.positionSI(this.fractionalLinkPositions.get(lane.getParentLink())), moveSI);
504         }
505 
506         // for (Lane lane : triggerLanes)
507         // {
508         // double dt = this.nextEvaluationTime.getSI() - this.getLastEvaluationTime().getSI();
509         //
510         // double frontPosSI = position(lane, getFront(), this.lastEvaluationTime).getSI();
511         // double positionSI = frontPosSI - lane.getLength().getSI() - getFront().getDx().getSI();
512         // fractionalLinkPosition = lane.fractionSI(positionSI);
513         // }
514     }
515 
516     /**
517      * Collect relevant traffic in adjacent lanes. Parallel traffic is included with headway equal to Double.NaN.
518      * @param directionality LateralDirectionality; either <cite>LateralDirectionality.LEFT</cite>, or
519      *            <cite>LateralDirectionality.RIGHT</cite>
520      * @param when DoubleScalar.Abs&lt;TimeUnit&gt;; the (current) time
521      * @param maximumForwardHeadway DoubleScalar.Rel&lt;LengthUnit&gt;; the maximum forward search distance
522      * @param maximumReverseHeadway DoubleScalar.Rel&lt;LengthUnit&gt;; the maximum reverse search distance
523      * @return Collection&lt;LaneBasedGTU&lt;?&gt;&gt;;
524      * @throws RemoteException on communications failure
525      * @throws NetworkException on network inconsistency
526      */
527     private Collection<HeadwayGTU> collectNeighborLaneTraffic(final LateralDirectionality directionality,
528         final DoubleScalar.Abs<TimeUnit> when, final DoubleScalar.Rel<LengthUnit> maximumForwardHeadway,
529         final DoubleScalar.Rel<LengthUnit> maximumReverseHeadway) throws RemoteException, NetworkException
530     {
531         Collection<HeadwayGTU> result = new HashSet<HeadwayGTU>();
532         for (LaneBasedGTU<?> p : parallel(directionality, when))
533         {
534             result.add(new HeadwayGTU(p, Double.NaN));
535         }
536         for (Lane adjacentLane : adjacentLanes(directionality))
537         {
538             HeadwayGTU leader = headway(adjacentLane, maximumForwardHeadway);
539             if (null != leader.getOtherGTU() && !result.contains(leader))
540             {
541                 result.add(leader);
542             }
543             HeadwayGTU follower = headway(adjacentLane, maximumReverseHeadway);
544             if (null != follower.getOtherGTU() && !result.contains(follower))
545             {
546                 result.add(new HeadwayGTU(follower.getOtherGTU(), -follower.getDistanceSI()));
547             }
548         }
549         return result;
550     }
551 
552     /** {@inheritDoc} */
553     @Override
554     public final Map<Lane, DoubleScalar.Rel<LengthUnit>> positions(final RelativePosition relativePosition)
555         throws NetworkException, RemoteException
556     {
557         return positions(relativePosition, getSimulator().getSimulatorTime().get());
558     }
559 
560     /** {@inheritDoc} */
561     @Override
562     public final Map<Lane, DoubleScalar.Rel<LengthUnit>> positions(final RelativePosition relativePosition,
563         final DoubleScalar.Abs<TimeUnit> when) throws NetworkException
564     {
565         Map<Lane, DoubleScalar.Rel<LengthUnit>> positions = new HashMap<>();
566         for (Lane lane : this.lanes)
567         {
568             positions.put(lane, position(lane, relativePosition, when));
569         }
570         return positions;
571     }
572 
573     /** {@inheritDoc} */
574     @Override
575     public final DoubleScalar.Rel<LengthUnit> position(final Lane lane, final RelativePosition relativePosition)
576         throws NetworkException, RemoteException
577     {
578         return position(lane, relativePosition, getSimulator().getSimulatorTime().get());
579     }
580 
581     /** {@inheritDoc} */
582     public final DoubleScalar.Rel<LengthUnit> projectedPosition(final Lane projectionLane,
583         final RelativePosition relativePosition, final DoubleScalar.Abs<TimeUnit> when) throws NetworkException
584     {
585         CrossSectionLink<?, ?> link = projectionLane.getParentLink();
586         for (CrossSectionElement cse : link.getCrossSectionElementList())
587         {
588             if (cse instanceof Lane)
589             {
590                 Lane cseLane = (Lane) cse;
591                 if (this.lanes.contains(cseLane))
592                 {
593                     double fractionalPosition = fractionalPosition(cseLane, relativePosition, when);
594                     return new DoubleScalar.Rel<LengthUnit>(projectionLane.getLength().getSI() * fractionalPosition,
595                         LengthUnit.SI);
596                 }
597             }
598         }
599         throw new NetworkException("GTU " + this + " is not on any lane of Link " + link);
600     }
601 
602     /** {@inheritDoc} */
603     @Override
604     public final DoubleScalar.Rel<LengthUnit> position(final Lane lane, final RelativePosition relativePosition,
605         final DoubleScalar.Abs<TimeUnit> when) throws NetworkException
606     {
607         if (null == lane)
608         {
609             throw new NetworkException("lane is null");
610         }
611         if (!this.lanes.contains(lane))
612         {
613             throw new NetworkException("GTU is not on lane " + lane.toString());
614         }
615         if (!this.fractionalLinkPositions.containsKey(lane.getParentLink()))
616         {
617             throw new NetworkException("GTU does not have a fractional position on " + lane.toString());
618         }
619         DoubleScalar.Rel<LengthUnit> longitudinalPosition =
620             lane.position(this.fractionalLinkPositions.get(lane.getParentLink()));
621         if (longitudinalPosition == null)
622         {
623             throw new NetworkException("GetPosition: GTU " + toString() + " not in lane " + lane);
624         }
625         DoubleScalar.Rel<TimeUnit> dT = DoubleScalar.minus(when, this.lastEvaluationTime).immutable();
626         DoubleScalar.Rel<LengthUnit> loc =
627             DoubleScalar.plus(
628                 DoubleScalar.plus(DoubleScalar.plus(longitudinalPosition, Calc.speedTimesTime(this.speed, dT)).immutable(),
629                     Calc.accelerationTimesTimeSquaredDiv2(this.getAcceleration(when), dT)).immutable(),
630                 relativePosition.getDx()).immutable();
631         if (Double.isNaN(loc.getSI()))
632         {
633             System.out.println("loc is NaN");
634         }
635         return loc;
636     }
637 
638     /** {@inheritDoc} */
639     @Override
640     public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition) throws NetworkException,
641         RemoteException
642     {
643         return fractionalPositions(relativePosition, getSimulator().getSimulatorTime().get());
644     }
645 
646     /** {@inheritDoc} */
647     @Override
648     public final Map<Lane, Double> fractionalPositions(final RelativePosition relativePosition,
649         final DoubleScalar.Abs<TimeUnit> when) throws NetworkException
650     {
651         Map<Lane, Double> positions = new HashMap<>();
652         for (Lane lane : this.lanes)
653         {
654             positions.put(lane, fractionalPosition(lane, relativePosition, when));
655         }
656         return positions;
657     }
658 
659     /** {@inheritDoc} */
660     @Override
661     public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition,
662         final DoubleScalar.Abs<TimeUnit> when) throws NetworkException
663     {
664         return position(lane, relativePosition, when).getSI() / lane.getLength().getSI();
665     }
666 
667     /** {@inheritDoc} */
668     @Override
669     public final double fractionalPosition(final Lane lane, final RelativePosition relativePosition)
670         throws NetworkException, RemoteException
671     {
672         return position(lane, relativePosition).getSI() / lane.getLength().getSI();
673     }
674 
675     /**
676      * Calculate the minimum headway, possibly on subsequent lanes, in forward direction.
677      * @param lane the lane where we are looking right now
678      * @param lanePositionSI from which position on this lane do we start measuring? This is the current position of the GTU
679      *            when we measure in the lane where the original GTU is positioned, and 0.0 for each subsequent lane
680      * @param cumDistanceSI the distance we have already covered searching on previous lanes
681      * @param maxDistanceSI the maximum distance to look for in SI units; stays the same in subsequent calls
682      * @param when the current or future time for which to calculate the headway
683      * @return the headway in SI units when we have found the GTU, or a null GTU with a distance of Double.MAX_VALUE meters when
684      *         no other GTU could not be found within maxDistanceSI meters
685      * @throws RemoteException when the simulation time cannot be retrieved
686      * @throws NetworkException when there is a problem with the geometry of the network
687      */
688     private HeadwayGTU headwayRecursiveForwardSI(final Lane lane, final double lanePositionSI, final double cumDistanceSI,
689         final double maxDistanceSI, final DoubleScalar.Abs<TimeUnit> when) throws RemoteException, NetworkException
690     {
691         LaneBasedGTU<?> otherGTU =
692             lane.getGtuAfter(new DoubleScalar.Rel<LengthUnit>(lanePositionSI, LengthUnit.METER), RelativePosition.REAR, when);
693         if (otherGTU != null)
694         {
695             double distanceM = cumDistanceSI + otherGTU.position(lane, otherGTU.getRear(), when).getSI() - lanePositionSI;
696             if (distanceM > 0 && distanceM <= maxDistanceSI)
697             {
698                 return new HeadwayGTU(otherGTU, distanceM);
699             }
700             return new HeadwayGTU(null, Double.MAX_VALUE);
701         }
702 
703         // Continue search on successor lanes.
704         if (cumDistanceSI + lane.getLength().getSI() - lanePositionSI < maxDistanceSI)
705         {
706             // is there a successor link?
707             Set<Lane> nextLanes = lane.nextLanes();
708             if (nextLanes.size() > 0)
709             {
710                 HeadwayGTU foundMaxGTUDistanceSI = new HeadwayGTU(null, Double.MAX_VALUE);
711                 for (Lane nextLane : nextLanes)
712                 {
713                     // Only follow links on the Route if there is a Route
714                     if (this.getRoute() == null || this.getRoute() != null
715                         && this.getRoute().containsLink(lane.getParentLink()))
716                     {
717                         double traveledDistanceSI = cumDistanceSI + lane.getLength().getSI() - lanePositionSI;
718                         HeadwayGTU closest =
719                             headwayRecursiveForwardSI(nextLane, 0.0, traveledDistanceSI, maxDistanceSI, when);
720                         if (closest.getDistanceSI() < maxDistanceSI
721                             && closest.getDistanceSI() < foundMaxGTUDistanceSI.getDistanceSI())
722                         {
723                             foundMaxGTUDistanceSI = closest;
724                         }
725                     }
726                 }
727                 return foundMaxGTUDistanceSI;
728             }
729         }
730 
731         // No other GTU was not on one of the current lanes or their successors.
732         return new HeadwayGTU(null, Double.MAX_VALUE);
733     }
734 
735     /**
736      * Calculate the minimum headway, possibly on subsequent lanes, in backward direction (so between our back, and the other
737      * GTU's front). Note: this method returns a POSITIVE number.
738      * @param lane the lane where we are looking right now
739      * @param lanePositionSI from which position on this lane do we start measuring? This is the current position of the rear of
740      *            the GTU when we measure in the lane where the original GTU is positioned, and lane.getLength() for each
741      *            subsequent lane.
742      * @param cumDistanceSI the distance we have already covered searching on previous lanes. Note: This is a POSITIVE number.
743      * @param maxDistanceSI the maximum distance to look for in SI units; stays the same in subsequent calls. Note: this is a
744      *            POSITIVE number.
745      * @param when the current or future time for which to calculate the headway
746      * @return the headway in SI units when we have found the GTU, or a null GTU with a distance of Double.MAX_VALUE meters when
747      *         no other GTU could not be found within maxDistanceSI meters
748      * @throws RemoteException when the simulation time cannot be retrieved
749      * @throws NetworkException when there is a problem with the geometry of the network
750      */
751     private HeadwayGTU headwayRecursiveBackwardSI(final Lane lane, final double lanePositionSI, final double cumDistanceSI,
752         final double maxDistanceSI, final DoubleScalar.Abs<TimeUnit> when) throws RemoteException, NetworkException
753     {
754         LaneBasedGTU<?> otherGTU =
755             lane.getGtuBefore(new DoubleScalar.Rel<LengthUnit>(lanePositionSI, LengthUnit.METER), RelativePosition.FRONT,
756                 when);
757         if (otherGTU != null)
758         {
759             double distanceM = cumDistanceSI + lanePositionSI - otherGTU.position(lane, otherGTU.getFront(), when).getSI();
760             if (distanceM > 0 && distanceM <= maxDistanceSI)
761             {
762                 return new HeadwayGTU(otherGTU, distanceM);
763             }
764             return new HeadwayGTU(null, Double.MAX_VALUE);
765         }
766 
767         // Continue search on predecessor lanes.
768         if (cumDistanceSI + lanePositionSI < maxDistanceSI)
769         {
770             // is there a predecessor link?
771             Set<Lane> prevLanes = lane.prevLanes();
772             if (prevLanes.size() > 0)
773             {
774                 HeadwayGTU foundMaxGTUDistanceSI = new HeadwayGTU(null, Double.MAX_VALUE);
775                 for (Lane prevLane : prevLanes)
776                 {
777                     // What is behind us is INDEPENDENT of the followed route!
778                     double traveledDistanceSI = cumDistanceSI + lanePositionSI;
779                     HeadwayGTU closest =
780                         headwayRecursiveBackwardSI(prevLane, prevLane.getLength().getSI(), traveledDistanceSI,
781                             maxDistanceSI, when);
782                     if (closest.getDistanceSI() < maxDistanceSI
783                         && closest.getDistanceSI() < foundMaxGTUDistanceSI.getDistanceSI())
784                     {
785                         foundMaxGTUDistanceSI = closest;
786                     }
787                 }
788                 return foundMaxGTUDistanceSI;
789             }
790         }
791 
792         // No other GTU was not on one of the current lanes or their successors.
793         return new HeadwayGTU(null, Double.MAX_VALUE);
794     }
795 
796     /**
797      * @param maxDistanceSI the maximum distance to look for in SI units
798      * @return the nearest GTU and the net headway to this GTU in SI units when we have found the GTU, or a null GTU with a
799      *         distance of Double.MAX_VALUE meters when no other GTU could not be found within maxDistanceSI meters
800      * @throws RemoteException when the simulation time cannot be retrieved
801      * @throws NetworkException when there is a problem with the geometry of the network
802      */
803     private HeadwayGTU headwayGTUSI(final double maxDistanceSI) throws RemoteException, NetworkException
804     {
805         DoubleScalar.Abs<TimeUnit> when = getSimulator().getSimulatorTime().get();
806         HeadwayGTU foundMaxGTUDistanceSI = new HeadwayGTU(null, Double.MAX_VALUE);
807         // search for the closest GTU on all current lanes we are registered on.
808         if (maxDistanceSI > 0.0)
809         {
810             // look forward.
811             for (Lane lane : positions(getFront()).keySet())
812             {
813                 HeadwayGTU closest =
814                     headwayRecursiveForwardSI(lane, this.position(lane, this.getFront(), when).getSI(), 0.0, maxDistanceSI,
815                         when);
816                 if (closest.getDistanceSI() < maxDistanceSI
817                     && closest.getDistanceSI() < foundMaxGTUDistanceSI.getDistanceSI())
818                 {
819                     foundMaxGTUDistanceSI = closest;
820                 }
821             }
822         }
823         else
824         {
825             // look backward.
826             for (Lane lane : positions(getRear()).keySet())
827             {
828                 HeadwayGTU closest =
829                     headwayRecursiveBackwardSI(lane, this.position(lane, this.getRear(), when).getSI(), 0.0, -maxDistanceSI,
830                         when);
831                 if (closest.getDistanceSI() < -maxDistanceSI
832                     && closest.getDistanceSI() < foundMaxGTUDistanceSI.getDistanceSI())
833                 {
834                     foundMaxGTUDistanceSI = closest;
835                 }
836             }
837         }
838         return foundMaxGTUDistanceSI;
839     }
840 
841     /** {@inheritDoc} */
842     @Override
843     public final HeadwayGTU headway(final DoubleScalar.Rel<LengthUnit> maxDistance) throws RemoteException, NetworkException
844     {
845         return headwayGTUSI(maxDistance.getSI());
846     }
847 
848     /** {@inheritDoc} */
849     @Override
850     public final HeadwayGTU headway(final Lane lane, final DoubleScalar.Rel<LengthUnit> maxDistance) throws RemoteException,
851         NetworkException
852     {
853         DoubleScalar.Abs<TimeUnit> when = getSimulator().getSimulatorTime().get();
854         if (maxDistance.getSI() > 0.0)
855         {
856             return headwayRecursiveForwardSI(lane, this.projectedPosition(lane, this.getFront(), when).getSI(), 0.0,
857                 maxDistance.getSI(), when);
858         }
859         else
860         {
861             return headwayRecursiveBackwardSI(lane, this.projectedPosition(lane, this.getRear(), when).getSI(), 0.0,
862                 -maxDistance.getSI(), when);
863         }
864     }
865 
866     /**
867      * Calculate the headway to a GTU, possibly on subsequent lanes, in forward direction.
868      * @param lane the lane where we are looking right now
869      * @param lanePositionSI from which position on this lane do we start measuring? This is the current position of the (front
870      *            of the) GTU when we measure in the lane where the original GTU is positioned, and 0.0 for each subsequent lane
871      * @param otherGTU the GTU to which the headway must be returned
872      * @param cumDistanceSI the distance we have already covered searching on previous lanes
873      * @param maxDistanceSI the maximum distance to look for; stays the same in subsequent calls
874      * @param when the future time for which to calculate the headway
875      * @return the headway in SI units when we have found the GTU, or Double.MAX_VALUE when the otherGTU could not be found
876      *         within maxDistanceSI
877      * @throws RemoteException when the simulation time cannot be retrieved
878      * @throws NetworkException when there is a problem with the geometry of the network
879      */
880     private double headwayRecursiveForwardSI(final Lane lane, final double lanePositionSI, final LaneBasedGTU<?> otherGTU,
881         final double cumDistanceSI, final double maxDistanceSI, final DoubleScalar.Abs<TimeUnit> when)
882         throws RemoteException, NetworkException
883     {
884         if (lane.getGtuList().contains(otherGTU))
885         {
886             double distanceM = cumDistanceSI + otherGTU.position(lane, otherGTU.getRear(), when).getSI() - lanePositionSI;
887             if (distanceM > 0 && distanceM <= maxDistanceSI)
888             {
889                 return distanceM;
890             }
891             return Double.MAX_VALUE;
892         }
893 
894         // Continue search on successor lanes.
895         if (cumDistanceSI + lane.getLength().getSI() - lanePositionSI < maxDistanceSI)
896         {
897             // is there a successor link?
898             Set<Lane> nextLanes = lane.nextLanes();
899             if (nextLanes.size() > 0)
900             {
901                 for (Lane nextLane : nextLanes)
902                 {
903                     // Only follow links on the Route if there is a Route
904                     if (this.getRoute() == null || this.getRoute() != null
905                         && this.getRoute().containsLink(lane.getParentLink()))
906                     {
907                         double traveledDistanceSI = cumDistanceSI + lane.getLength().getSI() - lanePositionSI;
908                         double headwaySuccessor =
909                             headwayRecursiveForwardSI(nextLane, 0.0, otherGTU, traveledDistanceSI, maxDistanceSI, when);
910                         if (headwaySuccessor < maxDistanceSI)
911                         {
912                             return headwaySuccessor;
913                         }
914                     }
915                 }
916             }
917         }
918 
919         // The otherGTU was not on one of the current lanes or their successors.
920         return Double.MAX_VALUE;
921     }
922 
923     /**
924      * Calculate the headway to a GTU, possibly on subsequent lanes, in backward direction.
925      * @param lane the lane where we are looking right now
926      * @param lanePositionSI from which position on this lane do we start measuring? This is the current position of the (back
927      *            of) the GTU when we measure in the lane where the original GTU is positioned, and the length of the lane for
928      *            each subsequent lane
929      * @param otherGTU the GTU to which the headway must be returned
930      * @param cumDistanceSI the distance we have already covered searching on previous lanes, as a POSITIVE number
931      * @param maxDistanceSI the maximum distance to look for; stays the same in subsequent calls, as a POSITIVE number
932      * @param when the future time for which to calculate the headway
933      * @return the headway in SI units when we have found the GTU, or Double.MAX_VALUE when the otherGTU could not be found
934      *         within maxDistanceSI
935      * @throws RemoteException when the simulation time cannot be retrieved
936      * @throws NetworkException when there is a problem with the geometry of the network
937      */
938     private double headwayRecursiveBackwardSI(final Lane lane, final double lanePositionSI, final LaneBasedGTU<?> otherGTU,
939         final double cumDistanceSI, final double maxDistanceSI, final DoubleScalar.Abs<TimeUnit> when)
940         throws RemoteException, NetworkException
941     {
942         if (lane.getGtuList().contains(otherGTU))
943         {
944             double distanceM = cumDistanceSI + lanePositionSI - otherGTU.position(lane, otherGTU.getFront(), when).getSI();
945             if (distanceM > 0 && distanceM <= maxDistanceSI)
946             {
947                 return distanceM;
948             }
949             return Double.MAX_VALUE;
950         }
951 
952         // Continue search on successor lanes.
953         if (cumDistanceSI + lanePositionSI < maxDistanceSI)
954         {
955             // is there a successor link?
956             Set<Lane> prevLanes = lane.prevLanes();
957             if (prevLanes.size() > 0)
958             {
959                 for (Lane prevLane : prevLanes)
960                 {
961                     // Routes are NOT IMPORTANT when we look backward.
962                     double traveledDistanceSI = cumDistanceSI + lanePositionSI;
963                     double headwayPredecessor =
964                         headwayRecursiveForwardSI(prevLane, prevLane.getLength().getSI(), otherGTU, traveledDistanceSI,
965                             maxDistanceSI, when);
966                     if (headwayPredecessor < maxDistanceSI)
967                     {
968                         return headwayPredecessor;
969                     }
970                 }
971             }
972         }
973 
974         // The otherGTU was not on one of the current lanes or their successors.
975         return Double.MAX_VALUE;
976     }
977 
978     /** {@inheritDoc} */
979     @Override
980     public final Set<LaneBasedGTU<?>> parallel(final Lane lane, final DoubleScalar.Abs<TimeUnit> when)
981         throws RemoteException, NetworkException
982     {
983         Set<LaneBasedGTU<?>> gtuSet = new HashSet<LaneBasedGTU<?>>();
984         for (Lane l : this.lanes)
985         {
986             // only take lanes that we can compare based on a shared design line
987             if (l.getParentLink().equals(lane.getParentLink()))
988             {
989                 // compare based on fractional positions.
990                 double posFractionFront = Math.max(0.0, this.fractionalPosition(l, getFront(), when));
991                 double posFractionRear = Math.min(1.0, this.fractionalPosition(l, getRear(), when));
992                 for (LaneBasedGTU<?> gtu : lane.getGtuList())
993                 {
994                     if (!gtu.equals(this))
995                     {
996                         double gtuFractionFront = Math.max(0.0, gtu.fractionalPosition(lane, gtu.getFront(), when));
997                         double gtuFractionRear = Math.min(1.0, gtu.fractionalPosition(lane, gtu.getRear(), when));
998                         if (gtuFractionFront >= posFractionRear && gtuFractionRear <= posFractionFront)
999                         {
1000                             gtuSet.add(gtu);
1001                         }
1002                     }
1003                 }
1004             }
1005         }
1006         return gtuSet;
1007     }
1008 
1009     /**
1010      * Build a set of Lanes that is adjacent to the lanes that this GTU is in, in the specified lateral direction.
1011      * @param lateralDirection LateralDirectionality; the lateral direction.
1012      * @return Set&lt;Lane&gt;
1013      */
1014     private Set<Lane> adjacentLanes(final LateralDirectionality lateralDirection)
1015     {
1016         Set<Lane> result = new HashSet<Lane>();
1017         for (Lane lane : this.lanes)
1018         {
1019             result.addAll(lane.accessibleAdjacentLanes(lateralDirection, getGTUType()));
1020         }
1021         return result;
1022     }
1023 
1024     /** {@inheritDoc} */
1025     @Override
1026     public final Set<LaneBasedGTU<?>> parallel(final LateralDirectionality lateralDirection,
1027         final DoubleScalar.Abs<TimeUnit> when) throws RemoteException, NetworkException
1028     {
1029         Set<Lane> adjacentLanes = adjacentLanes(lateralDirection);
1030         /*-                       new HashSet<Lane>();
1031         for (Lane lane : this.lanes)
1032         {
1033             adjacentLanes.addAll(lane.accessibleAdjacentLanes(lateralDirection, getGTUType()));
1034         }
1035          */
1036         Set<LaneBasedGTU<?>> gtuSet = new HashSet<LaneBasedGTU<?>>();
1037         for (Lane adjacentLane : adjacentLanes)
1038         {
1039             gtuSet.addAll(parallel(adjacentLane, when));
1040         }
1041         return gtuSet;
1042     }
1043 
1044     /** {@inheritDoc} */
1045     public final DoubleScalar.Abs<TimeUnit> timeAtDistance(final DoubleScalar.Rel<LengthUnit> distance)
1046     {
1047         Double result = solveTimeForDistance(distance);
1048         if (null == result)
1049         {
1050             return null;
1051         }
1052         return new DoubleScalar.Abs<TimeUnit>(this.lastEvaluationTime.getSI() + result, TimeUnit.SECOND);
1053     }
1054 
1055     /** {@inheritDoc} */
1056     public final DoubleScalar.Rel<TimeUnit> deltaTimeForDistance(final DoubleScalar.Rel<LengthUnit> distance)
1057     {
1058         Double result = solveTimeForDistance(distance);
1059         if (null == result)
1060         {
1061             return null;
1062         }
1063         return new DoubleScalar.Rel<TimeUnit>(result, TimeUnit.SECOND);
1064     }
1065 
1066     /** {@inheritDoc} */
1067     @Override
1068     @SuppressWarnings("checkstyle:designforextension")
1069     public void destroy()
1070     {
1071         while (!this.lanes.isEmpty())
1072         {
1073             Lane lane = this.lanes.get(0);
1074             removeLane(lane);
1075             lane.removeGTU(this);
1076         }
1077     }
1078 
1079     /**
1080      * Determine show long it will take for this GTU to cover the specified distance (both time and distance since the last
1081      * evaluation time).
1082      * @param distance double; the distance
1083      * @return Double; the relative time, or null when this GTU stops before covering the specified distance
1084      */
1085     private Double solveTimeForDistance(final DoubleScalar.Rel<LengthUnit> distance)
1086     {
1087         /*
1088          * Currently (!) a (Lane based) GTU commits to a constant acceleration until the next evaluation time. When/If that is
1089          * changed, this method will have to be re-written.
1090          */
1091         double c = -distance.getSI();
1092         double a = this.acceleration.getSI() / 2;
1093         double b = this.speed.getSI();
1094         if (0 == a)
1095         {
1096             if (b > 0)
1097             {
1098                 return -c / b;
1099             }
1100             return null;
1101         }
1102         // Solve a * t^2 + b * t + c = 0
1103         double discriminant = b * b - 4 * a * c;
1104         if (discriminant < 0)
1105         {
1106             return null;
1107         }
1108         // The solutions are (-b +/- sqrt(discriminant)) / 2 / a
1109         double solution1 = (-b - Math.sqrt(discriminant)) / 2 / a;
1110         double solution2 = (-b + Math.sqrt(discriminant)) / 2 / a;
1111         if (solution1 < 0 && solution2 < 0)
1112         {
1113             return null;
1114         }
1115         if (solution1 < 0)
1116         {
1117             return solution2;
1118         }
1119         if (solution2 < 0)
1120         {
1121             return solution1;
1122         }
1123         // Both are >= 0; return the smallest one
1124         if (solution1 < solution2)
1125         {
1126             return solution1;
1127         }
1128         return solution2;
1129     }
1130 
1131     /**
1132      * Retrieve the GTUFollowingModel of this GTU.
1133      * @return GTUFollowingModel
1134      */
1135     public final GTUFollowingModel getGTUFollowingModel()
1136     {
1137         return this.gtuFollowingModel;
1138     }
1139 
1140     /** {@inheritDoc} */
1141     @Override
1142     public final DirectedPoint getLocation() throws RemoteException
1143     {
1144         synchronized (this.lanes)
1145         {
1146             try
1147             {
1148                 if (this.lanes.size() == 0)
1149                 {
1150                     if (getSimulator().isRunning())
1151                     {
1152                         // getSimulator().stop();
1153                         System.out.println("GTU " + this.getId() + " is not on any lane");
1154                     }
1155                     // This happens temporarily when a GTU is moved to another Lane
1156                     return new DirectedPoint(Double.MAX_VALUE, Double.MAX_VALUE, 0);
1157                 }
1158                 Lane lane = this.lanes.get(0);
1159                 // TODO solve problem when point is still on previous lane.
1160                 DoubleScalar.Rel<LengthUnit> longitudinalPos = position(lane, getReference());
1161                 double fraction = (longitudinalPos.getSI() + getLength().getSI() / 2.0) / lane.getLength().getSI();
1162                 LineString line = lane.getCenterLine();
1163                 LengthIndexedLine lil = new LengthIndexedLine(line);
1164                 // if (fraction > 1)
1165                 // {
1166                 // System.out.println("fraction is " + fraction);
1167                 // }
1168                 double useFraction = fraction;
1169                 if (fraction < 0)
1170                 {
1171                     useFraction = 0;
1172                 }
1173                 if (fraction > 0.99)
1174                 {
1175                     useFraction = 0.99;
1176                 }
1177                 // DO NOT MODIFY THE RESULT OF extractPoint (it may be one of the coordinates in line).
1178                 Coordinate c = new Coordinate(lil.extractPoint(useFraction * line.getLength()));
1179                 c.z = 0d;
1180                 Coordinate cb = lil.extractPoint((useFraction + 0.01) * line.getLength());
1181                 double angle = Math.atan2(cb.y - c.y, cb.x - c.x);
1182                 // FindBugs does not like this comparison - looking for a way to suppress that warning
1183                 if (fraction != useFraction)
1184                 {
1185                     c =
1186                         new Coordinate(c.x + (fraction - useFraction) * 100 * (cb.x - c.x), c.y + (fraction - useFraction)
1187                             * 100 * (cb.y - c.y), c.z);
1188                 }
1189                 if (Double.isNaN(c.x))
1190                 {
1191                     System.out.println("Bad");
1192                 }
1193                 return new DirectedPoint(c.x, c.y, c.z + 0.01 /* raise it slightly above the lane surface */, 0.0, 0.0,
1194                     angle);
1195             }
1196             catch (Exception ne)
1197             {
1198                 System.err.println(this.getId());
1199                 ne.printStackTrace();
1200                 return new DirectedPoint(0, 0, 0);
1201             }
1202         }
1203     }
1204 
1205     /** {@inheritDoc} */
1206     @Override
1207     public final Bounds getBounds() throws RemoteException
1208     {
1209         double dx = 0.5 * getLength().doubleValue();
1210         double dy = 0.5 * getWidth().doubleValue();
1211         return new BoundingBox(new Point3d(-dx, -dy, 0.0), new Point3d(dx, dy, 0.0));
1212     }
1213 
1214     /**
1215      * Description of Car at specified time.
1216      * @param lane the position on this lane will be returned.
1217      * @param when DoubleScalarAbs&lt;TimeUnit&gt;; the time
1218      * @return String; description of this Car at the specified time
1219      */
1220     public final String toString(final Lane lane, final DoubleScalar.Abs<TimeUnit> when)
1221     {
1222         double pos = Double.NaN;
1223         try
1224         {
1225             pos = this.position(lane, getFront(), when).getSI();
1226         }
1227         catch (NetworkException exception)
1228         {
1229             // exception.printStackTrace();
1230         }
1231         // A space in the format after the % becomes a space for positive numbers or a minus for negative numbers
1232         return String.format("Car %5d lastEval %6.1fs, nextEval %6.1fs, % 9.3fm, v % 6.3fm/s, a % 6.3fm/s^2", getId(),
1233             this.lastEvaluationTime.getSI(), getNextEvaluationTime().getSI(), pos, this.getLongitudinalVelocity(when)
1234                 .getSI(), this.getAcceleration(when).getSI());
1235     }
1236 
1237 }