RollingLaneStructure.java

  1. package org.opentrafficsim.road.gtu.lane.perception;

  2. import java.io.Serializable;
  3. import java.rmi.RemoteException;
  4. import java.util.Iterator;
  5. import java.util.LinkedHashMap;
  6. import java.util.LinkedHashSet;
  7. import java.util.List;
  8. import java.util.Map;
  9. import java.util.Set;
  10. import java.util.SortedSet;
  11. import java.util.TreeMap;
  12. import java.util.TreeSet;

  13. import org.djunits.value.vdouble.scalar.Length;
  14. import org.djunits.value.vdouble.scalar.Time;
  15. import org.djutils.event.EventInterface;
  16. import org.djutils.event.EventListenerInterface;
  17. import org.djutils.exceptions.Throw;
  18. import org.djutils.exceptions.Try;
  19. import org.djutils.immutablecollections.ImmutableMap;
  20. import org.opentrafficsim.core.gtu.GTUDirectionality;
  21. import org.opentrafficsim.core.gtu.GTUException;
  22. import org.opentrafficsim.core.gtu.GTUType;
  23. import org.opentrafficsim.core.gtu.RelativePosition;
  24. import org.opentrafficsim.core.network.LateralDirectionality;
  25. import org.opentrafficsim.core.network.Link;
  26. import org.opentrafficsim.core.network.Node;
  27. import org.opentrafficsim.core.network.route.Route;
  28. import org.opentrafficsim.core.perception.Historical;
  29. import org.opentrafficsim.core.perception.HistoricalValue;
  30. import org.opentrafficsim.core.perception.HistoryManager;
  31. import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
  32. import org.opentrafficsim.road.gtu.lane.perception.RollingLaneStructureRecord.RecordLink;
  33. import org.opentrafficsim.road.gtu.lane.plan.operational.LaneBasedOperationalPlan;
  34. import org.opentrafficsim.road.network.lane.CrossSectionLink;
  35. import org.opentrafficsim.road.network.lane.DirectedLanePosition;
  36. import org.opentrafficsim.road.network.lane.Lane;
  37. import org.opentrafficsim.road.network.lane.LaneDirection;
  38. import org.opentrafficsim.road.network.lane.object.LaneBasedObject;

  39. /**
  40.  * This data structure can clearly indicate the lane structure ahead of us, e.g. in the following situation:
  41.  *
  42.  * <pre>
  43.  *     (---- a ----)(---- b ----)(---- c ----)(---- d ----)(---- e ----)(---- f ----)(---- g ----)  
  44.  *                                             __________                             __________
  45.  *                                            / _________ 1                          / _________ 2
  46.  *                                           / /                                    / /
  47.  *                                __________/ /             _______________________/ /
  48.  *  1  ____________ ____________ /_ _ _ _ _ _/____________ /_ _ _ _ _ _ _ _ _ _ _ _ /      
  49.  *  0 |_ _X_ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ |_ _ _ _ _ _ \____________
  50.  * -1 |____________|_ _ _ _ _ _ |____________|____________|  __________|____________|____________| 3
  51.  * -2              / __________/                           \ \  
  52.  *        ________/ /                                       \ \___________  
  53.  *      5 _________/                                         \____________  4
  54.  * </pre>
  55.  *
  56.  * 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
  57.  * 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
  58.  * 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
  59.  * 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
  60.  * destination 4. The rightmost lane just splits into two lanes at the end of link d, and the GTU can either continue driving to
  61.  * destination 3, turn right to destination 4. This means that the right lane of link d has <b>two</b> successor lanes.
  62.  * <p>
  63.  * In the data structures, lanes are numbered laterally. Suppose that the lane where vehicle X resides would be number 0.
  64.  * Consistent with "left is positive" for angles, the lane right of X would have number -1, and entry 5 would have number -2.
  65.  * <p>
  66.  * In the data structure, this can be indicated as follows (N = next, P = previous, L = left, R = right, D = lane drop, . =
  67.  * continued but not in this structure). The merge lane in b is considered "off limits" for the GTUs on the "main" lane -1; the
  68.  * "main" lane 0 is considered off limits from the exit lanes on c, e, and f. Still, we need to maintain pointers to these
  69.  * lanes, as we are interested in the GTUs potentially driving next to us, feeding into our lane, etc.
  70.  *
  71.  * <pre>
  72.  *       1                0               -1               -2
  73.  *      
  74.  *                       ROOT
  75.  *                   _____|_____      ___________      ___________            
  76.  *                  |_-_|_._|_R_|----|_L_|_._|_-_|    |_-_|_._|_-_|  a          
  77.  *                        |                |                |
  78.  *                   _____V_____      _____V_____      _____V_____            
  79.  *                  |_-_|_N_|_R_|----|_L_|_N_|_R_|&lt;---|_L_|_D_|_-_|  b          
  80.  *                        |                |                
  81.  *  ___________      _____V_____      _____V_____                
  82.  * |_-_|_N_|_R_|&lt;---|_L_|_N_|_R_|----|_L_|_N_|_-_|                   c
  83.  *       |                |                |                
  84.  *  _____V_____      _____V_____      _____V_____                
  85.  * |_-_|_._|_-_|    |_-_|_N_|_R_|----|_L_|_NN|_-_|                   d          
  86.  *                        |                ||_______________
  87.  *  ___________      _____V_____      _____V_____      _____V_____            
  88.  * |_-_|_N_|_R_|&lt;---|_L_|_N_|_R_|----|_L_|_N_|_-_|    |_-_|_N_|_-_|  e          
  89.  *       |                |                |                |
  90.  *  _____V_____      _____V_____      _____V_____      _____V_____            
  91.  * |_-_|_N_|_R_|&lt;---|_L_|_D_|_R_|----|_L_|_N_|_-_|    |_-_|_._|_-_|  f          
  92.  *       |                                 |                
  93.  *  _____V_____                       _____V_____                            
  94.  * |_-_|_._|_-_|                     |_-_|_._|_-_|                   g
  95.  * </pre>
  96.  * <p>
  97.  * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  98.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  99.  * </p>
  100.  * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
  101.  * initial version Feb 20, 2016 <br>
  102.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  103.  */
  104. public class RollingLaneStructure implements LaneStructure, Serializable, EventListenerInterface
  105. {
  106.     /** */
  107.     private static final long serialVersionUID = 20160400L;

  108.     /** The lanes from which we observe the situation. */
  109.     private final Historical<RollingLaneStructureRecord> root;

  110.     /** Look ahead distance. */
  111.     private Length lookAhead;

  112.     /** Route the structure is based on. */
  113.     private Route previousRoute;

  114.     /** Whether the previous plan was deviative. */
  115.     private boolean previouslyDeviative = false;

  116.     /** Lane structure records of the cross section. */
  117.     private TreeMap<RelativeLane, RollingLaneStructureRecord> crossSectionRecords = new TreeMap<>();

  118.     /** First lane structure records. */
  119.     private TreeMap<RelativeLane, RollingLaneStructureRecord> firstRecords = new TreeMap<>();

  120.     /** Lane structure records grouped per relative lane. */
  121.     private Map<RelativeLane, Set<RollingLaneStructureRecord>> relativeLaneMap = new LinkedHashMap<>();

  122.     /** Relative lanes storage per record, such that other records can be linked to the correct relative lane. */
  123.     private Map<LaneStructureRecord, RelativeLane> relativeLanes = new LinkedHashMap<>();

  124.     /** Set of lanes that can be ignored as they are beyond build bounds. */
  125.     private final Set<Lane> ignoreSet = new LinkedHashSet<>();

  126.     /** Upstream edges. */
  127.     private final Set<RollingLaneStructureRecord> upstreamEdge = new LinkedHashSet<>();

  128.     /** Downstream edges. */
  129.     private final Set<RollingLaneStructureRecord> downstreamEdge = new LinkedHashSet<>();

  130.     /** Downstream distance over which the structure is made. */
  131.     private final Length down;

  132.     /** Upstream distance over which the structure is made. */
  133.     private final Length up;

  134.     /** Downstream distance at splits (links not on route) included in the structure. */
  135.     private final Length downSplit;

  136.     /** Upstream distance at downstream merges (links not on route) included in the structure. */
  137.     private final Length upMerge;

  138.     /** GTU. */
  139.     private final LaneBasedGTU containingGtu;

  140.     /** the animation access. */
  141.     @SuppressWarnings("checkstyle:visibilitymodifier")
  142.     public AnimationAccess animationAccess = new AnimationAccess();

  143.     /**
  144.      * Constructor.
  145.      * @param lookAhead Length; distance over which visual objects are included
  146.      * @param down Length; downstream distance over which the structure is made
  147.      * @param up Length; upstream distance over which the structure is made, should include a margin for reaction time
  148.      * @param downSplit Length; downstream distance at splits (links not on route) included in the structure
  149.      * @param upMerge Length; upstream distance at downstream merges (links not on route) included in the structure
  150.      * @param gtu LaneBasedGTU; GTU
  151.      */
  152.     public RollingLaneStructure(final Length lookAhead, final Length down, final Length up, final Length downSplit,
  153.         final Length upMerge, final LaneBasedGTU gtu)
  154.     {
  155.         HistoryManager historyManager = gtu.getSimulator().getReplication().getHistoryManager(gtu.getSimulator());
  156.         this.root = new HistoricalValue<>(historyManager);
  157.         this.lookAhead = lookAhead;
  158.         this.down = down;
  159.         this.up = up;
  160.         this.downSplit = downSplit;
  161.         this.upMerge = upMerge;
  162.         this.containingGtu = gtu;
  163.         try
  164.         {
  165.             gtu.addListener(this, LaneBasedGTU.LANE_CHANGE_EVENT);
  166.         }
  167.         catch (RemoteException exception)
  168.         {
  169.             throw new RuntimeException(exception);
  170.         }
  171.     }

  172.     /**
  173.      * Updates the underlying structure shifting the root position to the input.
  174.      * @param pos DirectedLanePosition; current position of the GTU
  175.      * @param route Route; current route of the GTU
  176.      * @param gtuType GTUType; GTU type
  177.      * @throws GTUException on a problem while updating the structure
  178.      */
  179.     @Override
  180.     public final void update(final DirectedLanePosition pos, final Route route, final GTUType gtuType) throws GTUException
  181.     {

  182.         /*
  183.          * Implementation note: the LaneStructure was previously generated by AbstractLanePerception every time step. This
  184.          * functionality has been moved to LaneStructure itself, in a manner that can update the LaneStructure. Start distances
  185.          * of individual records are therefore made dynamic, calculated relative to a neighboring source record. For many time
  186.          * steps this means that only these distances have to be updated. In other cases, the sources for start distances are
  187.          * changed concerning the records that were involved in the previous time step. The LaneStructure now also maintains an
  188.          * upstream and a downstream edge, i.e. set of records. These are moved forward as the GTU moves.
  189.          */

  190.         // fractional position
  191.         Lane lane = pos.getLane();
  192.         GTUDirectionality direction = pos.getGtuDirection();
  193.         Length position = pos.getPosition();
  194.         double fracPos = direction.isPlus() ? position.si / lane.getLength().si : 1.0 - position.si / lane.getLength().si;
  195.         boolean deviative = this.containingGtu.getOperationalPlan() instanceof LaneBasedOperationalPlan
  196.             && ((LaneBasedOperationalPlan) this.containingGtu.getOperationalPlan()).isDeviative();

  197.         // TODO on complex networks, e.g. with sections connectors where lane changes are not possible, the update may fail
  198.         if (this.previousRoute != route || this.root.get() == null || deviative != this.previouslyDeviative)
  199.         {
  200.             // create new LaneStructure
  201.             this.previousRoute = route;

  202.             // clear
  203.             this.upstreamEdge.clear();
  204.             this.downstreamEdge.clear();
  205.             this.crossSectionRecords.clear();
  206.             this.relativeLanes.clear();
  207.             this.relativeLaneMap.clear();
  208.             this.firstRecords.clear();

  209.             // build cross-section
  210.             RollingLaneStructureRecord newRoot = constructRecord(lane, direction, null, RecordLink.CROSS,
  211.                 RelativeLane.CURRENT);
  212.             this.root.set(newRoot);
  213.             this.crossSectionRecords.put(RelativeLane.CURRENT, newRoot);
  214.             for (LateralDirectionality latDirection : new LateralDirectionality[] {LateralDirectionality.LEFT,
  215.                 LateralDirectionality.RIGHT})
  216.             {
  217.                 RollingLaneStructureRecord current = newRoot;
  218.                 RelativeLane relativeLane = RelativeLane.CURRENT;
  219.                 Set<Lane> adjacentLanes = current.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, current
  220.                     .getDirection());
  221.                 while (!adjacentLanes.isEmpty())
  222.                 {
  223.                     Throw.when(adjacentLanes.size() > 1, RuntimeException.class,
  224.                         "Multiple adjacent lanes encountered during construction of lane map.");
  225.                     relativeLane = latDirection.isLeft() ? relativeLane.getLeft() : relativeLane.getRight();
  226.                     Lane adjacentLane = adjacentLanes.iterator().next();
  227.                     RollingLaneStructureRecord adjacentRecord = constructRecord(adjacentLane, direction, current,
  228.                         RecordLink.CROSS, relativeLane);
  229.                     this.crossSectionRecords.put(relativeLane, adjacentRecord);
  230.                     if (latDirection.isLeft())
  231.                     {
  232.                         if (adjacentLane.accessibleAdjacentLanesPhysical(LateralDirectionality.RIGHT, gtuType, current
  233.                             .getDirection()).contains(current.getLane()))
  234.                         {
  235.                             adjacentRecord.setRight(current, gtuType);
  236.                         }
  237.                         current.setLeft(adjacentRecord, gtuType);
  238.                     }
  239.                     else
  240.                     {
  241.                         if (adjacentLane.accessibleAdjacentLanesPhysical(LateralDirectionality.LEFT, gtuType, current
  242.                             .getDirection()).contains(current.getLane()))
  243.                         {
  244.                             adjacentRecord.setLeft(current, gtuType);
  245.                         }
  246.                         current.setRight(adjacentRecord, gtuType);
  247.                     }
  248.                     current = adjacentRecord;
  249.                     adjacentLanes = current.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, current
  250.                         .getDirection());
  251.                 }
  252.             }
  253.             this.upstreamEdge.addAll(this.crossSectionRecords.values());
  254.             this.downstreamEdge.addAll(this.crossSectionRecords.values());
  255.             this.firstRecords.putAll(this.crossSectionRecords);

  256.             // set the start distances so the upstream expand can work
  257.             newRoot.updateStartDistance(fracPos, this);

  258.             // expand upstream edge
  259.             expandUpstreamEdge(gtuType, fracPos);

  260.             // derive first records
  261.             deriveFirstRecords();
  262.         }
  263.         else
  264.         {

  265.             // update LaneStructure
  266.             RollingLaneStructureRecord newRoot = this.root.get();
  267.             if (!lane.equals(newRoot.getLane()))
  268.             {
  269.                 // find the root, and possible lateral shift if changed lane
  270.                 newRoot = null;
  271.                 RelativeLane lateralMove = null;
  272.                 double closest = Double.POSITIVE_INFINITY;
  273.                 for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
  274.                 {
  275.                     for (RollingLaneStructureRecord record : this.relativeLaneMap.get(relativeLane))
  276.                     {
  277.                         if (record.getLane().equals(lane) && record.getStartDistance().si < closest && record
  278.                             .getStartDistance().si + record.getLength().si > 0.0)
  279.                         {
  280.                             newRoot = record;
  281.                             lateralMove = relativeLane;
  282.                             // multiple records may be present for the current lane due to a loop
  283.                             closest = record.getStartDistance().si;
  284.                         }
  285.                     }
  286.                     if (newRoot != null)
  287.                     {
  288.                         break;
  289.                     }
  290.                 }
  291.                 // newRoot.getPrev().contains(newRoot.getStartDistanceSource())
  292.                 this.root.set(newRoot);

  293.                 // update start distance sources
  294.                 updateStartDistanceSources();

  295.                 // shift if changed lane
  296.                 if (!lateralMove.isCurrent())
  297.                 {
  298.                     RelativeLane delta = new RelativeLane(lateralMove.getLateralDirectionality().flip(), lateralMove
  299.                         .getNumLanes());

  300.                     TreeMap<RelativeLane, Set<RollingLaneStructureRecord>> newRelativeLaneMap = new TreeMap<>();
  301.                     for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
  302.                     {
  303.                         RelativeLane newRelativeLane = relativeLane.add(delta);
  304.                         newRelativeLaneMap.put(newRelativeLane, this.relativeLaneMap.get(relativeLane));
  305.                     }
  306.                     this.relativeLaneMap = newRelativeLaneMap;

  307.                     Map<LaneStructureRecord, RelativeLane> newRelativeLanes = new LinkedHashMap<>();
  308.                     for (LaneStructureRecord record : this.relativeLanes.keySet())
  309.                     {
  310.                         newRelativeLanes.put(record, this.relativeLanes.get(record).add(delta));
  311.                     }
  312.                     this.relativeLanes = newRelativeLanes;
  313.                 }

  314.                 this.crossSectionRecords.clear();
  315.                 this.crossSectionRecords.put(RelativeLane.CURRENT, newRoot);
  316.                 for (LateralDirectionality latDirection : new LateralDirectionality[] {LateralDirectionality.LEFT,
  317.                     LateralDirectionality.RIGHT})
  318.                 {
  319.                     RollingLaneStructureRecord record = newRoot;
  320.                     RollingLaneStructureRecord next = newRoot;
  321.                     RelativeLane delta = new RelativeLane(latDirection, 1);
  322.                     RelativeLane relLane = RelativeLane.CURRENT;
  323.                     while (next != null)
  324.                     {
  325.                         next = latDirection.isLeft() ? record.getLeft() : record.getRight();
  326.                         if (next != null)
  327.                         {
  328.                             next.changeStartDistanceSource(record, RecordLink.CROSS);
  329.                             relLane = relLane.add(delta);
  330.                             this.crossSectionRecords.put(relLane, next);
  331.                             record = next;
  332.                         }
  333.                     }
  334.                 }

  335.             }
  336.             newRoot.updateStartDistance(fracPos, this);

  337.             // update upstream edges
  338.             retreatUpstreamEdge();

  339.             // derive first records
  340.             deriveFirstRecords();

  341.         }

  342.         this.previouslyDeviative = deviative;

  343.         // update downstream edges
  344.         expandDownstreamEdge(gtuType, fracPos, route);

  345.     }

  346.     /**
  347.      * Derives the first downstream records so the extended cross-section can be returned.
  348.      */
  349.     private void deriveFirstRecords()
  350.     {
  351.         this.firstRecords.clear();
  352.         this.firstRecords.putAll(this.crossSectionRecords);
  353.         for (RelativeLane lane : this.relativeLaneMap.keySet())
  354.         {
  355.             getFirstRecord(lane); // store non-null values internally
  356.         }
  357.     }

  358.     /**
  359.      * Upstream algorithm from the new source, where records along the way are assigned a new start distance source. These
  360.      * records were downstream in the previous time step, but are now upstream. Hence, their start distance source should now
  361.      * become their downstream record. This algorithm acts like an upstream tree, where each branch is stopped if there are no
  362.      * upstream records, or the upstream records already have their downstream record as source (i.e. the record was already
  363.      * upstream in the previous time step).
  364.      */
  365.     private void updateStartDistanceSources()
  366.     {

  367.         // initial cross section
  368.         Set<RollingLaneStructureRecord> set = new LinkedHashSet<>();
  369.         RollingLaneStructureRecord rootRecord = this.root.get();
  370.         set.add(rootRecord);
  371.         rootRecord.changeStartDistanceSource(null, RecordLink.CROSS);
  372.         RollingLaneStructureRecord prev = rootRecord;
  373.         RollingLaneStructureRecord next = prev.getLeft();
  374.         while (next != null)
  375.         {
  376.             set.add(next);
  377.             next.changeStartDistanceSource(prev, RecordLink.CROSS);
  378.             prev = next;
  379.             next = next.getLeft();
  380.         }
  381.         prev = rootRecord;
  382.         next = prev.getRight();
  383.         while (next != null)
  384.         {
  385.             set.add(next);
  386.             next.changeStartDistanceSource(prev, RecordLink.CROSS);
  387.             prev = next;
  388.             next = next.getRight();
  389.         }
  390.         // tree algorithm (branches flattened to a single set)
  391.         while (!set.isEmpty())
  392.         {
  393.             // lateral
  394.             Set<RollingLaneStructureRecord> newSet = new LinkedHashSet<>();
  395.             for (RollingLaneStructureRecord record : set)
  396.             {
  397.                 for (LateralDirectionality latDirection : new LateralDirectionality[] {LateralDirectionality.LEFT,
  398.                     LateralDirectionality.RIGHT})
  399.                 {
  400.                     prev = record;
  401.                     next = latDirection.isLeft() ? record.getLeft() : record.getRight();
  402.                     while (next != null && !set.contains(next))
  403.                     {
  404.                         next.changeStartDistanceSource(prev, RecordLink.LATERAL_END);
  405.                         removeDownstream(next, latDirection.flip()); // split not taken can be thrown away
  406.                         newSet.add(next);
  407.                         prev = next;
  408.                         next = latDirection.isLeft() ? next.getLeft() : next.getRight();
  409.                     }
  410.                 }
  411.             }
  412.             set.addAll(newSet);

  413.             // longitudinal
  414.             newSet.clear();
  415.             for (RollingLaneStructureRecord record : set)
  416.             {
  417.                 for (RollingLaneStructureRecord prevRecord : record.getPrev())
  418.                 {
  419.                     Iterator<RollingLaneStructureRecord> it = prevRecord.getNext().iterator();
  420.                     while (it.hasNext())
  421.                     {
  422.                         RollingLaneStructureRecord otherDown = it.next();
  423.                         if (!otherDown.getLane().getParentLink().equals(record.getLane().getParentLink()))
  424.                         {
  425.                             // split not taken can be thrown away
  426.                             otherDown.changeStartDistanceSource(null, null);
  427.                             // this can throw away records that are laterally connected as they later merge... ??
  428.                             removeDownstream(otherDown, LateralDirectionality.NONE);
  429.                             removeRecord(otherDown);
  430.                             it.remove();
  431.                         }
  432.                     }
  433.                     LaneStructureRecord source = prevRecord.getStartDistanceSource();
  434.                     if (source == null || (source != null && !source.equals(record)))
  435.                     {
  436.                         prevRecord.changeStartDistanceSource(record, RecordLink.UP);
  437.                         newSet.add(prevRecord);
  438.                     }
  439.                 }
  440.             }

  441.             // next loop
  442.             set = newSet;
  443.         }
  444.     }

  445.     /**
  446.      * Removes all records downstream of the given record from underlying data structures.
  447.      * @param record RollingLaneStructureRecord; record, downstream of which to remove all records
  448.      * @param lat LateralDirectionality; records with an adjacent record at this side are not deleted
  449.      */
  450.     private void removeDownstream(final RollingLaneStructureRecord record, final LateralDirectionality lat)
  451.     {
  452.         for (RollingLaneStructureRecord next : record.getNext())
  453.         {
  454.             RollingLaneStructureRecord adj = lat.isLeft() ? next.getLeft() : lat.isRight() ? next.getRight() : null;
  455.             if (adj == null)
  456.             {
  457.                 next.changeStartDistanceSource(null, null);
  458.                 removeDownstream(next, lat);
  459.                 removeRecord(next);
  460.             }
  461.         }
  462.         record.clearNextList();
  463.     }

  464.     /**
  465.      * Removes the record from underlying data structures.
  466.      * @param record RollingLaneStructureRecord; record to remove
  467.      */
  468.     private void removeRecord(final RollingLaneStructureRecord record)
  469.     {
  470.         RelativeLane lane = this.relativeLanes.get(record);
  471.         if (lane != null)
  472.         {
  473.             this.relativeLaneMap.get(lane).remove(record);
  474.             this.relativeLanes.remove(record);
  475.         }
  476.         record.changeStartDistanceSource(null, null);

  477.     }

  478.     /**
  479.      * On a new build, this method is used to create the upstream map.
  480.      * @param gtuType GTUType; GTU type
  481.      * @param fractionalPosition double; fractional position on reference link
  482.      * @throws GTUException on exception
  483.      */
  484.     private void expandUpstreamEdge(final GTUType gtuType, final double fractionalPosition) throws GTUException
  485.     {
  486.         this.ignoreSet.clear();
  487.         for (LaneStructureRecord record : this.upstreamEdge)
  488.         {
  489.             this.ignoreSet.add(record.getLane());
  490.         }
  491.         Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
  492.         boolean expand = true;
  493.         while (expand)
  494.         {
  495.             expand = false;

  496.             // longitudinal
  497.             Iterator<RollingLaneStructureRecord> iterator = this.upstreamEdge.iterator();
  498.             Set<RollingLaneStructureRecord> modifiedEdge = new LinkedHashSet<>(this.upstreamEdge);
  499.             while (iterator.hasNext())
  500.             {
  501.                 RollingLaneStructureRecord prev = iterator.next();
  502.                 ImmutableMap<Lane, GTUDirectionality> nexts = prev.getLane().upstreamLanes(prev.getDirection(), gtuType);
  503.                 if (prev.getStartDistance().si < this.up.si)
  504.                 {
  505.                     // upstream search ends on this lane
  506.                     prev.setCutOffStart(this.up.minus(prev.getStartDistance()));
  507.                     for (Lane prevLane : nexts.keySet())
  508.                     {
  509.                         this.ignoreSet.add(prevLane); // exclude in lateral search
  510.                     }
  511.                 }
  512.                 else
  513.                 {
  514.                     // upstream search goes further upstream
  515.                     prev.clearCutOffStart();
  516.                     iterator.remove();
  517.                     if (!nexts.isEmpty())
  518.                     {
  519.                         for (Lane prevLane : nexts.keySet())
  520.                         {
  521.                             RelativeLane relativeLane = this.relativeLanes.get(prev);
  522.                             RollingLaneStructureRecord next = constructRecord(prevLane, nexts.get(prevLane), prev,
  523.                                 RecordLink.UP, relativeLane);
  524.                             this.ignoreSet.add(prevLane);
  525.                             next.updateStartDistance(fractionalPosition, this);
  526.                             connectLaterally(next, gtuType, modifiedEdge);
  527.                             next.addNext(prev);
  528.                             prev.addPrev(next);
  529.                             nextSet.add(next);
  530.                             modifiedEdge.add(next);
  531.                         }
  532.                     }
  533.                 }
  534.             }
  535.             this.upstreamEdge.addAll(nextSet);
  536.             expand |= !nextSet.isEmpty();
  537.             nextSet.clear();

  538.             // lateral
  539.             Set<RollingLaneStructureRecord> lateralSet = expandLateral(this.upstreamEdge, RecordLink.LATERAL_END, gtuType,
  540.                 fractionalPosition);
  541.             nextSet.addAll(lateralSet);

  542.             // next iteration
  543.             this.upstreamEdge.addAll(nextSet);
  544.             expand |= !nextSet.isEmpty();
  545.             nextSet.clear();
  546.         }
  547.     }

  548.     /**
  549.      * Helper method for upstream and downstream expansion. This method returns all lanes that can be laterally found from the
  550.      * input set.
  551.      * @param edge Set&lt;RollingLaneStructureRecord&gt;; input set
  552.      * @param recordLink RecordLink; link to add between lateral records, depends on upstream or downstream search
  553.      * @param gtuType GTUType; GTU type
  554.      * @param fractionalPosition double; fractional position on reference link
  555.      * @return Set&lt;LaneStructureRecord&gt;; output set with all laterally found lanes
  556.      */
  557.     private Set<RollingLaneStructureRecord> expandLateral(final Set<RollingLaneStructureRecord> edge,
  558.         final RecordLink recordLink, final GTUType gtuType, final double fractionalPosition)
  559.     {
  560.         Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
  561.         Set<Lane> laneSet = new LinkedHashSet<>(); // set to check that an adjacent lane is not another lane already in the set
  562.         for (LaneStructureRecord record : edge)
  563.         {
  564.             laneSet.add(record.getLane());
  565.         }
  566.         Iterator<RollingLaneStructureRecord> iterator = edge.iterator();
  567.         while (iterator.hasNext())
  568.         {
  569.             RollingLaneStructureRecord record = iterator.next();
  570.             for (LateralDirectionality latDirection : new LateralDirectionality[] {LateralDirectionality.LEFT,
  571.                 LateralDirectionality.RIGHT})
  572.             {
  573.                 if (record.getRight() != null && latDirection.isRight() || record.getLeft() != null && latDirection.isLeft())
  574.                 {
  575.                     // skip if there already is a record on that side
  576.                     continue;
  577.                 }
  578.                 RelativeLane relativeLane = this.relativeLanes.get(record);
  579.                 RollingLaneStructureRecord prev = record;
  580.                 Set<Lane> adjacentLanes = prev.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, prev
  581.                     .getDirection());
  582.                 while (!adjacentLanes.isEmpty())
  583.                 {
  584.                     Throw.when(adjacentLanes.size() > 1, RuntimeException.class,
  585.                         "Multiple adjacent lanes encountered during construction of lane map.");
  586.                     relativeLane = latDirection.isLeft() ? relativeLane.getLeft() : relativeLane.getRight();
  587.                     Lane nextLane = adjacentLanes.iterator().next();
  588.                     if (!laneSet.contains(nextLane) && !this.ignoreSet.contains(nextLane))
  589.                     {
  590.                         RollingLaneStructureRecord next = constructRecord(nextLane, record.getDirection(), prev, recordLink,
  591.                             relativeLane);
  592.                         this.ignoreSet.add(nextLane);
  593.                         next.updateStartDistance(fractionalPosition, this);
  594.                         nextSet.add(next);
  595.                         laneSet.add(nextLane);
  596.                         if (latDirection.isLeft())
  597.                         {
  598.                             prev.setLeft(next, gtuType);
  599.                             if (nextLane.accessibleAdjacentLanesPhysical(LateralDirectionality.RIGHT, gtuType, prev
  600.                                 .getDirection()).contains(prev.getLane()))
  601.                             {
  602.                                 next.setRight(prev, gtuType);
  603.                             }
  604.                             for (RollingLaneStructureRecord edgeRecord : edge)
  605.                             {
  606.                                 if (!edgeRecord.equals(prev) && edgeRecord.getLane().getParentLink().equals(next.getLane()
  607.                                     .getParentLink()))
  608.                                 {
  609.                                     for (Lane adjLane : edgeRecord.getLane().accessibleAdjacentLanesPhysical(
  610.                                         LateralDirectionality.RIGHT, gtuType, edgeRecord.getDirection()))
  611.                                     {
  612.                                         if (adjLane.equals(next.getLane()))
  613.                                         {
  614.                                             edgeRecord.setRight(next, gtuType);
  615.                                             next.setLeft(edgeRecord, gtuType);
  616.                                         }
  617.                                     }
  618.                                 }
  619.                             }
  620.                         }
  621.                         else
  622.                         {
  623.                             prev.setRight(next, gtuType);
  624.                             if (nextLane.accessibleAdjacentLanesPhysical(LateralDirectionality.LEFT, gtuType, prev
  625.                                 .getDirection()).contains(prev.getLane()))
  626.                             {
  627.                                 next.setLeft(prev, gtuType);
  628.                             }
  629.                             for (RollingLaneStructureRecord edgeRecord : edge)
  630.                             {
  631.                                 if (!edgeRecord.equals(prev) && edgeRecord.getLane().getParentLink().equals(next.getLane()
  632.                                     .getParentLink()))
  633.                                 {
  634.                                     for (Lane adjLane : edgeRecord.getLane().accessibleAdjacentLanesPhysical(
  635.                                         LateralDirectionality.LEFT, gtuType, edgeRecord.getDirection()))
  636.                                     {
  637.                                         if (adjLane.equals(next.getLane()))
  638.                                         {
  639.                                             edgeRecord.setLeft(next, gtuType);
  640.                                             next.setRight(edgeRecord, gtuType);
  641.                                         }
  642.                                     }
  643.                                 }
  644.                             }
  645.                         }
  646.                         // connect longitudinally due to merge or split
  647.                         Set<RollingLaneStructureRecord> adjs = new LinkedHashSet<>();
  648.                         if (next.getLeft() != null)
  649.                         {
  650.                             adjs.add(next.getLeft());
  651.                         }
  652.                         if (next.getRight() != null)
  653.                         {
  654.                             adjs.add(next.getRight());
  655.                         }
  656.                         for (RollingLaneStructureRecord adj : adjs)
  657.                         {
  658.                             for (Lane lane : next.getLane().upstreamLanes(next.getDirection(), gtuType).keySet())
  659.                             {
  660.                                 for (RollingLaneStructureRecord adjPrev : adj.getPrev())
  661.                                 {
  662.                                     if (lane.equals(adjPrev.getLane()))
  663.                                     {
  664.                                         Try.execute(() -> next.addPrev(adjPrev), "Cut-off record added as prev.");
  665.                                     }
  666.                                 }
  667.                             }
  668.                             for (Lane lane : next.getLane().downstreamLanes(next.getDirection(), gtuType).keySet())
  669.                             {
  670.                                 for (RollingLaneStructureRecord adjNext : adj.getNext())
  671.                                 {
  672.                                     if (lane.equals(adjNext.getLane()))
  673.                                     {
  674.                                         Try.execute(() -> next.addNext(adjNext), "Cut-off record added as next.");
  675.                                     }
  676.                                 }
  677.                             }
  678.                         }

  679.                         prev = next;
  680.                         adjacentLanes = prev.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, prev
  681.                             .getDirection());
  682.                     }
  683.                     else
  684.                     {
  685.                         break;
  686.                     }
  687.                 }
  688.             }
  689.         }
  690.         return nextSet;
  691.     }

  692.     /**
  693.      * This method makes sure that not all history is maintained forever, and the upstream edge moves with the GTU.
  694.      * @throws GTUException on exception
  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.                 // nexts may contain 'prev' as two lanes are removed from the upstream edge that merge in to 1 downstream lane
  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.             // check adjacent lanes
  745.             for (RollingLaneStructureRecord record : nexts)
  746.             {
  747.                 RollingLaneStructureRecord prev = record;
  748.                 for (LateralDirectionality latDirection : new LateralDirectionality[] {LateralDirectionality.LEFT,
  749.                     LateralDirectionality.RIGHT})
  750.                 {
  751.                     while (prev != null)
  752.                     {
  753.                         RollingLaneStructureRecord next = latDirection.isLeft() ? prev.getLeft() : prev.getRight();
  754.                         if (next != null && !this.upstreamEdge.contains(next))
  755.                         {
  756.                             moved |= findUpstreamEdge(next);
  757.                         }
  758.                         prev = next;
  759.                     }
  760.                 }
  761.             }
  762.         }
  763.     }

  764.     /**
  765.      * Recursive method to find downstream record(s) on the upstream edge, as the edge was moved downstream and a laterally
  766.      * connected lane was not yet in the upstream edge. All edge records are added to the edge set.
  767.      * @param record RollingLaneStructureRecord; newly found adjacent record after moving the upstream edge downstream
  768.      * @return boolean; whether a record was added to the edge, note that no record is added of the record is fully downstream
  769.      *         of the upstream view distance
  770.      * @throws GTUException on exception
  771.      */
  772.     private boolean findUpstreamEdge(final RollingLaneStructureRecord record) throws GTUException
  773.     {
  774.         Length cutOff = this.up.minus(record.getStartDistance());
  775.         boolean moved = false;
  776.         if (cutOff.gt0())
  777.         {
  778.             if (cutOff.lt(record.getLane().getLength()))
  779.             {
  780.                 record.clearPrevList();
  781.                 record.setCutOffStart(cutOff);
  782.                 this.upstreamEdge.add(record);
  783.                 moved = true;
  784.             }
  785.             else
  786.             {
  787.                 if (this.relativeLanes.containsKey(record))
  788.                 {
  789.                     // could have been removed from upstream already
  790.                     removeRecord(record);
  791.                 }
  792.                 for (RollingLaneStructureRecord next : record.getNext())
  793.                 {
  794.                     moved |= findUpstreamEdge(next);
  795.                 }
  796.             }
  797.         }
  798.         return moved;
  799.     }

  800.     /**
  801.      * Main downstream search for the map. Can be used at initial build and to update.
  802.      * @param gtuType GTUType; GTU type
  803.      * @param fractionalPosition double; fractional position on reference link
  804.      * @param route Route; route of the GTU
  805.      * @throws GTUException on exception
  806.      */
  807.     private void expandDownstreamEdge(final GTUType gtuType, final double fractionalPosition, final Route route)
  808.         throws GTUException
  809.     {
  810.         this.ignoreSet.clear();
  811.         for (LaneStructureRecord record : this.downstreamEdge)
  812.         {
  813.             this.ignoreSet.add(record.getLane());
  814.         }
  815.         Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
  816.         Set<RollingLaneStructureRecord> splitSet = new LinkedHashSet<>();
  817.         boolean expand = true;
  818.         while (expand)
  819.         {
  820.             expand = false;

  821.             // longitudinal
  822.             // find links to extend from so we can add lanes if -any- of the next lanes comes within the perception distance
  823.             Set<Link> linksToExpandFrom = new LinkedHashSet<>();
  824.             Iterator<RollingLaneStructureRecord> iterator = this.downstreamEdge.iterator();
  825.             while (iterator.hasNext())
  826.             {
  827.                 RollingLaneStructureRecord record = iterator.next();
  828.                 if (record.getStartDistance().si + record.getLane().getLength().si < this.down.si)
  829.                 {
  830.                     linksToExpandFrom.add(record.getLane().getParentLink());
  831.                 }
  832.             }
  833.             Set<RollingLaneStructureRecord> modifiedEdge = new LinkedHashSet<>(this.downstreamEdge);
  834.             iterator = this.downstreamEdge.iterator();
  835.             while (iterator.hasNext())
  836.             {
  837.                 RollingLaneStructureRecord record = iterator.next();
  838.                 ImmutableMap<Lane, GTUDirectionality> nexts = record.getLane().downstreamLanes(record.getDirection(),
  839.                     gtuType);
  840.                 if (!linksToExpandFrom.contains(record.getLane().getParentLink()))
  841.                 {
  842.                     // downstream search ends on this lane
  843.                     record.setCutOffEnd(this.down.minus(record.getStartDistance()));
  844.                     for (Lane nextLane : nexts.keySet())
  845.                     {
  846.                         this.ignoreSet.add(nextLane); // exclude in lateral search
  847.                     }
  848.                 }
  849.                 else
  850.                 {
  851.                     // downstream search goes further downstream

  852.                     // in case there are multiple lanes on the same link after a lane split, we need to choose one
  853.                     LaneDirection nextLaneDirection = new LaneDirection(record.getLane(), record.getDirection())
  854.                         .getNextLaneDirection(this.containingGtu);

  855.                     record.clearCutOffEnd();
  856.                     iterator.remove(); // can remove from edge, no algorithm needs it anymore in the downstream edge
  857.                     for (Lane nextLane : nexts.keySet())
  858.                     {
  859.                         if (nextLaneDirection != null && nextLane.getParentLink().equals(nextLaneDirection.getLane()
  860.                             .getParentLink()) && !nextLane.equals(nextLaneDirection.getLane()))
  861.                         {
  862.                             // skip this lane as its a not chosen lane on the next link after a lane split
  863.                             continue;
  864.                         }
  865.                         RelativeLane relativeLane = this.relativeLanes.get(record);
  866.                         GTUDirectionality dir = nexts.get(nextLane);
  867.                         RollingLaneStructureRecord next = constructRecord(nextLane, dir, record, RecordLink.DOWN,
  868.                             relativeLane);
  869.                         this.ignoreSet.add(nextLane);
  870.                         next.updateStartDistance(fractionalPosition, this);
  871.                         record.addNext(next);
  872.                         next.addPrev(record);
  873.                         connectLaterally(next, gtuType, modifiedEdge);
  874.                         // check route
  875.                         int from = route == null ? 0 : route.indexOf(next.getFromNode());
  876.                         int to = route == null ? 1 : route.indexOf(next.getToNode());
  877.                         if (to < 0 || to - from != 1)
  878.                         {
  879.                             // not on our route, add some distance and stop
  880.                             splitSet.add(next);
  881.                         }
  882.                         else
  883.                         {
  884.                             // regular edge
  885.                             nextSet.add(next);
  886.                         }
  887.                         modifiedEdge.add(next);
  888.                         // expand upstream over any possible other lane that merges in to this
  889.                         Set<RollingLaneStructureRecord> set = new LinkedHashSet<>();
  890.                         set.add(next);
  891.                         expandUpstreamMerge(set, gtuType, fractionalPosition, route);
  892.                     }
  893.                 }
  894.             }
  895.             this.downstreamEdge.addAll(nextSet);
  896.             expand |= !nextSet.isEmpty();
  897.             nextSet.clear();

  898.             // split
  899.             expandDownstreamSplit(splitSet, gtuType, fractionalPosition, route);
  900.             splitSet.clear();

  901.             // lateral
  902.             Set<RollingLaneStructureRecord> lateralSet = expandLateral(this.downstreamEdge, RecordLink.LATERAL_END, gtuType,
  903.                 fractionalPosition);
  904.             nextSet.addAll(lateralSet);
  905.             expandUpstreamMerge(lateralSet, gtuType, fractionalPosition, route);

  906.             // next iteration
  907.             this.downstreamEdge.addAll(nextSet);
  908.             expand |= !nextSet.isEmpty();
  909.             nextSet.clear();
  910.         }
  911.     }

  912.     /**
  913.      * Expand the map to include a limited section downstream of a split, regarding links not on the route.
  914.      * @param set Set&lt;RollingLaneStructureRecord&gt;; set of lanes that have been laterally found
  915.      * @param gtuType GTUType; GTU type
  916.      * @param fractionalPosition double; fractional position on reference link
  917.      * @param route Route; route
  918.      * @throws GTUException on exception
  919.      */
  920.     private void expandDownstreamSplit(final Set<RollingLaneStructureRecord> set, final GTUType gtuType,
  921.         final double fractionalPosition, final Route route) throws GTUException
  922.     {
  923.         Map<RollingLaneStructureRecord, Length> prevs = new LinkedHashMap<>();
  924.         Map<RollingLaneStructureRecord, Length> nexts = new LinkedHashMap<>();
  925.         for (RollingLaneStructureRecord record : set)
  926.         {
  927.             prevs.put(record, record.getStartDistance().plus(this.downSplit));
  928.         }
  929.         while (!prevs.isEmpty())
  930.         {
  931.             for (RollingLaneStructureRecord prev : prevs.keySet())
  932.             {
  933.                 ImmutableMap<Lane, GTUDirectionality> nextLanes = prev.getLane().downstreamLanes(prev.getDirection(),
  934.                     gtuType);
  935.                 RelativeLane relativeLane = this.relativeLanes.get(prev);
  936.                 for (Lane nextLane : nextLanes.keySet())
  937.                 {
  938.                     GTUDirectionality dir = nextLanes.get(nextLane);
  939.                     Node fromNode = dir.isPlus() ? nextLane.getParentLink().getStartNode() : nextLane.getParentLink()
  940.                         .getEndNode();
  941.                     Node toNode = dir.isPlus() ? nextLane.getParentLink().getEndNode() : nextLane.getParentLink()
  942.                         .getStartNode();
  943.                     int from = route.indexOf(fromNode);
  944.                     int to = route.indexOf(toNode);
  945.                     if (from == -1 || to == -1 || to - from != 1)
  946.                     {
  947.                         RollingLaneStructureRecord next = constructRecord(nextLane, dir, prev, RecordLink.DOWN,
  948.                             relativeLane);
  949.                         next.updateStartDistance(fractionalPosition, this);
  950.                         next.addPrev(prev);
  951.                         prev.addNext(next);
  952.                         connectLaterally(next, gtuType, nexts.keySet());
  953.                         Length downHere = prevs.get(prev);
  954.                         if (next.getStartDistance().si > downHere.si)
  955.                         {
  956.                             next.setCutOffEnd(downHere.minus(next.getStartDistance()));
  957.                         }
  958.                         else
  959.                         {
  960.                             nexts.put(next, downHere);
  961.                         }
  962.                     }
  963.                 }
  964.             }
  965.             prevs = nexts;
  966.             nexts = new LinkedHashMap<>();
  967.         }
  968.     }

  969.     /**
  970.      * Expand the map to include a limited section upstream of a merge that is downstream, regarding links not on the route.
  971.      * @param set Set&lt;RollingLaneStructureRecord&gt;; set of lanes that have been laterally found
  972.      * @param gtuType GTUType; GTU type
  973.      * @param fractionalPosition double; fractional position on reference link
  974.      * @param route Route; route of the GTU
  975.      * @throws GTUException on exception
  976.      */
  977.     private void expandUpstreamMerge(final Set<RollingLaneStructureRecord> set, final GTUType gtuType,
  978.         final double fractionalPosition, final Route route) throws GTUException
  979.     {
  980.         Map<RollingLaneStructureRecord, Length> prevs = new LinkedHashMap<>();
  981.         Map<RollingLaneStructureRecord, Length> nexts = new LinkedHashMap<>();
  982.         for (RollingLaneStructureRecord record : set)
  983.         {
  984.             prevs.put(record, record.getStartDistance().plus(this.upMerge)); // upMerge is negative
  985.         }
  986.         while (!prevs.isEmpty())
  987.         {
  988.             for (RollingLaneStructureRecord prev : prevs.keySet())
  989.             {
  990.                 ImmutableMap<Lane, GTUDirectionality> nextLanes = prev.getLane().upstreamLanes(prev.getDirection(), gtuType);
  991.                 boolean anyAdded = false;
  992.                 for (Lane nextLane : nextLanes.keySet())
  993.                 {
  994.                     GTUDirectionality dir = nextLanes.get(nextLane);
  995.                     Node fromNode = dir.isPlus() ? nextLane.getParentLink().getStartNode() : nextLane.getParentLink()
  996.                         .getEndNode();
  997.                     Node toNode = dir.isPlus() ? nextLane.getParentLink().getEndNode() : nextLane.getParentLink()
  998.                         .getStartNode();
  999.                     int from = route == null ? 0 : route.indexOf(fromNode);
  1000.                     int to = route == null ? 1 : route.indexOf(toNode);
  1001.                     // TODO we now assume everything is on the route, but merges could be ok without route
  1002.                     // so, without a route we should be able to recognize which upstream 'nextLane' is on the other link
  1003.                     if (from == -1 || to == -1 || to - from != 1)
  1004.                     {
  1005.                         anyAdded = true;
  1006.                         RelativeLane relativeLane = this.relativeLanes.get(prev);
  1007.                         RollingLaneStructureRecord next = constructRecord(nextLane, nextLanes.get(nextLane), prev,
  1008.                             RecordLink.UP, relativeLane);
  1009.                         next.updateStartDistance(fractionalPosition, this);
  1010.                         next.addNext(prev);
  1011.                         prev.addPrev(next);
  1012.                         connectLaterally(next, gtuType, nexts.keySet());
  1013.                         Length upHere = prevs.get(prev);
  1014.                         if (next.getStartDistance().si < upHere.si)
  1015.                         {
  1016.                             next.setCutOffStart(upHere.minus(next.getStartDistance()));
  1017.                             this.upstreamEdge.add(next);
  1018.                         }
  1019.                         else
  1020.                         {
  1021.                             nexts.put(next, upHere);
  1022.                         }
  1023.                     }
  1024.                 }
  1025.                 if (!anyAdded && !set.contains(prev))
  1026.                 {
  1027.                     this.upstreamEdge.add(prev);
  1028.                 }
  1029.             }
  1030.             prevs = nexts;
  1031.             nexts = new LinkedHashMap<>();
  1032.         }
  1033.     }

  1034.     /**
  1035.      * Helper method of various other methods that laterally couples lanes that have been longitudinally found.
  1036.      * @param record RollingLaneStructureRecord; longitudinally found lane
  1037.      * @param gtuType GTUType; GTU type
  1038.      * @param nextSet Set&lt;RollingLaneStructureRecord&gt;; set of records on current build edge
  1039.      */
  1040.     private void connectLaterally(final RollingLaneStructureRecord record, final GTUType gtuType,
  1041.         final Set<RollingLaneStructureRecord> nextSet)
  1042.     {
  1043.         for (RollingLaneStructureRecord other : nextSet)
  1044.         {
  1045.             for (LateralDirectionality latDirection : new LateralDirectionality[] {LateralDirectionality.LEFT,
  1046.                 LateralDirectionality.RIGHT})
  1047.             {
  1048.                 if ((latDirection.isLeft() ? other.getLeft() : other.getRight()) == null)
  1049.                 {
  1050.                     for (Lane otherLane : other.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, other
  1051.                         .getDirection()))
  1052.                     {
  1053.                         if (otherLane.equals(record.getLane()))
  1054.                         {
  1055.                             if (latDirection.isLeft())
  1056.                             {
  1057.                                 other.setLeft(record, gtuType);
  1058.                                 record.setRight(other, gtuType);
  1059.                             }
  1060.                             else
  1061.                             {
  1062.                                 other.setRight(record, gtuType);
  1063.                                 record.setLeft(other, gtuType);
  1064.                             }
  1065.                         }
  1066.                     }
  1067.                 }
  1068.             }
  1069.         }
  1070.     }

  1071.     /**
  1072.      * Creates a lane structure record and adds it to relevant maps.
  1073.      * @param lane Lane; lane
  1074.      * @param direction GTUDirectionality; direction
  1075.      * @param startDistanceSource RollingLaneStructureRecord; source of the start distance
  1076.      * @param recordLink RecordLink; record link
  1077.      * @param relativeLane RelativeLane; relative lane
  1078.      * @return created lane structure record
  1079.      */
  1080.     private RollingLaneStructureRecord constructRecord(final Lane lane, final GTUDirectionality direction,
  1081.         final RollingLaneStructureRecord startDistanceSource, final RecordLink recordLink, final RelativeLane relativeLane)
  1082.     {
  1083.         RollingLaneStructureRecord record = new RollingLaneStructureRecord(lane, direction, startDistanceSource, recordLink);
  1084.         if (!this.relativeLaneMap.containsKey(relativeLane))
  1085.         {
  1086.             this.relativeLaneMap.put(relativeLane, new LinkedHashSet<>());
  1087.         }
  1088.         this.relativeLaneMap.get(relativeLane).add(record);
  1089.         this.relativeLanes.put(record, relativeLane);
  1090.         return record;
  1091.     }

  1092.     /** {@inheritDoc} */
  1093.     @Override
  1094.     public final LaneStructureRecord getRootRecord()
  1095.     {
  1096.         return this.root.get();
  1097.     }

  1098.     /**
  1099.      * @param time Time; time to obtain the root at
  1100.      * @return rootRecord
  1101.      */
  1102.     public final LaneStructureRecord getRootRecord(final Time time)
  1103.     {
  1104.         return this.root.get(time);
  1105.     }

  1106.     /** {@inheritDoc} */
  1107.     @Override
  1108.     public final SortedSet<RelativeLane> getExtendedCrossSection()
  1109.     {
  1110.         return this.firstRecords.navigableKeySet();
  1111.     }

  1112.     /**
  1113.      * Returns the first record on the given lane. This is often a record in the current cross section, but it may be one
  1114.      * downstream for a lane that starts further downstream.
  1115.      * @param lane RelativeLane; lane
  1116.      * @return first record on the given lane, or {@code null} if no such record
  1117.      */
  1118.     @Override
  1119.     public final RollingLaneStructureRecord getFirstRecord(final RelativeLane lane)
  1120.     {
  1121.         if (this.firstRecords.containsKey(lane))
  1122.         {
  1123.             return this.firstRecords.get(lane);
  1124.         }
  1125.         // not in current cross section, get first via downstream
  1126.         RelativeLane rel = RelativeLane.CURRENT;
  1127.         int dMin = Integer.MAX_VALUE;
  1128.         for (RelativeLane relLane : this.crossSectionRecords.keySet())
  1129.         {
  1130.             if (relLane.getLateralDirectionality().equals(lane.getLateralDirectionality()))
  1131.             {
  1132.                 int d = lane.getNumLanes() - relLane.getNumLanes();
  1133.                 if (d < dMin)
  1134.                 {
  1135.                     rel = relLane;
  1136.                     d = dMin;
  1137.                 }
  1138.             }
  1139.         }
  1140.         RollingLaneStructureRecord record = this.crossSectionRecords.get(rel);
  1141.         // move downstream until a lateral move is made to the right relative lane
  1142.         while (rel.getNumLanes() < lane.getNumLanes())
  1143.         {
  1144.             RollingLaneStructureRecord adj = lane.getLateralDirectionality().isLeft() ? record.getLeft() : record.getRight();
  1145.             if (adj != null)
  1146.             {
  1147.                 rel = lane.getLateralDirectionality().isLeft() ? rel.getLeft() : rel.getRight();
  1148.                 record = adj;
  1149.             }
  1150.             else if (!record.getNext().isEmpty())
  1151.             {
  1152.                 LaneDirection laneDir = new LaneDirection(record.getLane(), record.getDirection()).getNextLaneDirection(
  1153.                     this.containingGtu);
  1154.                 if (laneDir == null)
  1155.                 {
  1156.                     record = null;
  1157.                     break;
  1158.                 }
  1159.                 RollingLaneStructureRecord chosenNext = null;
  1160.                 for (RollingLaneStructureRecord next : record.getNext())
  1161.                 {
  1162.                     if (next.getLane().equals(laneDir.getLane()))
  1163.                     {
  1164.                         chosenNext = next;
  1165.                         break;
  1166.                     }
  1167.                 }
  1168.                 // Throw.when(chosenNext == null, RuntimeException.class,
  1169.                 // "Unexpected exception while deriving first record not on the cross-section.");
  1170.                 record = chosenNext;
  1171.                 if (record == null)
  1172.                 {
  1173.                     // TODO: Temporary fix for Aimsun demo
  1174.                     break;
  1175.                 }
  1176.             }
  1177.             else
  1178.             {
  1179.                 // reached a dead-end
  1180.                 record = null;
  1181.                 break;
  1182.             }
  1183.         }
  1184.         if (record != null)
  1185.         {
  1186.             // now move upstream until we are at x = 0
  1187.             while (record.getPrev().size() == 1 && record.getStartDistance().gt0())
  1188.             {
  1189.                 record = record.getPrev().get(0);
  1190.             }
  1191.             this.firstRecords.put(lane, record);
  1192.         }
  1193.         return record;
  1194.     }

  1195.     /**
  1196.      * Retrieve objects of a specific type. Returns objects over a maximum length of the look ahead distance downstream from the
  1197.      * relative position, or as far as the lane map goes.
  1198.      * @param clazz Class&lt;T&gt;; class of objects to find
  1199.      * @param gtu LaneBasedGTU; gtu
  1200.      * @param pos RelativePosition.TYPE; relative position to start search from
  1201.      * @param <T> type of objects to find
  1202.      * @return Sorted set of objects of requested type per lane
  1203.      * @throws GTUException if lane is not in current set
  1204.      */
  1205.     @Override
  1206.     public final <T extends LaneBasedObject> Map<RelativeLane, SortedSet<Entry<T>>> getDownstreamObjects(
  1207.         final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos) throws GTUException
  1208.     {
  1209.         Map<RelativeLane, SortedSet<Entry<T>>> out = new LinkedHashMap<>();
  1210.         for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
  1211.         {
  1212.             out.put(relativeLane, getDownstreamObjects(relativeLane, clazz, gtu, pos));
  1213.         }
  1214.         return out;
  1215.     }

  1216.     /**
  1217.      * Retrieve objects on a lane of a specific type. Returns objects over a maximum length of the look ahead distance
  1218.      * downstream from the relative position, or as far as the lane map goes.
  1219.      * @param lane RelativeLane; lane
  1220.      * @param clazz Class&lt;T&gt;; class of objects to find
  1221.      * @param gtu LaneBasedGTU; gtu
  1222.      * @param pos RelativePosition.TYPE; relative position to start search from
  1223.      * @param <T> type of objects to find
  1224.      * @return Sorted set of objects of requested type
  1225.      * @throws GTUException if lane is not in current set
  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.                 // the list is ordered, but only for DIR_PLUS, need to do our own ordering
  1240.                 Length minimumPosition;
  1241.                 Length maximumPosition;
  1242.                 if (record.getDirection().isPlus())
  1243.                 {
  1244.                     minimumPosition = Length.instantiateSI(ds - record.getStartDistance().si);
  1245.                     maximumPosition = Length.instantiateSI(record.getLane().getLength().si);
  1246.                 }
  1247.                 else
  1248.                 {
  1249.                     minimumPosition = Length.ZERO;
  1250.                     maximumPosition = Length.instantiateSI(record.getLane().getLength().si + record.getStartDistance().si
  1251.                         - ds);
  1252.                 }

  1253.                 for (LaneBasedObject object : record.getLane().getLaneBasedObjects(minimumPosition, maximumPosition))
  1254.                 {
  1255.                     if (clazz.isAssignableFrom(object.getClass()) && ((record.getDirection().isPlus() && object
  1256.                         .getDirection().isForwardOrBoth()) || (record.getDirection().isMinus() && object.getDirection()
  1257.                             .isBackwardOrBoth())))
  1258.                     {
  1259.                         // unchecked, but the above isAssignableFrom assures correctness
  1260.                         double distance = record.getDistanceToPosition(object.getLongitudinalPosition()).si - ds;
  1261.                         if (distance <= this.lookAhead.si)
  1262.                         {
  1263.                             set.add(new Entry<>(Length.instantiateSI(distance), (T) object));
  1264.                         }
  1265.                     }
  1266.                 }
  1267.             }
  1268.             getDownstreamObjectsRecursive(set, record, clazz, ds);
  1269.         }
  1270.         return set;
  1271.     }

  1272.     /**
  1273.      * Retrieve objects on a lane of a specific type. Returns objects over a maximum length of the look ahead distance
  1274.      * downstream from the relative position, or as far as the lane map goes. Objects on links not on the route are ignored.
  1275.      * @param lane RelativeLane; lane
  1276.      * @param clazz Class&lt;T&gt;; class of objects to find
  1277.      * @param gtu LaneBasedGTU; gtu
  1278.      * @param pos RelativePosition.TYPE; relative position to start search from
  1279.      * @param <T> type of objects to find
  1280.      * @param route Route; the route
  1281.      * @return Sorted set of objects of requested type
  1282.      * @throws GTUException if lane is not in current set
  1283.      */
  1284.     @Override
  1285.     public final <T extends LaneBasedObject> SortedSet<Entry<T>> getDownstreamObjectsOnRoute(final RelativeLane lane,
  1286.         final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos, final Route route) throws GTUException
  1287.     {
  1288.         SortedSet<Entry<T>> set = getDownstreamObjects(lane, clazz, gtu, pos);
  1289.         if (route != null)
  1290.         {
  1291.             Iterator<Entry<T>> iterator = set.iterator();
  1292.             while (iterator.hasNext())
  1293.             {
  1294.                 Entry<T> entry = iterator.next();
  1295.                 CrossSectionLink link = entry.getLaneBasedObject().getLane().getParentLink();
  1296.                 if (!route.contains(link.getStartNode()) || !route.contains(link.getEndNode()) || Math.abs(route.indexOf(link
  1297.                     .getStartNode()) - route.indexOf(link.getEndNode())) != 1)
  1298.                 {
  1299.                     iterator.remove();
  1300.                 }
  1301.             }
  1302.         }
  1303.         return set;
  1304.     }

  1305.     /**
  1306.      * Recursive search for lane based objects downstream.
  1307.      * @param set SortedSet&lt;Entry&lt;T&gt;&gt;; set to store entries into
  1308.      * @param record LaneStructureRecord; current record
  1309.      * @param clazz Class&lt;T&gt;; class of objects to find
  1310.      * @param ds double; distance from reference to chosen relative position
  1311.      * @param <T> type of objects to find
  1312.      */
  1313.     @SuppressWarnings("unchecked")
  1314.     private <T extends LaneBasedObject> void getDownstreamObjectsRecursive(final SortedSet<Entry<T>> set,
  1315.         final LaneStructureRecord record, final Class<T> clazz, final double ds)
  1316.     {
  1317.         if (record.getNext().isEmpty() || record.getNext().get(0).getStartDistance().gt(this.lookAhead))
  1318.         {
  1319.             return;
  1320.         }
  1321.         for (LaneStructureRecord next : record.getNext())
  1322.         {
  1323.             if (next.isDownstreamBranch())
  1324.             {
  1325.                 List<LaneBasedObject> list = next.getLane().getLaneBasedObjects();
  1326.                 int iStart, di;
  1327.                 if (record.getDirection().isPlus())
  1328.                 {
  1329.                     iStart = 0;
  1330.                     di = 1;
  1331.                 }
  1332.                 else
  1333.                 {
  1334.                     iStart = list.size() - 1;
  1335.                     di = -1;
  1336.                 }
  1337.                 for (int i = iStart; i >= 0 & i < list.size(); i += di)
  1338.                 {
  1339.                     LaneBasedObject object = list.get(i);
  1340.                     if (clazz.isAssignableFrom(object.getClass()) && ((record.getDirection().isPlus() && object
  1341.                         .getDirection().isForwardOrBoth()) || (record.getDirection().isMinus() && object.getDirection()
  1342.                             .isBackwardOrBoth())))
  1343.                     {
  1344.                         // unchecked, but the above isAssignableFrom assures correctness
  1345.                         double distance = next.getDistanceToPosition(object.getLongitudinalPosition()).si - ds;
  1346.                         if (distance <= this.lookAhead.si)
  1347.                         {
  1348.                             set.add(new Entry<>(Length.instantiateSI(distance), (T) object));
  1349.                         }
  1350.                         else
  1351.                         {
  1352.                             return;
  1353.                         }
  1354.                     }
  1355.                 }
  1356.             }
  1357.             getDownstreamObjectsRecursive(set, next, clazz, ds);
  1358.         }
  1359.     }

  1360.     /**
  1361.      * Retrieve objects of a specific type. Returns objects over a maximum length of the look ahead distance downstream from the
  1362.      * relative position, or as far as the lane map goes. Objects on links not on the route are ignored.
  1363.      * @param clazz Class&lt;T&gt;; class of objects to find
  1364.      * @param gtu LaneBasedGTU; gtu
  1365.      * @param pos RelativePosition.TYPE; relative position to start search from
  1366.      * @param <T> type of objects to find
  1367.      * @param route Route; the route
  1368.      * @return Sorted set of objects of requested type per lane
  1369.      * @throws GTUException if lane is not in current set
  1370.      */
  1371.     @Override
  1372.     public final <T extends LaneBasedObject> Map<RelativeLane, SortedSet<Entry<T>>> getDownstreamObjectsOnRoute(
  1373.         final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos, final Route route) throws GTUException
  1374.     {
  1375.         Map<RelativeLane, SortedSet<Entry<T>>> out = new LinkedHashMap<>();
  1376.         for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
  1377.         {
  1378.             out.put(relativeLane, getDownstreamObjectsOnRoute(relativeLane, clazz, gtu, pos, route));
  1379.         }
  1380.         return out;
  1381.     }

  1382.     /**
  1383.      * Retrieve objects on a lane of a specific type. Returns upstream objects from the relative position for as far as the lane
  1384.      * map goes. Distances to upstream objects are given as positive values.
  1385.      * @param lane RelativeLane; lane
  1386.      * @param clazz Class&lt;T&gt;; class of objects to find
  1387.      * @param gtu LaneBasedGTU; gtu
  1388.      * @param pos RelativePosition.TYPE; relative position to start search from
  1389.      * @param <T> type of objects to find
  1390.      * @return Sorted set of objects of requested type
  1391.      * @throws GTUException if lane is not in current set
  1392.      */
  1393.     @Override
  1394.     @SuppressWarnings("unchecked")
  1395.     public final <T extends LaneBasedObject> SortedSet<Entry<T>> getUpstreamObjects(final RelativeLane lane,
  1396.         final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos) throws GTUException
  1397.     {
  1398.         SortedSet<Entry<T>> set = new TreeSet<>();
  1399.         LaneStructureRecord record = this.getFirstRecord(lane);
  1400.         if (record.getStartDistance().gt0())
  1401.         {
  1402.             return set; // this lane is only downstream
  1403.         }
  1404.         Length ds = gtu.getReference().getDx().minus(gtu.getRelativePositions().get(pos).getDx());
  1405.         // the list is ordered, but only for DIR_PLUS, need to do our own ordering
  1406.         Length minimumPosition;
  1407.         Length maximumPosition;
  1408.         if (record.getDirection().isPlus())
  1409.         {
  1410.             minimumPosition = Length.ZERO;
  1411.             maximumPosition = record.getStartDistance().neg().minus(ds);
  1412.         }
  1413.         else
  1414.         {
  1415.             minimumPosition = record.getLane().getLength().plus(record.getStartDistance()).plus(ds);
  1416.             maximumPosition = record.getLane().getLength();
  1417.         }
  1418.         Length distance;
  1419.         for (LaneBasedObject object : record.getLane().getLaneBasedObjects(minimumPosition, maximumPosition))
  1420.         {
  1421.             if (clazz.isAssignableFrom(object.getClass()) && ((record.getDirection().isPlus() && object.getDirection()
  1422.                 .isForwardOrBoth()) || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
  1423.             {
  1424.                 distance = record.getDistanceToPosition(object.getLongitudinalPosition()).neg().minus(ds);
  1425.                 // unchecked, but the above isAssignableFrom assures correctness
  1426.                 set.add(new Entry<>(distance, (T) object));
  1427.             }
  1428.         }
  1429.         getUpstreamObjectsRecursive(set, record, clazz, ds);
  1430.         return set;
  1431.     }

  1432.     /**
  1433.      * Recursive search for lane based objects upstream.
  1434.      * @param set SortedSet&lt;Entry&lt;T&gt;&gt;; set to store entries into
  1435.      * @param record LaneStructureRecord; current record
  1436.      * @param clazz Class&lt;T&gt;; class of objects to find
  1437.      * @param ds Length; distance from reference to chosen relative position
  1438.      * @param <T> type of objects to find
  1439.      */
  1440.     @SuppressWarnings("unchecked")
  1441.     private <T extends LaneBasedObject> void getUpstreamObjectsRecursive(final SortedSet<Entry<T>> set,
  1442.         final LaneStructureRecord record, final Class<T> clazz, final Length ds)
  1443.     {
  1444.         for (LaneStructureRecord prev : record.getPrev())
  1445.         {
  1446.             Length distance;
  1447.             for (LaneBasedObject object : prev.getLane().getLaneBasedObjects())
  1448.             {
  1449.                 if (clazz.isAssignableFrom(object.getClass()) && ((record.getDirection().isPlus() && object.getDirection()
  1450.                     .isForwardOrBoth()) || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
  1451.                 {
  1452.                     distance = prev.getDistanceToPosition(object.getLongitudinalPosition()).neg().minus(ds);
  1453.                     // unchecked, but the above isAssignableFrom assures correctness
  1454.                     set.add(new Entry<>(distance, (T) object));
  1455.                 }
  1456.             }
  1457.             getUpstreamObjectsRecursive(set, prev, clazz, ds);
  1458.         }
  1459.     }

  1460.     /**
  1461.      * Print the lane structure as a number of lines in a String.
  1462.      * @param ls RollingLaneStructure; the lane structure to print
  1463.      * @param gtu LaneBasedGTU; the GTTU for which the lane structure is printed
  1464.      * @return a String with information about the RollingLaneStructire
  1465.      */
  1466.     public static String print(final RollingLaneStructure ls, final LaneBasedGTU gtu)
  1467.     {
  1468.         StringBuffer s = new StringBuffer();
  1469.         s.append(gtu.getSimulator().getSimulatorTime() + " " + gtu.getId() + " LANESTRUCTURE: ");
  1470.         for (LaneStructureRecord lsr : ls.relativeLanes.keySet())
  1471.         {
  1472.             s.append(lsr.toString() + "  ");
  1473.         }
  1474.         int totSize = 0;
  1475.         for (Set<RollingLaneStructureRecord> set : ls.relativeLaneMap.values())
  1476.         {
  1477.             totSize += set.size();
  1478.         }
  1479.         s.append("\n  relativeLanes.size()=" + ls.relativeLanes.size() + "  relativeLaneMap.totalSize()=" + totSize);
  1480.         return s.toString();
  1481.     }

  1482.     /** {@inheritDoc} */
  1483.     @Override
  1484.     public final String toString()
  1485.     {
  1486.         return "LaneStructure [rootLSR=" + this.root + "]";
  1487.     }

  1488.     /**
  1489.      * AnimationAccess provides access to a number of private fields in the structure, which should only be used read-only! <br>
  1490.      * <br>
  1491.      * Copyright (c) 2003-2020 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved.
  1492.      * See for project information <a href="https://www.simulation.tudelft.nl/" target="_blank">www.simulation.tudelft.nl</a>.
  1493.      * The source code and binary code of this software is proprietary information of Delft University of Technology.
  1494.      * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank">Alexander Verbraeck</a>
  1495.      */
  1496.     public class AnimationAccess
  1497.     {
  1498.         /**
  1499.          * @return the lane structure records of the cross section
  1500.          */
  1501.         @SuppressWarnings("synthetic-access")
  1502.         public TreeMap<RelativeLane, RollingLaneStructureRecord> getCrossSectionRecords()
  1503.         {
  1504.             return RollingLaneStructure.this.crossSectionRecords;
  1505.         }

  1506.         /**
  1507.          * @return the upstream edge
  1508.          */
  1509.         @SuppressWarnings("synthetic-access")
  1510.         public Set<RollingLaneStructureRecord> getUpstreamEdge()
  1511.         {
  1512.             return RollingLaneStructure.this.upstreamEdge;
  1513.         }

  1514.         /**
  1515.          * @return the downstream edge
  1516.          */
  1517.         @SuppressWarnings("synthetic-access")
  1518.         public Set<RollingLaneStructureRecord> getDownstreamEdge()
  1519.         {
  1520.             return RollingLaneStructure.this.downstreamEdge;
  1521.         }
  1522.     }

  1523.     /** {@inheritDoc} */
  1524.     @Override
  1525.     public void notify(final EventInterface event) throws RemoteException
  1526.     {
  1527.         // triggers an update of the lane structure at the end of the final plan during the lane change, which is deviative
  1528.         this.previouslyDeviative = false;
  1529.     }
  1530. }