View Javadoc
1   package org.opentrafficsim.core.network.lane;
2   
3   import java.rmi.RemoteException;
4   import java.util.ArrayList;
5   import java.util.HashSet;
6   import java.util.List;
7   import java.util.Set;
8   import java.util.SortedMap;
9   import java.util.TreeMap;
10  
11  import nl.tudelft.simulation.dsol.SimRuntimeException;
12  
13  import org.opentrafficsim.core.gtu.GTUType;
14  import org.opentrafficsim.core.gtu.RelativePosition;
15  import org.opentrafficsim.core.gtu.lane.AbstractLaneBasedGTU;
16  import org.opentrafficsim.core.gtu.lane.LaneBasedGTU;
17  import org.opentrafficsim.core.network.LateralDirectionality;
18  import org.opentrafficsim.core.network.Link;
19  import org.opentrafficsim.core.network.LongitudinalDirectionality;
20  import org.opentrafficsim.core.network.NetworkException;
21  import org.opentrafficsim.core.unit.FrequencyUnit;
22  import org.opentrafficsim.core.unit.LengthUnit;
23  import org.opentrafficsim.core.unit.TimeUnit;
24  import org.opentrafficsim.core.value.vdouble.scalar.DoubleScalar;
25  import org.opentrafficsim.graphs.LaneBasedGTUSampler;
26  
27  /**
28   * <p>
29   * Copyright (c) 2013-2014 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights
30   * reserved. <br>
31   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
32   * <p>
33   * @version Aug 19, 2014 <br>
34   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
35   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
36   * @author <a href="http://www.citg.tudelft.nl">Guus Tamminga</a>
37   */
38  public class Lane extends CrossSectionElement
39  {
40      /** type of lane to deduce compatibility with GTU types. */
41      private final LaneType<?> laneType;
42  
43      /** in direction of geometry, reverse, or both. */
44      private final LongitudinalDirectionality directionality;
45  
46      /** Lane capacity in vehicles per time unit. This is a mutable property (e.g., blockage); thus not final. */
47      private DoubleScalar.Abs<FrequencyUnit> capacity;
48  
49      /** Sensors on the lane to trigger behavior of the GTU, sorted by longitudinal position. */
50      private final SortedMap<Double, List<Sensor>> sensors = new TreeMap<>();
51  
52      /** GTUs ordered by increasing longitudinal position. */
53      private final List<LaneBasedGTU<?>> gtuList = new ArrayList<LaneBasedGTU<?>>();
54  
55      /** Adjacent left lanes that some GTU types can change onto. */
56      private Set<Lane> leftNeighbors = new HashSet<Lane>(1);
57  
58      /** Adjacent right lanes that some GTU types can change onto. */
59      private Set<Lane> rightNeighbors = new HashSet<Lane>(1);
60  
61      /**
62       * Next lane(s) following this lane. Initially null so we can calculate and cache the first time the method is
63       * called.
64       */
65      private Set<Lane> nextLanes = null;
66  
67      /**
68       * Next lane(s) following this lane. Initially null so we can calculate and cache the first time the method is
69       * called.
70       */
71      private Set<Lane> prevLanes = null;
72  
73      // TODO write interface for samplers
74      /** List of graphs that want to sample GTUs on this Lane. */
75      private ArrayList<LaneBasedGTUSampler> samplers = new ArrayList<LaneBasedGTUSampler>();
76  
77      /**
78       * @param parentLink Cross Section Link to which the element belongs.
79       * @param lateralOffsetAtStart DoubleScalar.Rel&lt;LengthUnit&gt;; the lateral offset of the design line of the new
80       *            CrossSectionLink with respect to the design line of the parent Link at the start of the parent Link
81       * @param lateralOffsetAtEnd DoubleScalar.Rel&lt;LengthUnit&gt;; the lateral offset of the design line of the new
82       *            CrossSectionLink with respect to the design line of the parent Link at the end of the parent Link
83       * @param beginWidth DoubleScalar.Rel&lt;LengthUnit&gt;; start width, positioned <i>symmetrically around</i> the
84       *            design line
85       * @param endWidth DoubleScalar.Rel&lt;LengthUnit&gt;; end width, positioned <i>symmetrically around</i> the design
86       *            line
87       * @param laneType type of lane to deduce compatibility with GTU types
88       * @param directionality in direction of geometry, reverse, or both
89       * @param capacity Lane capacity in vehicles per time unit. This is a mutable property (e.g., blockage)
90       * @throws NetworkException when creation of the geometry fails
91       */
92      @SuppressWarnings("checkstyle:parameternumber")
93      public Lane(final CrossSectionLink<?, ?> parentLink, final DoubleScalar.Rel<LengthUnit> lateralOffsetAtStart,
94              final DoubleScalar.Rel<LengthUnit> lateralOffsetAtEnd, final DoubleScalar.Rel<LengthUnit> beginWidth,
95              final DoubleScalar.Rel<LengthUnit> endWidth, final LaneType<?> laneType,
96              final LongitudinalDirectionality directionality, final DoubleScalar.Abs<FrequencyUnit> capacity)
97              throws NetworkException
98      {
99          super(parentLink, lateralOffsetAtStart, lateralOffsetAtEnd, beginWidth, endWidth);
100         this.laneType = laneType;
101         this.directionality = directionality;
102         this.capacity = capacity;
103         // TODO Take care of directionality.
104         try
105         {
106             addSensor(new SensorLaneStart(this));
107             addSensor(new SensorLaneEnd(this));
108         }
109         catch (NetworkException exception)
110         {
111             throw new Error("Oops - Caught NetworkException adding sensor at begin or and of Lane " + exception);
112         }
113     }
114 
115     /**
116      * Retrieve one of the sets of neighboring Lanes.
117      * @param direction LateralDirectionality; either LEFT or RIGHT
118      * @return Set&lt;Lane&gt;; the indicated set of neighboring Lanes
119      */
120     private Set<Lane> neighbors(final LateralDirectionality direction)
121     {
122         return direction == LateralDirectionality.LEFT ? this.leftNeighbors : this.rightNeighbors;
123     }
124 
125     /**
126      * Indicate that a Lane is adjacent to this Lane.
127      * @param adjacentLane Lane; the adjacent Lane
128      * @param direction LateralDirectionality; the direction in which the Lane is adjacent to this Lane
129      */
130     public final void addAccessibleAdjacentLane(final Lane adjacentLane, final LateralDirectionality direction)
131     {
132         neighbors(direction).add(adjacentLane);
133     }
134 
135     /**
136      * Indicate that a Lane is no longer adjacent to this Lane (may be useful for lanes that are sometimes closed, e.g.
137      * tidal flow lanes).
138      * @param adjacentLane Lane; the adjacent Lane that must be unregistered
139      * @param direction LateralDirectionality; the direction in which the Lane was adjacent to this Lane
140      * @throws NetworkException when the adjacentLane was not registered as adjacent in the indicated direction
141      */
142     public final void removeAccessibleAdjacentLane(final Lane adjacentLane, final LateralDirectionality direction)
143             throws NetworkException
144     {
145         Set<Lane> neighbors = neighbors(direction);
146         if (!neighbors.contains(adjacentLane))
147         {
148             throw new NetworkException("Lane " + adjacentLane + " is not among the " + direction
149                     + " neighbors of this Lane");
150         }
151         neighbors.remove(adjacentLane);
152     }
153 
154     /**
155      * Insert the sensor at the right place in the sensor list of this lane.
156      * @param sensor the sensor to add
157      * @throws NetworkException when the position of the sensor is beyond (or before) the range of this Lane
158      */
159     public final void addSensor(final Sensor sensor) throws NetworkException
160     {
161         double position = sensor.getLongitudinalPositionSI();
162         if (position < 0 || position > getLength().getSI())
163         {
164             throw new NetworkException("Illegal position for sensor " + position + " valid range is 0.."
165                     + getLength().getSI());
166         }
167         List<Sensor> sensorList = this.sensors.get(position);
168         if (null == sensorList)
169         {
170             sensorList = new ArrayList<Sensor>(1);
171             this.sensors.put(position, sensorList);
172         }
173         sensorList.add(sensor);
174     }
175 
176     /**
177      * Remove a sensor from the sensor list of this lane.
178      * @param sensor the sensor to remove.
179      * @throws NetworkException when the sensor was not found on this Lane
180      */
181     public final void removeSensor(final Sensor sensor) throws NetworkException
182     {
183         List<Sensor> sensorList = this.sensors.get(sensor.getLongitudinalPosition().getSI());
184         if (null == sensorList)
185         {
186             throw new NetworkException("No sensor at " + sensor.getLongitudinalPositionSI());
187         }
188         sensorList.remove(sensor);
189         if (sensorList.size() == 0)
190         {
191             this.sensors.remove(sensor.getLongitudinalPosition().getSI());
192         }
193     }
194 
195     /**
196      * Retrieve the list of Sensors of this Lane in the specified distance range.
197      * @param minimumPosition DoubleScalar.Rel&lt;LengthUnit&gt;; the minimum distance on the Lane
198      * @param maximumPosition DoubleScalar.Rel&lt;LengthUnit&gt;; the maximum distance on the Lane
199      * @return List&lt;Sensor&gt;; list of the sensor in the specified range
200      */
201     public final List<Sensor> getSensors(final DoubleScalar.Rel<LengthUnit> minimumPosition,
202             final DoubleScalar.Rel<LengthUnit> maximumPosition)
203     {
204         ArrayList<Sensor> result = new ArrayList<Sensor>();
205         for (List<Sensor> sensorList : this.sensors.subMap(minimumPosition.getSI(), maximumPosition.getSI()).values())
206         {
207             result.addAll(sensorList);
208         }
209         return result;
210     }
211 
212     /**
213      * @return sensors.
214      */
215     protected final SortedMap<Double, List<Sensor>> getSensors()
216     {
217         return this.sensors;
218     }
219 
220     /**
221      * Trigger the sensors for a certain time step; from now until the nextEvaluationTime of the GTU.
222      * @param gtu the LaneBasedGTU for which to trigger the sensors.
223      * @param referenceStartSI the SI distance of the GTU reference point on the lane at the current time
224      * @param referenceMoveSI the SI distance traveled in the next time step.
225      * @throws RemoteException when simulation time cannot be retrieved.
226      * @throws NetworkException when GTU not on this lane.
227      * @throws SimRuntimeException when method cannot be scheduled.
228      */
229     public final void scheduleTriggers(final LaneBasedGTU<?> gtu, final double referenceStartSI,
230             final double referenceMoveSI) throws RemoteException, NetworkException, SimRuntimeException
231     {
232         for (List<Sensor> sensorList : this.sensors.values())
233         {
234             for (Sensor sensor : sensorList)
235             {
236                 for (RelativePosition relativePosition : gtu.getRelativePositions().values())
237                 {
238                     if (sensor.getPositionType().equals(relativePosition.getType())
239                             && referenceStartSI + relativePosition.getDx().getSI() <= sensor
240                                     .getLongitudinalPositionSI()
241                             && referenceStartSI + referenceMoveSI + relativePosition.getDx().getSI() > sensor
242                                     .getLongitudinalPositionSI())
243                     {
244                         // the exact time of triggering is based on the distance between the current position of the
245                         // relative position on the GTU and the location of the sensor.
246                         // FIXME: PK does not understand the use of Math.max here.
247                         double d =
248                                 Math.max(0.0, sensor.getLongitudinalPositionSI() - referenceStartSI
249                                         - relativePosition.getDx().getSI());
250                         DoubleScalar.Abs<TimeUnit> triggerTime =
251                                 gtu.timeAtDistance(new DoubleScalar.Rel<LengthUnit>(d, LengthUnit.METER));
252                         gtu.getSimulator().scheduleEventAbs(triggerTime, this, sensor, "trigger", new Object[]{gtu});
253                     }
254                 }
255             }
256         }
257     }
258 
259     /**
260      * Transform a fraction on the lane to a relative length (can be less than zero or larger than the lane length).
261      * @param fraction fraction relative to the lane length.
262      * @return relative length corresponding to the fraction.
263      */
264     public final DoubleScalar.Rel<LengthUnit> position(final double fraction)
265     {
266         return new DoubleScalar.Rel<LengthUnit>(this.getLength().getInUnit() * fraction, this.getLength().getUnit());
267     }
268 
269     /**
270      * Transform a fraction on the lane to a relative length in SI units (can be less than zero or larger than the lane
271      * length).
272      * @param fraction fraction relative to the lane length.
273      * @return relative length corresponding to the fraction, in SI units.
274      */
275     public final double positionSI(final double fraction)
276     {
277         return this.getLength().getSI() * fraction;
278     }
279 
280     /**
281      * Transform a position on the lane (can be less than zero or larger than the lane length) to a fraction.
282      * @param position relative length on the lane (may be less than zero or larger than the lane length).
283      * @return fraction fraction relative to the lane length.
284      */
285     public final double fraction(final DoubleScalar.Rel<LengthUnit> position)
286     {
287         return position.getSI() / this.getLength().getSI();
288     }
289 
290     /**
291      * Transform a position on the lane in SI units (can be less than zero or larger than the lane length) to a
292      * fraction.
293      * @param positionSI relative length on the lane in SI units (may be less than zero or larger than the lane length).
294      * @return fraction fraction relative to the lane length.
295      */
296     public final double fractionSI(final double positionSI)
297     {
298         return positionSI / this.getLength().getSI();
299     }
300 
301     /**
302      * Add a LaneBasedGTU&lt;?&gt; to the list of this Lane.
303      * @param gtu LaneBasedGTU&lt;?&gt;; the GTU to add
304      * @param fractionalPosition double; the fractional position that the newly added GTU will have on this Lane
305      * @return int; the rank that the newly added GTU has on this Lane (should be 0, except when the GTU enters this
306      *         Lane due to a lane change operation)
307      * @throws RemoteException on communication failure
308      * @throws NetworkException when the fractionalPosition is outside the range 0..1, or the GTU is already registered
309      *             on this Lane
310      */
311     public final int addGTU(final LaneBasedGTU<?> gtu, final double fractionalPosition) throws RemoteException,
312             NetworkException
313     {
314         // figure out the rank for the new GTU
315         int index;
316         for (index = 0; index < this.gtuList.size(); index++)
317         {
318             LaneBasedGTU<?> otherGTU = this.gtuList.get(index);
319             if (gtu == otherGTU)
320             {
321                 System.err.println("GTU " + gtu + " already registered on Lane " + this + " [registered lanes: "
322                         + gtu.positions(gtu.getFront()).keySet() + "] locations: "
323                         + gtu.positions(gtu.getFront()).values() + " time: "
324                         + gtu.getSimulator().getSimulatorTime().get());
325                 new Exception().printStackTrace();
326                 return index;
327                 /*-
328                 throw new NetworkException("GTU " + gtu + " already registered on Lane " + this
329                         + " [registered lanes: " + gtu.positions(gtu.getFront()).keySet() + "] locations: "
330                         + gtu.positions(gtu.getFront()).values() + " time: "
331                         + gtu.getSimulator().getSimulatorTime().get());
332                 */
333             }
334             try
335             {
336                 if (otherGTU.fractionalPosition(this, otherGTU.getFront()) >= fractionalPosition)
337                 {
338                     break;
339                 }
340             }
341             catch (NetworkException exception)
342             {
343                 // Should never happen; implies that there is a GTU on this Lane that does not think it is on this Lane
344                 exception.printStackTrace();
345             }
346         }
347         this.gtuList.add(index, gtu);
348         //System.out.println("Added gtu " + gtu.getId() + " to lane " + this + " at index " + index);
349         return index;
350     }
351 
352     /**
353      * Add a LaneBasedGTU&lt;?&gt; to the list of this Lane.
354      * @param gtu LaneBasedGTU&lt;?&gt;; the GTU to add
355      * @param longitudinalPosition DoubleScalar.Rel&lt;LengthUnit&gt;; the longitudinal position that the newly added
356      *            GTU will have on this Lane
357      * @return int; the rank that the newly added GTU has on this Lane (should be 0, except when the GTU enters this
358      *         Lane due to a lane change operation)
359      * @throws RemoteException on communication failure
360      * @throws NetworkException when longitudinalPosition is negative or exceeds the length of this Lane
361      */
362     public final int addGTU(final LaneBasedGTU<?> gtu, final DoubleScalar.Rel<LengthUnit> longitudinalPosition)
363             throws RemoteException, NetworkException
364     {
365         return addGTU(gtu, longitudinalPosition.getSI() / getLength().getSI());
366     }
367 
368     /**
369      * Remove a GTU from the GTU list of this lane.
370      * @param gtu the GTU to remove.
371      */
372     public final void removeGTU(final LaneBasedGTU<?> gtu)
373     {
374         this.gtuList.remove(gtu);
375     }
376 
377     /**
378      * @param position the front position after which the relative position of a GTU will be searched.
379      * @param relativePosition the relative position of the GTU we are looking for.
380      * @param when the time for which to evaluate the positions.
381      * @return the first GTU after a position on this lane, or null if no GTU could be found.
382      * @throws NetworkException when there is a problem with the position of the GTUs on the lane.
383      */
384     public final LaneBasedGTU<?> getGtuAfter(final DoubleScalar.Rel<LengthUnit> position,
385             final RelativePosition.TYPE relativePosition, final DoubleScalar.Abs<TimeUnit> when)
386             throws NetworkException
387     {
388         for (LaneBasedGTU<?> gtu : this.gtuList)
389         {
390             if (relativePosition.equals(RelativePosition.FRONT))
391             {
392                 if (gtu.position(this, gtu.getFront(), when).getSI() > position.getSI())
393                 {
394                     return gtu;
395                 }
396             }
397             else if (relativePosition.equals(RelativePosition.REAR))
398             {
399                 if (gtu.position(this, gtu.getRear(), when).getSI() >= position.getSI())// PK was >; not >=
400                 {
401                     return gtu;
402                 }
403             }
404             else
405             {
406                 throw new NetworkException("Can only use Lane.getGtuAfter(...) method with FRONT and REAR positions");
407             }
408         }
409         return null;
410     }
411 
412     /**
413      * @param position the front position before which the relative position of a GTU will be searched.
414      * @param relativePosition the relative position of the GTU we are looking for.
415      * @param when the time for which to evaluate the positions.
416      * @return the first GTU before a position on this lane, or null if no GTU could be found.
417      * @throws NetworkException when there is a problem with the position of the GTUs on the lane.
418      */
419     public final LaneBasedGTU<?> getGtuBefore(final DoubleScalar.Rel<LengthUnit> position,
420             final RelativePosition.TYPE relativePosition, final DoubleScalar.Abs<TimeUnit> when)
421             throws NetworkException
422     {
423         for (int i = this.gtuList.size() - 1; i >= 0; i--)
424         {
425             LaneBasedGTU<?> gtu = this.gtuList.get(i);
426             if (relativePosition.equals(RelativePosition.FRONT))
427             {
428                 if (gtu.position(this, gtu.getFront(), when).getSI() < position.getSI())
429                 {
430                     return gtu;
431                 }
432             }
433             else if (relativePosition.equals(RelativePosition.REAR))
434             {
435                 if (gtu.position(this, gtu.getRear(), when).getSI() < position.getSI())
436                 {
437                     return gtu;
438                 }
439             }
440             else
441             {
442                 throw new NetworkException("Can only use Lane.getGtuBefore(...) method with FRONT and REAR positions");
443             }
444         }
445         return null;
446     }
447 
448     /**
449      * Are two cross section elements laterally well enough aligned to be longitudinally connected?
450      * @param incomingCSE CrossSectionElement; the cross section element where the end position is considered
451      * @param outgoingCSE CrossSectionElement; the cross section element where the begin position is considered
452      * @param margin DoubleScalar.Rel&lt;LengthUnit&gt;; the maximum accepted alignment error
453      * @return boolean; true if the two cross section elements are well enough aligned to be connected
454      */
455     private boolean laterallyCloseEnough(final CrossSectionElement incomingCSE, final CrossSectionElement outgoingCSE,
456             final DoubleScalar.Rel<LengthUnit> margin)
457     {
458         return Math.abs(DoubleScalar.minus(incomingCSE.getLateralCenterPosition(1),
459                 outgoingCSE.getLateralCenterPosition(0)).getSI()) <= margin.getSI();
460     }
461 
462     /*
463      * TODO only center position? Or also width? What is a good cutoff? Base on average width of the GTU type that can
464      * drive on this Lane? E.g., for a Tram or Train, a 5 cm deviation is a problem; for a Car or a Bicycle, more
465      * deviation is acceptable.
466      */
467     /** Lateral alignment margin for longitudinally connected Lanes. */
468     static final DoubleScalar.Rel<LengthUnit> LATERAL_MARGIN = new DoubleScalar.Rel<LengthUnit>(0.5, LengthUnit.METER);
469 
470     /**
471      * The next lane(s) are cached, as it is too expensive to make the calculation every time. There are several
472      * possibilities: returning an empty set when the lane stops and there is no longitudinal transfer method to a next
473      * lane. Returning a set with just one lane if the lateral position of the next lane matches the lateral position of
474      * this lane (based on an overlap of the lateral positions of the two joining lanes of more than a certain
475      * percentage). Multiple lanes in case the Node where the underlying Link for this Lane has multiple outgoing Links,
476      * and there are multiple lanes that match the lateral position of this lane.
477      * @return set of Lanes following this lane.
478      */
479     public final Set<Lane> nextLanes()
480     {
481         if (this.nextLanes == null)
482         {
483             // Construct (and cache) the result.
484             this.nextLanes = new HashSet<Lane>(1);
485             for (Link<?, ?> link : getParentLink().getEndNode().getLinksOut())
486             {
487                 if (link instanceof CrossSectionLink<?, ?>)
488                 {
489                     for (CrossSectionElement cse : ((CrossSectionLink<?, ?>) link).getCrossSectionElementList())
490                     {
491                         if (cse instanceof Lane && laterallyCloseEnough(this, cse, LATERAL_MARGIN))
492                         {
493                             this.nextLanes.add((Lane) cse);
494                         }
495                     }
496                 }
497             }
498         }
499         return this.nextLanes;
500     }
501 
502     /**
503      * The previous lane(s) are cached, as it is too expensive to make the calculation every time. There are several
504      * possibilities: returning an empty set when the lane starts and there is no longitudinal transfer method from a
505      * previous lane. Returning a set with just one lane if the lateral position of the previous lane matches the
506      * lateral position of this lane (based on an overlap of the lateral positions of the two joining lanes of more than
507      * a certain percentage). Multiple lanes in case the Node where the underlying Link for this Lane has multiple
508      * incoming Links, and there are multiple lanes that match the lateral position of this lane.
509      * @return set of Lanes preceding this lane.
510      */
511     public final Set<Lane> prevLanes()
512     {
513         if (this.prevLanes == null)
514         {
515             // Construct (and cache) the result.
516             this.prevLanes = new HashSet<Lane>(1);
517             for (Link<?, ?> link : getParentLink().getStartNode().getLinksIn())
518             {
519                 if (link instanceof CrossSectionLink<?, ?>)
520                 {
521                     for (CrossSectionElement cse : ((CrossSectionLink<?, ?>) link).getCrossSectionElementList())
522                     {
523                         if (cse instanceof Lane && laterallyCloseEnough(cse, this, LATERAL_MARGIN))
524                         {
525                             this.prevLanes.add((Lane) cse);
526                         }
527                     }
528                 }
529             }
530         }
531         return this.prevLanes;
532     }
533 
534     /**
535      * Determine the set of lanes to the left or to the right of this lane, which are accessible from this lane, or an
536      * empty set if no lane could be found. The method takes the LongitidinalDirectionality of the lane into account. In
537      * other words, if we drive FORWARD and look for a lane on the LEFT, and there is a lane but the Directionality of
538      * that lane is not FORWARD or BOTH, it will not be included.<br>
539      * A lane is called adjacent to another lane if the lateral edges are not more than a delta distance apart. This
540      * means that a lane that <i>overlaps</i> with another lane is <b>not</b> returned as an adjacent lane. <br>
541      * The algorithm also looks for RoadMarkerAcross elements between the lanes to determine the lateral permeability
542      * for a GTU. A RoadMarkerAcross is seen as being between two lanes if its center line is not more than delta
543      * distance from the relevant lateral edges of the two adjacent lanes. <br>
544      * <b>Note:</b> LEFT is seen as a positive lateral direction, RIGHT as a negative lateral direction. <br>
545      * @param lateralDirection LEFT or RIGHT.
546      * @param gtuType the type of GTU for which this an adjacent lane.
547      * @return the set of lanes that are accessible, or null if there is no lane that is accessiblewith a matching
548      *         driving direction.
549      */
550     public final Set<Lane> accessibleAdjacentLanes(final LateralDirectionality lateralDirection,
551             final GTUType<?> gtuType)
552     {
553         Set<Lane> candidates = new HashSet<>();
554         for (Lane l : neighbors(lateralDirection))
555         {
556             if (l.getLaneType().isCompatible(gtuType)
557                     && (l.getDirectionality().equals(LongitudinalDirectionality.BOTH) || l.getDirectionality().equals(
558                             this.getDirectionality())))
559             {
560                 candidates.add(l);
561             }
562         }
563         return candidates;
564     }
565 
566     /**
567      * Determine whether there is a lane to the left or to the right of this lane, which is accessible from this lane,
568      * or null if no lane could be found. The method takes the LongitidinalDirectionality of the lane into account. In
569      * other words, if we drive FORWARD and look for a lane on the LEFT, and there is a lane but the Directionality of
570      * that lane is not FORWARD or BOTH, null will be returned.<br>
571      * A lane is called adjacent to another lane if the lateral edges are not more than a delta distance apart. This
572      * means that a lane that <i>overlaps</i> with another lane is <b>not</b> returned as an adjacent lane. <br>
573      * The algorithm also looks for RoadMarkerAcross elements between the lanes to determine the lateral permeability
574      * for a GTU. A RoadMarkerAcross is seen as being between two lanes if its center line is not more than delta
575      * distance from the relevant lateral edges of the two adjacent lanes. <br>
576      * When there are multiple lanes that are adjacent, which could e.g. be the case if an overlapping tram lane and a
577      * car lane are adjacent to the current lane, the widest lane that best matches the GTU accessibility of the
578      * provided GTUType is returned. <br>
579      * <b>Note:</b> LEFT is seen as a positive lateral direction, RIGHT as a negative lateral direction. <br>
580      * @param lateralDirection LEFT or RIGHT.
581      * @param longitudinalPosition DoubleScalar.Rel&lt;LengthUnit&gt;; the position of the GTU along this Lane
582      * @param gtuType the type of GTU for which this an adjacent lane.
583      * @return the lane if it is accessible, or null if there is no lane, it is not accessible, or the driving direction
584      *         does not match.
585      */
586     public final Lane bestAccessibleAdjacentLane(final LateralDirectionality lateralDirection,
587             final DoubleScalar.Rel<LengthUnit> longitudinalPosition, final GTUType<?> gtuType)
588     {
589         Set<Lane> candidates = accessibleAdjacentLanes(lateralDirection, gtuType);
590 
591         if (candidates.isEmpty())
592         {
593             return null; // There is no adjacent Lane that this GTU type can cross into
594         }
595         if (candidates.size() == 1)
596         {
597             return candidates.iterator().next(); // There is exactly one adjacent Lane that this GTU type can cross into
598         }
599         // There are several candidates; find the one that is widest at the beginning.
600         Lane bestLane = null;
601         double widthM = -1.0;
602         for (Lane lane : candidates)
603         {
604             if (lane.getWidth(longitudinalPosition).getSI() > widthM)
605             {
606                 widthM = lane.getWidth(longitudinalPosition).getSI();
607                 bestLane = lane;
608             }
609         }
610         return bestLane;
611     }
612 
613     /**
614      * Register a LaneBasedGTUSampler on this Lane.
615      * @param sampler LaneBasedGTUSampler; the sampler to register
616      */
617     public final void addSampler(final LaneBasedGTUSampler sampler)
618     {
619         this.samplers.add(sampler);
620     }
621 
622     /**
623      * Unregister a LaneBasedGTUSampler from this Lane.
624      * @param sampler LaneBasedGTUSampler; the sampler to unregister
625      */
626     public final void removeSampler(final LaneBasedGTUSampler sampler)
627     {
628         this.samplers.remove(sampler);
629     }
630 
631     /**
632      * Add the movement of a GTU to all graphs that sample this Lane.
633      * @param gtu AbstractLaneBasedGTU&lt;?&gt;; the GTU to sample
634      * @throws NetworkException on network inconsistency
635      * @throws RemoteException on communications failure
636      */
637     public final void sample(final AbstractLaneBasedGTU<?> gtu) throws RemoteException, NetworkException
638     {
639         for (LaneBasedGTUSampler sampler : this.samplers)
640         {
641             sampler.addData(gtu, this);
642         }
643     }
644 
645     /**
646      * @return capacity.
647      */
648     public final DoubleScalar.Abs<FrequencyUnit> getCapacity()
649     {
650         return this.capacity;
651     }
652 
653     /**
654      * @param capacity set capacity.
655      */
656     public final void setCapacity(final DoubleScalar.Abs<FrequencyUnit> capacity)
657     {
658         this.capacity = capacity;
659     }
660 
661     /**
662      * @return laneType.
663      */
664     public final LaneType<?> getLaneType()
665     {
666         return this.laneType;
667     }
668 
669     /**
670      * @return directionality.
671      */
672     public final LongitudinalDirectionality getDirectionality()
673     {
674         return this.directionality;
675     }
676 
677     /**
678      * @return gtuList.
679      */
680     public final List<LaneBasedGTU<?>> getGtuList()
681     {
682         return this.gtuList;
683     }
684 
685     /** {@inheritDoc} */
686     @Override
687     @SuppressWarnings("checkstyle:designforextension")
688     protected double getZ()
689     {
690         return 0.0;
691     }
692 
693     /** {@inheritDoc} */
694     public final String toString()
695     {
696         CrossSectionLink<?, ?> link = getParentLink();
697         // FIXME indexOf may not be the correct way to determine the rank of a Lane (counts stripes as well)
698         return String.format("Lane %d of %s", link.getCrossSectionElementList().indexOf(this), link.toString());
699     }
700 
701 }