View Javadoc
1   package org.opentrafficsim.road.gtu.lane.perception;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.HashMap;
6   import java.util.HashSet;
7   import java.util.LinkedHashSet;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  
12  import org.djunits.unit.LengthUnit;
13  import org.djunits.unit.SpeedUnit;
14  import org.djunits.value.vdouble.scalar.Length;
15  import org.djunits.value.vdouble.scalar.Speed;
16  import org.djunits.value.vdouble.scalar.Time;
17  import org.opentrafficsim.core.Throw;
18  import org.opentrafficsim.core.gtu.GTUDirectionality;
19  import org.opentrafficsim.core.gtu.GTUException;
20  import org.opentrafficsim.core.gtu.RelativePosition;
21  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterException;
22  import org.opentrafficsim.core.gtu.behavioralcharacteristics.ParameterTypes;
23  import org.opentrafficsim.core.gtu.perception.TimeStampedObject;
24  import org.opentrafficsim.core.network.LateralDirectionality;
25  import org.opentrafficsim.core.network.NetworkException;
26  import org.opentrafficsim.core.perception.PerceivedObject;
27  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
28  import org.opentrafficsim.road.gtu.lane.tactical.AbstractLaneBasedTacticalPlanner;
29  import org.opentrafficsim.road.gtu.lane.tactical.LanePathInfo;
30  import org.opentrafficsim.road.network.lane.Lane;
31  import org.opentrafficsim.road.network.lane.LaneDirection;
32  
33  /**
34   * The perception module of a GTU based on lanes. It is responsible for perceiving (sensing) the environment of the GTU, which
35   * includes the locations of other GTUs. Perception is done at a certain time, and the perceived information might have a
36   * limited validity. In that sense, Perception is stateful. Information can be requested as often as needed, but will only be
37   * recalculated when asked explicitly. This abstract class provides the building blocks for lane-based perception. <br>
38   * Perception for lane-based GTUs involves information about GTUs in front of the owner GTU on the same lane (the 'leader' GTU),
39   * parallel vehicles (important if we want to change lanes), distance to other vehicles on parallel lanes, as well in front as
40   * to the back (important if we want to change lanes), and information about obstacles, traffic lights, speed signs, and ending
41   * lanes.
42   * <p>
43   * Copyright (c) 2013-2015 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
44   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
45   * </p>
46   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
47   * initial version Nov 15, 2015 <br>
48   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
49   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
50   */
51  public abstract class AbstractLanePerception implements LanePerception
52  {
53      /** */
54      private static final long serialVersionUID = 20151128L;
55  
56      /** The lane based GTU for which this perception module stores information. */
57      private LaneBasedGTU gtu;
58  
59      /** The forward headway and (leader) object. */
60      private TimeStampedObject<Headway> forwardHeadway;
61  
62      /** The backward headway and (follower) object. */
63      private TimeStampedObject<Headway> backwardHeadway;
64  
65      /** The minimum speed limit of all lanes where the GTU is registered. */
66      private TimeStampedObject<Speed> speedLimit;
67  
68      /** The adjacent lanes that are accessible for the GTU at the left side. */
69      private TimeStampedObject<Map<Lane, Set<Lane>>> accessibleAdjacentLanesLeft;
70  
71      /** The adjacent lanes that are accessible for the GTU at the right side. */
72      private TimeStampedObject<Map<Lane, Set<Lane>>> accessibleAdjacentLanesRight;
73  
74      /** The objects parallel to us on the left side. */
75      private TimeStampedObject<Collection<Headway>> parallelHeadwaysLeft;
76  
77      /** The objects parallel to us on the right side. */
78      private TimeStampedObject<Collection<Headway>> parallelHeadwaysRight;
79  
80      /** The GTUs on the left side. */
81      private TimeStampedObject<Collection<Headway>> neighboringHeadwaysLeft;
82  
83      /** The GTUs on the right side. */
84      private TimeStampedObject<Collection<Headway>> neighboringHeadwaysRight;
85  
86      /** The lanes and path we expect to take if we do not change lanes. */
87      private TimeStampedObject<LanePathInfo> lanePathInfo;
88  
89      /** The structure of the lanes in front of the GTU. */
90      // TODO private LaneStructure laneStructure;
91  
92      /**
93       * Create a new LanePerception module. Because the constructor is often called inside the constructor of a GTU, this
94       * constructor does not ask for the pointer to the GTU, as it is often impossible to provide at the time of construction.
95       * Use the setter of the GTU instead.
96       */
97      public AbstractLanePerception()
98      {
99          super();
100     }
101 
102     /** {@inheritDoc} */
103     @Override
104     public final void setGTU(final LaneBasedGTU laneBasedGtu)
105     {
106         this.gtu = laneBasedGtu;
107     }
108 
109     /**
110      * Check whether the GTU has been initialized, and returns the current time.
111      * @return the current time according to the simulator.
112      * @throws GTUException when the GTU was not initialized yet.
113      */
114     private Time getTimestamp() throws GTUException
115     {
116         if (this.gtu == null)
117         {
118             throw new GTUException("gtu value has not been initialized for LanePerception when perceiving.");
119         }
120         return this.gtu.getSimulator().getSimulatorTime().getTime();
121     }
122 
123     /**
124      * @throws GTUException when the GTU was not initialized yet.
125      * @throws NetworkException when the speed limit for a GTU type cannot be retrieved from the network.
126      * @throws ParameterException in case of not being able to retrieve parameter ParameterTypes.LOOKAHEAD
127      */
128     public final void updateLanePathInfo() throws GTUException, NetworkException, ParameterException
129     {
130         Time timestamp = getTimestamp();
131         this.lanePathInfo =
132                 new TimeStampedObject<LanePathInfo>(AbstractLaneBasedTacticalPlanner.buildLanePathInfo(this.gtu, this.gtu
133                         .getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD)), timestamp);
134     }
135 
136     /** {@inheritDoc} */
137     @Override
138     public final void updateSpeedLimit() throws GTUException, NetworkException
139     {
140         Time timestamp = getTimestamp();
141         // assess the speed limit where we are right now
142         this.speedLimit = new TimeStampedObject<>(new Speed(Double.MAX_VALUE, SpeedUnit.SI), timestamp);
143         for (Lane lane : this.gtu.getLanes().keySet())
144         {
145             if (lane.getSpeedLimit(this.gtu.getGTUType()).lt(this.speedLimit.getObject()))
146             {
147                 this.speedLimit = new TimeStampedObject<>(lane.getSpeedLimit(this.gtu.getGTUType()), timestamp);
148             }
149         }
150     }
151 
152     /** {@inheritDoc} */
153     @Override
154     public final void updateForwardHeadway() throws GTUException, NetworkException, ParameterException
155     {
156         Time timestamp = getTimestamp();
157         if (this.lanePathInfo == null || this.lanePathInfo.getTimestamp().ne(timestamp))
158         {
159             updateLanePathInfo();
160         }
161         Length maximumForwardHeadway = this.gtu.getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD);
162         this.forwardHeadway = new TimeStampedObject<>(forwardHeadway(maximumForwardHeadway), timestamp);
163     }
164 
165     /** {@inheritDoc} */
166     @Override
167     public final void updateBackwardHeadway() throws GTUException, NetworkException, ParameterException
168     {
169         Time timestamp = getTimestamp();
170         Length maximumReverseHeadway = this.gtu.getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKBACKOLD);
171         this.backwardHeadway = new TimeStampedObject<>(backwardHeadway(maximumReverseHeadway), timestamp);
172     }
173 
174     /** {@inheritDoc} */
175     @Override
176     public final void updateAccessibleAdjacentLanesLeft() throws GTUException
177     {
178         Time timestamp = getTimestamp();
179         Map<Lane, Set<Lane>> accessibleAdjacentLanesMap = new HashMap<>();
180         for (Lane lane : this.gtu.getLanes().keySet())
181         {
182             Set<Lane> adjacentLanes = new HashSet<Lane>(1);
183             adjacentLanes.addAll(lane.accessibleAdjacentLanes(LateralDirectionality.LEFT, this.gtu.getGTUType()));
184             accessibleAdjacentLanesMap.put(lane, adjacentLanes);
185         }
186         this.accessibleAdjacentLanesLeft = new TimeStampedObject<>(accessibleAdjacentLanesMap, timestamp);
187     }
188 
189     /** {@inheritDoc} */
190     @Override
191     public final void updateAccessibleAdjacentLanesRight() throws GTUException
192     {
193         Time timestamp = getTimestamp();
194         Map<Lane, Set<Lane>> accessibleAdjacentLanesMap = new HashMap<>();
195         for (Lane lane : this.gtu.getLanes().keySet())
196         {
197             Set<Lane> adjacentLanes = new HashSet<Lane>(1);
198             adjacentLanes.addAll(lane.accessibleAdjacentLanes(LateralDirectionality.RIGHT, this.gtu.getGTUType()));
199             accessibleAdjacentLanesMap.put(lane, adjacentLanes);
200         }
201         this.accessibleAdjacentLanesRight = new TimeStampedObject<>(accessibleAdjacentLanesMap, timestamp);
202     }
203 
204     /** {@inheritDoc} */
205     @Override
206     public final void updateParallelHeadwaysLeft() throws GTUException
207     {
208         Time timestamp = getTimestamp();
209         if (this.accessibleAdjacentLanesLeft == null || !timestamp.equals(this.accessibleAdjacentLanesLeft.getTimestamp()))
210         {
211             updateAccessibleAdjacentLanesLeft();
212         }
213         Set<Headway> parallelHeadwaySet = new HashSet<>();
214         for (Lane lane : this.accessibleAdjacentLanesLeft.getObject().keySet())
215         {
216             for (Lane adjacentLane : this.accessibleAdjacentLanesLeft.getObject().get(lane))
217             {
218                 parallelHeadwaySet.addAll(parallel(adjacentLane, timestamp));
219             }
220         }
221         this.parallelHeadwaysLeft = new TimeStampedObject<>(parallelHeadwaySet, timestamp);
222     }
223 
224     /** {@inheritDoc} */
225     @Override
226     public final void updateParallelHeadwaysRight() throws GTUException
227     {
228         Time timestamp = getTimestamp();
229         if (this.accessibleAdjacentLanesRight == null || !timestamp.equals(this.accessibleAdjacentLanesRight.getTimestamp()))
230         {
231             updateAccessibleAdjacentLanesRight();
232         }
233         Set<Headway> parallelHeadwaySet = new HashSet<>();
234         for (Lane lane : this.accessibleAdjacentLanesRight.getObject().keySet())
235         {
236             for (Lane adjacentLane : this.accessibleAdjacentLanesRight.getObject().get(lane))
237             {
238                 parallelHeadwaySet.addAll(parallel(adjacentLane, timestamp));
239             }
240         }
241         this.parallelHeadwaysRight = new TimeStampedObject<>(parallelHeadwaySet, timestamp);
242     }
243 
244     /** {@inheritDoc} */
245     @Override
246     public final void updateLaneTrafficLeft() throws GTUException, NetworkException, ParameterException
247     {
248         Time timestamp = getTimestamp();
249         if (this.accessibleAdjacentLanesLeft == null || !timestamp.equals(this.accessibleAdjacentLanesLeft.getTimestamp()))
250         {
251             updateAccessibleAdjacentLanesLeft();
252         }
253 
254         if (this.parallelHeadwaysLeft == null || !timestamp.equals(this.parallelHeadwaysLeft.getTimestamp()))
255         {
256             updateParallelHeadwaysLeft();
257         }
258 
259         // for the accessible lanes, see who is ahead of us and in front of us
260         Length maximumForwardHeadway = this.gtu.getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD);
261         Length maximumReverseHeadway = this.gtu.getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKBACKOLD);
262         this.neighboringHeadwaysLeft =
263                 new TimeStampedObject<>(collectNeighborLaneTraffic(LateralDirectionality.LEFT, timestamp,
264                         maximumForwardHeadway, maximumReverseHeadway), timestamp);
265     }
266 
267     /** {@inheritDoc} */
268     @Override
269     public final void updateLaneTrafficRight() throws GTUException, NetworkException, ParameterException
270     {
271         Time timestamp = getTimestamp();
272         if (this.accessibleAdjacentLanesRight == null || !timestamp.equals(this.accessibleAdjacentLanesRight.getTimestamp()))
273         {
274             updateAccessibleAdjacentLanesRight();
275         }
276 
277         if (this.parallelHeadwaysRight == null || !timestamp.equals(this.parallelHeadwaysRight.getTimestamp()))
278         {
279             updateParallelHeadwaysRight();
280         }
281 
282         // for the accessible lanes, see who is ahead of us and in front of us
283         Length maximumForwardHeadway = this.gtu.getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKAHEAD);
284         Length maximumReverseHeadway = this.gtu.getBehavioralCharacteristics().getParameter(ParameterTypes.LOOKBACKOLD);
285         this.neighboringHeadwaysRight =
286                 new TimeStampedObject<>(collectNeighborLaneTraffic(LateralDirectionality.RIGHT, timestamp,
287                         maximumForwardHeadway, maximumReverseHeadway), timestamp);
288     }
289 
290     /** {@inheritDoc} */
291     @Override
292     public final Map<Lane, Set<Lane>> accessibleAdjacentLaneMap(final LateralDirectionality lateralDirection)
293     {
294         return lateralDirection.equals(LateralDirectionality.LEFT) ? this.accessibleAdjacentLanesLeft.getObject()
295                 : this.accessibleAdjacentLanesRight.getObject();
296     }
297 
298     /** {@inheritDoc} */
299     @Override
300     public final Collection<Headway> getNeighboringHeadways(final LateralDirectionality lateralDirection)
301     {
302         return lateralDirection.equals(LateralDirectionality.LEFT) ? this.parallelHeadwaysLeft.getObject()
303                 : this.parallelHeadwaysRight.getObject();
304     }
305 
306     /** {@inheritDoc} */
307     @Override
308     public final Collection<Headway> getParallelHeadways(final LateralDirectionality lateralDirection)
309     {
310         return lateralDirection.equals(LateralDirectionality.LEFT) ? this.neighboringHeadwaysLeft.getObject()
311                 : this.neighboringHeadwaysRight.getObject();
312     }
313 
314     /**************************************************************************************************************************/
315     /**************************************************** HEADWAY ALGORITHMS **************************************************/
316     /**************************************************************************************************************************/
317 
318     /**
319      * Determine which GTU is in front of this GTU. This method looks in all lanes where this GTU is registered, and not further
320      * than the value of the given maxDistance. The minimum headway is returned of all Lanes where the GTU is registered. When
321      * no GTU is found within the given maxDistance, a HeadwayGTU with <b>null</b> as the gtuId and maxDistance as the distance
322      * is returned. The search will extend into successive lanes if the maxDistance is larger than the remaining length on the
323      * lane. When Lanes (or underlying CrossSectionLinks) diverge, a route planner may be used to determine which kinks and
324      * lanes to take into account and which ones not. When the Lanes (or underlying CrossSectionLinks) converge, "parallel"
325      * traffic is not taken into account in the headway calculation. Instead, gap acceptance algorithms or their equivalent
326      * should guide the merging behavior.<br>
327      * <b>Note:</b> Headway is the net headway and calculated on a front-to-back basis.
328      * @param maxDistance the maximum distance to look for the nearest GTU; positive values search forwards; negative values
329      *            search backwards
330      * @return HeadwayGTU; the headway and the GTU information
331      * @throws GTUException when there is an error with the next lanes in the network.
332      * @throws NetworkException when there is a problem with the route planner
333      */
334     private Headway forwardHeadway(final Length maxDistance) throws GTUException, NetworkException
335     {
336         LanePathInfo lpi = getLanePathInfo();
337         return forwardHeadway(lpi, maxDistance);
338     }
339 
340     /**
341      * Determine which GTU is in front of this GTU. This method uses a given lanePathInfo to look forward, but not further than
342      * the value of the given maxDistance. The minimum headway is returned of all Lanes where the GTU is registered. When no GTU
343      * is found within the given maxDistance, a HeadwayGTU with <b>null</b> as the gtuId and maxDistance as the distance is
344      * returned. The search will extend into successive lanes if the maxDistance is larger than the remaining length on the
345      * lane. When Lanes (or underlying CrossSectionLinks) diverge, a route planner may be used to determine which kinks and
346      * lanes to take into account and which ones not. When the Lanes (or underlying CrossSectionLinks) converge, "parallel"
347      * traffic is not taken into account in the headway calculation. Instead, gap acceptance algorithms or their equivalent
348      * should guide the merging behavior.<br>
349      * <b>Note:</b> Headway is the net headway and calculated on a front-to-back basis.
350      * @param lpi the lanePathInfo object that informs the headway algorithm in which lanes to look, and from which position on
351      *            the first lane.
352      * @param maxDistance the maximum distance to look for the nearest GTU; positive values search forwards; negative values
353      *            search backwards
354      * @return HeadwayGTU; the headway and the GTU information
355      * @throws GTUException when there is an error with the next lanes in the network.
356      * @throws NetworkException when there is a problem with the route planner
357      */
358     private Headway forwardHeadway(final LanePathInfo lpi, final Length maxDistance) throws GTUException, NetworkException
359     {
360         Throw.when(maxDistance.le(Length.ZERO), GTUException.class, "forwardHeadway: maxDistance should be positive");
361 
362         int ldIndex = 0;
363         LaneDirection ld = lpi.getReferenceLaneDirection();
364         double gtuPosFrontSI = lpi.getReferencePosition().si;
365         if (lpi.getReferenceLaneDirection().getDirection().isPlus())
366         {
367             gtuPosFrontSI += this.gtu.getFront().getDx().si;
368         }
369         else
370         {
371             gtuPosFrontSI -= this.gtu.getFront().getDx().si;
372         }
373 
374         // TODO end of lanepath
375 
376         while ((gtuPosFrontSI > ld.getLane().getLength().si || gtuPosFrontSI < 0.0)
377                 && ldIndex < lpi.getLaneDirectionList().size() - 1)
378         {
379             ldIndex++;
380             if (ld.getDirection().isPlus()) // e.g. 1005 on length of lane = 1000
381             {
382                 if (lpi.getLaneDirectionList().get(ldIndex).getDirection().isPlus())
383                 {
384                     gtuPosFrontSI -= ld.getLane().getLength().si;
385                 }
386                 else
387                 {
388                     gtuPosFrontSI = lpi.getLaneDirectionList().get(ldIndex).getLane().getLength().si - gtuPosFrontSI;
389                 }
390                 ld = lpi.getLaneDirectionList().get(ldIndex);
391             }
392             else
393             // e.g. -5 on lane of whatever length
394             {
395                 if (lpi.getLaneDirectionList().get(ldIndex).getDirection().isPlus())
396                 {
397                     gtuPosFrontSI += ld.getLane().getLength().si;
398                 }
399                 else
400                 {
401                     gtuPosFrontSI += lpi.getLaneDirectionList().get(ldIndex).getLane().getLength().si;
402                 }
403                 ld = lpi.getLaneDirectionList().get(ldIndex);
404             }
405         }
406 
407         double maxDistanceSI = maxDistance.si;
408         Time time = this.gtu.getSimulator().getSimulatorTime().getTime();
409 
410         // look forward based on the provided lanePathInfo.
411         Headway closest = headwayLane(ld, gtuPosFrontSI, 0.0, time);
412         if (closest != null)
413         {
414             if (closest.getDistance().si > maxDistanceSI)
415             {
416                 return new HeadwayDistance(maxDistanceSI);
417             }
418             return closest;
419         }
420         double cumDistSI = ld.getDirection().isPlus() ? ld.getLane().getLength().si - gtuPosFrontSI : gtuPosFrontSI;
421         for (int i = ldIndex + 1; i < lpi.getLaneDirectionList().size(); i++)
422         {
423             ld = lpi.getLaneDirectionList().get(i);
424             closest = headwayLane(ld, ld.getDirection().isPlus() ? 0.0 : ld.getLane().getLength().si, cumDistSI, time);
425             if (closest != null)
426             {
427                 if (closest.getDistance().si > maxDistanceSI)
428                 {
429                     return new HeadwayDistance(maxDistanceSI);
430                 }
431                 return closest;
432             }
433             cumDistSI += ld.getLane().getLength().si;
434         }
435         return new HeadwayDistance(maxDistanceSI);
436     }
437 
438     /**
439      * Determine the positive headway on a lane, or null if no GTU can be found on this lane.
440      * @param laneDirection the lane and direction to look
441      * @param startPosSI the start position to look from in meters
442      * @param cumDistSI the cumulative distance that has already been observed on other lanes
443      * @param now the current time to determine the GTU positions on the lane
444      * @return the HeadwayGTU, containing information on a GTU that is ahead of the given start position, or null if no GTU can
445      *         be found on this lane
446      * @throws GTUException when the GTUs ahead on the lane cannot be determined
447      */
448     private Headway headwayLane(final LaneDirection laneDirection, final double startPosSI, final double cumDistSI,
449             final Time now) throws GTUException
450     {
451         Lane lane = laneDirection.getLane();
452         LaneBasedGTU laneBasedGTU =
453                 lane.getGtuAhead(new Length(startPosSI, LengthUnit.SI), laneDirection.getDirection(),
454                         RelativePosition.REAR, now);
455         if (laneBasedGTU == null)
456         {
457             return null;
458         }
459         double distanceSI = Math.abs(laneBasedGTU.position(lane, laneBasedGTU.getRear()).si - startPosSI);
460         return new HeadwayGTU(laneBasedGTU.getId(), laneBasedGTU.getGTUType(), new Length(cumDistSI + distanceSI,
461                 LengthUnit.SI), laneBasedGTU.getSpeed(), laneBasedGTU.getAcceleration());
462     }
463 
464     /**
465      * Determine which GTU is behind this GTU. This method looks in all lanes where this GTU is registered, and not further back
466      * than the absolute value of the given maxDistance. The minimum net headway is returned of all Lanes where the GTU is
467      * registered. When no GTU is found within the given maxDistance, <b>null</b> is returned. The search will extend into
468      * successive lanes if the maxDistance is larger than the remaining length on the lane. When Lanes (or underlying
469      * CrossSectionLinks) diverge, the headway algorithms have to look at multiple Lanes and return the minimum headway in each
470      * of the Lanes. When the Lanes (or underlying CrossSectionLinks) converge, "parallel" traffic is not taken into account in
471      * the headway calculation. Instead, gap acceptance algorithms or their equivalent should guide the merging behavior.<br>
472      * <b>Note:</b> Headway is the net headway and calculated on a back-to-front basis.
473      * @param maxDistance the maximum distance to look for the nearest GTU; it should have a negative value to search backwards
474      * @return HeadwayGTU; the headway and the GTU information
475      * @throws GTUException when there is an error with the next lanes in the network.
476      * @throws NetworkException when there is a problem with the route planner
477      */
478     private Headway backwardHeadway(final Length maxDistance) throws GTUException, NetworkException
479     {
480         Throw.when(maxDistance.ge(Length.ZERO), GTUException.class, "backwardHeadway: maxDistance should be negative");
481         Time time = this.gtu.getSimulator().getSimulatorTime().getTime();
482         double maxDistanceSI = maxDistance.si;
483         Headway foundHeadway = new HeadwayDistance(-maxDistanceSI);
484         for (Lane lane : this.gtu.positions(this.gtu.getRear()).keySet())
485         {
486             Headway closest =
487                     headwayRecursiveBackwardSI(lane, this.gtu.getLanes().get(lane),
488                             this.gtu.position(lane, this.gtu.getRear(), time).getSI(), 0.0, -maxDistanceSI, time);
489             if (closest.getDistance().si < -maxDistanceSI && closest.getDistance().si < -foundHeadway.getDistance().si)
490             {
491                 foundHeadway = closest;
492             }
493         }
494         if (foundHeadway instanceof HeadwayGTU)
495         {
496             return new HeadwayGTU(foundHeadway.getId(), ((HeadwayGTU) foundHeadway).getGtuType(), foundHeadway.getDistance()
497                     .multiplyBy(-1.0), foundHeadway.getSpeed(), null);
498         }
499         if (foundHeadway instanceof HeadwayDistance)
500         {
501             return new HeadwayDistance(foundHeadway.getDistance().multiplyBy(-1.0));
502         }
503         // TODO allow observation of other objects as well.
504         throw new GTUException("backwardHeadway not implemented yet for other object types than GTU");
505     }
506 
507     /**
508      * Calculate the minimum headway, possibly on subsequent lanes, in backward direction (so between our back, and the other
509      * GTU's front). Note: this method returns a POSITIVE number.
510      * @param lane the lane where we are looking right now
511      * @param direction the direction we are driving on that lane
512      * @param lanePositionSI from which position on this lane do we start measuring? This is the current position of the rear of
513      *            the GTU when we measure in the lane where the original GTU is positioned, and lane.getLength() for each
514      *            subsequent lane.
515      * @param cumDistanceSI the distance we have already covered searching on previous lanes. Note: This is a POSITIVE number.
516      * @param maxDistanceSI the maximum distance to look for in SI units; stays the same in subsequent calls. Note: this is a
517      *            POSITIVE number.
518      * @param when the current or future time for which to calculate the headway
519      * @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
520      *         no other GTU could not be found within maxDistanceSI meters
521      * @throws GTUException when there is a problem with the geometry of the network
522      */
523     private Headway headwayRecursiveBackwardSI(final Lane lane, final GTUDirectionality direction, final double lanePositionSI,
524             final double cumDistanceSI, final double maxDistanceSI, final Time when) throws GTUException
525     {
526         LaneBasedGTU otherGTU =
527                 lane.getGtuBehind(new Length(lanePositionSI, LengthUnit.SI), direction, RelativePosition.FRONT, when);
528         if (otherGTU != null)
529         {
530             double distanceM = cumDistanceSI + lanePositionSI - otherGTU.position(lane, otherGTU.getFront(), when).getSI();
531             if (distanceM > 0 && distanceM <= maxDistanceSI)
532             {
533                 return new HeadwayGTU(otherGTU.getId(), otherGTU.getGTUType(), new Length(distanceM, LengthUnit.SI),
534                         otherGTU.getSpeed(), null);
535             }
536             return new HeadwayDistance(Double.MAX_VALUE);
537         }
538 
539         // Continue search on predecessor lanes.
540         if (cumDistanceSI + lanePositionSI < maxDistanceSI)
541         {
542             // is there a predecessor link?
543             if (lane.prevLanes(this.gtu.getGTUType()).size() > 0)
544             {
545                 Headway foundMaxGTUDistanceSI = new HeadwayDistance(Double.MAX_VALUE);
546                 for (Lane prevLane : lane.prevLanes(this.gtu.getGTUType()).keySet())
547                 {
548                     // What is behind us is INDEPENDENT of the followed route!
549                     double traveledDistanceSI = cumDistanceSI + lanePositionSI;
550                     // WRONG - adapt method to forward perception method!
551                     Headway closest =
552                             headwayRecursiveBackwardSI(prevLane, direction, prevLane.getLength().getSI(), traveledDistanceSI,
553                                     maxDistanceSI, when);
554                     if (closest.getDistance().si < maxDistanceSI
555                             && closest.getDistance().si < foundMaxGTUDistanceSI.getDistance().si)
556                     {
557                         foundMaxGTUDistanceSI = closest;
558                     }
559                 }
560                 return foundMaxGTUDistanceSI;
561             }
562         }
563 
564         // No other GTU was not on one of the current lanes or their successors.
565         return new HeadwayDistance(Double.MAX_VALUE);
566     }
567 
568     /**************************************************************************************************************************/
569     /************************************************ ADJACENT LANE TRAFFIC ***************************************************/
570     /**************************************************************************************************************************/
571 
572     /**
573      * Determine which GTUs are parallel with us on another lane, based on fractional positions. <br>
574      * Note: When the GTU that calls the method is also registered on the given lane, it is excluded from the return set.
575      * @param lane the lane to look for parallel (partial or full overlapping) GTUs.
576      * @param when the future time for which to calculate the headway
577      * @return the set of GTUs parallel to us on the other lane (partial overlap counts as parallel), based on fractional
578      *         positions, or an empty set when no GTUs were found.
579      * @throws GTUException when the vehicle's route is inconclusive, when vehicles are not registered correctly on their lanes,
580      *             or when the given lane is not parallel to one of the lanes where we are registered.
581      */
582     private Collection<Headway> parallel(final Lane lane, final Time when) throws GTUException
583     {
584         Collection<Headway> headwayCollection = new LinkedHashSet<Headway>();
585         for (Lane l : this.gtu.getLanes().keySet())
586         {
587             // only take lanes that we can compare based on a shared design line
588             if (l.getParentLink().equals(lane.getParentLink()))
589             {
590                 // compare based on fractional positions.
591                 double posFractionRef = this.gtu.fractionalPosition(l, this.gtu.getReference(), when);
592                 double posFractionFront = Math.max(0.0, posFractionRef + this.gtu.getFront().getDx().si / lane.getLength().si);
593                 double posFractionRear = Math.min(1.0, posFractionRef + this.gtu.getRear().getDx().si / lane.getLength().si);
594                 // double posFractionFront = Math.max(0.0, this.gtu.fractionalPosition(l, this.gtu.getFront(), when));
595                 // double posFractionRear = Math.min(1.0, this.gtu.fractionalPosition(l, this.gtu.getRear(), when));
596                 double posMin = Math.min(posFractionFront, posFractionRear);
597                 double posMax = Math.max(posFractionFront, posFractionRear);
598                 for (LaneBasedGTU otherGTU : lane.getGtuList())
599                 {
600                     if (!otherGTU.equals(this)) // TODO
601                     {
602                         /*- cater for: *-----*         *-----*       *-----*       *----------*
603                          *                *-----*    *----*      *------------*       *-----*
604                          * where the GTUs can each drive in two directions (!)
605                          */
606                         double gtuFractionRef = otherGTU.fractionalPosition(lane, otherGTU.getReference(), when);
607                         double gtuFractionFront =
608                                 Math.max(0.0, gtuFractionRef + otherGTU.getFront().getDx().si / lane.getLength().si);
609                         double gtuFractionRear =
610                                 Math.min(1.0, gtuFractionRef + otherGTU.getRear().getDx().si / lane.getLength().si);
611                         double gtuMin = Math.min(gtuFractionFront, gtuFractionRear);
612                         double gtuMax = Math.max(gtuFractionFront, gtuFractionRear);
613                         // TODO calculate real overlaps
614                         Length overlapFront = new Length(1.0, LengthUnit.SI);
615                         Length overlap = new Length(1.0, LengthUnit.SI);
616                         Length overlapRear = new Length(1.0, LengthUnit.SI);
617                         if ((gtuMin >= posMin && gtuMin <= posMax) || (gtuMax >= posMin && gtuMax <= posMax)
618                                 || (posMin >= gtuMin && posMin <= gtuMax) || (posMax >= gtuMin && posMax <= gtuMax))
619                         {
620                             headwayCollection.add(new HeadwayGTU(otherGTU.getId(), otherGTU.getGTUType(), overlapFront,
621                                     overlap, overlapRear, otherGTU.getSpeed(), otherGTU.getAcceleration()));
622                         }
623                     }
624                 }
625             }
626         }
627         return headwayCollection;
628     }
629 
630     /**
631      * Determine which GTUs are parallel with us in a certain lateral direction, based on fractional positions. <br>
632      * Note 1: This method will look to the adjacent lanes of all lanes where the vehicle has been registered.<br>
633      * Note 2: When the GTU that calls the method is also registered on the given lane, it is excluded from the return set.
634      * @param lateralDirection the direction of the adjacent lane(s) to look for parallel (partial or full overlapping) GTUs.
635      * @param when the future time for which to calculate the headway
636      * @return the set of GTUs parallel to us on other lane(s) in the given direction (partial overlap counts as parallel),
637      *         based on fractional positions, or an empty set when no GTUs were found.
638      * @throws GTUException when the vehicle's route is inconclusive, when vehicles are not registered correctly on their lanes,
639      *             or when there are no lanes parallel to one of the lanes where we are registered in the given direction.
640      */
641     private Collection<Headway> parallel(final LateralDirectionality lateralDirection, final Time when) throws GTUException
642     {
643         Collection<Headway> gtuSet = new LinkedHashSet<Headway>();
644         for (Lane lane : this.gtu.getLanes().keySet())
645         {
646             for (Lane adjacentLane : accessibleAdjacentLaneMap(lateralDirection).get(lane))
647             {
648                 gtuSet.addAll(parallel(adjacentLane, when));
649             }
650         }
651         return gtuSet;
652     }
653 
654     /**
655      * Determine whether there is a lane to the left or to the right of this lane, which is accessible from this lane, or null
656      * if no lane could be found. The method takes the LongitidinalDirectionality of the lane into account. In other words, if
657      * we drive FORWARD and look for a lane on the LEFT, and there is a lane but the Directionality of that lane is not FORWARD
658      * or BOTH, null will be returned.<br>
659      * A lane is called adjacent to another lane if the lateral edges are not more than a delta distance apart. This means that
660      * a lane that <i>overlaps</i> with another lane is <b>not</b> returned as an adjacent lane. <br>
661      * The algorithm also looks for RoadMarkerAcross elements between the lanes to determine the lateral permeability for a GTU.
662      * A RoadMarkerAcross is seen as being between two lanes if its center line is not more than delta distance from the
663      * relevant lateral edges of the two adjacent lanes. <br>
664      * When there are multiple lanes that are adjacent, which could e.g. be the case if an overlapping tram lane and a car lane
665      * are adjacent to the current lane, the widest lane that best matches the GTU accessibility of the provided GTUType is
666      * returned. <br>
667      * <b>Note:</b> LEFT is seen as a negative lateral direction, RIGHT as a positive lateral direction. <br>
668      * FIXME In other places in OTS LEFT is positive (and RIGHT is negative). This should be made more consistent.
669      * @param currentLane the lane to look for the best accessible adjacent lane
670      * @param lateralDirection the direction (LEFT, RIGHT) to look at
671      * @param longitudinalPosition Length; the position of the GTU along <cite>currentLane</cite>
672      * @return the lane if it is accessible, or null if there is no lane, it is not accessible, or the driving direction does
673      *         not match.
674      */
675     public final Lane bestAccessibleAdjacentLane(final Lane currentLane, final LateralDirectionality lateralDirection,
676             final Length longitudinalPosition)
677     {
678         Set<Lane> candidates = accessibleAdjacentLaneMap(lateralDirection).get(currentLane);
679         if (candidates.isEmpty())
680         {
681             return null; // There is no adjacent Lane that this GTU type can cross into
682         }
683         if (candidates.size() == 1)
684         {
685             return candidates.iterator().next(); // There is exactly one adjacent Lane that this GTU type can cross into
686         }
687         // There are several candidates; find the one that is widest at the beginning.
688         Lane bestLane = null;
689         double widestSeen = Double.NEGATIVE_INFINITY;
690         for (Lane lane : candidates)
691         {
692             if (lane.getWidth(longitudinalPosition).getSI() > widestSeen)
693             {
694                 widestSeen = lane.getWidth(longitudinalPosition).getSI();
695                 bestLane = lane;
696             }
697         }
698         return bestLane;
699     }
700 
701     /**
702      * Collect relevant traffic in adjacent lanes. Parallel traffic is included with headway equal to Double.NaN.
703      * @param directionality LateralDirectionality; either <cite>LateralDirectionality.LEFT</cite>, or
704      *            <cite>LateralDirectionality.RIGHT</cite>
705      * @param when DoubleScalar.Abs&lt;TimeUnit&gt;; the (current) time
706      * @param maximumForwardHeadway DoubleScalar.Rel&lt;LengthUnit&gt;; the maximum forward search distance
707      * @param maximumReverseHeadway DoubleScalar.Rel&lt;LengthUnit&gt;; the maximum reverse search distance
708      * @return Collection&lt;LaneBasedGTU&gt;;
709      * @throws NetworkException on network inconsistency
710      * @throws GTUException on problems with the GTU state (e.g., position)
711      * @throws ParameterException in case of a parameter problem
712      */
713     private Collection<Headway> collectNeighborLaneTraffic(final LateralDirectionality directionality, final Time when,
714             final Length maximumForwardHeadway, final Length maximumReverseHeadway) throws NetworkException,
715             GTUException, ParameterException
716     {
717         Collection<Headway> result = new HashSet<Headway>();
718         for (Headway p : parallel(directionality, when))
719         {
720             // TODO expand for other types of Headways
721             result.add(new HeadwayGTU(p.getId(), ((HeadwayGTU) p).getGtuType(), new Length(Double.NaN, LengthUnit.SI), p
722                     .getSpeed(), p.getAcceleration()));
723         }
724 
725         // forward
726         for (Lane adjacentLane : accessibleAdjacentLaneMap(directionality).get(getLanePathInfo().getReferenceLane()))
727         {
728             LanePathInfo lpiAdjacent = buildLanePathInfoAdjacent(adjacentLane, directionality, when);
729             Headway leader = forwardHeadway(lpiAdjacent, maximumForwardHeadway);
730             if (null != leader.getId() && !result.contains(leader))
731             {
732                 result.add(leader);
733             }
734         }
735 
736         // backward
737         for (Lane lane : this.gtu.getLanes().keySet())
738         {
739             for (Lane adjacentLane : accessibleAdjacentLaneMap(directionality).get(lane))
740             {
741                 Headway follower =
742                         headwayRecursiveBackwardSI(adjacentLane, this.gtu.getLanes().get(lane),
743                                 this.gtu.projectedPosition(adjacentLane, this.gtu.getRear(), when).getSI(), 0.0,
744                                 -maximumReverseHeadway.getSI(), when);
745                 if (follower instanceof HeadwayGTU)
746                 {
747                     boolean found = false;
748                     for (Headway headway : result)
749                     {
750                         if (headway.getId().equals(follower.getId()))
751                         {
752                             found = true;
753                         }
754                     }
755                     if (!found)
756                     {
757                         result.add(new HeadwayGTU(follower.getId(), ((HeadwayGTU) follower).getGtuType(), follower
758                                 .getDistance().multiplyBy(-1.0), follower.getSpeed(), null));
759                     }
760                 }
761                 else if (follower instanceof HeadwayDistance) // always add for potential lane drop
762                 {
763                     result.add(new HeadwayDistance(follower.getDistance().multiplyBy(-1.0)));
764                 }
765                 else
766                 {
767                     throw new GTUException(
768                             "collectNeighborLaneTraffic not yet suited to observe obstacles on neighboring lanes");
769                 }
770             }
771         }
772         return result;
773     }
774 
775     /**
776      * Find a lanePathInfo left or right of the current LanePath.
777      * @param adjacentLane the start adjacent lane for which we calculate the LanePathInfo
778      * @param direction LateralDirectionality; either <cite>LateralDirectionality.LEFT</cite>, or
779      *            <cite>LateralDirectionality.RIGHT</cite>
780      * @param when DoubleScalar.Abs&lt;TimeUnit&gt;; the (current) time
781      * @return the adjacent LanePathInfo
782      * @throws GTUException when the GTU was not initialized yet.
783      * @throws NetworkException when the speed limit for a GTU type cannot be retrieved from the network.
784      * @throws ParameterException in case of a parameter problem
785      */
786     private LanePathInfo buildLanePathInfoAdjacent(final Lane adjacentLane, final LateralDirectionality direction,
787             final Time when) throws GTUException, NetworkException, ParameterException
788     {
789         if (this.lanePathInfo == null || this.lanePathInfo.getTimestamp().ne(when))
790         {
791             updateLanePathInfo();
792         }
793         LanePathInfo lpi = getLanePathInfo();
794         List<LaneDirection> laneDirectionList = new ArrayList<>();
795         laneDirectionList.add(new LaneDirection(adjacentLane, lpi.getReferenceLaneDirection().getDirection()));
796         Length referencePosition = this.gtu.projectedPosition(adjacentLane, this.gtu.getReference(), when);
797         for (int i = 1; i < lpi.getLaneDirectionList().size(); i++)
798         {
799             LaneDirection ld = lpi.getLaneDirectionList().get(i);
800             Set<Lane> accessibleLanes = ld.getLane().accessibleAdjacentLanes(direction, this.gtu.getGTUType());
801             Lane adjLane = null;
802             for (Lane lane : accessibleLanes)
803             {
804                 if (lane.getParentLink().equals(ld.getLane().getParentLink()))
805                 {
806                     adjLane = lane;
807                 }
808             }
809             if (adjLane == null)
810             {
811                 break;
812             }
813             laneDirectionList.add(new LaneDirection(adjLane, ld.getDirection()));
814         }
815         return new LanePathInfo(null, laneDirectionList, referencePosition);
816     }
817 
818     /**************************************************************************************************************************/
819     /*************************************************** GETTERS FOR THE INFORMATION ******************************************/
820     /**************************************************************************************************************************/
821 
822     /** {@inheritDoc} */
823     @Override
824     public final LaneBasedGTU getGTU()
825     {
826         return this.gtu;
827     }
828 
829     /**
830      * Retrieve the last perceived lane path info.
831      * @return LanePathInfo
832      */
833     public final LanePathInfo getLanePathInfo()
834     {
835         return this.lanePathInfo.getObject();
836     }
837 
838     /** {@inheritDoc} */
839     @Override
840     public final Headway getForwardHeadway()
841     {
842         return this.forwardHeadway.getObject();
843     }
844 
845     /** {@inheritDoc} */
846     @Override
847     public final Headway getBackwardHeadway()
848     {
849         return this.backwardHeadway.getObject();
850     }
851 
852     /** {@inheritDoc} */
853     @Override
854     public final Map<Lane, Set<Lane>> getAccessibleAdjacentLanesLeft()
855     {
856         return this.accessibleAdjacentLanesLeft.getObject();
857     }
858 
859     /** {@inheritDoc} */
860     @Override
861     public final Map<Lane, Set<Lane>> getAccessibleAdjacentLanesRight()
862     {
863         return this.accessibleAdjacentLanesRight.getObject();
864     }
865 
866     /** {@inheritDoc} */
867     @Override
868     public final Collection<Headway> getNeighboringHeadwaysLeft()
869     {
870         return this.neighboringHeadwaysLeft.getObject();
871     }
872 
873     /** {@inheritDoc} */
874     @Override
875     public final Collection<Headway> getNeighboringHeadwaysRight()
876     {
877         return this.neighboringHeadwaysRight.getObject();
878     }
879 
880     /** {@inheritDoc} */
881     @Override
882     public final Collection<Headway> getParallelHeadwaysLeft()
883     {
884         return this.parallelHeadwaysLeft.getObject();
885     }
886 
887     /** {@inheritDoc} */
888     @Override
889     public final Collection<Headway> getParallelHeadwaysRight()
890     {
891         return this.parallelHeadwaysRight.getObject();
892     }
893 
894     /** {@inheritDoc} */
895     @Override
896     public final Speed getSpeedLimit()
897     {
898         return this.speedLimit.getObject();
899     }
900 
901     /** {@inheritDoc} */
902     @Override
903     public final Set<PerceivedObject> getPerceivedObjects()
904     {
905         // TODO getPerceivedObjects() in LanePerception
906         return new HashSet<PerceivedObject>();
907     }
908 
909     /** {@inheritDoc} */
910     @Override
911     public final TimeStampedObject<Headway> getTimeStampedForwardHeadway()
912     {
913         return this.forwardHeadway;
914     }
915 
916     /** {@inheritDoc} */
917     @Override
918     public final TimeStampedObject<Headway> getTimeStampedBackwardHeadway()
919     {
920         return this.backwardHeadway;
921     }
922 
923     /** {@inheritDoc} */
924     @Override
925     public final TimeStampedObject<Map<Lane, Set<Lane>>> getTimeStampedAccessibleAdjacentLanesLeft()
926     {
927         return this.accessibleAdjacentLanesLeft;
928     }
929 
930     /** {@inheritDoc} */
931     @Override
932     public final TimeStampedObject<Map<Lane, Set<Lane>>> getTimeStampedAccessibleAdjacentLanesRight()
933     {
934         return this.accessibleAdjacentLanesRight;
935     }
936 
937     /** {@inheritDoc} */
938     @Override
939     public final TimeStampedObject<Collection<Headway>> getTimeStampedNeighboringHeadwaysLeft()
940     {
941         return this.neighboringHeadwaysLeft;
942     }
943 
944     /** {@inheritDoc} */
945     @Override
946     public final TimeStampedObject<Collection<Headway>> getTimeStampedNeighboringHeadwaysRight()
947     {
948         return this.neighboringHeadwaysRight;
949     }
950 
951     /** {@inheritDoc} */
952     @Override
953     public final TimeStampedObject<Collection<Headway>> getTimeStampedParallelHeadwaysLeft()
954     {
955         return this.parallelHeadwaysLeft;
956     }
957 
958     /** {@inheritDoc} */
959     @Override
960     public final TimeStampedObject<Collection<Headway>> getTimeStampedParallelHeadwaysRight()
961     {
962         return this.parallelHeadwaysRight;
963     }
964 
965     /** {@inheritDoc} */
966     @Override
967     public final TimeStampedObject<Speed> getTimeStampedSpeedLimit()
968     {
969         return this.speedLimit;
970     }
971 
972     /** {@inheritDoc} */
973     @Override
974     public final TimeStampedObject<Collection<PerceivedObject>> getTimeStampedPerceivedObjects() throws GTUException
975     {
976         // TODO getPerceivedObjects() in LanePerception
977         return new TimeStampedObject<Collection<PerceivedObject>>(new HashSet<PerceivedObject>(), getTimestamp());
978     }
979 
980 }