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