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