View Javadoc
1   package org.opentrafficsim.road.gtu.lane.perception;
2   
3   import java.io.Serializable;
4   import java.util.ArrayList;
5   import java.util.HashSet;
6   import java.util.LinkedHashSet;
7   import java.util.List;
8   import java.util.Set;
9   
10  import org.djunits.value.vdouble.scalar.Length;
11  import org.djutils.exceptions.Throw;
12  import org.djutils.exceptions.Try;
13  import org.opentrafficsim.core.gtu.GTUDirectionality;
14  import org.opentrafficsim.core.gtu.GTUException;
15  import org.opentrafficsim.core.gtu.GTUType;
16  import org.opentrafficsim.core.gtu.NestedCache;
17  import org.opentrafficsim.core.network.LateralDirectionality;
18  import org.opentrafficsim.core.network.Link;
19  import org.opentrafficsim.core.network.NetworkException;
20  import org.opentrafficsim.core.network.Node;
21  import org.opentrafficsim.core.network.route.Route;
22  import org.opentrafficsim.road.network.lane.Lane;
23  
24  /**
25   * A LaneStructureRecord contains information about the lanes that can be accessed from this lane by a GTUType. It tells whether
26   * there is a left and/or right lane by pointing to other LaneStructureRecords, and which successor LaneStructureRecord(s) there
27   * are at the end of the lane of this LaneStructureRecord. All information (left, right, next) is calculated relative to the
28   * driving direction of the GTU that owns this structure.
29   * <p>
30   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
31   * BSD-style license. See <a href="http://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
32   * </p>
33   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
34   * initial version Feb 21, 2016 <br>
35   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
36   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
37   */
38  public class RollingLaneStructureRecord implements LaneStructureRecord, Serializable
39  {
40      /** */
41      private static final long serialVersionUID = 20160400L;
42  
43      /** Cache of allows route information. */
44      // TODO clear on network change, with an event listener?
45      private static NestedCache<Boolean> allowsRouteCache =
46              new NestedCache<>(Lane.class, Route.class, GTUType.class, Boolean.class);
47  
48      /** The lane of the LSR. */
49      private final Lane lane;
50  
51      /** The direction in which we process this lane. */
52      private final GTUDirectionality gtuDirectionality;
53  
54      /** The left LSR or null if not available. Left and right are relative to the <b>driving</b> direction. */
55      private RollingLaneStructureRecord left;
56  
57      /** Legal left lane change possibility. */
58      private boolean mayChangeLeft;
59  
60      /** The right LSR or null if not available. Left and right are relative to the <b>driving</b> direction. */
61      private RollingLaneStructureRecord right;
62  
63      /** Legal right lane change possibility. */
64      private boolean mayChangeRight;
65  
66      /** Where this lane was cut-off resulting in no next lanes, if so. */
67      private Length cutOffEnd = null;
68  
69      /** Where this lane was cut-off resulting in no prev lanes, if so. */
70      private Length cutOffStart = null;
71  
72      /** Distance to start of the record, negative for backwards. */
73      private Length startDistance;
74  
75      /**
76       * The next LSRs. The list is empty if no LSRs are available. Next is relative to the driving direction, not to the design
77       * line direction.
78       */
79      private List<RollingLaneStructureRecord> nextList = new ArrayList<>();
80  
81      /**
82       * The previous LSRs. The list is empty if no LSRs are available. Previous is relative to the driving direction, not to the
83       * design line direction.
84       */
85      private List<RollingLaneStructureRecord> prevList = new ArrayList<>();
86  
87      /** Record who's start start distance is used to calculate the start distance of this record. */
88      private RollingLaneStructureRecord source;
89  
90      /** Start distance link between records. */
91      private RecordLink sourceLink;
92  
93      /** Set of records who's starting position depends on this record. */
94      private final Set<RollingLaneStructureRecord> dependentRecords = new LinkedHashSet<>();
95  
96      /**
97       * Constructor.
98       * @param lane Lane; lane
99       * @param direction GTUDirectionality; direction of travel for the GTU
100      * @param startDistanceSource RollingLaneStructureRecord; record on which the start distance is based
101      * @param recordLink RecordLink; link type to source
102      */
103     public RollingLaneStructureRecord(final Lane lane, final GTUDirectionality direction,
104             final RollingLaneStructureRecord startDistanceSource, final RecordLink recordLink)
105     {
106         this.lane = lane;
107         this.gtuDirectionality = direction;
108         this.source = startDistanceSource;
109         this.sourceLink = recordLink;
110         if (startDistanceSource != null)
111         {
112             startDistanceSource.dependentRecords.add(this);
113         }
114     }
115 
116     /**
117      * @param lane Lane; the lane of the LSR
118      * @param direction GTUDirectionality; the direction on which we process this lane
119      * @param startDistance Length; distance to start of the record, negative for backwards
120      */
121     public RollingLaneStructureRecord(final Lane lane, final GTUDirectionality direction, final Length startDistance)
122     {
123         this.lane = lane;
124         this.gtuDirectionality = direction;
125         this.startDistance = startDistance;
126 
127         this.source = null;
128         this.sourceLink = null;
129     }
130 
131     /** {@inheritDoc} */
132     @Override
133     public Length getLength()
134     {
135         return getLane().getLength();
136     }
137 
138     /**
139      * Change the source of the distance.
140      * @param startDistanceSource RollingLaneStructureRecord; record on which the start distance is based
141      * @param recordLink RecordLink; link type to source
142      */
143     final void changeStartDistanceSource(final RollingLaneStructureRecord startDistanceSource, final RecordLink recordLink)
144     {
145         // clear link
146         if (this.source != null)
147         {
148             this.source.dependentRecords.remove(this);
149         }
150         // set new link
151         this.source = startDistanceSource;
152         this.sourceLink = recordLink;
153         if (this.source != null)
154         {
155             this.source.dependentRecords.add(this);
156         }
157     }
158 
159     /**
160      * Updates the start distance, including all records who's start distance depends on this value. Advised is to only initiate
161      * this at the root record. Note that before this is invoked, all record-links should be updated.
162      * @param fractionalPosition double; fractional position at the current cross-section
163      * @param laneStructure RollingLaneStructure; parent lane structure
164      */
165     final void updateStartDistance(final double fractionalPosition, final RollingLaneStructure laneStructure)
166     {
167         this.startDistance = this.sourceLink.calculateStartDistance(this.source, this, fractionalPosition);
168         for (RollingLaneStructureRecord record : this.dependentRecords)
169         {
170             record.updateStartDistance(fractionalPosition, laneStructure);
171         }
172     }
173 
174     /**
175      * Returns the source of the start distance.
176      * @return LaneStructureRecord; source of the start distance
177      */
178     final RollingLaneStructureRecord getStartDistanceSource()
179     {
180         return this.source;
181     }
182 
183     /** {@inheritDoc} */
184     @Override
185     public final Node getFromNode()
186     {
187         return this.gtuDirectionality.isPlus() ? this.lane.getParentLink().getStartNode()
188                 : this.lane.getParentLink().getEndNode();
189     }
190 
191     /** {@inheritDoc} */
192     @Override
193     public final Node getToNode()
194     {
195         return this.gtuDirectionality.isPlus() ? this.lane.getParentLink().getEndNode()
196                 : this.lane.getParentLink().getStartNode();
197     }
198 
199     /**
200      * @return whether the link to which this lane belongs splits, i.e. some of the parallel, connected lanes lead to a
201      *         different destination than others
202      */
203     @Deprecated
204     public final boolean isLinkSplit()
205     {
206         if (isCutOffEnd())
207         {
208             // if the end is a split, it's out of range
209             return false;
210         }
211         Set<Node> toNodes = new HashSet<>();
212         LaneStructureRecord lsr = this;
213         while (lsr != null)
214         {
215             for (LaneStructureRecord next : lsr.getNext())
216             {
217                 toNodes.add(next.getToNode());
218             }
219             lsr = lsr.getLeft();
220         }
221         lsr = this.getRight();
222         while (lsr != null)
223         {
224             for (LaneStructureRecord next : lsr.getNext())
225             {
226                 toNodes.add(next.getToNode());
227             }
228             lsr = lsr.getRight();
229         }
230         return toNodes.size() > 1;
231     }
232 
233     /**
234      * @return whether the link to which this lane belongs merges, i.e. some of the parallel, connected lanes follow from a
235      *         different origin than others
236      */
237     public final boolean isLinkMerge()
238     {
239         if (isCutOffStart())
240         {
241             // if the start is a merge, it's out of range
242             return false;
243         }
244         Set<Node> fromNodes = new HashSet<>();
245         LaneStructureRecord lsr = this;
246         while (lsr != null)
247         {
248             for (LaneStructureRecord prev : lsr.getPrev())
249             {
250                 fromNodes.add(prev.getFromNode());
251             }
252             lsr = lsr.getLeft();
253         }
254         lsr = this.getRight();
255         while (lsr != null)
256         {
257             for (LaneStructureRecord prev : lsr.getPrev())
258             {
259                 fromNodes.add(prev.getFromNode());
260             }
261             lsr = lsr.getRight();
262         }
263         return fromNodes.size() > 1;
264     }
265 
266     /** {@inheritDoc} */
267     @Override
268     public final boolean allowsRoute(final Route route, final GTUType gtuType) throws NetworkException
269     {
270         return allowsRoute(route, gtuType, false);
271     }
272 
273     /** {@inheritDoc} */
274     @Override
275     public final boolean allowsRouteAtEnd(final Route route, final GTUType gtuType) throws NetworkException
276     {
277         return allowsRoute(route, gtuType, true);
278     }
279 
280     /**
281      * Returns whether (the end of) this lane allows the route to be followed, using caching.
282      * @param route Route; the route to follow
283      * @param gtuType GTUType; gtu type
284      * @param end boolean; whether to consider the end (or otherwise the lane itself, i.e. allow lane change from this lane)
285      * @return whether the end of this lane allows the route to be followed
286      * @throws NetworkException if no destination node
287      */
288     private boolean allowsRoute(final Route route, final GTUType gtuType, final boolean end) throws NetworkException
289     {
290         return allowsRouteCache.getValue(() -> Try.assign(() -> allowsRoute0(route, gtuType, end), "no destination"), this.lane,
291                 route, gtuType, end);
292     }
293 
294     /**
295      * Returns whether (the end of) this lane allows the route to be followed.
296      * @param route Route; the route to follow
297      * @param gtuType GTUType; gtu type
298      * @param end boolean; whether to consider the end (or otherwise the lane itself, i.e. allow lane change from this lane)
299      * @return whether the end of this lane allows the route to be followed
300      * @throws NetworkException if no destination node
301      */
302     private boolean allowsRoute0(final Route route, final GTUType gtuType, final boolean end) throws NetworkException
303     {
304 
305         // driving without route
306         if (route == null)
307         {
308             return true;
309         }
310 
311         // start with simple check
312         int from = route.indexOf(getFromNode());
313         int to = route.indexOf(getToNode());
314         if (from == -1 || to == -1 || from != to - 1)
315         {
316             return leadsToRoute(route, gtuType, null);
317         }
318 
319         // link is on the route, but lane markings may still prevent the route from being followed
320         Set<LaneStructureRecord> currentSet = new LinkedHashSet<>();
321         Set<LaneStructureRecord> nextSet = new LinkedHashSet<>();
322         currentSet.add(this);
323 
324         boolean firstLoop = true;
325         while (!currentSet.isEmpty())
326         {
327 
328             if (!firstLoop || end)
329             {
330                 // move longitudinal
331                 for (LaneStructureRecord laneRecord : currentSet)
332                 {
333                     to = route.indexOf(laneRecord.getToNode());
334                     if (to == route.getNodes().size() - 2)
335                     {
336                         // check connector
337                         for (Link link : laneRecord.getToNode().nextLinks(gtuType, laneRecord.getLane().getParentLink()))
338                         {
339                             if (link.getLinkType().isConnector())
340                             {
341                                 if ((link.getStartNode().equals(laneRecord.getToNode())
342                                         && link.getEndNode().equals(route.destinationNode()))
343                                         || (link.getEndNode().equals(laneRecord.getToNode())
344                                                 && link.getStartNode().equals(route.destinationNode())))
345                                 {
346                                     return true;
347                                 }
348                             }
349                         }
350                     }
351                     for (LaneStructureRecord next : laneRecord.getNext())
352                     {
353                         if (next.getToNode().equals(route.destinationNode()))
354                         {
355                             // reached destination, by definition ok
356                             return true;
357                         }
358                         if (route.indexOf(next.getToNode()) == to + 1)
359                         {
360                             nextSet.add(next);
361                         }
362                     }
363                 }
364                 currentSet = nextSet;
365                 nextSet = new LinkedHashSet<>();
366             }
367             firstLoop = false;
368 
369             // move lateral
370             nextSet.addAll(currentSet);
371             for (LaneStructureRecord laneRecord : currentSet)
372             {
373                 while (laneRecord.legalLeft() && !nextSet.contains(laneRecord.getLeft()))
374                 {
375                     nextSet.add(laneRecord.getLeft());
376                     laneRecord = laneRecord.getLeft();
377                 }
378             }
379             for (LaneStructureRecord laneRecord : currentSet)
380             {
381                 while (laneRecord.legalRight() && !nextSet.contains(laneRecord.getRight()))
382                 {
383                     nextSet.add(laneRecord.getRight());
384                     laneRecord = laneRecord.getRight();
385                 }
386             }
387 
388             // none of the next lanes was on the route
389             if (nextSet.isEmpty())
390             {
391                 return false;
392             }
393 
394             // reached a link on the route where all lanes can be reached?
395             int nLanesOnNextLink = 0;
396             LaneStructureRecord nextRecord = nextSet.iterator().next();
397             for (Lane l : nextRecord.getLane().getParentLink().getLanes())
398             {
399                 if (l.getLaneType().getDirectionality(gtuType).getDirectionalities().contains(nextRecord.getDirection()))
400                 {
401                     nLanesOnNextLink++;
402                 }
403             }
404             if (nextSet.size() == nLanesOnNextLink)
405             {
406                 // in this case we don't need to look further, anything is possible again
407                 return true;
408             }
409 
410             currentSet = nextSet;
411             nextSet = new LinkedHashSet<>();
412 
413         }
414 
415         // never reached our destination or a link with all lanes accessible
416         return false;
417     }
418 
419     /**
420      * Returns whether continuing on this lane will allow the route to be followed, while the lane itself is not on the route.
421      * @param route Route; the route to follow
422      * @param gtuType GTUType; gtu type
423      * @param original LaneStructureRecord; source record, should be {@code null} to prevent loop recognition on first iteration
424      * @return whether continuing on this lane will allow the route to be followed
425      * @throws NetworkException if no destination node
426      */
427     private boolean leadsToRoute(final Route route, final GTUType gtuType, final LaneStructureRecord original)
428             throws NetworkException
429     {
430         if (original == this)
431         {
432             return false; // stop loop
433         }
434         if (original != null && allowsRoute(route, gtuType))
435         {
436             return true;
437         }
438         // move downstream until we are at the route
439         for (LaneStructureRecord record : getNext())
440         {
441             boolean leadsTo =
442                     ((RollingLaneStructureRecord) record).leadsToRoute(route, gtuType, original == null ? this : original);
443             if (leadsTo)
444             {
445                 return true;
446             }
447         }
448         return false;
449     }
450 
451     /** {@inheritDoc} */
452     @Override
453     public final RollingLaneStructureRecord getLeft()
454     {
455         return this.left;
456     }
457 
458     /**
459      * @param leftRecord RollingLaneStructureRecord; set the left LSR or null if not available. Left and right are relative to
460      *            the &lt;b&gt;driving&lt;/b&gt; direction.
461      * @param gtuType GTUType; GTU type
462      */
463     public final void setLeft(final RollingLaneStructureRecord leftRecord, final GTUType gtuType)
464     {
465         this.left = leftRecord;
466         this.mayChangeLeft = getLane().accessibleAdjacentLanesLegal(LateralDirectionality.LEFT, gtuType, this.gtuDirectionality)
467                 .contains(leftRecord.getLane());
468         if (getLane().getFullId().equals("1023.FORWARD3") && !this.mayChangeLeft)
469         {
470             System.out.println("Lane 1023.FORWARD3 allows left:" + this.mayChangeLeft);
471         }
472     }
473 
474     /** {@inheritDoc} */
475     @Override
476     public final boolean legalLeft()
477     {
478         return this.mayChangeLeft;
479     }
480 
481     /** {@inheritDoc} */
482     @Override
483     public final boolean physicalLeft()
484     {
485         return this.left != null;
486     }
487 
488     /** {@inheritDoc} */
489     @Override
490     public final RollingLaneStructureRecord getRight()
491     {
492         return this.right;
493     }
494 
495     /**
496      * @param rightRecord RollingLaneStructureRecord; set the right LSR or null if not available. Left and right are relative to
497      *            the &lt;b&gt;driving&lt;/b&gt; direction
498      * @param gtuType GTUType; GTU type
499      */
500     public final void setRight(final RollingLaneStructureRecord rightRecord, final GTUType gtuType)
501     {
502         this.right = rightRecord;
503         this.mayChangeRight =
504                 getLane().accessibleAdjacentLanesLegal(LateralDirectionality.RIGHT, gtuType, this.gtuDirectionality)
505                         .contains(rightRecord.getLane());
506     }
507 
508     /** {@inheritDoc} */
509     @Override
510     public final boolean legalRight()
511     {
512         return this.mayChangeRight;
513     }
514 
515     /** {@inheritDoc} */
516     @Override
517     public final boolean physicalRight()
518     {
519         return this.right != null;
520     }
521 
522     /** {@inheritDoc} */
523     @Override
524     public final List<RollingLaneStructureRecord> getNext()
525     {
526         return this.nextList;
527     }
528 
529     /**
530      * Clears the next list.
531      */
532     final void clearNextList()
533     {
534         this.nextList.clear();
535     }
536 
537     /**
538      * @param next RollingLaneStructureRecord; a next LSRs to add. Next is relative to the driving direction, not to the design
539      *            line direction.
540      * @throws GTUException if the records is cut-off at the end
541      */
542     public final void addNext(final RollingLaneStructureRecord next) throws GTUException
543     {
544         Throw.when(this.cutOffEnd != null, GTUException.class,
545                 "Cannot add next records to a record that was cut-off at the end.");
546         this.nextList.add(next);
547     }
548 
549     /** {@inheritDoc} */
550     @Override
551     public final List<RollingLaneStructureRecord> getPrev()
552     {
553         return this.prevList;
554     }
555 
556     /**
557      * Clears the prev list.
558      */
559     final void clearPrevList()
560     {
561         this.prevList.clear();
562     }
563 
564     /**
565      * @param prev RollingLaneStructureRecord; a previous LSRs to add. Previous is relative to the driving direction, not to the
566      *            design line direction.
567      * @throws GTUException if the records is cut-off at the start
568      */
569     public final void addPrev(final RollingLaneStructureRecord prev) throws GTUException
570     {
571         Throw.when(this.cutOffStart != null, GTUException.class,
572                 "Cannot add previous records to a record that was cut-off at the start.");
573         this.prevList.add(prev);
574     }
575 
576     /**
577      * Sets this record as being cut-off, i.e. there are no next records due to cut-off.
578      * @param cutOffEnd Length; where this lane was cut-off (in the driving direction) resulting in no next lanes
579      * @throws GTUException if there are next records
580      */
581     public final void setCutOffEnd(final Length cutOffEnd) throws GTUException
582     {
583         Throw.when(!this.nextList.isEmpty(), GTUException.class,
584                 "Setting lane record with cut-off end, but there are next records.");
585         this.cutOffEnd = cutOffEnd;
586     }
587 
588     /**
589      * Sets this record as being cut-off, i.e. there are no previous records due to cut-off.
590      * @param cutOffStart Length; where this lane was cut-off (in the driving direction) resulting in no prev lanes
591      * @throws GTUException if there are previous records
592      */
593     public final void setCutOffStart(final Length cutOffStart) throws GTUException
594     {
595         Throw.when(!this.prevList.isEmpty(), GTUException.class,
596                 "Setting lane record with cut-off start, but there are previous records.");
597         this.cutOffStart = cutOffStart;
598     }
599 
600     /** {@inheritDoc} */
601     @Override
602     public final boolean isCutOffEnd()
603     {
604         return this.cutOffEnd != null;
605     }
606 
607     /** {@inheritDoc} */
608     @Override
609     public final boolean isCutOffStart()
610     {
611         return this.cutOffStart != null;
612     }
613 
614     /**
615      * Returns distance where the structure was cut-off.
616      * @return distance where the structure was cut-off
617      */
618     public final Length getCutOffEnd()
619     {
620         return this.cutOffEnd;
621     }
622 
623     /**
624      * Returns distance where the structure was cut-off.
625      * @return distance where the structure was cut-off
626      */
627     public final Length getCutOffStart()
628     {
629         return this.cutOffStart;
630     }
631 
632     /**
633      * Clears the cut-off at the end.
634      */
635     public final void clearCutOffEnd()
636     {
637         this.cutOffEnd = null;
638     }
639 
640     /**
641      * Clears the cut-off at the start.
642      */
643     public final void clearCutOffStart()
644     {
645         this.cutOffStart = null;
646     }
647 
648     /** {@inheritDoc} */
649     @Override
650     public final boolean isDeadEnd()
651     {
652         return this.cutOffEnd == null && this.nextList.isEmpty();
653     }
654 
655     /** {@inheritDoc} */
656     @Override
657     public final Lane getLane()
658     {
659         return this.lane;
660     }
661 
662     /** {@inheritDoc} */
663     @Override
664     public final GTUDirectionality getDirection()
665     {
666         return this.gtuDirectionality;
667     }
668 
669     /** {@inheritDoc} */
670     @Override
671     public final Length getStartDistance()
672     {
673         return this.startDistance;
674     }
675 
676     /** {@inheritDoc} */
677     @Override
678     public boolean isDownstreamBranch()
679     {
680         // DOWN, LATERAL_START and CROSS are part of the downstream branch
681         return !RecordLink.UP.equals(this.sourceLink) && !RecordLink.LATERAL_END.equals(this.sourceLink);
682     }
683 
684     /** {@inheritDoc} */
685     @Override
686     public final String toString()
687     {
688         // left and right may cause stack overflow
689         String s;
690         if (this.source == null)
691         {
692             s = "o";
693         }
694         else if (this.source == this.left)
695         {
696             s = "^";
697         }
698         else if (this.source == this.right)
699         {
700             s = "v";
701         }
702         else if (this.prevList.contains(this.source))
703         {
704             s = "<";
705         }
706         else if (this.nextList.contains(this.source))
707         {
708             s = ">";
709         }
710         else
711         {
712             s = "?";
713         }
714         return "LaneStructureRecord [lane=" + this.lane + " (" + s + "), direction=" + this.gtuDirectionality + "]";
715     }
716 
717     /**
718      * Link between records that defines the dependence of start position and hence how this is updated as the GTU moves.
719      * <p>
720      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
721      * <br>
722      * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
723      * <p>
724      * @version $Revision$, $LastChangedDate$, by $Author$, initial version 22 jan. 2018 <br>
725      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
726      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
727      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
728      */
729     public enum RecordLink
730     {
731 
732         /** This record is upstream of the start distance source. */
733         UP
734         {
735             /** {@inheritDoc} */
736             @Override
737             public Length calculateStartDistance(final RollingLaneStructureRecord startDistanceSource,
738                     final RollingLaneStructureRecord self, final double fractionalPosition)
739             {
740                 return startDistanceSource.getStartDistance().minus(self.getLane().getLength());
741             }
742         },
743 
744         /** This record is downstream of the start distance source. */
745         DOWN
746         {
747             /** {@inheritDoc} */
748             @Override
749             public Length calculateStartDistance(final RollingLaneStructureRecord startDistanceSource,
750                     final RollingLaneStructureRecord self, final double fractionalPosition)
751             {
752                 return startDistanceSource.getStartDistance().plus(startDistanceSource.getLane().getLength());
753             }
754         },
755 
756         /** This record is laterally adjacent to the start distance source, and found in an upstream search. */
757         LATERAL_END
758         {
759             /** {@inheritDoc} */
760             @Override
761             public Length calculateStartDistance(final RollingLaneStructureRecord startDistanceSource,
762                     final RollingLaneStructureRecord self, final double fractionalPosition)
763             {
764                 return startDistanceSource.getStartDistance().plus(startDistanceSource.getLane().getLength())
765                         .minus(self.getLane().getLength());
766             }
767         },
768 
769         /** This record is laterally adjacent to the start distance source, and found in a downstream search. */
770         LATERAL_START
771         {
772             /** {@inheritDoc} */
773             @Override
774             public Length calculateStartDistance(final RollingLaneStructureRecord startDistanceSource,
775                     final RollingLaneStructureRecord self, final double fractionalPosition)
776             {
777                 return startDistanceSource.getStartDistance();
778             }
779         },
780 
781         /** Part of the current cross-section. */
782         CROSS
783         {
784             /** {@inheritDoc} */
785             @Override
786             public Length calculateStartDistance(final RollingLaneStructureRecord startDistanceSource,
787                     final RollingLaneStructureRecord self, final double fractionalPosition)
788             {
789                 return self.getLane().getLength().multiplyBy(fractionalPosition).neg();
790             }
791         };
792 
793         /**
794          * Calculate the start position of this record based on a neighboring source.
795          * @param startDistanceSource RollingLaneStructureRecord; source record in the tree
796          * @param self RollingLaneStructureRecord; own record
797          * @param fractionalPosition double; fractional position on the cross-section
798          * @return start position of this record based on a neighboring source
799          */
800         public abstract Length calculateStartDistance(RollingLaneStructureRecord startDistanceSource,
801                 RollingLaneStructureRecord self, double fractionalPosition);
802 
803     }
804 
805 }