1   package org.opentrafficsim.road.gtu.lane.perception;
2   
3   import java.io.Serializable;
4   import java.util.HashMap;
5   import java.util.HashSet;
6   import java.util.Iterator;
7   import java.util.LinkedHashMap;
8   import java.util.LinkedHashSet;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Set;
12  import java.util.SortedSet;
13  import java.util.TreeMap;
14  import java.util.TreeSet;
15  
16  import org.djunits.value.vdouble.scalar.Length;
17  import org.djunits.value.vdouble.scalar.Time;
18  import org.djutils.exceptions.Throw;
19  import org.opentrafficsim.core.gtu.GTUDirectionality;
20  import org.opentrafficsim.core.gtu.GTUException;
21  import org.opentrafficsim.core.gtu.GTUType;
22  import org.opentrafficsim.core.gtu.RelativePosition;
23  import org.opentrafficsim.core.network.LateralDirectionality;
24  import org.opentrafficsim.core.network.Node;
25  import org.opentrafficsim.core.network.route.Route;
26  import org.opentrafficsim.core.perception.Historical;
27  import org.opentrafficsim.core.perception.HistoricalValue;
28  import org.opentrafficsim.core.perception.HistoryManager;
29  import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
30  import org.opentrafficsim.road.gtu.lane.perception.RollingLaneStructureRecord.RecordLink;
31  import org.opentrafficsim.road.network.lane.CrossSectionLink;
32  import org.opentrafficsim.road.network.lane.DirectedLanePosition;
33  import org.opentrafficsim.road.network.lane.Lane;
34  import org.opentrafficsim.road.network.lane.LaneDirection;
35  import org.opentrafficsim.road.network.lane.object.LaneBasedObject;
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100 
101 
102 public class RollingLaneStructure implements LaneStructure, Serializable
103 {
104     
105     private static final long serialVersionUID = 20160400L;
106 
107     
108     private final Historical<RollingLaneStructureRecord> root;
109 
110     
111     private Length lookAhead;
112 
113     
114     private Route previousRoute;
115 
116     
117     private TreeMap<RelativeLane, RollingLaneStructureRecord> crossSectionRecords = new TreeMap<>();
118 
119     
120     private TreeMap<RelativeLane, RollingLaneStructureRecord> firstRecords = new TreeMap<>();
121 
122     
123     private Map<RelativeLane, Set<RollingLaneStructureRecord>> relativeLaneMap = new HashMap<>();
124 
125     
126     private Map<LaneStructureRecord, RelativeLane> relativeLanes = new HashMap<>();
127 
128     
129     private final Set<Lane> ignoreSet = new HashSet<>();
130 
131     
132     private final Set<RollingLaneStructureRecord> upstreamEdge = new LinkedHashSet<>();
133 
134     
135     private final Set<RollingLaneStructureRecord> downstreamEdge = new LinkedHashSet<>();
136 
137     
138     private final Length down;
139 
140     
141     private final Length up;
142 
143     
144     private final Length downSplit;
145 
146     
147     private final Length upMerge;
148 
149     
150     private final LaneBasedGTU containingGtu;
151 
152     
153     @SuppressWarnings("checkstyle:visibilitymodifier")
154     public AnimationAccess animationAccess = new AnimationAccess();
155 
156     
157 
158 
159 
160 
161 
162 
163 
164 
165     public RollingLaneStructure(final Length lookAhead, final Length down, final Length up, final Length downSplit,
166             final Length upMerge, final LaneBasedGTU gtu)
167     {
168         HistoryManager historyManager = gtu.getSimulator().getReplication().getHistoryManager(gtu.getSimulator());
169         this.root = new HistoricalValue<>(historyManager);
170         this.lookAhead = lookAhead;
171         this.down = down;
172         this.up = up;
173         this.downSplit = downSplit;
174         this.upMerge = upMerge;
175         this.containingGtu = gtu;
176         
177         
178         
179         
180     }
181 
182     
183 
184 
185 
186 
187 
188 
189     @Override
190     public final void update(final DirectedLanePosition pos, final Route route, final GTUType gtuType) throws GTUException
191     {
192 
193         
194 
195 
196 
197 
198 
199 
200 
201 
202         
203         Lane lane = pos.getLane();
204         GTUDirectionality direction = pos.getGtuDirection();
205         Length position = pos.getPosition();
206         double fracPos = direction.isPlus() ? position.si / lane.getLength().si : 1.0 - position.si / lane.getLength().si;
207 
208         
209         if (this.previousRoute != route || this.root.get() == null)
210         {
211             
212             this.previousRoute = route;
213 
214             
215             this.upstreamEdge.clear();
216             this.downstreamEdge.clear();
217             this.crossSectionRecords.clear();
218             this.relativeLanes.clear();
219 
220             
221             RollingLaneStructureRecord newRoot = constructRecord(lane, direction, null, RecordLink.CROSS, RelativeLane.CURRENT);
222             this.root.set(newRoot);
223             this.crossSectionRecords.put(RelativeLane.CURRENT, newRoot);
224             for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
225                     LateralDirectionality.RIGHT })
226             {
227                 RollingLaneStructureRecord current = newRoot;
228                 RelativeLane relativeLane = RelativeLane.CURRENT;
229                 Set<Lane> adjacentLanes =
230                         current.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, current.getDirection());
231                 while (!adjacentLanes.isEmpty())
232                 {
233                     Throw.when(adjacentLanes.size() > 1, RuntimeException.class,
234                             "Multiple adjacent lanes encountered during construction of lane map.");
235                     relativeLane = latDirection.isLeft() ? relativeLane.getLeft() : relativeLane.getRight();
236                     Lane adjacentLane = adjacentLanes.iterator().next();
237                     RollingLaneStructureRecord adjacentRecord =
238                             constructRecord(adjacentLane, direction, current, RecordLink.CROSS, relativeLane);
239                     this.crossSectionRecords.put(relativeLane, adjacentRecord);
240                     if (latDirection.isLeft())
241                     {
242                         if (adjacentLane
243                                 .accessibleAdjacentLanesPhysical(LateralDirectionality.RIGHT, gtuType, current.getDirection())
244                                 .contains(current.getLane()))
245                         {
246                             adjacentRecord.setRight(current, gtuType);
247                         }
248                         current.setLeft(adjacentRecord, gtuType);
249                     }
250                     else
251                     {
252                         if (adjacentLane
253                                 .accessibleAdjacentLanesPhysical(LateralDirectionality.LEFT, gtuType, current.getDirection())
254                                 .contains(current.getLane()))
255                         {
256                             adjacentRecord.setLeft(current, gtuType);
257                         }
258                         current.setRight(adjacentRecord, gtuType);
259                     }
260                     current = adjacentRecord;
261                     adjacentLanes =
262                             current.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, current.getDirection());
263                 }
264             }
265             this.upstreamEdge.addAll(this.crossSectionRecords.values());
266             this.downstreamEdge.addAll(this.crossSectionRecords.values());
267             this.firstRecords.putAll(this.crossSectionRecords);
268 
269             
270             newRoot.updateStartDistance(fracPos, this);
271 
272             
273             expandUpstreamEdge(gtuType, fracPos);
274 
275             
276             deriveFirstRecords();
277         }
278         else
279         {
280 
281             
282             RollingLaneStructureRecord newRoot = this.root.get();
283             if (!lane.equals(newRoot.getLane()))
284             {
285 
286                 
287                 newRoot = null;
288                 RelativeLane lateralMove = null;
289                 double closest = Double.POSITIVE_INFINITY;
290                 for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
291                 {
292                     for (RollingLaneStructureRecord record : this.relativeLaneMap.get(relativeLane))
293                     {
294                         if (record.getLane().equals(lane) && record.getStartDistance().si < closest
295                                 && record.getStartDistance().si + record.getLength().si > 0.0)
296                         {
297                             newRoot = record;
298                             lateralMove = relativeLane;
299                             
300                             closest = record.getStartDistance().si;
301                         }
302                     }
303                 }
304                 
305                 this.root.set(newRoot);
306 
307                 
308                 updateStartDistanceSources();
309 
310                 
311                 if (!lateralMove.isCurrent())
312                 {
313                     RelativeLane delta =
314                             new RelativeLane(lateralMove.getLateralDirectionality().flip(), lateralMove.getNumLanes());
315 
316                     TreeMap<RelativeLane, Set<RollingLaneStructureRecord>> newRelativeLaneMap = new TreeMap<>();
317                     for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
318                     {
319                         RelativeLane newRelativeLane = relativeLane.add(delta);
320                         newRelativeLaneMap.put(newRelativeLane, this.relativeLaneMap.get(relativeLane));
321                     }
322                     this.relativeLaneMap = newRelativeLaneMap;
323 
324                     Map<LaneStructureRecord, RelativeLane> newRelativeLanes = new HashMap<>();
325                     for (LaneStructureRecord record : this.relativeLanes.keySet())
326                     {
327                         newRelativeLanes.put(record, this.relativeLanes.get(record).add(delta));
328                     }
329                     this.relativeLanes = newRelativeLanes;
330                 }
331 
332                 this.crossSectionRecords.clear();
333                 this.crossSectionRecords.put(RelativeLane.CURRENT, newRoot);
334                 for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
335                         LateralDirectionality.RIGHT })
336                 {
337                     RollingLaneStructureRecord record = newRoot;
338                     RollingLaneStructureRecord next = newRoot;
339                     RelativeLane delta = new RelativeLane(latDirection, 1);
340                     RelativeLane relLane = RelativeLane.CURRENT;
341                     while (next != null)
342                     {
343                         next = latDirection.isLeft() ? record.getLeft() : record.getRight();
344                         if (next != null)
345                         {
346                             next.changeStartDistanceSource(record, RecordLink.CROSS);
347                             relLane = relLane.add(delta);
348                             this.crossSectionRecords.put(relLane, next);
349                             record = next;
350                         }
351                     }
352                 }
353 
354             }
355             newRoot.updateStartDistance(fracPos, this);
356 
357             
358             retreatUpstreamEdge();
359 
360             
361             deriveFirstRecords();
362 
363         }
364 
365         
366         expandDownstreamEdge(gtuType, fracPos, route);
367 
368     }
369 
370     
371 
372 
373     private void deriveFirstRecords()
374     {
375         this.firstRecords.clear();
376         this.firstRecords.putAll(this.crossSectionRecords);
377         for (RelativeLane lane : this.relativeLaneMap.keySet())
378         {
379             getFirstRecord(lane); 
380         }
381     }
382 
383     
384 
385 
386 
387 
388 
389 
390     private void updateStartDistanceSources()
391     {
392 
393         
394         Set<RollingLaneStructureRecord> set = new HashSet<>();
395         RollingLaneStructureRecord rootRecord = this.root.get();
396         set.add(rootRecord);
397         rootRecord.changeStartDistanceSource(null, RecordLink.CROSS);
398         RollingLaneStructureRecord prev = rootRecord;
399         RollingLaneStructureRecord next = prev.getLeft();
400         while (next != null)
401         {
402             set.add(next);
403             next.changeStartDistanceSource(prev, RecordLink.CROSS);
404             prev = next;
405             next = next.getLeft();
406         }
407         prev = rootRecord;
408         next = prev.getRight();
409         while (next != null)
410         {
411             set.add(next);
412             next.changeStartDistanceSource(prev, RecordLink.CROSS);
413             prev = next;
414             next = next.getRight();
415         }
416         
417         while (!set.isEmpty())
418         {
419             
420             Set<RollingLaneStructureRecord> newSet = new HashSet<>();
421             for (RollingLaneStructureRecord record : set)
422             {
423                 for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
424                         LateralDirectionality.RIGHT })
425                 {
426                     prev = record;
427                     next = latDirection.isLeft() ? record.getLeft() : record.getRight();
428                     while (next != null && !set.contains(next))
429                     {
430                         next.changeStartDistanceSource(prev, RecordLink.LATERAL_END);
431                         removeDownstream(next, latDirection.flip()); 
432                         newSet.add(next);
433                         prev = next;
434                         next = latDirection.isLeft() ? next.getLeft() : next.getRight();
435                     }
436                 }
437             }
438             set.addAll(newSet);
439 
440             
441             newSet.clear();
442             for (RollingLaneStructureRecord record : set)
443             {
444                 for (RollingLaneStructureRecord prevRecord : record.getPrev())
445                 {
446                     Iterator<RollingLaneStructureRecord> it = prevRecord.getNext().iterator();
447                     while (it.hasNext())
448                     {
449                         RollingLaneStructureRecord otherDown = it.next();
450                         if (!otherDown.getLane().getParentLink().equals(record.getLane().getParentLink()))
451                         {
452                             
453                             otherDown.changeStartDistanceSource(null, null);
454                             
455                             removeDownstream(otherDown, LateralDirectionality.NONE);
456                             removeRecord(otherDown);
457                             it.remove();
458                         }
459                     }
460                     LaneStructureRecord source = prevRecord.getStartDistanceSource();
461                     if (source == null || (source != null && !source.equals(record)))
462                     {
463                         prevRecord.changeStartDistanceSource(record, RecordLink.UP);
464                         newSet.add(prevRecord);
465                     }
466                 }
467             }
468 
469             
470             set = newSet;
471         }
472     }
473 
474     
475 
476 
477 
478 
479     private void removeDownstream(final RollingLaneStructureRecord record, final LateralDirectionality lat)
480     {
481         for (RollingLaneStructureRecord next : record.getNext())
482         {
483             RollingLaneStructureRecord adj = lat.isLeft() ? next.getLeft() : lat.isRight() ? next.getRight() : null;
484             if (adj == null)
485             {
486                 next.changeStartDistanceSource(null, null);
487                 removeDownstream(next, lat);
488                 removeRecord(next);
489             }
490         }
491         record.clearNextList();
492     }
493 
494     
495 
496 
497 
498     private void removeRecord(final RollingLaneStructureRecord record)
499     {
500         RelativeLane lane = this.relativeLanes.get(record);
501         if (lane != null)
502         {
503             this.relativeLanes.remove(record);
504         }
505         record.changeStartDistanceSource(null, null);
506 
507     }
508 
509     
510 
511 
512 
513 
514 
515     private void expandUpstreamEdge(final GTUType gtuType, final double fractionalPosition) throws GTUException
516     {
517         this.ignoreSet.clear();
518         for (LaneStructureRecord record : this.upstreamEdge)
519         {
520             this.ignoreSet.add(record.getLane());
521         }
522         Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
523         boolean expand = true;
524         while (expand)
525         {
526             expand = false;
527 
528             
529             Iterator<RollingLaneStructureRecord> iterator = this.upstreamEdge.iterator();
530             Set<RollingLaneStructureRecord> modifiedEdge = new LinkedHashSet<>(this.upstreamEdge);
531             while (iterator.hasNext())
532             {
533                 RollingLaneStructureRecord prev = iterator.next();
534                 Map<Lane, GTUDirectionality> nexts = prev.getLane().upstreamLanes(prev.getDirection(), gtuType);
535                 if (prev.getStartDistance().si < this.up.si)
536                 {
537                     
538                     prev.setCutOffStart(this.up.minus(prev.getStartDistance()));
539                     for (Lane prevLane : nexts.keySet())
540                     {
541                         this.ignoreSet.add(prevLane); 
542                     }
543                 }
544                 else
545                 {
546                     
547                     prev.clearCutOffStart();
548                     iterator.remove();
549                     if (!nexts.isEmpty())
550                     {
551                         for (Lane prevLane : nexts.keySet())
552                         {
553                             RelativeLane relativeLane = this.relativeLanes.get(prev);
554                             RollingLaneStructureRecord next =
555                                     constructRecord(prevLane, nexts.get(prevLane), prev, RecordLink.UP, relativeLane);
556                             this.ignoreSet.add(prevLane);
557                             next.updateStartDistance(fractionalPosition, this);
558                             connectLaterally(next, gtuType, modifiedEdge);
559                             next.addNext(prev);
560                             prev.addPrev(next);
561                             nextSet.add(next);
562                             modifiedEdge.add(next);
563                         }
564                     }
565                 }
566             }
567             this.upstreamEdge.addAll(nextSet);
568             expand |= !nextSet.isEmpty();
569             nextSet.clear();
570 
571             
572             Set<RollingLaneStructureRecord> lateralSet =
573                     expandLateral(this.upstreamEdge, RecordLink.LATERAL_END, gtuType, fractionalPosition);
574             nextSet.addAll(lateralSet);
575 
576             
577             this.upstreamEdge.addAll(nextSet);
578             expand |= !nextSet.isEmpty();
579             nextSet.clear();
580         }
581     }
582 
583     
584 
585 
586 
587 
588 
589 
590 
591 
592     private Set<RollingLaneStructureRecord> expandLateral(final Set<RollingLaneStructureRecord> edge,
593             final RecordLink recordLink, final GTUType gtuType, final double fractionalPosition)
594     {
595         Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
596         Set<Lane> laneSet = new HashSet<>(); 
597         for (LaneStructureRecord record : edge)
598         {
599             laneSet.add(record.getLane());
600         }
601         Iterator<RollingLaneStructureRecord> iterator = edge.iterator();
602         while (iterator.hasNext())
603         {
604             RollingLaneStructureRecord record = iterator.next();
605             for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
606                     LateralDirectionality.RIGHT })
607             {
608                 RelativeLane relativeLane = this.relativeLanes.get(record);
609                 RollingLaneStructureRecord prev = record;
610                 Set<Lane> adjacentLanes =
611                         prev.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, prev.getDirection());
612                 while (!adjacentLanes.isEmpty())
613                 {
614                     Throw.when(adjacentLanes.size() > 1, RuntimeException.class,
615                             "Multiple adjacent lanes encountered during construction of lane map.");
616                     relativeLane = latDirection.isLeft() ? relativeLane.getLeft() : relativeLane.getRight();
617                     Lane nextLane = adjacentLanes.iterator().next();
618                     if (!laneSet.contains(nextLane) && !this.ignoreSet.contains(nextLane))
619                     {
620                         RollingLaneStructureRecord next =
621                                 constructRecord(nextLane, record.getDirection(), prev, recordLink, relativeLane);
622                         this.ignoreSet.add(nextLane);
623                         next.updateStartDistance(fractionalPosition, this);
624                         nextSet.add(next);
625                         laneSet.add(nextLane);
626                         if (latDirection.isLeft())
627                         {
628                             prev.setLeft(next, gtuType);
629                             if (nextLane
630                                     .accessibleAdjacentLanesPhysical(LateralDirectionality.RIGHT, gtuType, prev.getDirection())
631                                     .contains(prev.getLane()))
632                             {
633                                 next.setRight(prev, gtuType);
634                             }
635                             for (RollingLaneStructureRecord edgeRecord : edge)
636                             {
637                                 if (!edgeRecord.equals(prev)
638                                         && edgeRecord.getLane().getParentLink().equals(next.getLane().getParentLink()))
639                                 {
640                                     for (Lane adjLane : edgeRecord.getLane().accessibleAdjacentLanesPhysical(
641                                             LateralDirectionality.RIGHT, gtuType, edgeRecord.getDirection()))
642                                     {
643                                         if (adjLane.equals(next.getLane()))
644                                         {
645                                             edgeRecord.setRight(next, gtuType);
646                                             next.setLeft(edgeRecord, gtuType);
647                                         }
648                                     }
649                                 }
650                             }
651                         }
652                         else
653                         {
654                             prev.setRight(next, gtuType);
655                             if (nextLane
656                                     .accessibleAdjacentLanesPhysical(LateralDirectionality.LEFT, gtuType, prev.getDirection())
657                                     .contains(prev.getLane()))
658                             {
659                                 next.setLeft(prev, gtuType);
660                             }
661                             for (RollingLaneStructureRecord edgeRecord : edge)
662                             {
663                                 if (!edgeRecord.equals(prev)
664                                         && edgeRecord.getLane().getParentLink().equals(next.getLane().getParentLink()))
665                                 {
666                                     for (Lane adjLane : edgeRecord.getLane().accessibleAdjacentLanesPhysical(
667                                             LateralDirectionality.LEFT, gtuType, edgeRecord.getDirection()))
668                                     {
669                                         if (adjLane.equals(next.getLane()))
670                                         {
671                                             edgeRecord.setLeft(next, gtuType);
672                                             next.setRight(edgeRecord, gtuType);
673                                         }
674                                     }
675                                 }
676                             }
677                         }
678                         prev = next;
679                         adjacentLanes =
680                                 prev.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, prev.getDirection());
681                     }
682                     else
683                     {
684                         break;
685                     }
686                 }
687             }
688         }
689         return nextSet;
690     }
691 
692     
693 
694 
695 
696     private void retreatUpstreamEdge() throws GTUException
697     {
698         boolean moved = true;
699         Set<RollingLaneStructureRecord> nexts = new LinkedHashSet<>();
700         while (moved)
701         {
702             moved = false;
703             nexts.clear();
704             Iterator<RollingLaneStructureRecord> iterator = this.upstreamEdge.iterator();
705             while (iterator.hasNext())
706             {
707                 RollingLaneStructureRecord prev = iterator.next();
708                 
709                 if (!nexts.contains(prev) && prev.getStartDistance().si + prev.getLane().getLength().si < this.up.si)
710                 {
711                     for (RollingLaneStructureRecord next : prev.getNext())
712                     {
713                         next.clearPrevList();
714                         next.setCutOffStart(this.up.minus(next.getStartDistance()));
715                         moved = true;
716                         nexts.add(next);
717                         RollingLaneStructureRecord lat = next.getLeft();
718                         while (lat != null && lat.getPrev().isEmpty())
719                         {
720                             nexts.add(lat);
721                             lat = lat.getLeft();
722                         }
723                         lat = next.getRight();
724                         while (lat != null && lat.getPrev().isEmpty())
725                         {
726                             nexts.add(lat);
727                             lat = lat.getRight();
728                         }
729                     }
730                     prev.clearNextList();
731                     removeRecord(prev);
732                     iterator.remove();
733                 }
734                 else
735                 {
736                     Length cutOff = this.up.minus(prev.getStartDistance());
737                     if (cutOff.si > 0)
738                     {
739                         prev.setCutOffStart(cutOff);
740                     }
741                 }
742             }
743             this.upstreamEdge.addAll(nexts);
744 
745             
746             for (RollingLaneStructureRecord record : nexts)
747             {
748                 RollingLaneStructureRecord prev = record;
749                 for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
750                         LateralDirectionality.RIGHT })
751                 {
752                     while (prev != null)
753                     {
754                         RollingLaneStructureRecord next = latDirection.isLeft() ? prev.getLeft() : prev.getRight();
755                         if (next != null && !this.upstreamEdge.contains(next))
756                         {
757                             moved |= findUpstreamEdge(next);
758                         }
759                         prev = next;
760                     }
761                 }
762             }
763         }
764     }
765 
766     
767 
768 
769 
770 
771 
772 
773 
774     private boolean findUpstreamEdge(final RollingLaneStructureRecord record) throws GTUException
775     {
776         Length cutOff = this.up.minus(record.getStartDistance());
777         boolean moved = false;
778         if (cutOff.gt0())
779         {
780             if (cutOff.lt(record.getLane().getLength()))
781             {
782                 record.clearPrevList();
783                 record.setCutOffStart(cutOff);
784                 this.upstreamEdge.add(record);
785                 moved = true;
786             }
787             else
788             {
789                 if (this.relativeLanes.containsKey(record))
790                 {
791                     
792                     removeRecord(record);
793                 }
794                 for (RollingLaneStructureRecord next : record.getNext())
795                 {
796                     moved |= findUpstreamEdge(next);
797                 }
798             }
799         }
800         return moved;
801     }
802 
803     
804 
805 
806 
807 
808 
809 
810     private void expandDownstreamEdge(final GTUType gtuType, final double fractionalPosition, final Route route)
811             throws GTUException
812     {
813         this.ignoreSet.clear();
814         for (LaneStructureRecord record : this.downstreamEdge)
815         {
816             this.ignoreSet.add(record.getLane());
817         }
818         Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
819         Set<RollingLaneStructureRecord> splitSet = new LinkedHashSet<>();
820         boolean expand = true;
821         while (expand)
822         {
823             expand = false;
824 
825             
826             Iterator<RollingLaneStructureRecord> iterator = this.downstreamEdge.iterator();
827             Set<RollingLaneStructureRecord> modifiedEdge = new LinkedHashSet<>(this.downstreamEdge);
828             while (iterator.hasNext())
829             {
830                 RollingLaneStructureRecord record = iterator.next();
831                 Map<Lane, GTUDirectionality> nexts = record.getLane().downstreamLanes(record.getDirection(), gtuType);
832                 if (record.getStartDistance().si + record.getLane().getLength().si > this.down.si)
833                 {
834                     
835                     record.setCutOffEnd(this.down.minus(record.getStartDistance()));
836                     for (Lane nextLane : nexts.keySet())
837                     {
838                         this.ignoreSet.add(nextLane); 
839                     }
840                 }
841                 else
842                 {
843                     
844 
845                     
846                     LaneDirection nextLaneDirection =
847                             new LaneDirection(record.getLane(), record.getDirection()).getNextLaneDirection(this.containingGtu);
848 
849                     record.clearCutOffEnd();
850                     iterator.remove(); 
851                     for (Lane nextLane : nexts.keySet())
852                     {
853                         if (nextLaneDirection != null
854                                 && nextLane.getParentLink().equals(nextLaneDirection.getLane().getParentLink())
855                                 && !nextLane.equals(nextLaneDirection.getLane()))
856                         {
857                             
858                             continue;
859                         }
860                         RelativeLane relativeLane = this.relativeLanes.get(record);
861                         GTUDirectionality dir = nexts.get(nextLane);
862                         RollingLaneStructureRecord next = constructRecord(nextLane, dir, record, RecordLink.DOWN, relativeLane);
863                         this.ignoreSet.add(nextLane);
864                         next.updateStartDistance(fractionalPosition, this);
865                         record.addNext(next);
866                         next.addPrev(record);
867                         connectLaterally(next, gtuType, modifiedEdge);
868                         
869                         int from = route == null ? 0 : route.indexOf(next.getFromNode());
870                         int to = route == null ? 1 : route.indexOf(next.getToNode());
871                         if (to < 0 || to - from != 1)
872                         {
873                             
874                             splitSet.add(next);
875                         }
876                         else
877                         {
878                             
879                             nextSet.add(next);
880                         }
881                         modifiedEdge.add(next);
882                         
883                         Set<RollingLaneStructureRecord> set = new HashSet<>();
884                         set.add(next);
885                         expandUpstreamMerge(set, gtuType, fractionalPosition, route);
886                     }
887                 }
888             }
889             this.downstreamEdge.addAll(nextSet);
890             expand |= !nextSet.isEmpty();
891             nextSet.clear();
892 
893             
894             expandDownstreamSplit(splitSet, gtuType, fractionalPosition, route);
895             splitSet.clear();
896 
897             
898             Set<RollingLaneStructureRecord> lateralSet =
899                     expandLateral(this.downstreamEdge, RecordLink.LATERAL_END, gtuType, fractionalPosition);
900             nextSet.addAll(lateralSet);
901             expandUpstreamMerge(lateralSet, gtuType, fractionalPosition, route);
902 
903             
904             this.downstreamEdge.addAll(nextSet);
905             expand |= !nextSet.isEmpty();
906             nextSet.clear();
907         }
908     }
909 
910     
911 
912 
913 
914 
915 
916 
917 
918     private void expandDownstreamSplit(final Set<RollingLaneStructureRecord> set, final GTUType gtuType,
919             final double fractionalPosition, final Route route) throws GTUException
920     {
921         Map<RollingLaneStructureRecord, Length> prevs = new LinkedHashMap<>();
922         Map<RollingLaneStructureRecord, Length> nexts = new LinkedHashMap<>();
923         for (RollingLaneStructureRecord record : set)
924         {
925             prevs.put(record, record.getStartDistance().plus(this.downSplit));
926         }
927         while (!prevs.isEmpty())
928         {
929             for (RollingLaneStructureRecord prev : prevs.keySet())
930             {
931                 Map<Lane, GTUDirectionality> nextLanes = prev.getLane().downstreamLanes(prev.getDirection(), gtuType);
932                 RelativeLane relativeLane = this.relativeLanes.get(prev);
933                 for (Lane nextLane : nextLanes.keySet())
934                 {
935                     GTUDirectionality dir = nextLanes.get(nextLane);
936                     Node fromNode =
937                             dir.isPlus() ? nextLane.getParentLink().getStartNode() : nextLane.getParentLink().getEndNode();
938                     Node toNode =
939                             dir.isPlus() ? nextLane.getParentLink().getEndNode() : nextLane.getParentLink().getStartNode();
940                     int from = route.indexOf(fromNode);
941                     int to = route.indexOf(toNode);
942                     if (from == -1 || to == -1 || to - from != 1)
943                     {
944                         RollingLaneStructureRecord next = constructRecord(nextLane, dir, prev, RecordLink.DOWN, relativeLane);
945                         next.updateStartDistance(fractionalPosition, this);
946                         next.addPrev(prev);
947                         prev.addNext(next);
948                         connectLaterally(next, gtuType, nexts.keySet());
949                         Length downHere = prevs.get(prev);
950                         if (next.getStartDistance().si > downHere.si)
951                         {
952                             next.setCutOffEnd(downHere.minus(next.getStartDistance()));
953                         }
954                         else
955                         {
956                             nexts.put(next, downHere);
957                         }
958                     }
959                 }
960             }
961             prevs = nexts;
962             nexts = new LinkedHashMap<>();
963         }
964     }
965 
966     
967 
968 
969 
970 
971 
972 
973 
974     private void expandUpstreamMerge(final Set<RollingLaneStructureRecord> set, final GTUType gtuType,
975             final double fractionalPosition, final Route route) throws GTUException
976     {
977         Map<RollingLaneStructureRecord, Length> prevs = new LinkedHashMap<>();
978         Map<RollingLaneStructureRecord, Length> nexts = new LinkedHashMap<>();
979         for (RollingLaneStructureRecord record : set)
980         {
981             prevs.put(record, record.getStartDistance().plus(this.upMerge)); 
982         }
983         while (!prevs.isEmpty())
984         {
985             for (RollingLaneStructureRecord prev : prevs.keySet())
986             {
987                 Map<Lane, GTUDirectionality> nextLanes = prev.getLane().upstreamLanes(prev.getDirection(), gtuType);
988                 boolean anyAdded = false;
989                 for (Lane nextLane : nextLanes.keySet())
990                 {
991                     GTUDirectionality dir = nextLanes.get(nextLane);
992                     Node fromNode =
993                             dir.isPlus() ? nextLane.getParentLink().getStartNode() : nextLane.getParentLink().getEndNode();
994                     Node toNode =
995                             dir.isPlus() ? nextLane.getParentLink().getEndNode() : nextLane.getParentLink().getStartNode();
996                     int from = route == null ? 0 : route.indexOf(fromNode);
997                     int to = route == null ? 1 : route.indexOf(toNode);
998                     
999                     
1000                     if (from == -1 || to == -1 || to - from != 1)
1001                     {
1002                         anyAdded = true;
1003                         RelativeLane relativeLane = this.relativeLanes.get(prev);
1004                         RollingLaneStructureRecord next =
1005                                 constructRecord(nextLane, nextLanes.get(nextLane), prev, RecordLink.UP, relativeLane);
1006                         next.updateStartDistance(fractionalPosition, this);
1007                         next.addNext(prev);
1008                         prev.addPrev(next);
1009                         connectLaterally(next, gtuType, nexts.keySet());
1010                         Length upHere = prevs.get(prev);
1011                         if (next.getStartDistance().si < upHere.si)
1012                         {
1013                             next.setCutOffStart(upHere.minus(next.getStartDistance()));
1014                             this.upstreamEdge.add(next);
1015                         }
1016                         else
1017                         {
1018                             nexts.put(next, upHere);
1019                         }
1020                     }
1021                 }
1022                 if (!anyAdded && !set.contains(prev))
1023                 {
1024                     this.upstreamEdge.add(prev);
1025                 }
1026             }
1027             prevs = nexts;
1028             nexts = new LinkedHashMap<>();
1029         }
1030     }
1031 
1032     
1033 
1034 
1035 
1036 
1037 
1038     private void connectLaterally(final RollingLaneStructureRecord record, final GTUType gtuType,
1039             final Set<RollingLaneStructureRecord> nextSet)
1040     {
1041         for (RollingLaneStructureRecord other : nextSet)
1042         {
1043             for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
1044                     LateralDirectionality.RIGHT })
1045             {
1046                 if ((latDirection.isLeft() ? other.getLeft() : other.getRight()) == null)
1047                 {
1048                     for (Lane otherLane : other.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType,
1049                             other.getDirection()))
1050                     {
1051                         if (otherLane.equals(record.getLane()))
1052                         {
1053                             if (latDirection.isLeft())
1054                             {
1055                                 other.setLeft(record, gtuType);
1056                                 record.setRight(other, gtuType);
1057                             }
1058                             else
1059                             {
1060                                 other.setRight(record, gtuType);
1061                                 record.setLeft(other, gtuType);
1062                             }
1063                         }
1064                     }
1065                 }
1066             }
1067         }
1068     }
1069 
1070     
1071 
1072 
1073 
1074 
1075 
1076 
1077 
1078 
1079     private RollingLaneStructureRecord constructRecord(final Lane lane, final GTUDirectionality direction,
1080             final RollingLaneStructureRecord startDistanceSource, final RecordLink recordLink, final RelativeLane relativeLane)
1081     {
1082         RollingLaneStructureRecord record = new RollingLaneStructureRecord(lane, direction, startDistanceSource, recordLink);
1083         if (!this.relativeLaneMap.containsKey(relativeLane))
1084         {
1085             this.relativeLaneMap.put(relativeLane, new LinkedHashSet<>());
1086         }
1087         this.relativeLaneMap.get(relativeLane).add(record);
1088         this.relativeLanes.put(record, relativeLane);
1089         return record;
1090     }
1091 
1092     
1093     @Override
1094     public final LaneStructureRecord getRootRecord()
1095     {
1096         return this.root.get();
1097     }
1098 
1099     
1100 
1101 
1102 
1103     public final LaneStructureRecord getRootRecord(final Time time)
1104     {
1105         return this.root.get(time);
1106     }
1107 
1108     
1109     @Override
1110     public final SortedSet<RelativeLane> getExtendedCrossSection()
1111     {
1112         return this.firstRecords.navigableKeySet();
1113     }
1114 
1115     
1116 
1117 
1118 
1119 
1120 
1121     @Override
1122     public final RollingLaneStructureRecord getFirstRecord(final RelativeLane lane)
1123     {
1124         if (this.firstRecords.containsKey(lane))
1125         {
1126             return this.firstRecords.get(lane);
1127         }
1128         
1129         RelativeLane rel = RelativeLane.CURRENT;
1130         int dMin = Integer.MAX_VALUE;
1131         for (RelativeLane relLane : this.crossSectionRecords.keySet())
1132         {
1133             if (relLane.getLateralDirectionality().equals(lane.getLateralDirectionality()))
1134             {
1135                 int d = lane.getNumLanes() - relLane.getNumLanes();
1136                 if (d < dMin)
1137                 {
1138                     rel = relLane;
1139                     d = dMin;
1140                 }
1141             }
1142         }
1143         RollingLaneStructureRecord record = this.crossSectionRecords.get(rel);
1144         
1145         while (rel.getNumLanes() < lane.getNumLanes())
1146         {
1147             RollingLaneStructureRecord adj = lane.getLateralDirectionality().isLeft() ? record.getLeft() : record.getRight();
1148             if (adj != null)
1149             {
1150                 rel = lane.getLateralDirectionality().isLeft() ? rel.getLeft() : rel.getRight();
1151                 record = adj;
1152             }
1153             else if (!record.getNext().isEmpty())
1154             {
1155                 LaneDirection laneDir =
1156                         new LaneDirection(record.getLane(), record.getDirection()).getNextLaneDirection(this.containingGtu);
1157                 if (laneDir == null)
1158                 {
1159                     record = null;
1160                     break;
1161                 }
1162                 RollingLaneStructureRecord chosenNext = null;
1163                 for (RollingLaneStructureRecord next : record.getNext())
1164                 {
1165                     if (next.getLane().equals(laneDir.getLane()))
1166                     {
1167                         chosenNext = next;
1168                         break;
1169                     }
1170                 }
1171                 Throw.when(chosenNext == null, RuntimeException.class,
1172                         "Unexpected exception while deriving first record not on the cross-section.");
1173                 record = chosenNext;
1174             }
1175             else
1176             {
1177                 
1178                 record = null;
1179                 break;
1180             }
1181         }
1182         if (record != null)
1183         {
1184             
1185             while (record.getPrev().size() == 1 && record.getStartDistance().gt0())
1186             {
1187                 record = record.getPrev().get(0);
1188             }
1189             this.firstRecords.put(lane, record);
1190         }
1191         return record;
1192     }
1193 
1194     
1195 
1196 
1197 
1198 
1199 
1200 
1201 
1202 
1203 
1204     @Override
1205     public final <T extends LaneBasedObject> Map<RelativeLane, SortedSet<Entry<T>>> getDownstreamObjects(final Class<T> clazz,
1206             final LaneBasedGTU gtu, final RelativePosition.TYPE pos) throws GTUException
1207     {
1208         Map<RelativeLane, SortedSet<Entry<T>>> out = new HashMap<>();
1209         for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
1210         {
1211             out.put(relativeLane, getDownstreamObjects(relativeLane, clazz, gtu, pos));
1212         }
1213         return out;
1214     }
1215 
1216     
1217 
1218 
1219 
1220 
1221 
1222 
1223 
1224 
1225 
1226 
1227     @Override
1228     @SuppressWarnings("unchecked")
1229     public final <T extends LaneBasedObject> SortedSet<Entry<T>> getDownstreamObjects(final RelativeLane lane,
1230             final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos) throws GTUException
1231     {
1232         LaneStructureRecord record = getFirstRecord(lane);
1233         SortedSet<Entry<T>> set = new TreeSet<>();
1234         if (record != null)
1235         {
1236             double ds = gtu.getRelativePositions().get(pos).getDx().si - gtu.getReference().getDx().si;
1237             if (record.isDownstreamBranch())
1238             {
1239                 
1240                 Length minimumPosition;
1241                 Length maximumPosition;
1242                 if (record.getDirection().isPlus())
1243                 {
1244                     minimumPosition = Length.createSI(ds - record.getStartDistance().si);
1245                     maximumPosition = Length.createSI(record.getLane().getLength().si);
1246                 }
1247                 else
1248                 {
1249                     minimumPosition = Length.ZERO;
1250                     maximumPosition = Length.createSI(record.getLane().getLength().si + record.getStartDistance().si - ds);
1251                 }
1252 
1253                 for (LaneBasedObject object : record.getLane().getLaneBasedObjects(minimumPosition, maximumPosition))
1254                 {
1255                     if (clazz.isAssignableFrom(object.getClass())
1256                             && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
1257                                     || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
1258                     {
1259                         
1260                         double distance = record.getDistanceToPosition(object.getLongitudinalPosition()).si - ds;
1261                         if (distance <= this.lookAhead.si)
1262                         {
1263                             set.add(new Entry<>(Length.createSI(distance), (T) object));
1264                         }
1265                     }
1266                 }
1267             }
1268             getDownstreamObjectsRecursive(set, record, clazz, ds);
1269         }
1270         return set;
1271     }
1272 
1273     
1274 
1275 
1276 
1277 
1278 
1279 
1280 
1281 
1282 
1283 
1284 
1285     @Override
1286     public final <T extends LaneBasedObject> SortedSet<Entry<T>> getDownstreamObjectsOnRoute(final RelativeLane lane,
1287             final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos, final Route route)
1288             throws GTUException
1289     {
1290         SortedSet<Entry<T>> set = getDownstreamObjects(lane, clazz, gtu, pos);
1291         if (route != null)
1292         {
1293             Iterator<Entry<T>> iterator = set.iterator();
1294             while (iterator.hasNext())
1295             {
1296                 Entry<T> entry = iterator.next();
1297                 CrossSectionLink link = entry.getLaneBasedObject().getLane().getParentLink();
1298                 if (!route.contains(link.getStartNode()) || !route.contains(link.getEndNode())
1299                         || Math.abs(route.indexOf(link.getStartNode()) - route.indexOf(link.getEndNode())) != 1)
1300                 {
1301                     iterator.remove();
1302                 }
1303             }
1304         }
1305         return set;
1306     }
1307 
1308     
1309 
1310 
1311 
1312 
1313 
1314 
1315 
1316     @SuppressWarnings("unchecked")
1317     private <T extends LaneBasedObject> void getDownstreamObjectsRecursive(final SortedSet<Entry<T>> set,
1318             final LaneStructureRecord record, final Class<T> clazz, final double ds)
1319     {
1320         if (record.getNext().isEmpty() || record.getNext().get(0).getStartDistance().gt(this.lookAhead))
1321         {
1322             return;
1323         }
1324         for (LaneStructureRecord next : record.getNext())
1325         {
1326             if (next.isDownstreamBranch())
1327             {
1328                 List<LaneBasedObject> list = next.getLane().getLaneBasedObjects();
1329                 int iStart, di;
1330                 if (record.getDirection().isPlus())
1331                 {
1332                     iStart = 0;
1333                     di = 1;
1334                 }
1335                 else
1336                 {
1337                     iStart = list.size() - 1;
1338                     di = -1;
1339                 }
1340                 for (int i = iStart; i >= 0 & i < list.size(); i += di)
1341                 {
1342                     LaneBasedObject object = list.get(i);
1343                     if (clazz.isAssignableFrom(object.getClass())
1344                             && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
1345                                     || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
1346                     {
1347                         
1348                         double distance = next.getDistanceToPosition(object.getLongitudinalPosition()).si - ds;
1349                         if (distance <= this.lookAhead.si)
1350                         {
1351                             set.add(new Entry<>(Length.createSI(distance), (T) object));
1352                         }
1353                         else
1354                         {
1355                             return;
1356                         }
1357                     }
1358                 }
1359             }
1360             getDownstreamObjectsRecursive(set, next, clazz, ds);
1361         }
1362     }
1363 
1364     
1365 
1366 
1367 
1368 
1369 
1370 
1371 
1372 
1373 
1374 
1375     @Override
1376     public final <T extends LaneBasedObject> Map<RelativeLane, SortedSet<Entry<T>>> getDownstreamObjectsOnRoute(
1377             final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos, final Route route)
1378             throws GTUException
1379     {
1380         Map<RelativeLane, SortedSet<Entry<T>>> out = new HashMap<>();
1381         for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
1382         {
1383             out.put(relativeLane, getDownstreamObjectsOnRoute(relativeLane, clazz, gtu, pos, route));
1384         }
1385         return out;
1386     }
1387 
1388     
1389 
1390 
1391 
1392 
1393 
1394 
1395 
1396 
1397 
1398 
1399     @Override
1400     @SuppressWarnings("unchecked")
1401     public final <T extends LaneBasedObject> SortedSet<Entry<T>> getUpstreamObjects(final RelativeLane lane,
1402             final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos) throws GTUException
1403     {
1404         SortedSet<Entry<T>> set = new TreeSet<>();
1405         LaneStructureRecord record = this.getFirstRecord(lane);
1406         if (record.getStartDistance().gt0())
1407         {
1408             return set; 
1409         }
1410         Length ds = gtu.getReference().getDx().minus(gtu.getRelativePositions().get(pos).getDx());
1411         
1412         Length minimumPosition;
1413         Length maximumPosition;
1414         if (record.getDirection().isPlus())
1415         {
1416             minimumPosition = Length.ZERO;
1417             maximumPosition = record.getStartDistance().neg().minus(ds);
1418         }
1419         else
1420         {
1421             minimumPosition = record.getLane().getLength().plus(record.getStartDistance()).plus(ds);
1422             maximumPosition = record.getLane().getLength();
1423         }
1424         Length distance;
1425         for (LaneBasedObject object : record.getLane().getLaneBasedObjects(minimumPosition, maximumPosition))
1426         {
1427             if (clazz.isAssignableFrom(object.getClass())
1428                     && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
1429                             || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
1430             {
1431                 distance = record.getDistanceToPosition(object.getLongitudinalPosition()).neg().minus(ds);
1432                 
1433                 set.add(new Entry<>(distance, (T) object));
1434             }
1435         }
1436         getUpstreamObjectsRecursive(set, record, clazz, ds);
1437         return set;
1438     }
1439 
1440     
1441 
1442 
1443 
1444 
1445 
1446 
1447 
1448     @SuppressWarnings("unchecked")
1449     private <T extends LaneBasedObject> void getUpstreamObjectsRecursive(final SortedSet<Entry<T>> set,
1450             final LaneStructureRecord record, final Class<T> clazz, final Length ds)
1451     {
1452         for (LaneStructureRecord prev : record.getPrev())
1453         {
1454             Length distance;
1455             for (LaneBasedObject object : prev.getLane().getLaneBasedObjects())
1456             {
1457                 if (clazz.isAssignableFrom(object.getClass())
1458                         && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
1459                                 || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
1460                 {
1461                     distance = prev.getDistanceToPosition(object.getLongitudinalPosition()).neg().minus(ds);
1462                     
1463                     set.add(new Entry<>(distance, (T) object));
1464                 }
1465             }
1466             getUpstreamObjectsRecursive(set, prev, clazz, ds);
1467         }
1468     }
1469 
1470     
1471     @Override
1472     public final String toString()
1473     {
1474         return "LaneStructure [rootLSR=" + this.root + "]";
1475     }
1476 
1477     
1478 
1479 
1480 
1481 
1482 
1483 
1484 
1485     public class AnimationAccess
1486     {
1487         
1488 
1489 
1490         @SuppressWarnings("synthetic-access")
1491         public TreeMap<RelativeLane, RollingLaneStructureRecord> getCrossSectionRecords()
1492         {
1493             return RollingLaneStructure.this.crossSectionRecords;
1494         }
1495 
1496         
1497 
1498 
1499         @SuppressWarnings("synthetic-access")
1500         public Set<RollingLaneStructureRecord> getUpstreamEdge()
1501         {
1502             return RollingLaneStructure.this.upstreamEdge;
1503         }
1504 
1505         
1506 
1507 
1508         @SuppressWarnings("synthetic-access")
1509         public Set<RollingLaneStructureRecord> getDownstreamEdge()
1510         {
1511             return RollingLaneStructure.this.downstreamEdge;
1512         }
1513     }
1514 }