RollingLaneStructureRecord.java

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

  2. import java.io.Serializable;
  3. import java.util.ArrayList;
  4. import java.util.HashSet;
  5. import java.util.LinkedHashSet;
  6. import java.util.List;
  7. import java.util.Set;

  8. import org.djunits.value.vdouble.scalar.Length;
  9. import org.opentrafficsim.core.gtu.GTUDirectionality;
  10. import org.opentrafficsim.core.gtu.GTUException;
  11. import org.opentrafficsim.core.gtu.GTUType;
  12. import org.opentrafficsim.core.gtu.NestedCache;
  13. import org.opentrafficsim.core.gtu.Try;
  14. import org.opentrafficsim.core.network.LateralDirectionality;
  15. import org.opentrafficsim.core.network.Link;
  16. import org.opentrafficsim.core.network.NetworkException;
  17. import org.opentrafficsim.core.network.Node;
  18. import org.opentrafficsim.core.network.route.Route;
  19. import org.opentrafficsim.road.network.lane.Lane;

  20. import nl.tudelft.simulation.language.Throw;

  21. /**
  22.  * A LaneStructureRecord contains information about the lanes that can be accessed from this lane by a GTUType. It tells whether
  23.  * there is a left and/or right lane by pointing to other LaneStructureRecords, and which successor LaneStructureRecord(s) there
  24.  * are at the end of the lane of this LaneStructureRecord. All information (left, right, next) is calculated relative to the
  25.  * driving direction of the GTU that owns this structure.
  26.  * <p>
  27.  * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  28.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
  29.  * </p>
  30.  * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
  31.  * initial version Feb 21, 2016 <br>
  32.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  33.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  34.  */
  35. public class RollingLaneStructureRecord implements LaneStructureRecord, Serializable
  36. {
  37.     /** */
  38.     private static final long serialVersionUID = 20160400L;

  39.     /** Cache of allows route information. */
  40.     // TODO clear on network change, with an event listener?
  41.     private static NestedCache<Boolean> allowsRouteCache =
  42.             new NestedCache<>(Lane.class, Route.class, GTUType.class, Boolean.class);

  43.     /** The lane of the LSR. */
  44.     private final Lane lane;

  45.     /** The direction in which we process this lane. */
  46.     private final GTUDirectionality gtuDirectionality;

  47.     /** The left LSR or null if not available. Left and right are relative to the <b>driving</b> direction. */
  48.     private RollingLaneStructureRecord left;

  49.     /** Legal left lane change possibility. */
  50.     private boolean mayChangeLeft;

  51.     /** The right LSR or null if not available. Left and right are relative to the <b>driving</b> direction. */
  52.     private RollingLaneStructureRecord right;

  53.     /** Legal right lane change possibility. */
  54.     private boolean mayChangeRight;

  55.     /** Where this lane was cut-off resulting in no next lanes, if so. */
  56.     private Length cutOffEnd = null;

  57.     /** Where this lane was cut-off resulting in no prev lanes, if so. */
  58.     private Length cutOffStart = null;

  59.     /** Distance to start of the record, negative for backwards. */
  60.     private Length startDistance;

  61.     /**
  62.      * The next LSRs. The list is empty if no LSRs are available. Next is relative to the driving direction, not to the design
  63.      * line direction.
  64.      */
  65.     private List<RollingLaneStructureRecord> nextList = new ArrayList<>();

  66.     /**
  67.      * The previous LSRs. The list is empty if no LSRs are available. Previous is relative to the driving direction, not to the
  68.      * design line direction.
  69.      */
  70.     private List<RollingLaneStructureRecord> prevList = new ArrayList<>();

  71.     /** Record who's start start distance is used to calculate the start distance of this record. */
  72.     private RollingLaneStructureRecord source;

  73.     /** Start distance link between records. */
  74.     private RecordLink sourceLink;

  75.     /** Set of records who's starting position depends on this record. */
  76.     private final Set<RollingLaneStructureRecord> dependentRecords = new LinkedHashSet<>();

  77.     /**
  78.      * Constructor.
  79.      * @param lane Lane; lane
  80.      * @param direction GTUDirectionality; direction of travel for the GTU
  81.      * @param startDistanceSource LaneStructureRecord; record on which the start distance is based
  82.      * @param recordLink RecordLink; link type to source
  83.      */
  84.     public RollingLaneStructureRecord(final Lane lane, final GTUDirectionality direction,
  85.             final RollingLaneStructureRecord startDistanceSource, final RecordLink recordLink)
  86.     {
  87.         this.lane = lane;
  88.         this.gtuDirectionality = direction;
  89.         this.source = startDistanceSource;
  90.         this.sourceLink = recordLink;
  91.         if (startDistanceSource != null)
  92.         {
  93.             startDistanceSource.dependentRecords.add(this);
  94.         }
  95.     }

  96.     /**
  97.      * @param lane the lane of the LSR
  98.      * @param direction the direction on which we process this lane
  99.      * @param startDistance distance to start of the record, negative for backwards
  100.      */
  101.     public RollingLaneStructureRecord(final Lane lane, final GTUDirectionality direction, final Length startDistance)
  102.     {
  103.         this.lane = lane;
  104.         this.gtuDirectionality = direction;
  105.         this.startDistance = startDistance;

  106.         this.source = null;
  107.         this.sourceLink = null;
  108.     }

  109.     /** {@inheritDoc} */
  110.     @Override
  111.     public Length getLength()
  112.     {
  113.         return getLane().getLength();
  114.     }

  115.     /**
  116.      * Change the source of the distance.
  117.      * @param startDistanceSource LaneStructureRecord; record on which the start distance is based
  118.      * @param recordLink RecordLink; link type to source
  119.      */
  120.     final void changeStartDistanceSource(final RollingLaneStructureRecord startDistanceSource, final RecordLink recordLink)
  121.     {
  122.         // clear link
  123.         if (this.source != null)
  124.         {
  125.             this.source.dependentRecords.remove(this);
  126.         }
  127.         // set new link
  128.         this.source = startDistanceSource;
  129.         this.sourceLink = recordLink;
  130.         if (this.source != null)
  131.         {
  132.             this.source.dependentRecords.add(this);
  133.         }
  134.     }

  135.     /**
  136.      * Updates the start distance, including all records who's start distance depends on this value. Advised is to only initiate
  137.      * this at the root record. Note that before this is invoked, all record-links should be updated.
  138.      * @param fractionalPosition double; fractional position at the current cross-section
  139.      * @param laneStructure LaneStructure; parent lane structure
  140.      */
  141.     final void updateStartDistance(final double fractionalPosition, final RollingLaneStructure laneStructure)
  142.     {
  143.         this.startDistance = this.sourceLink.calculateStartDistance(this.source, this, fractionalPosition);
  144.         for (RollingLaneStructureRecord record : this.dependentRecords)
  145.         {
  146.             record.updateStartDistance(fractionalPosition, laneStructure);
  147.         }
  148.     }

  149.     /**
  150.      * Returns the source of the start distance.
  151.      * @return LaneStructureRecord; source of the start distance
  152.      */
  153.     final RollingLaneStructureRecord getStartDistanceSource()
  154.     {
  155.         return this.source;
  156.     }

  157.     /** {@inheritDoc} */
  158.     @Override
  159.     public final Node getFromNode()
  160.     {
  161.         return this.gtuDirectionality.isPlus() ? this.lane.getParentLink().getStartNode()
  162.                 : this.lane.getParentLink().getEndNode();
  163.     }

  164.     /** {@inheritDoc} */
  165.     @Override
  166.     public final Node getToNode()
  167.     {
  168.         return this.gtuDirectionality.isPlus() ? this.lane.getParentLink().getEndNode()
  169.                 : this.lane.getParentLink().getStartNode();
  170.     }

  171.     /**
  172.      * @return whether the link to which this lane belongs splits, i.e. some of the parallel, connected lanes lead to a
  173.      *         different destination than others
  174.      */
  175.     @Deprecated
  176.     public final boolean isLinkSplit()
  177.     {
  178.         if (isCutOffEnd())
  179.         {
  180.             // if the end is a split, it's out of range
  181.             return false;
  182.         }
  183.         Set<Node> toNodes = new HashSet<>();
  184.         LaneStructureRecord lsr = this;
  185.         while (lsr != null)
  186.         {
  187.             for (LaneStructureRecord next : lsr.getNext())
  188.             {
  189.                 toNodes.add(next.getToNode());
  190.             }
  191.             lsr = lsr.getLeft();
  192.         }
  193.         lsr = this.getRight();
  194.         while (lsr != null)
  195.         {
  196.             for (LaneStructureRecord next : lsr.getNext())
  197.             {
  198.                 toNodes.add(next.getToNode());
  199.             }
  200.             lsr = lsr.getRight();
  201.         }
  202.         return toNodes.size() > 1;
  203.     }

  204.     /**
  205.      * @return whether the link to which this lane belongs merges, i.e. some of the parallel, connected lanes follow from a
  206.      *         different origin than others
  207.      */
  208.     public final boolean isLinkMerge()
  209.     {
  210.         if (isCutOffStart())
  211.         {
  212.             // if the start is a merge, it's out of range
  213.             return false;
  214.         }
  215.         Set<Node> fromNodes = new HashSet<>();
  216.         LaneStructureRecord lsr = this;
  217.         while (lsr != null)
  218.         {
  219.             for (LaneStructureRecord prev : lsr.getPrev())
  220.             {
  221.                 fromNodes.add(prev.getFromNode());
  222.             }
  223.             lsr = lsr.getLeft();
  224.         }
  225.         lsr = this.getRight();
  226.         while (lsr != null)
  227.         {
  228.             for (LaneStructureRecord prev : lsr.getPrev())
  229.             {
  230.                 fromNodes.add(prev.getFromNode());
  231.             }
  232.             lsr = lsr.getRight();
  233.         }
  234.         return fromNodes.size() > 1;
  235.     }

  236.     /** {@inheritDoc} */
  237.     @Override
  238.     public final boolean allowsRoute(final Route route, final GTUType gtuType) throws NetworkException
  239.     {
  240.         return allowsRoute(route, gtuType, false);
  241.     }

  242.     /** {@inheritDoc} */
  243.     @Override
  244.     public final boolean allowsRouteAtEnd(final Route route, final GTUType gtuType) throws NetworkException
  245.     {
  246.         return allowsRoute(route, gtuType, true);
  247.     }

  248.     /**
  249.      * Returns whether (the end of) this lane allows the route to be followed, using caching.
  250.      * @param route Route; the route to follow
  251.      * @param gtuType GTUType; gtu type
  252.      * @param end boolean; whether to consider the end (or otherwise the lane itself, i.e. allow lane change from this lane)
  253.      * @return whether the end of this lane allows the route to be followed
  254.      * @throws NetworkException if no destination node
  255.      */
  256.     private boolean allowsRoute(final Route route, final GTUType gtuType, final boolean end) throws NetworkException
  257.     {
  258.         return allowsRouteCache.getValue(() -> Try.assign(() -> allowsRoute0(route, gtuType, end), "no destination"), this.lane,
  259.                 route, gtuType, end);
  260.     }

  261.     /**
  262.      * Returns whether (the end of) this lane allows the route to be followed.
  263.      * @param route Route; the route to follow
  264.      * @param gtuType GTUType; gtu type
  265.      * @param end boolean; whether to consider the end (or otherwise the lane itself, i.e. allow lane change from this lane)
  266.      * @return whether the end of this lane allows the route to be followed
  267.      * @throws NetworkException if no destination node
  268.      */
  269.     private boolean allowsRoute0(final Route route, final GTUType gtuType, final boolean end) throws NetworkException
  270.     {

  271.         // driving without route
  272.         if (route == null)
  273.         {
  274.             return true;
  275.         }

  276.         // start with simple check
  277.         int from = route.indexOf(getFromNode());
  278.         int to = route.indexOf(getToNode());
  279.         if (from == -1 || to == -1 || from != to - 1)
  280.         {
  281.             return leadsToRoute(route, gtuType, null);
  282.         }

  283.         // link is on the route, but lane markings may still prevent the route from being followed
  284.         Set<LaneStructureRecord> currentSet = new LinkedHashSet<>();
  285.         Set<LaneStructureRecord> nextSet = new LinkedHashSet<>();
  286.         currentSet.add(this);

  287.         boolean firstLoop = true;
  288.         while (!currentSet.isEmpty())
  289.         {

  290.             if (!firstLoop || end)
  291.             {
  292.                 // move longitudinal
  293.                 for (LaneStructureRecord laneRecord : currentSet)
  294.                 {
  295.                     to = route.indexOf(laneRecord.getToNode());
  296.                     if (to == route.getNodes().size() - 2)
  297.                     {
  298.                         // check connector
  299.                         for (Link link : laneRecord.getToNode().nextLinks(gtuType, laneRecord.getLane().getParentLink()))
  300.                         {
  301.                             if (link.getLinkType().isConnector())
  302.                             {
  303.                                 if ((link.getStartNode().equals(laneRecord.getToNode())
  304.                                         && link.getEndNode().equals(route.destinationNode()))
  305.                                         || (link.getEndNode().equals(laneRecord.getToNode())
  306.                                                 && link.getStartNode().equals(route.destinationNode())))
  307.                                 {
  308.                                     return true;
  309.                                 }
  310.                             }
  311.                         }
  312.                     }
  313.                     for (LaneStructureRecord next : laneRecord.getNext())
  314.                     {
  315.                         if (next.getToNode().equals(route.destinationNode()))
  316.                         {
  317.                             // reached destination, by definition ok
  318.                             return true;
  319.                         }
  320.                         if (route.indexOf(next.getToNode()) == to + 1)
  321.                         {
  322.                             nextSet.add(next);
  323.                         }
  324.                     }
  325.                 }
  326.                 currentSet = nextSet;
  327.                 nextSet = new LinkedHashSet<>();
  328.             }
  329.             firstLoop = false;

  330.             // move lateral
  331.             nextSet.addAll(currentSet);
  332.             for (LaneStructureRecord laneRecord : currentSet)
  333.             {
  334.                 while (laneRecord.legalLeft() && !nextSet.contains(laneRecord.getLeft()))
  335.                 {
  336.                     nextSet.add(laneRecord.getLeft());
  337.                     laneRecord = laneRecord.getLeft();
  338.                 }
  339.             }
  340.             for (LaneStructureRecord laneRecord : currentSet)
  341.             {
  342.                 while (laneRecord.legalRight() && !nextSet.contains(laneRecord.getRight()))
  343.                 {
  344.                     nextSet.add(laneRecord.getRight());
  345.                     laneRecord = laneRecord.getRight();
  346.                 }
  347.             }

  348.             // none of the next lanes was on the route
  349.             if (nextSet.isEmpty())
  350.             {
  351.                 return false;
  352.             }

  353.             // reached a link on the route where all lanes can be reached?
  354.             int nLanesOnNextLink = 0;
  355.             LaneStructureRecord nextRecord = nextSet.iterator().next();
  356.             for (Lane l : nextRecord.getLane().getParentLink().getLanes())
  357.             {
  358.                 if (l.getLaneType().getDirectionality(gtuType).getDirectionalities().contains(nextRecord.getDirection()))
  359.                 {
  360.                     nLanesOnNextLink++;
  361.                 }
  362.             }
  363.             if (nextSet.size() == nLanesOnNextLink)
  364.             {
  365.                 // in this case we don't need to look further, anything is possible again
  366.                 return true;
  367.             }

  368.             currentSet = nextSet;
  369.             nextSet = new LinkedHashSet<>();

  370.         }

  371.         // never reached our destination or a link with all lanes accessible
  372.         return false;
  373.     }

  374.     /**
  375.      * Returns whether continuing on this lane will allow the route to be followed, while the lane itself is not on the route.
  376.      * @param route Route; the route to follow
  377.      * @param gtuType GTUType; gtu type
  378.      * @param original LaneStructureRecord; source record, should be {@code null} to prevent loop recognition on first iteration
  379.      * @return whether continuing on this lane will allow the route to be followed
  380.      * @throws NetworkException if no destination node
  381.      */
  382.     private boolean leadsToRoute(final Route route, final GTUType gtuType, final LaneStructureRecord original)
  383.             throws NetworkException
  384.     {
  385.         if (original == this)
  386.         {
  387.             return false; // stop loop
  388.         }
  389.         if (original != null && allowsRoute(route, gtuType))
  390.         {
  391.             return true;
  392.         }
  393.         // move downstream until we are at the route
  394.         for (LaneStructureRecord record : getNext())
  395.         {
  396.             boolean leadsTo =
  397.                     ((RollingLaneStructureRecord) record).leadsToRoute(route, gtuType, original == null ? this : original);
  398.             if (leadsTo)
  399.             {
  400.                 return true;
  401.             }
  402.         }
  403.         return false;
  404.     }

  405.     /** {@inheritDoc} */
  406.     @Override
  407.     public final RollingLaneStructureRecord getLeft()
  408.     {
  409.         return this.left;
  410.     }

  411.     /**
  412.      * @param leftRecord set the left LSR or null if not available. Left and right are relative to the <b>driving</b> direction.
  413.      * @param gtuType GTU type
  414.      */
  415.     public final void setLeft(final RollingLaneStructureRecord leftRecord, final GTUType gtuType)
  416.     {
  417.         this.left = leftRecord;
  418.         this.mayChangeLeft = getLane().accessibleAdjacentLanesLegal(LateralDirectionality.LEFT, gtuType, this.gtuDirectionality)
  419.                 .contains(leftRecord.getLane());
  420.         if (getLane().getFullId().equals("1023.FORWARD3") && !this.mayChangeLeft)
  421.         {
  422.             System.out.println("Lane 1023.FORWARD3 allows left:" + this.mayChangeLeft);
  423.         }
  424.     }

  425.     /** {@inheritDoc} */
  426.     @Override
  427.     public final boolean legalLeft()
  428.     {
  429.         return this.mayChangeLeft;
  430.     }

  431.     /** {@inheritDoc} */
  432.     @Override
  433.     public final boolean physicalLeft()
  434.     {
  435.         return this.left != null;
  436.     }

  437.     /** {@inheritDoc} */
  438.     @Override
  439.     public final RollingLaneStructureRecord getRight()
  440.     {
  441.         return this.right;
  442.     }

  443.     /**
  444.      * @param rightRecord set the right LSR or null if not available. Left and right are relative to the <b>driving</b>
  445.      *            direction
  446.      * @param gtuType GTU type
  447.      */
  448.     public final void setRight(final RollingLaneStructureRecord rightRecord, final GTUType gtuType)
  449.     {
  450.         this.right = rightRecord;
  451.         this.mayChangeRight =
  452.                 getLane().accessibleAdjacentLanesLegal(LateralDirectionality.RIGHT, gtuType, this.gtuDirectionality)
  453.                         .contains(rightRecord.getLane());
  454.     }

  455.     /** {@inheritDoc} */
  456.     @Override
  457.     public final boolean legalRight()
  458.     {
  459.         return this.mayChangeRight;
  460.     }

  461.     /** {@inheritDoc} */
  462.     @Override
  463.     public final boolean physicalRight()
  464.     {
  465.         return this.right != null;
  466.     }

  467.     /** {@inheritDoc} */
  468.     @Override
  469.     public final List<RollingLaneStructureRecord> getNext()
  470.     {
  471.         return this.nextList;
  472.     }

  473.     /**
  474.      * Clears the next list.
  475.      */
  476.     final void clearNextList()
  477.     {
  478.         this.nextList.clear();
  479.     }

  480.     /**
  481.      * @param next a next LSRs to add. Next is relative to the driving direction, not to the design line direction.
  482.      * @throws GTUException if the records is cut-off at the end
  483.      */
  484.     public final void addNext(final RollingLaneStructureRecord next) throws GTUException
  485.     {
  486.         Throw.when(this.cutOffEnd != null, GTUException.class,
  487.                 "Cannot add next records to a record that was cut-off at the end.");
  488.         this.nextList.add(next);
  489.     }

  490.     /** {@inheritDoc} */
  491.     @Override
  492.     public final List<RollingLaneStructureRecord> getPrev()
  493.     {
  494.         return this.prevList;
  495.     }

  496.     /**
  497.      * Clears the prev list.
  498.      */
  499.     final void clearPrevList()
  500.     {
  501.         this.prevList.clear();
  502.     }

  503.     /**
  504.      * @param prev a previous LSRs to add. Previous is relative to the driving direction, not to the design line direction.
  505.      * @throws GTUException if the records is cut-off at the start
  506.      */
  507.     public final void addPrev(final RollingLaneStructureRecord prev) throws GTUException
  508.     {
  509.         Throw.when(this.cutOffStart != null, GTUException.class,
  510.                 "Cannot add previous records to a record that was cut-off at the start.");
  511.         this.prevList.add(prev);
  512.     }

  513.     /**
  514.      * Sets this record as being cut-off, i.e. there are no next records due to cut-off.
  515.      * @param cutOffEnd where this lane was cut-off (in the driving direction) resulting in no next lanes
  516.      * @throws GTUException if there are next records
  517.      */
  518.     public final void setCutOffEnd(final Length cutOffEnd) throws GTUException
  519.     {
  520.         Throw.when(!this.nextList.isEmpty(), GTUException.class,
  521.                 "Setting lane record with cut-off end, but there are next records.");
  522.         this.cutOffEnd = cutOffEnd;
  523.     }

  524.     /**
  525.      * Sets this record as being cut-off, i.e. there are no previous records due to cut-off.
  526.      * @param cutOffStart where this lane was cut-off (in the driving direction) resulting in no prev lanes
  527.      * @throws GTUException if there are previous records
  528.      */
  529.     public final void setCutOffStart(final Length cutOffStart) throws GTUException
  530.     {
  531.         Throw.when(!this.prevList.isEmpty(), GTUException.class,
  532.                 "Setting lane record with cut-off start, but there are previous records.");
  533.         this.cutOffStart = cutOffStart;
  534.     }

  535.     /** {@inheritDoc} */
  536.     @Override
  537.     public final boolean isCutOffEnd()
  538.     {
  539.         return this.cutOffEnd != null;
  540.     }

  541.     /** {@inheritDoc} */
  542.     @Override
  543.     public final boolean isCutOffStart()
  544.     {
  545.         return this.cutOffStart != null;
  546.     }

  547.     /**
  548.      * Returns distance where the structure was cut-off.
  549.      * @return distance where the structure was cut-off
  550.      */
  551.     public final Length getCutOffEnd()
  552.     {
  553.         return this.cutOffEnd;
  554.     }

  555.     /**
  556.      * Returns distance where the structure was cut-off.
  557.      * @return distance where the structure was cut-off
  558.      */
  559.     public final Length getCutOffStart()
  560.     {
  561.         return this.cutOffStart;
  562.     }

  563.     /**
  564.      * Clears the cut-off at the end.
  565.      */
  566.     public final void clearCutOffEnd()
  567.     {
  568.         this.cutOffEnd = null;
  569.     }

  570.     /**
  571.      * Clears the cut-off at the start.
  572.      */
  573.     public final void clearCutOffStart()
  574.     {
  575.         this.cutOffStart = null;
  576.     }

  577.     /** {@inheritDoc} */
  578.     @Override
  579.     public final boolean isDeadEnd()
  580.     {
  581.         return this.cutOffEnd == null && this.nextList.isEmpty();
  582.     }

  583.     /** {@inheritDoc} */
  584.     @Override
  585.     public final Lane getLane()
  586.     {
  587.         return this.lane;
  588.     }

  589.     /** {@inheritDoc} */
  590.     @Override
  591.     public final GTUDirectionality getDirection()
  592.     {
  593.         return this.gtuDirectionality;
  594.     }

  595.     /** {@inheritDoc} */
  596.     @Override
  597.     public final Length getStartDistance()
  598.     {
  599.         return this.startDistance;
  600.     }
  601.    
  602.     /** {@inheritDoc} */
  603.     @Override
  604.     public boolean isDownstreamBranch()
  605.     {
  606.         // DOWN, LATERAL_START and CROSS are part of the downstream branch
  607.         return !RecordLink.UP.equals(this.sourceLink) && !RecordLink.LATERAL_END.equals(this.sourceLink);
  608.     }

  609.     /** {@inheritDoc} */
  610.     @Override
  611.     public final String toString()
  612.     {
  613.         // left and right may cause stack overflow
  614.         String s;
  615.         if (this.source == null)
  616.         {
  617.             s = "o";
  618.         }
  619.         else if (this.source == this.left)
  620.         {
  621.             s = "^";
  622.         }
  623.         else if (this.source == this.right)
  624.         {
  625.             s = "v";
  626.         }
  627.         else if (this.prevList.contains(this.source))
  628.         {
  629.             s = "<";
  630.         }
  631.         else if (this.nextList.contains(this.source))
  632.         {
  633.             s = ">";
  634.         }
  635.         else
  636.         {
  637.             s = "?";
  638.         }
  639.         return "LaneStructureRecord [lane=" + this.lane + " (" + s + "), direction=" + this.gtuDirectionality + "]";
  640.     }

  641.     /**
  642.      * Link between records that defines the dependence of start position and hence how this is updated as the GTU moves.
  643.      * <p>
  644.      * Copyright (c) 2013-2018 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  645.      * <br>
  646.      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
  647.      * <p>
  648.      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 22 jan. 2018 <br>
  649.      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  650.      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  651.      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  652.      */
  653.     public enum RecordLink
  654.     {

  655.         /** This record is upstream of the start distance source. */
  656.         UP
  657.         {
  658.             /** {@inheritDoc} */
  659.             @Override
  660.             public Length calculateStartDistance(final RollingLaneStructureRecord startDistanceSource,
  661.                     final RollingLaneStructureRecord self, final double fractionalPosition)
  662.             {
  663.                 return startDistanceSource.getStartDistance().minus(self.getLane().getLength());
  664.             }
  665.         },

  666.         /** This record is downstream of the start distance source. */
  667.         DOWN
  668.         {
  669.             /** {@inheritDoc} */
  670.             @Override
  671.             public Length calculateStartDistance(final RollingLaneStructureRecord startDistanceSource,
  672.                     final RollingLaneStructureRecord self, final double fractionalPosition)
  673.             {
  674.                 return startDistanceSource.getStartDistance().plus(startDistanceSource.getLane().getLength());
  675.             }
  676.         },

  677.         /** This record is laterally adjacent to the start distance source, and found in an upstream search. */
  678.         LATERAL_END
  679.         {
  680.             /** {@inheritDoc} */
  681.             @Override
  682.             public Length calculateStartDistance(final RollingLaneStructureRecord startDistanceSource,
  683.                     final RollingLaneStructureRecord self, final double fractionalPosition)
  684.             {
  685.                 return startDistanceSource.getStartDistance().plus(startDistanceSource.getLane().getLength())
  686.                         .minus(self.getLane().getLength());
  687.             }
  688.         },

  689.         /** This record is laterally adjacent to the start distance source, and found in a downstream search. */
  690.         LATERAL_START
  691.         {
  692.             /** {@inheritDoc} */
  693.             @Override
  694.             public Length calculateStartDistance(final RollingLaneStructureRecord startDistanceSource,
  695.                     final RollingLaneStructureRecord self, final double fractionalPosition)
  696.             {
  697.                 return startDistanceSource.getStartDistance();
  698.             }
  699.         },

  700.         /** Part of the current cross-section. */
  701.         CROSS
  702.         {
  703.             /** {@inheritDoc} */
  704.             @Override
  705.             public Length calculateStartDistance(final RollingLaneStructureRecord startDistanceSource,
  706.                     final RollingLaneStructureRecord self, final double fractionalPosition)
  707.             {
  708.                 return self.getLane().getLength().multiplyBy(fractionalPosition).neg();
  709.             }
  710.         };

  711.         /**
  712.          * Calculate the start position of this record based on a neighboring source.
  713.          * @param startDistanceSource RollingLaneStructureRecord; source record in the tree
  714.          * @param self RollingLaneStructureRecord; own record
  715.          * @param fractionalPosition double; fractional position on the cross-section
  716.          * @return start position of this record based on a neighboring source
  717.          */
  718.         public abstract Length calculateStartDistance(RollingLaneStructureRecord startDistanceSource,
  719.                 RollingLaneStructureRecord self, double fractionalPosition);

  720.     }

  721. }