View Javadoc
1   package org.opentrafficsim.road.gtu.lane.perception;
2   
3   import java.io.Serializable;
4   import java.rmi.RemoteException;
5   import java.util.Iterator;
6   import java.util.LinkedHashMap;
7   import java.util.LinkedHashSet;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Set;
11  import java.util.SortedSet;
12  import java.util.TreeMap;
13  import java.util.TreeSet;
14  
15  import org.djunits.value.vdouble.scalar.Length;
16  import org.djunits.value.vdouble.scalar.Time;
17  import org.djutils.exceptions.Throw;
18  import org.djutils.exceptions.Try;
19  import org.djutils.immutablecollections.ImmutableMap;
20  import org.opentrafficsim.core.gtu.GTUDirectionality;
21  import org.opentrafficsim.core.gtu.GTUException;
22  import org.opentrafficsim.core.gtu.GTUType;
23  import org.opentrafficsim.core.gtu.RelativePosition;
24  import org.opentrafficsim.core.network.LateralDirectionality;
25  import org.opentrafficsim.core.network.Link;
26  import org.opentrafficsim.core.network.Node;
27  import org.opentrafficsim.core.network.route.Route;
28  import org.opentrafficsim.core.perception.Historical;
29  import org.opentrafficsim.core.perception.HistoricalValue;
30  import org.opentrafficsim.core.perception.HistoryManager;
31  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
32  import org.opentrafficsim.road.gtu.lane.perception.RollingLaneStructureRecord.RecordLink;
33  import org.opentrafficsim.road.gtu.lane.plan.operational.LaneBasedOperationalPlan;
34  import org.opentrafficsim.road.network.lane.CrossSectionLink;
35  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
36  import org.opentrafficsim.road.network.lane.Lane;
37  import org.opentrafficsim.road.network.lane.LaneDirection;
38  import org.opentrafficsim.road.network.lane.object.LaneBasedObject;
39  
40  import nl.tudelft.simulation.event.EventInterface;
41  import nl.tudelft.simulation.event.EventListenerInterface;
42  
43  /**
44   * This data structure can clearly indicate the lane structure ahead of us, e.g. in the following situation:
45   * 
46   * <pre>
47   *     (---- a ----)(---- b ----)(---- c ----)(---- d ----)(---- e ----)(---- f ----)(---- g ----)  
48   *                                             __________                             __________
49   *                                            / _________ 1                          / _________ 2
50   *                                           / /                                    / /
51   *                                __________/ /             _______________________/ /
52   *  1  ____________ ____________ /_ _ _ _ _ _/____________ /_ _ _ _ _ _ _ _ _ _ _ _ /      
53   *  0 |_ _X_ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ \____________
54   * -1 |____________|_ _ _ _ _ _ |____________|____________|  __________|____________|____________| 3
55   * -2              / __________/                           \ \  
56   *        ________/ /                                       \ \___________  
57   *      5 _________/                                         \____________  4
58   * </pre>
59   * 
60   * When the GTU is looking ahead, it needs to know that when it continues to destination 3, it needs to shift one lane to the
61   * right at some point, but <b>not</b> two lanes to the right in link b, and not later than at the end of link f. When it needs
62   * to go to destination 1, it needs to shift to the left in link c. When it has to go to destination 2, it has to shift to the
63   * left, but not earlier than at link e. At node [de], it is possible to leave the rightmost lane of link e, and go to
64   * destination 4. The rightmost lane just splits into two lanes at the end of link d, and the GTU can either continue driving to
65   * destination 3, turn right to destination 4. This means that the right lane of link d has <b>two</b> successor lanes.
66   * <p>
67   * In the data structures, lanes are numbered laterally. Suppose that the lane where vehicle X resides would be number 0.
68   * Consistent with "left is positive" for angles, the lane right of X would have number -1, and entry 5 would have number -2.
69   * <p>
70   * In the data structure, this can be indicated as follows (N = next, P = previous, L = left, R = right, D = lane drop, . =
71   * continued but not in this structure). The merge lane in b is considered "off limits" for the GTUs on the "main" lane -1; the
72   * "main" lane 0 is considered off limits from the exit lanes on c, e, and f. Still, we need to maintain pointers to these
73   * lanes, as we are interested in the GTUs potentially driving next to us, feeding into our lane, etc.
74   * 
75   * <pre>
76   *       1                0               -1               -2
77   *       
78   *                       ROOT 
79   *                   _____|_____      ___________      ___________            
80   *                  |_-_|_._|_R_|----|_L_|_._|_-_|    |_-_|_._|_-_|  a           
81   *                        |                |                |
82   *                   _____V_____      _____V_____      _____V_____            
83   *                  |_-_|_N_|_R_|----|_L_|_N_|_R_|&lt;---|_L_|_D_|_-_|  b           
84   *                        |                |                 
85   *  ___________      _____V_____      _____V_____                 
86   * |_-_|_N_|_R_|&lt;---|_L_|_N_|_R_|----|_L_|_N_|_-_|                   c
87   *       |                |                |                 
88   *  _____V_____      _____V_____      _____V_____                 
89   * |_-_|_._|_-_|    |_-_|_N_|_R_|----|_L_|_NN|_-_|                   d          
90   *                        |                ||_______________ 
91   *  ___________      _____V_____      _____V_____      _____V_____            
92   * |_-_|_N_|_R_|&lt;---|_L_|_N_|_R_|----|_L_|_N_|_-_|    |_-_|_N_|_-_|  e          
93   *       |                |                |                |
94   *  _____V_____      _____V_____      _____V_____      _____V_____            
95   * |_-_|_N_|_R_|&lt;---|_L_|_D_|_R_|----|_L_|_N_|_-_|    |_-_|_._|_-_|  f          
96   *       |                                 |                 
97   *  _____V_____                       _____V_____                             
98   * |_-_|_._|_-_|                     |_-_|_._|_-_|                   g
99   * </pre>
100  * <p>
101  * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
102  * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
103  * </p>
104  * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
105  * initial version Feb 20, 2016 <br>
106  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
107  */
108 public class RollingLaneStructure implements LaneStructure, Serializable, EventListenerInterface
109 {
110     /** */
111     private static final long serialVersionUID = 20160400L;
112 
113     /** The lanes from which we observe the situation. */
114     private final Historical<RollingLaneStructureRecord> root;
115 
116     /** Look ahead distance. */
117     private Length lookAhead;
118 
119     /** Route the structure is based on. */
120     private Route previousRoute;
121 
122     /** Whether the previous plan was deviative. */
123     private boolean previouslyDeviative = false;
124 
125     /** Lane structure records of the cross section. */
126     private TreeMap<RelativeLane, RollingLaneStructureRecord> crossSectionRecords = new TreeMap<>();
127 
128     /** First lane structure records. */
129     private TreeMap<RelativeLane, RollingLaneStructureRecord> firstRecords = new TreeMap<>();
130 
131     /** Lane structure records grouped per relative lane. */
132     private Map<RelativeLane, Set<RollingLaneStructureRecord>> relativeLaneMap = new LinkedHashMap<>();
133 
134     /** Relative lanes storage per record, such that other records can be linked to the correct relative lane. */
135     private Map<LaneStructureRecord, RelativeLane> relativeLanes = new LinkedHashMap<>();
136 
137     /** Set of lanes that can be ignored as they are beyond build bounds. */
138     private final Set<Lane> ignoreSet = new LinkedHashSet<>();
139 
140     /** Upstream edges. */
141     private final Set<RollingLaneStructureRecord> upstreamEdge = new LinkedHashSet<>();
142 
143     /** Downstream edges. */
144     private final Set<RollingLaneStructureRecord> downstreamEdge = new LinkedHashSet<>();
145 
146     /** Downstream distance over which the structure is made. */
147     private final Length down;
148 
149     /** Upstream distance over which the structure is made. */
150     private final Length up;
151 
152     /** Downstream distance at splits (links not on route) included in the structure. */
153     private final Length downSplit;
154 
155     /** Upstream distance at downstream merges (links not on route) included in the structure. */
156     private final Length upMerge;
157 
158     /** GTU. */
159     private final LaneBasedGTU containingGtu;
160 
161     /** the animation access. */
162     @SuppressWarnings("checkstyle:visibilitymodifier")
163     public AnimationAccess animationAccess = new AnimationAccess();
164 
165     /**
166      * Constructor.
167      * @param lookAhead Length; distance over which visual objects are included
168      * @param down Length; downstream distance over which the structure is made
169      * @param up Length; upstream distance over which the structure is made, should include a margin for reaction time
170      * @param downSplit Length; downstream distance at splits (links not on route) included in the structure
171      * @param upMerge Length; upstream distance at downstream merges (links not on route) included in the structure
172      * @param gtu LaneBasedGTU; GTU
173      */
174     public RollingLaneStructure(final Length lookAhead, final Length down, final Length up, final Length downSplit,
175             final Length upMerge, final LaneBasedGTU gtu)
176     {
177         HistoryManager historyManager = gtu.getSimulator().getReplication().getHistoryManager(gtu.getSimulator());
178         this.root = new HistoricalValue<>(historyManager);
179         this.lookAhead = lookAhead;
180         this.down = down;
181         this.up = up;
182         this.downSplit = downSplit;
183         this.upMerge = upMerge;
184         this.containingGtu = gtu;
185         try
186         {
187             gtu.addListener(this, LaneBasedGTU.LANE_CHANGE_EVENT);
188         }
189         catch (RemoteException exception)
190         {
191             throw new RuntimeException(exception);
192         }
193     }
194 
195     /**
196      * Updates the underlying structure shifting the root position to the input.
197      * @param pos DirectedLanePosition; current position of the GTU
198      * @param route Route; current route of the GTU
199      * @param gtuType GTUType; GTU type
200      * @throws GTUException on a problem while updating the structure
201      */
202     @Override
203     public final void update(final DirectedLanePosition pos, final Route route, final GTUType gtuType) throws GTUException
204     {
205 
206         /*
207          * Implementation note: the LaneStructure was previously generated by AbstractLanePerception every time step. This
208          * functionality has been moved to LaneStructure itself, in a manner that can update the LaneStructure. Start distances
209          * of individual records are therefore made dynamic, calculated relative to a neighboring source record. For many time
210          * steps this means that only these distances have to be updated. In other cases, the sources for start distances are
211          * changed concerning the records that were involved in the previous time step. The LaneStructure now also maintains an
212          * upstream and a downstream edge, i.e. set of records. These are moved forward as the GTU moves.
213          */
214 
215         // fractional position
216         Lane lane = pos.getLane();
217         GTUDirectionality direction = pos.getGtuDirection();
218         Length position = pos.getPosition();
219         double fracPos = direction.isPlus() ? position.si / lane.getLength().si : 1.0 - position.si / lane.getLength().si;
220         boolean deviative = this.containingGtu.getOperationalPlan() instanceof LaneBasedOperationalPlan
221                 && ((LaneBasedOperationalPlan) this.containingGtu.getOperationalPlan()).isDeviative();
222 
223         // TODO on complex networks, e.g. with sections connectors where lane changes are not possible, the update may fail
224         if (this.previousRoute != route || this.root.get() == null || deviative != this.previouslyDeviative)
225         {
226             // create new LaneStructure
227             this.previousRoute = route;
228 
229             // clear
230             this.upstreamEdge.clear();
231             this.downstreamEdge.clear();
232             this.crossSectionRecords.clear();
233             this.relativeLanes.clear();
234 
235             // build cross-section
236             RollingLaneStructureRecord newRoot = constructRecord(lane, direction, null, RecordLink.CROSS, RelativeLane.CURRENT);
237             this.root.set(newRoot);
238             this.crossSectionRecords.put(RelativeLane.CURRENT, newRoot);
239             for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
240                     LateralDirectionality.RIGHT })
241             {
242                 RollingLaneStructureRecord current = newRoot;
243                 RelativeLane relativeLane = RelativeLane.CURRENT;
244                 Set<Lane> adjacentLanes =
245                         current.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, current.getDirection());
246                 while (!adjacentLanes.isEmpty())
247                 {
248                     Throw.when(adjacentLanes.size() > 1, RuntimeException.class,
249                             "Multiple adjacent lanes encountered during construction of lane map.");
250                     relativeLane = latDirection.isLeft() ? relativeLane.getLeft() : relativeLane.getRight();
251                     Lane adjacentLane = adjacentLanes.iterator().next();
252                     RollingLaneStructureRecord adjacentRecord =
253                             constructRecord(adjacentLane, direction, current, RecordLink.CROSS, relativeLane);
254                     this.crossSectionRecords.put(relativeLane, adjacentRecord);
255                     if (latDirection.isLeft())
256                     {
257                         if (adjacentLane
258                                 .accessibleAdjacentLanesPhysical(LateralDirectionality.RIGHT, gtuType, current.getDirection())
259                                 .contains(current.getLane()))
260                         {
261                             adjacentRecord.setRight(current, gtuType);
262                         }
263                         current.setLeft(adjacentRecord, gtuType);
264                     }
265                     else
266                     {
267                         if (adjacentLane
268                                 .accessibleAdjacentLanesPhysical(LateralDirectionality.LEFT, gtuType, current.getDirection())
269                                 .contains(current.getLane()))
270                         {
271                             adjacentRecord.setLeft(current, gtuType);
272                         }
273                         current.setRight(adjacentRecord, gtuType);
274                     }
275                     current = adjacentRecord;
276                     adjacentLanes =
277                             current.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, current.getDirection());
278                 }
279             }
280             this.upstreamEdge.addAll(this.crossSectionRecords.values());
281             this.downstreamEdge.addAll(this.crossSectionRecords.values());
282             this.firstRecords.putAll(this.crossSectionRecords);
283 
284             // set the start distances so the upstream expand can work
285             newRoot.updateStartDistance(fracPos, this);
286 
287             // expand upstream edge
288             expandUpstreamEdge(gtuType, fracPos);
289 
290             // derive first records
291             deriveFirstRecords();
292         }
293         else
294         {
295 
296             // update LaneStructure
297             RollingLaneStructureRecord newRoot = this.root.get();
298             if (!lane.equals(newRoot.getLane()))
299             {
300 
301                 // find the root, and possible lateral shift if changed lane
302                 newRoot = null;
303                 RelativeLane lateralMove = null;
304                 double closest = Double.POSITIVE_INFINITY;
305                 for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
306                 {
307                     for (RollingLaneStructureRecord record : this.relativeLaneMap.get(relativeLane))
308                     {
309                         if (record.getLane().equals(lane) && record.getStartDistance().si < closest
310                                 && record.getStartDistance().si + record.getLength().si > 0.0)
311                         {
312                             newRoot = record;
313                             lateralMove = relativeLane;
314                             // multiple records may be present for the current lane due to a loop
315                             closest = record.getStartDistance().si;
316                         }
317                     }
318                 }
319                 // newRoot.getPrev().contains(newRoot.getStartDistanceSource())
320                 this.root.set(newRoot);
321 
322                 // update start distance sources
323                 updateStartDistanceSources();
324 
325                 // shift if changed lane
326                 if (!lateralMove.isCurrent())
327                 {
328                     RelativeLane delta =
329                             new RelativeLane(lateralMove.getLateralDirectionality().flip(), lateralMove.getNumLanes());
330 
331                     TreeMap<RelativeLane, Set<RollingLaneStructureRecord>> newRelativeLaneMap = new TreeMap<>();
332                     for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
333                     {
334                         RelativeLane newRelativeLane = relativeLane.add(delta);
335                         newRelativeLaneMap.put(newRelativeLane, this.relativeLaneMap.get(relativeLane));
336                     }
337                     this.relativeLaneMap = newRelativeLaneMap;
338 
339                     Map<LaneStructureRecord, RelativeLane> newRelativeLanes = new LinkedHashMap<>();
340                     for (LaneStructureRecord record : this.relativeLanes.keySet())
341                     {
342                         newRelativeLanes.put(record, this.relativeLanes.get(record).add(delta));
343                     }
344                     this.relativeLanes = newRelativeLanes;
345                 }
346 
347                 this.crossSectionRecords.clear();
348                 this.crossSectionRecords.put(RelativeLane.CURRENT, newRoot);
349                 for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
350                         LateralDirectionality.RIGHT })
351                 {
352                     RollingLaneStructureRecord record = newRoot;
353                     RollingLaneStructureRecord next = newRoot;
354                     RelativeLane/lane/perception/RelativeLane.html#RelativeLane">RelativeLane delta = new RelativeLane(latDirection, 1);
355                     RelativeLane relLane = RelativeLane.CURRENT;
356                     while (next != null)
357                     {
358                         next = latDirection.isLeft() ? record.getLeft() : record.getRight();
359                         if (next != null)
360                         {
361                             next.changeStartDistanceSource(record, RecordLink.CROSS);
362                             relLane = relLane.add(delta);
363                             this.crossSectionRecords.put(relLane, next);
364                             record = next;
365                         }
366                     }
367                 }
368 
369             }
370             newRoot.updateStartDistance(fracPos, this);
371 
372             // update upstream edges
373             retreatUpstreamEdge();
374 
375             // derive first records
376             deriveFirstRecords();
377 
378         }
379 
380         this.previouslyDeviative = deviative;
381 
382         // update downstream edges
383         expandDownstreamEdge(gtuType, fracPos, route);
384 
385     }
386 
387     /**
388      * Derives the first downstream records so the extended cross-section can be returned.
389      */
390     private void deriveFirstRecords()
391     {
392         this.firstRecords.clear();
393         this.firstRecords.putAll(this.crossSectionRecords);
394         for (RelativeLane lane : this.relativeLaneMap.keySet())
395         {
396             getFirstRecord(lane); // store non-null values internally
397         }
398     }
399 
400     /**
401      * Upstream algorithm from the new source, where records along the way are assigned a new start distance source. These
402      * records were downstream in the previous time step, but are now upstream. Hence, their start distance source should now
403      * become their downstream record. This algorithm acts like an upstream tree, where each branch is stopped if there are no
404      * upstream records, or the upstream records already have their downstream record as source (i.e. the record was already
405      * upstream in the previous time step).
406      */
407     private void updateStartDistanceSources()
408     {
409 
410         // initial cross section
411         Set<RollingLaneStructureRecord> set = new LinkedHashSet<>();
412         RollingLaneStructureRecord rootRecord = this.root.get();
413         set.add(rootRecord);
414         rootRecord.changeStartDistanceSource(null, RecordLink.CROSS);
415         RollingLaneStructureRecord prev = rootRecord;
416         RollingLaneStructureRecord next = prev.getLeft();
417         while (next != null)
418         {
419             set.add(next);
420             next.changeStartDistanceSource(prev, RecordLink.CROSS);
421             prev = next;
422             next = next.getLeft();
423         }
424         prev = rootRecord;
425         next = prev.getRight();
426         while (next != null)
427         {
428             set.add(next);
429             next.changeStartDistanceSource(prev, RecordLink.CROSS);
430             prev = next;
431             next = next.getRight();
432         }
433         // tree algorithm (branches flattened to a single set)
434         while (!set.isEmpty())
435         {
436             // lateral
437             Set<RollingLaneStructureRecord> newSet = new LinkedHashSet<>();
438             for (RollingLaneStructureRecord record : set)
439             {
440                 for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
441                         LateralDirectionality.RIGHT })
442                 {
443                     prev = record;
444                     next = latDirection.isLeft() ? record.getLeft() : record.getRight();
445                     while (next != null && !set.contains(next))
446                     {
447                         next.changeStartDistanceSource(prev, RecordLink.LATERAL_END);
448                         removeDownstream(next, latDirection.flip()); // split not taken can be thrown away
449                         newSet.add(next);
450                         prev = next;
451                         next = latDirection.isLeft() ? next.getLeft() : next.getRight();
452                     }
453                 }
454             }
455             set.addAll(newSet);
456 
457             // longitudinal
458             newSet.clear();
459             for (RollingLaneStructureRecord record : set)
460             {
461                 for (RollingLaneStructureRecord prevRecord : record.getPrev())
462                 {
463                     Iterator<RollingLaneStructureRecord> it = prevRecord.getNext().iterator();
464                     while (it.hasNext())
465                     {
466                         RollingLaneStructureRecord otherDown = it.next();
467                         if (!otherDown.getLane().getParentLink().equals(record.getLane().getParentLink()))
468                         {
469                             // split not taken can be thrown away
470                             otherDown.changeStartDistanceSource(null, null);
471                             // this can throw away records that are laterally connected as they later merge... ??
472                             removeDownstream(otherDown, LateralDirectionality.NONE);
473                             removeRecord(otherDown);
474                             it.remove();
475                         }
476                     }
477                     LaneStructureRecord source = prevRecord.getStartDistanceSource();
478                     if (source == null || (source != null && !source.equals(record)))
479                     {
480                         prevRecord.changeStartDistanceSource(record, RecordLink.UP);
481                         newSet.add(prevRecord);
482                     }
483                 }
484             }
485 
486             // next loop
487             set = newSet;
488         }
489     }
490 
491     /**
492      * Removes all records downstream of the given record from underlying data structures.
493      * @param record RollingLaneStructureRecord; record, downstream of which to remove all records
494      * @param lat LateralDirectionality; records with an adjacent record at this side are not deleted
495      */
496     private void removeDownstream(final RollingLaneStructureRecord record, final LateralDirectionality lat)
497     {
498         for (RollingLaneStructureRecord next : record.getNext())
499         {
500             RollingLaneStructureRecord adj = lat.isLeft() ? next.getLeft() : lat.isRight() ? next.getRight() : null;
501             if (adj == null)
502             {
503                 next.changeStartDistanceSource(null, null);
504                 removeDownstream(next, lat);
505                 removeRecord(next);
506             }
507         }
508         record.clearNextList();
509     }
510 
511     /**
512      * Removes the record from underlying data structures.
513      * @param record RollingLaneStructureRecord; record to remove
514      */
515     private void removeRecord(final RollingLaneStructureRecord record)
516     {
517         RelativeLane lane = this.relativeLanes.get(record);
518         if (lane != null)
519         {
520             this.relativeLanes.remove(record);
521         }
522         record.changeStartDistanceSource(null, null);
523 
524     }
525 
526     /**
527      * On a new build, this method is used to create the upstream map.
528      * @param gtuType GTUType; GTU type
529      * @param fractionalPosition double; fractional position on reference link
530      * @throws GTUException on exception
531      */
532     private void expandUpstreamEdge(final GTUType gtuType, final double fractionalPosition) throws GTUException
533     {
534         this.ignoreSet.clear();
535         for (LaneStructureRecord record : this.upstreamEdge)
536         {
537             this.ignoreSet.add(record.getLane());
538         }
539         Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
540         boolean expand = true;
541         while (expand)
542         {
543             expand = false;
544 
545             // longitudinal
546             Iterator<RollingLaneStructureRecord> iterator = this.upstreamEdge.iterator();
547             Set<RollingLaneStructureRecord> modifiedEdge = new LinkedHashSet<>(this.upstreamEdge);
548             while (iterator.hasNext())
549             {
550                 RollingLaneStructureRecord prev = iterator.next();
551                 ImmutableMap<Lane, GTUDirectionality> nexts = prev.getLane().upstreamLanes(prev.getDirection(), gtuType);
552                 if (prev.getStartDistance().si < this.up.si)
553                 {
554                     // upstream search ends on this lane
555                     prev.setCutOffStart(this.up.minus(prev.getStartDistance()));
556                     for (Lane prevLane : nexts.keySet())
557                     {
558                         this.ignoreSet.add(prevLane); // exclude in lateral search
559                     }
560                 }
561                 else
562                 {
563                     // upstream search goes further upstream
564                     prev.clearCutOffStart();
565                     iterator.remove();
566                     if (!nexts.isEmpty())
567                     {
568                         for (Lane prevLane : nexts.keySet())
569                         {
570                             RelativeLane relativeLane = this.relativeLanes.get(prev);
571                             RollingLaneStructureRecord next =
572                                     constructRecord(prevLane, nexts.get(prevLane), prev, RecordLink.UP, relativeLane);
573                             this.ignoreSet.add(prevLane);
574                             next.updateStartDistance(fractionalPosition, this);
575                             connectLaterally(next, gtuType, modifiedEdge);
576                             next.addNext(prev);
577                             prev.addPrev(next);
578                             nextSet.add(next);
579                             modifiedEdge.add(next);
580                         }
581                     }
582                 }
583             }
584             this.upstreamEdge.addAll(nextSet);
585             expand |= !nextSet.isEmpty();
586             nextSet.clear();
587 
588             // lateral
589             Set<RollingLaneStructureRecord> lateralSet =
590                     expandLateral(this.upstreamEdge, RecordLink.LATERAL_END, gtuType, fractionalPosition);
591             nextSet.addAll(lateralSet);
592 
593             // next iteration
594             this.upstreamEdge.addAll(nextSet);
595             expand |= !nextSet.isEmpty();
596             nextSet.clear();
597         }
598     }
599 
600     /**
601      * Helper method for upstream and downstream expansion. This method returns all lanes that can be laterally found from the
602      * input set.
603      * @param edge Set&lt;RollingLaneStructureRecord&gt;; input set
604      * @param recordLink RecordLink; link to add between lateral records, depends on upstream or downstream search
605      * @param gtuType GTUType; GTU type
606      * @param fractionalPosition double; fractional position on reference link
607      * @return Set&lt;LaneStructureRecord&gt;; output set with all laterally found lanes
608      */
609     private Set<RollingLaneStructureRecord> expandLateral(final Set<RollingLaneStructureRecord> edge,
610             final RecordLink recordLink, final GTUType gtuType, final double fractionalPosition)
611     {
612         Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
613         Set<Lane> laneSet = new LinkedHashSet<>(); // set to check that an adjacent lane is not another lane already in the set
614         for (LaneStructureRecord record : edge)
615         {
616             laneSet.add(record.getLane());
617         }
618         Iterator<RollingLaneStructureRecord> iterator = edge.iterator();
619         while (iterator.hasNext())
620         {
621             RollingLaneStructureRecord record = iterator.next();
622             for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
623                     LateralDirectionality.RIGHT })
624             {
625                 if (record.getRight() != null && latDirection.isRight() || record.getLeft() != null && latDirection.isLeft())
626                 {
627                     // skip if there already is a record on that side
628                     continue;
629                 }
630                 RelativeLane relativeLane = this.relativeLanes.get(record);
631                 RollingLaneStructureRecord prev = record;
632                 Set<Lane> adjacentLanes =
633                         prev.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, prev.getDirection());
634                 while (!adjacentLanes.isEmpty())
635                 {
636                     Throw.when(adjacentLanes.size() > 1, RuntimeException.class,
637                             "Multiple adjacent lanes encountered during construction of lane map.");
638                     relativeLane = latDirection.isLeft() ? relativeLane.getLeft() : relativeLane.getRight();
639                     Lane nextLane = adjacentLanes.iterator().next();
640                     if (!laneSet.contains(nextLane) && !this.ignoreSet.contains(nextLane))
641                     {
642                         RollingLaneStructureRecord next =
643                                 constructRecord(nextLane, record.getDirection(), prev, recordLink, relativeLane);
644                         this.ignoreSet.add(nextLane);
645                         next.updateStartDistance(fractionalPosition, this);
646                         nextSet.add(next);
647                         laneSet.add(nextLane);
648                         if (latDirection.isLeft())
649                         {
650                             prev.setLeft(next, gtuType);
651                             if (nextLane
652                                     .accessibleAdjacentLanesPhysical(LateralDirectionality.RIGHT, gtuType, prev.getDirection())
653                                     .contains(prev.getLane()))
654                             {
655                                 next.setRight(prev, gtuType);
656                             }
657                             for (RollingLaneStructureRecord edgeRecord : edge)
658                             {
659                                 if (!edgeRecord.equals(prev)
660                                         && edgeRecord.getLane().getParentLink().equals(next.getLane().getParentLink()))
661                                 {
662                                     for (Lane adjLane : edgeRecord.getLane().accessibleAdjacentLanesPhysical(
663                                             LateralDirectionality.RIGHT, gtuType, edgeRecord.getDirection()))
664                                     {
665                                         if (adjLane.equals(next.getLane()))
666                                         {
667                                             edgeRecord.setRight(next, gtuType);
668                                             next.setLeft(edgeRecord, gtuType);
669                                         }
670                                     }
671                                 }
672                             }
673                         }
674                         else
675                         {
676                             prev.setRight(next, gtuType);
677                             if (nextLane
678                                     .accessibleAdjacentLanesPhysical(LateralDirectionality.LEFT, gtuType, prev.getDirection())
679                                     .contains(prev.getLane()))
680                             {
681                                 next.setLeft(prev, gtuType);
682                             }
683                             for (RollingLaneStructureRecord edgeRecord : edge)
684                             {
685                                 if (!edgeRecord.equals(prev)
686                                         && edgeRecord.getLane().getParentLink().equals(next.getLane().getParentLink()))
687                                 {
688                                     for (Lane adjLane : edgeRecord.getLane().accessibleAdjacentLanesPhysical(
689                                             LateralDirectionality.LEFT, gtuType, edgeRecord.getDirection()))
690                                     {
691                                         if (adjLane.equals(next.getLane()))
692                                         {
693                                             edgeRecord.setLeft(next, gtuType);
694                                             next.setRight(edgeRecord, gtuType);
695                                         }
696                                     }
697                                 }
698                             }
699                         }
700                         // connect longitudinally due to merge or split
701                         Set<RollingLaneStructureRecord> adjs = new LinkedHashSet<>();
702                         if (next.getLeft() != null)
703                         {
704                             adjs.add(next.getLeft());
705                         }
706                         if (next.getRight() != null)
707                         {
708                             adjs.add(next.getRight());
709                         }
710                         for (RollingLaneStructureRecord adj : adjs)
711                         {
712                             for (Lane lane : next.getLane().upstreamLanes(next.getDirection(), gtuType).keySet())
713                             {
714                                 for (RollingLaneStructureRecord adjPrev : adj.getPrev())
715                                 {
716                                     if (lane.equals(adjPrev.getLane()))
717                                     {
718                                         Try.execute(() -> next.addPrev(adjPrev), "Cut-off record added as prev.");
719                                     }
720                                 }
721                             }
722                             for (Lane lane : next.getLane().downstreamLanes(next.getDirection(), gtuType).keySet())
723                             {
724                                 for (RollingLaneStructureRecord adjNext : adj.getNext())
725                                 {
726                                     if (lane.equals(adjNext.getLane()))
727                                     {
728                                         Try.execute(() -> next.addNext(adjNext), "Cut-off record added as next.");
729                                     }
730                                 }
731                             }
732                         }
733                         
734                         prev = next;
735                         adjacentLanes =
736                                 prev.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, prev.getDirection());
737                     }
738                     else
739                     {
740                         break;
741                     }
742                 }
743             }
744         }
745         return nextSet;
746     }
747 
748     /**
749      * This method makes sure that not all history is maintained forever, and the upstream edge moves with the GTU.
750      * @throws GTUException on exception
751      */
752     private void retreatUpstreamEdge() throws GTUException
753     {
754         boolean moved = true;
755         Set<RollingLaneStructureRecord> nexts = new LinkedHashSet<>();
756         while (moved)
757         {
758             moved = false;
759             nexts.clear();
760             Iterator<RollingLaneStructureRecord> iterator = this.upstreamEdge.iterator();
761             while (iterator.hasNext())
762             {
763                 RollingLaneStructureRecord prev = iterator.next();
764                 // nexts may contain 'prev' as two lanes are removed from the upstream edge that merge in to 1 downstream lane
765                 if (!nexts.contains(prev) && prev.getStartDistance().si + prev.getLane().getLength().si < this.up.si)
766                 {
767                     for (RollingLaneStructureRecord next : prev.getNext())
768                     {
769                         next.clearPrevList();
770                         next.setCutOffStart(this.up.minus(next.getStartDistance()));
771                         moved = true;
772                         nexts.add(next);
773                         RollingLaneStructureRecord lat = next.getLeft();
774                         while (lat != null && lat.getPrev().isEmpty())
775                         {
776                             nexts.add(lat);
777                             lat = lat.getLeft();
778                         }
779                         lat = next.getRight();
780                         while (lat != null && lat.getPrev().isEmpty())
781                         {
782                             nexts.add(lat);
783                             lat = lat.getRight();
784                         }
785                     }
786                     prev.clearNextList();
787                     removeRecord(prev);
788                     iterator.remove();
789                 }
790                 else
791                 {
792                     Length cutOff = this.up.minus(prev.getStartDistance());
793                     if (cutOff.si > 0)
794                     {
795                         prev.setCutOffStart(cutOff);
796                     }
797                 }
798             }
799             this.upstreamEdge.addAll(nexts);
800 
801             // check adjacent lanes
802             for (RollingLaneStructureRecord record : nexts)
803             {
804                 RollingLaneStructureRecord prev = record;
805                 for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
806                         LateralDirectionality.RIGHT })
807                 {
808                     while (prev != null)
809                     {
810                         RollingLaneStructureRecord next = latDirection.isLeft() ? prev.getLeft() : prev.getRight();
811                         if (next != null && !this.upstreamEdge.contains(next))
812                         {
813                             moved |= findUpstreamEdge(next);
814                         }
815                         prev = next;
816                     }
817                 }
818             }
819         }
820     }
821 
822     /**
823      * Recursive method to find downstream record(s) on the upstream edge, as the edge was moved downstream and a laterally
824      * connected lane was not yet in the upstream edge. All edge records are added to the edge set.
825      * @param record RollingLaneStructureRecord; newly found adjacent record after moving the upstream edge downstream
826      * @return boolean; whether a record was added to the edge, note that no record is added of the record is fully downstream
827      *         of the upstream view distance
828      * @throws GTUException on exception
829      */
830     private boolean findUpstreamEdge(final RollingLaneStructureRecord record) throws GTUException
831     {
832         Length cutOff = this.up.minus(record.getStartDistance());
833         boolean moved = false;
834         if (cutOff.gt0())
835         {
836             if (cutOff.lt(record.getLane().getLength()))
837             {
838                 record.clearPrevList();
839                 record.setCutOffStart(cutOff);
840                 this.upstreamEdge.add(record);
841                 moved = true;
842             }
843             else
844             {
845                 if (this.relativeLanes.containsKey(record))
846                 {
847                     // could have been removed from upstream already
848                     removeRecord(record);
849                 }
850                 for (RollingLaneStructureRecord next : record.getNext())
851                 {
852                     moved |= findUpstreamEdge(next);
853                 }
854             }
855         }
856         return moved;
857     }
858 
859     /**
860      * Main downstream search for the map. Can be used at initial build and to update.
861      * @param gtuType GTUType; GTU type
862      * @param fractionalPosition double; fractional position on reference link
863      * @param route Route; route of the GTU
864      * @throws GTUException on exception
865      */
866     private void expandDownstreamEdge(final GTUType gtuType, final double fractionalPosition, final Route route)
867             throws GTUException
868     {
869         this.ignoreSet.clear();
870         for (LaneStructureRecord record : this.downstreamEdge)
871         {
872             this.ignoreSet.add(record.getLane());
873         }
874         Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
875         Set<RollingLaneStructureRecord> splitSet = new LinkedHashSet<>();
876         boolean expand = true;
877         while (expand)
878         {
879             expand = false;
880 
881             // longitudinal
882             // find links to extend from so we can add lanes if -any- of the next lanes comes within the perception distance
883             Set<Link> linksToExpandFrom = new LinkedHashSet<>();
884             Iterator<RollingLaneStructureRecord> iterator = this.downstreamEdge.iterator();
885             while (iterator.hasNext())
886             {
887                 RollingLaneStructureRecord record = iterator.next();
888                 if (record.getStartDistance().si + record.getLane().getLength().si < this.down.si)
889                 {
890                     linksToExpandFrom.add(record.getLane().getParentLink());
891                 }
892             }
893             Set<RollingLaneStructureRecord> modifiedEdge = new LinkedHashSet<>(this.downstreamEdge);
894             iterator = this.downstreamEdge.iterator();
895             while (iterator.hasNext())
896             {
897                 RollingLaneStructureRecord record = iterator.next();
898                 ImmutableMap<Lane, GTUDirectionality> nexts = record.getLane().downstreamLanes(record.getDirection(), gtuType);
899                 if (!linksToExpandFrom.contains(record.getLane().getParentLink()))
900                 {
901                     // downstream search ends on this lane
902                     record.setCutOffEnd(this.down.minus(record.getStartDistance()));
903                     for (Lane nextLane : nexts.keySet())
904                     {
905                         this.ignoreSet.add(nextLane); // exclude in lateral search
906                     }
907                 }
908                 else
909                 {
910                     // downstream search goes further downstream
911 
912                     // in case there are multiple lanes on the same link after a lane split, we need to choose one
913                     LaneDirection nextLaneDirection =
914                             new LaneDirection(record.getLane(), record.getDirection()).getNextLaneDirection(this.containingGtu);
915 
916                     record.clearCutOffEnd();
917                     iterator.remove(); // can remove from edge, no algorithm needs it anymore in the downstream edge
918                     for (Lane nextLane : nexts.keySet())
919                     {
920                         if (nextLaneDirection != null
921                                 && nextLane.getParentLink().equals(nextLaneDirection.getLane().getParentLink())
922                                 && !nextLane.equals(nextLaneDirection.getLane()))
923                         {
924                             // skip this lane as its a not chosen lane on the next link after a lane split
925                             continue;
926                         }
927                         RelativeLane relativeLane = this.relativeLanes.get(record);
928                         GTUDirectionality dir = nexts.get(nextLane);
929                         RollingLaneStructureRecord next = constructRecord(nextLane, dir, record, RecordLink.DOWN, relativeLane);
930                         this.ignoreSet.add(nextLane);
931                         next.updateStartDistance(fractionalPosition, this);
932                         record.addNext(next);
933                         next.addPrev(record);
934                         connectLaterally(next, gtuType, modifiedEdge);
935                         // check route
936                         int from = route == null ? 0 : route.indexOf(next.getFromNode());
937                         int to = route == null ? 1 : route.indexOf(next.getToNode());
938                         if (to < 0 || to - from != 1)
939                         {
940                             // not on our route, add some distance and stop
941                             splitSet.add(next);
942                         }
943                         else
944                         {
945                             // regular edge
946                             nextSet.add(next);
947                         }
948                         modifiedEdge.add(next);
949                         // expand upstream over any possible other lane that merges in to this
950                         Set<RollingLaneStructureRecord> set = new LinkedHashSet<>();
951                         set.add(next);
952                         expandUpstreamMerge(set, gtuType, fractionalPosition, route);
953                     }
954                 }
955             }
956             this.downstreamEdge.addAll(nextSet);
957             expand |= !nextSet.isEmpty();
958             nextSet.clear();
959 
960             // split
961             expandDownstreamSplit(splitSet, gtuType, fractionalPosition, route);
962             splitSet.clear();
963 
964             // lateral
965             Set<RollingLaneStructureRecord> lateralSet =
966                     expandLateral(this.downstreamEdge, RecordLink.LATERAL_END, gtuType, fractionalPosition);
967             nextSet.addAll(lateralSet);
968             expandUpstreamMerge(lateralSet, gtuType, fractionalPosition, route);
969 
970             // next iteration
971             this.downstreamEdge.addAll(nextSet);
972             expand |= !nextSet.isEmpty();
973             nextSet.clear();
974         }
975     }
976 
977     /**
978      * Expand the map to include a limited section downstream of a split, regarding links not on the route.
979      * @param set Set&lt;RollingLaneStructureRecord&gt;; set of lanes that have been laterally found
980      * @param gtuType GTUType; GTU type
981      * @param fractionalPosition double; fractional position on reference link
982      * @param route Route; route
983      * @throws GTUException on exception
984      */
985     private void expandDownstreamSplit(final Set<RollingLaneStructureRecord> set, final GTUType gtuType,
986             final double fractionalPosition, final Route route) throws GTUException
987     {
988         Map<RollingLaneStructureRecord, Length> prevs = new LinkedHashMap<>();
989         Map<RollingLaneStructureRecord, Length> nexts = new LinkedHashMap<>();
990         for (RollingLaneStructureRecord record : set)
991         {
992             prevs.put(record, record.getStartDistance().plus(this.downSplit));
993         }
994         while (!prevs.isEmpty())
995         {
996             for (RollingLaneStructureRecord prev : prevs.keySet())
997             {
998                 ImmutableMap<Lane, GTUDirectionality> nextLanes = prev.getLane().downstreamLanes(prev.getDirection(), gtuType);
999                 RelativeLane relativeLane = this.relativeLanes.get(prev);
1000                 for (Lane nextLane : nextLanes.keySet())
1001                 {
1002                     GTUDirectionality dir = nextLanes.get(nextLane);
1003                     Node fromNode =
1004                             dir.isPlus() ? nextLane.getParentLink().getStartNode() : nextLane.getParentLink().getEndNode();
1005                     Node toNode =
1006                             dir.isPlus() ? nextLane.getParentLink().getEndNode() : nextLane.getParentLink().getStartNode();
1007                     int from = route.indexOf(fromNode);
1008                     int to = route.indexOf(toNode);
1009                     if (from == -1 || to == -1 || to - from != 1)
1010                     {
1011                         RollingLaneStructureRecord next = constructRecord(nextLane, dir, prev, RecordLink.DOWN, relativeLane);
1012                         next.updateStartDistance(fractionalPosition, this);
1013                         next.addPrev(prev);
1014                         prev.addNext(next);
1015                         connectLaterally(next, gtuType, nexts.keySet());
1016                         Length downHere = prevs.get(prev);
1017                         if (next.getStartDistance().si > downHere.si)
1018                         {
1019                             next.setCutOffEnd(downHere.minus(next.getStartDistance()));
1020                         }
1021                         else
1022                         {
1023                             nexts.put(next, downHere);
1024                         }
1025                     }
1026                 }
1027             }
1028             prevs = nexts;
1029             nexts = new LinkedHashMap<>();
1030         }
1031     }
1032 
1033     /**
1034      * Expand the map to include a limited section upstream of a merge that is downstream, regarding links not on the route.
1035      * @param set Set&lt;RollingLaneStructureRecord&gt;; set of lanes that have been laterally found
1036      * @param gtuType GTUType; GTU type
1037      * @param fractionalPosition double; fractional position on reference link
1038      * @param route Route; route of the GTU
1039      * @throws GTUException on exception
1040      */
1041     private void expandUpstreamMerge(final Set<RollingLaneStructureRecord> set, final GTUType gtuType,
1042             final double fractionalPosition, final Route route) throws GTUException
1043     {
1044         Map<RollingLaneStructureRecord, Length> prevs = new LinkedHashMap<>();
1045         Map<RollingLaneStructureRecord, Length> nexts = new LinkedHashMap<>();
1046         for (RollingLaneStructureRecord record : set)
1047         {
1048             prevs.put(record, record.getStartDistance().plus(this.upMerge)); // upMerge is negative
1049         }
1050         while (!prevs.isEmpty())
1051         {
1052             for (RollingLaneStructureRecord prev : prevs.keySet())
1053             {
1054                 ImmutableMap<Lane, GTUDirectionality> nextLanes = prev.getLane().upstreamLanes(prev.getDirection(), gtuType);
1055                 boolean anyAdded = false;
1056                 for (Lane nextLane : nextLanes.keySet())
1057                 {
1058                     GTUDirectionality dir = nextLanes.get(nextLane);
1059                     Node fromNode =
1060                             dir.isPlus() ? nextLane.getParentLink().getStartNode() : nextLane.getParentLink().getEndNode();
1061                     Node toNode =
1062                             dir.isPlus() ? nextLane.getParentLink().getEndNode() : nextLane.getParentLink().getStartNode();
1063                     int from = route == null ? 0 : route.indexOf(fromNode);
1064                     int to = route == null ? 1 : route.indexOf(toNode);
1065                     // TODO we now assume everything is on the route, but merges could be ok without route
1066                     // so, without a route we should be able to recognize which upstream 'nextLane' is on the other link
1067                     if (from == -1 || to == -1 || to - from != 1)
1068                     {
1069                         anyAdded = true;
1070                         RelativeLane relativeLane = this.relativeLanes.get(prev);
1071                         RollingLaneStructureRecord next =
1072                                 constructRecord(nextLane, nextLanes.get(nextLane), prev, RecordLink.UP, relativeLane);
1073                         next.updateStartDistance(fractionalPosition, this);
1074                         next.addNext(prev);
1075                         prev.addPrev(next);
1076                         connectLaterally(next, gtuType, nexts.keySet());
1077                         Length upHere = prevs.get(prev);
1078                         if (next.getStartDistance().si < upHere.si)
1079                         {
1080                             next.setCutOffStart(upHere.minus(next.getStartDistance()));
1081                             this.upstreamEdge.add(next);
1082                         }
1083                         else
1084                         {
1085                             nexts.put(next, upHere);
1086                         }
1087                     }
1088                 }
1089                 if (!anyAdded && !set.contains(prev))
1090                 {
1091                     this.upstreamEdge.add(prev);
1092                 }
1093             }
1094             prevs = nexts;
1095             nexts = new LinkedHashMap<>();
1096         }
1097     }
1098 
1099     /**
1100      * Helper method of various other methods that laterally couples lanes that have been longitudinally found.
1101      * @param record RollingLaneStructureRecord; longitudinally found lane
1102      * @param gtuType GTUType; GTU type
1103      * @param nextSet Set&lt;RollingLaneStructureRecord&gt;; set of records on current build edge
1104      */
1105     private void connectLaterally(final RollingLaneStructureRecord record, final GTUType gtuType,
1106             final Set<RollingLaneStructureRecord> nextSet)
1107     {
1108         for (RollingLaneStructureRecord other : nextSet)
1109         {
1110             for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
1111                     LateralDirectionality.RIGHT })
1112             {
1113                 if ((latDirection.isLeft() ? other.getLeft() : other.getRight()) == null)
1114                 {
1115                     for (Lane otherLane : other.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType,
1116                             other.getDirection()))
1117                     {
1118                         if (otherLane.equals(record.getLane()))
1119                         {
1120                             if (latDirection.isLeft())
1121                             {
1122                                 other.setLeft(record, gtuType);
1123                                 record.setRight(other, gtuType);
1124                             }
1125                             else
1126                             {
1127                                 other.setRight(record, gtuType);
1128                                 record.setLeft(other, gtuType);
1129                             }
1130                         }
1131                     }
1132                 }
1133             }
1134         }
1135     }
1136 
1137     /**
1138      * Creates a lane structure record and adds it to relevant maps.
1139      * @param lane Lane; lane
1140      * @param direction GTUDirectionality; direction
1141      * @param startDistanceSource RollingLaneStructureRecord; source of the start distance
1142      * @param recordLink RecordLink; record link
1143      * @param relativeLane RelativeLane; relative lane
1144      * @return created lane structure record
1145      */
1146     private RollingLaneStructureRecord constructRecord(final Lane lane, final GTUDirectionality direction,
1147             final RollingLaneStructureRecord startDistanceSource, final RecordLink recordLink, final RelativeLane relativeLane)
1148     {
1149         RollingLaneStructureRecordn/RollingLaneStructureRecord.html#RollingLaneStructureRecord">RollingLaneStructureRecord record = new RollingLaneStructureRecord(lane, direction, startDistanceSource, recordLink);
1150         if (!this.relativeLaneMap.containsKey(relativeLane))
1151         {
1152             this.relativeLaneMap.put(relativeLane, new LinkedHashSet<>());
1153         }
1154         this.relativeLaneMap.get(relativeLane).add(record);
1155         this.relativeLanes.put(record, relativeLane);
1156         return record;
1157     }
1158 
1159     /** {@inheritDoc} */
1160     @Override
1161     public final LaneStructureRecord getRootRecord()
1162     {
1163         return this.root.get();
1164     }
1165 
1166     /**
1167      * @param time Time; time to obtain the root at
1168      * @return rootRecord
1169      */
1170     public final LaneStructureRecord getRootRecord(final Time time)
1171     {
1172         return this.root.get(time);
1173     }
1174 
1175     /** {@inheritDoc} */
1176     @Override
1177     public final SortedSet<RelativeLane> getExtendedCrossSection()
1178     {
1179         return this.firstRecords.navigableKeySet();
1180     }
1181 
1182     /**
1183      * Returns the first record on the given lane. This is often a record in the current cross section, but it may be one
1184      * downstream for a lane that starts further downstream.
1185      * @param lane RelativeLane; lane
1186      * @return first record on the given lane, or {@code null} if no such record
1187      */
1188     @Override
1189     public final RollingLaneStructureRecord getFirstRecord(final RelativeLane lane)
1190     {
1191         if (this.firstRecords.containsKey(lane))
1192         {
1193             return this.firstRecords.get(lane);
1194         }
1195         // not in current cross section, get first via downstream
1196         RelativeLane rel = RelativeLane.CURRENT;
1197         int dMin = Integer.MAX_VALUE;
1198         for (RelativeLane relLane : this.crossSectionRecords.keySet())
1199         {
1200             if (relLane.getLateralDirectionality().equals(lane.getLateralDirectionality()))
1201             {
1202                 int d = lane.getNumLanes() - relLane.getNumLanes();
1203                 if (d < dMin)
1204                 {
1205                     rel = relLane;
1206                     d = dMin;
1207                 }
1208             }
1209         }
1210         RollingLaneStructureRecord record = this.crossSectionRecords.get(rel);
1211         // move downstream until a lateral move is made to the right relative lane
1212         while (rel.getNumLanes() < lane.getNumLanes())
1213         {
1214             RollingLaneStructureRecord adj = lane.getLateralDirectionality().isLeft() ? record.getLeft() : record.getRight();
1215             if (adj != null)
1216             {
1217                 rel = lane.getLateralDirectionality().isLeft() ? rel.getLeft() : rel.getRight();
1218                 record = adj;
1219             }
1220             else if (!record.getNext().isEmpty())
1221             {
1222                 LaneDirection laneDir =
1223                         new LaneDirection(record.getLane(), record.getDirection()).getNextLaneDirection(this.containingGtu);
1224                 if (laneDir == null)
1225                 {
1226                     record = null;
1227                     break;
1228                 }
1229                 RollingLaneStructureRecord chosenNext = null;
1230                 for (RollingLaneStructureRecord next : record.getNext())
1231                 {
1232                     if (next.getLane().equals(laneDir.getLane()))
1233                     {
1234                         chosenNext = next;
1235                         break;
1236                     }
1237                 }
1238                 // Throw.when(chosenNext == null, RuntimeException.class,
1239                 // "Unexpected exception while deriving first record not on the cross-section.");
1240                 record = chosenNext;
1241                 if (record == null)
1242                 {
1243                     // TODO: Temporary fix for Aimsun demo
1244                     break;
1245                 }
1246             }
1247             else
1248             {
1249                 // reached a dead-end
1250                 record = null;
1251                 break;
1252             }
1253         }
1254         if (record != null)
1255         {
1256             // now move upstream until we are at x = 0
1257             while (record.getPrev().size() == 1 && record.getStartDistance().gt0())
1258             {
1259                 record = record.getPrev().get(0);
1260             }
1261             this.firstRecords.put(lane, record);
1262         }
1263         return record;
1264     }
1265 
1266     /**
1267      * Retrieve objects of a specific type. Returns objects over a maximum length of the look ahead distance downstream from the
1268      * relative position, or as far as the lane map goes.
1269      * @param clazz Class&lt;T&gt;; class of objects to find
1270      * @param gtu LaneBasedGTU; gtu
1271      * @param pos RelativePosition.TYPE; relative position to start search from
1272      * @param <T> type of objects to find
1273      * @return Sorted set of objects of requested type per lane
1274      * @throws GTUException if lane is not in current set
1275      */
1276     @Override
1277     public final <T extends LaneBasedObject> Map<RelativeLane, SortedSet<Entry<T>>> getDownstreamObjects(final Class<T> clazz,
1278             final LaneBasedGTU gtu, final RelativePosition.TYPE pos) throws GTUException
1279     {
1280         Map<RelativeLane, SortedSet<Entry<T>>> out = new LinkedHashMap<>();
1281         for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
1282         {
1283             out.put(relativeLane, getDownstreamObjects(relativeLane, clazz, gtu, pos));
1284         }
1285         return out;
1286     }
1287 
1288     /**
1289      * Retrieve objects on a lane of a specific type. Returns objects over a maximum length of the look ahead distance
1290      * downstream from the relative position, or as far as the lane map goes.
1291      * @param lane RelativeLane; lane
1292      * @param clazz Class&lt;T&gt;; class of objects to find
1293      * @param gtu LaneBasedGTU; gtu
1294      * @param pos RelativePosition.TYPE; relative position to start search from
1295      * @param <T> type of objects to find
1296      * @return Sorted set of objects of requested type
1297      * @throws GTUException if lane is not in current set
1298      */
1299     @Override
1300     @SuppressWarnings("unchecked")
1301     public final <T extends LaneBasedObject> SortedSet<Entry<T>> getDownstreamObjects(final RelativeLane lane,
1302             final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos) throws GTUException
1303     {
1304         LaneStructureRecord record = getFirstRecord(lane);
1305         SortedSet<Entry<T>> set = new TreeSet<>();
1306         if (record != null)
1307         {
1308             double ds = gtu.getRelativePositions().get(pos).getDx().si - gtu.getReference().getDx().si;
1309             if (record.isDownstreamBranch())
1310             {
1311                 // the list is ordered, but only for DIR_PLUS, need to do our own ordering
1312                 Length minimumPosition;
1313                 Length maximumPosition;
1314                 if (record.getDirection().isPlus())
1315                 {
1316                     minimumPosition = Length.instantiateSI(ds - record.getStartDistance().si);
1317                     maximumPosition = Length.instantiateSI(record.getLane().getLength().si);
1318                 }
1319                 else
1320                 {
1321                     minimumPosition = Length.ZERO;
1322                     maximumPosition = Length.instantiateSI(record.getLane().getLength().si + record.getStartDistance().si - ds);
1323                 }
1324 
1325                 for (LaneBasedObject object : record.getLane().getLaneBasedObjects(minimumPosition, maximumPosition))
1326                 {
1327                     if (clazz.isAssignableFrom(object.getClass())
1328                             && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
1329                                     || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
1330                     {
1331                         // unchecked, but the above isAssignableFrom assures correctness
1332                         double distance = record.getDistanceToPosition(object.getLongitudinalPosition()).si - ds;
1333                         if (distance <= this.lookAhead.si)
1334                         {
1335                             set.add(new Entry<>(Length.instantiateSI(distance), (T) object));
1336                         }
1337                     }
1338                 }
1339             }
1340             getDownstreamObjectsRecursive(set, record, clazz, ds);
1341         }
1342         return set;
1343     }
1344 
1345     /**
1346      * Retrieve objects on a lane of a specific type. Returns objects over a maximum length of the look ahead distance
1347      * downstream from the relative position, or as far as the lane map goes. Objects on links not on the route are ignored.
1348      * @param lane RelativeLane; lane
1349      * @param clazz Class&lt;T&gt;; class of objects to find
1350      * @param gtu LaneBasedGTU; gtu
1351      * @param pos RelativePosition.TYPE; relative position to start search from
1352      * @param <T> type of objects to find
1353      * @param route Route; the route
1354      * @return Sorted set of objects of requested type
1355      * @throws GTUException if lane is not in current set
1356      */
1357     @Override
1358     public final <T extends LaneBasedObject> SortedSet<Entry<T>> getDownstreamObjectsOnRoute(final RelativeLane lane,
1359             final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos, final Route route)
1360             throws GTUException
1361     {
1362         SortedSet<Entry<T>> set = getDownstreamObjects(lane, clazz, gtu, pos);
1363         if (route != null)
1364         {
1365             Iterator<Entry<T>> iterator = set.iterator();
1366             while (iterator.hasNext())
1367             {
1368                 Entry<T> entry = iterator.next();
1369                 CrossSectionLink link = entry.getLaneBasedObject().getLane().getParentLink();
1370                 if (!route.contains(link.getStartNode()) || !route.contains(link.getEndNode())
1371                         || Math.abs(route.indexOf(link.getStartNode()) - route.indexOf(link.getEndNode())) != 1)
1372                 {
1373                     iterator.remove();
1374                 }
1375             }
1376         }
1377         return set;
1378     }
1379 
1380     /**
1381      * Recursive search for lane based objects downstream.
1382      * @param set SortedSet&lt;Entry&lt;T&gt;&gt;; set to store entries into
1383      * @param record LaneStructureRecord; current record
1384      * @param clazz Class&lt;T&gt;; class of objects to find
1385      * @param ds double; distance from reference to chosen relative position
1386      * @param <T> type of objects to find
1387      */
1388     @SuppressWarnings("unchecked")
1389     private <T extends LaneBasedObject> void getDownstreamObjectsRecursive(final SortedSet<Entry<T>> set,
1390             final LaneStructureRecord record, final Class<T> clazz, final double ds)
1391     {
1392         if (record.getNext().isEmpty() || record.getNext().get(0).getStartDistance().gt(this.lookAhead))
1393         {
1394             return;
1395         }
1396         for (LaneStructureRecord next : record.getNext())
1397         {
1398             if (next.isDownstreamBranch())
1399             {
1400                 List<LaneBasedObject> list = next.getLane().getLaneBasedObjects();
1401                 int iStart, di;
1402                 if (record.getDirection().isPlus())
1403                 {
1404                     iStart = 0;
1405                     di = 1;
1406                 }
1407                 else
1408                 {
1409                     iStart = list.size() - 1;
1410                     di = -1;
1411                 }
1412                 for (int i = iStart; i >= 0 & i < list.size(); i += di)
1413                 {
1414                     LaneBasedObject object = list.get(i);
1415                     if (clazz.isAssignableFrom(object.getClass())
1416                             && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
1417                                     || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
1418                     {
1419                         // unchecked, but the above isAssignableFrom assures correctness
1420                         double distance = next.getDistanceToPosition(object.getLongitudinalPosition()).si - ds;
1421                         if (distance <= this.lookAhead.si)
1422                         {
1423                             set.add(new Entry<>(Length.instantiateSI(distance), (T) object));
1424                         }
1425                         else
1426                         {
1427                             return;
1428                         }
1429                     }
1430                 }
1431             }
1432             getDownstreamObjectsRecursive(set, next, clazz, ds);
1433         }
1434     }
1435 
1436     /**
1437      * Retrieve objects of a specific type. Returns objects over a maximum length of the look ahead distance downstream from the
1438      * relative position, or as far as the lane map goes. Objects on links not on the route are ignored.
1439      * @param clazz Class&lt;T&gt;; class of objects to find
1440      * @param gtu LaneBasedGTU; gtu
1441      * @param pos RelativePosition.TYPE; relative position to start search from
1442      * @param <T> type of objects to find
1443      * @param route Route; the route
1444      * @return Sorted set of objects of requested type per lane
1445      * @throws GTUException if lane is not in current set
1446      */
1447     @Override
1448     public final <T extends LaneBasedObject> Map<RelativeLane, SortedSet<Entry<T>>> getDownstreamObjectsOnRoute(
1449             final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos, final Route route)
1450             throws GTUException
1451     {
1452         Map<RelativeLane, SortedSet<Entry<T>>> out = new LinkedHashMap<>();
1453         for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
1454         {
1455             out.put(relativeLane, getDownstreamObjectsOnRoute(relativeLane, clazz, gtu, pos, route));
1456         }
1457         return out;
1458     }
1459 
1460     /**
1461      * Retrieve objects on a lane of a specific type. Returns upstream objects from the relative position for as far as the lane
1462      * map goes. Distances to upstream objects are given as positive values.
1463      * @param lane RelativeLane; lane
1464      * @param clazz Class&lt;T&gt;; class of objects to find
1465      * @param gtu LaneBasedGTU; gtu
1466      * @param pos RelativePosition.TYPE; relative position to start search from
1467      * @param <T> type of objects to find
1468      * @return Sorted set of objects of requested type
1469      * @throws GTUException if lane is not in current set
1470      */
1471     @Override
1472     @SuppressWarnings("unchecked")
1473     public final <T extends LaneBasedObject> SortedSet<Entry<T>> getUpstreamObjects(final RelativeLane lane,
1474             final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos) throws GTUException
1475     {
1476         SortedSet<Entry<T>> set = new TreeSet<>();
1477         LaneStructureRecord record = this.getFirstRecord(lane);
1478         if (record.getStartDistance().gt0())
1479         {
1480             return set; // this lane is only downstream
1481         }
1482         Length ds = gtu.getReference().getDx().minus(gtu.getRelativePositions().get(pos).getDx());
1483         // the list is ordered, but only for DIR_PLUS, need to do our own ordering
1484         Length minimumPosition;
1485         Length maximumPosition;
1486         if (record.getDirection().isPlus())
1487         {
1488             minimumPosition = Length.ZERO;
1489             maximumPosition = record.getStartDistance().neg().minus(ds);
1490         }
1491         else
1492         {
1493             minimumPosition = record.getLane().getLength().plus(record.getStartDistance()).plus(ds);
1494             maximumPosition = record.getLane().getLength();
1495         }
1496         Length distance;
1497         for (LaneBasedObject object : record.getLane().getLaneBasedObjects(minimumPosition, maximumPosition))
1498         {
1499             if (clazz.isAssignableFrom(object.getClass())
1500                     && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
1501                             || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
1502             {
1503                 distance = record.getDistanceToPosition(object.getLongitudinalPosition()).neg().minus(ds);
1504                 // unchecked, but the above isAssignableFrom assures correctness
1505                 set.add(new Entry<>(distance, (T) object));
1506             }
1507         }
1508         getUpstreamObjectsRecursive(set, record, clazz, ds);
1509         return set;
1510     }
1511 
1512     /**
1513      * Recursive search for lane based objects upstream.
1514      * @param set SortedSet&lt;Entry&lt;T&gt;&gt;; set to store entries into
1515      * @param record LaneStructureRecord; current record
1516      * @param clazz Class&lt;T&gt;; class of objects to find
1517      * @param ds Length; distance from reference to chosen relative position
1518      * @param <T> type of objects to find
1519      */
1520     @SuppressWarnings("unchecked")
1521     private <T extends LaneBasedObject> void getUpstreamObjectsRecursive(final SortedSet<Entry<T>> set,
1522             final LaneStructureRecord record, final Class<T> clazz, final Length ds)
1523     {
1524         for (LaneStructureRecord prev : record.getPrev())
1525         {
1526             Length distance;
1527             for (LaneBasedObject object : prev.getLane().getLaneBasedObjects())
1528             {
1529                 if (clazz.isAssignableFrom(object.getClass())
1530                         && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
1531                                 || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
1532                 {
1533                     distance = prev.getDistanceToPosition(object.getLongitudinalPosition()).neg().minus(ds);
1534                     // unchecked, but the above isAssignableFrom assures correctness
1535                     set.add(new Entry<>(distance, (T) object));
1536                 }
1537             }
1538             getUpstreamObjectsRecursive(set, prev, clazz, ds);
1539         }
1540     }
1541 
1542     /**
1543      * Print the lane structure as a number of lines in a String.
1544      * @param ls RollingLaneStructure; the lane structure to print
1545      * @param gtu LaneBasedGTU; the GTTU for which the lane structure is printed
1546      * @return a String with information about the RollingLaneStructire
1547      */
1548     public static String print(final RollingLaneStructure ls, final LaneBasedGTU gtu)
1549     {
1550         StringBuffer s = new StringBuffer();
1551         s.append(gtu.getSimulator().getSimulatorTime() + " " + gtu.getId() + " LANESTRUCTURE: ");
1552         for (LaneStructureRecord lsr : ls.relativeLanes.keySet())
1553         {
1554             s.append(lsr.toString() + "  ");
1555         }
1556         int totSize = 0;
1557         for (Set<RollingLaneStructureRecord> set : ls.relativeLaneMap.values())
1558         {
1559             totSize += set.size();
1560         }
1561         s.append("\n  relativeLanes.size()=" + ls.relativeLanes.size() + "  relativeLaneMap.totalSize()=" + totSize);
1562         return s.toString();
1563     }
1564 
1565     /** {@inheritDoc} */
1566     @Override
1567     public final String toString()
1568     {
1569         return "LaneStructure [rootLSR=" + this.root + "]";
1570     }
1571 
1572     /**
1573      * AnimationAccess provides access to a number of private fields in the structure, which should only be used read-only! <br>
1574      * <br>
1575      * Copyright (c) 2003-2019 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
1576      * See for project information <a href="https://www.simulation.tudelft.nl/" target="_blank">www.simulation.tudelft.nl</a>.
1577      * The source code and binary code of this software is proprietary information of Delft University of Technology.
1578      * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank">Alexander Verbraeck</a>
1579      */
1580     public class AnimationAccess
1581     {
1582         /**
1583          * @return the lane structure records of the cross section
1584          */
1585         @SuppressWarnings("synthetic-access")
1586         public TreeMap<RelativeLane, RollingLaneStructureRecord> getCrossSectionRecords()
1587         {
1588             return RollingLaneStructure.this.crossSectionRecords;
1589         }
1590 
1591         /**
1592          * @return the upstream edge
1593          */
1594         @SuppressWarnings("synthetic-access")
1595         public Set<RollingLaneStructureRecord> getUpstreamEdge()
1596         {
1597             return RollingLaneStructure.this.upstreamEdge;
1598         }
1599 
1600         /**
1601          * @return the downstream edge
1602          */
1603         @SuppressWarnings("synthetic-access")
1604         public Set<RollingLaneStructureRecord> getDownstreamEdge()
1605         {
1606             return RollingLaneStructure.this.downstreamEdge;
1607         }
1608     }
1609 
1610     /** {@inheritDoc} */
1611     @Override
1612     public void notify(final EventInterface event) throws RemoteException
1613     {
1614         // triggers an update of the lane structure at the end of the final plan during the lane change, which is deviative
1615         this.previouslyDeviative = false;
1616     }
1617 }