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.List;
7   import java.util.Set;
8   
9   import org.djunits.value.vdouble.scalar.Length;
10  import org.opentrafficsim.core.gtu.GTUDirectionality;
11  import org.opentrafficsim.core.gtu.GTUException;
12  import org.opentrafficsim.core.gtu.GTUType;
13  import org.opentrafficsim.core.network.LongitudinalDirectionality;
14  import org.opentrafficsim.core.network.NetworkException;
15  import org.opentrafficsim.core.network.Node;
16  import org.opentrafficsim.core.network.route.Route;
17  import org.opentrafficsim.road.network.lane.Lane;
18  
19  import nl.tudelft.simulation.language.Throw;
20  
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-2017 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 LaneStructureRecord implements Serializable
36  {
37      /** */
38      private static final long serialVersionUID = 20160400L;
39  
40      /** The lane of the LSR. */
41      private final Lane lane;
42  
43      /** The direction in which we process this lane. */
44      private final GTUDirectionality gtuDirectionality;
45  
46      /** The left LSR or null if not available. Left and right are relative to the <b>driving</b> direction. */
47      private LaneStructureRecord left;
48  
49      /** The right LSR or null if not available. Left and right are relative to the <b>driving</b> direction. */
50      private LaneStructureRecord right;
51  
52      /** Where this lane was cut-off resulting in no next lanes, if so. */
53      private Length cutOffEnd = null;
54  
55      /** Where this lane was cut-off resulting in no prev lanes, if so. */
56      private Length cutOffStart = null;
57  
58      /** Distance to start of the record, negative for backwards. */
59      private final Length startDistance;
60  
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<LaneStructureRecord> nextList = new ArrayList<>();
66  
67      /**
68       * The previous LSRs. The list is empty if no LSRs are available. Previous is relative to the driving direction, not to the
69       * design line direction.
70       */
71      private List<LaneStructureRecord> prevList = new ArrayList<>();
72  
73      /**
74       * @param lane the lane of the LSR
75       * @param direction the direction on which we process this lane
76       * @param startDistance distance to start of the record, negative for backwards
77       */
78      public LaneStructureRecord(final Lane lane, final GTUDirectionality direction, final Length startDistance)
79      {
80          this.lane = lane;
81          this.gtuDirectionality = direction;
82          this.startDistance = startDistance;
83      }
84  
85      /**
86       * @return the 'from' node of the link belonging to this lane, in the driving direction.
87       */
88      public final Node getFromNode()
89      {
90          return this.gtuDirectionality.isPlus() ? this.lane.getParentLink().getStartNode()
91                  : this.lane.getParentLink().getEndNode();
92      }
93  
94      /**
95       * @return the 'to' node of the link belonging to this lane, in the driving direction.
96       */
97      public final Node getToNode()
98      {
99          return this.gtuDirectionality.isPlus() ? this.lane.getParentLink().getEndNode()
100                 : this.lane.getParentLink().getStartNode();
101     }
102 
103     /**
104      * Returns total distance towards the object at the given position. This method accounts for the GTU directionality.
105      * @param longitudinalPosition position on the design line
106      * @return total distance towards the object at the given position
107      */
108     public final Length getDistanceToPosition(final Length longitudinalPosition)
109     {
110         return this.startDistance.plus(
111                 this.gtuDirectionality.isPlus() ? longitudinalPosition : this.lane.getLength().minus(longitudinalPosition));
112     }
113 
114     /**
115      * @return whether the link to which this lane belongs splits, i.e. some of the parallel, connected lanes lead to a
116      *         different destination than others
117      */
118     public final boolean isLinkSplit()
119     {
120         if (isCutOffEnd())
121         {
122             // if the end is a split, it's out of range
123             return false;
124         }
125         Set<Node> toNodes = new HashSet<>();
126         LaneStructureRecord lsr = this;
127         while (lsr != null)
128         {
129             for (LaneStructureRecord next : lsr.getNext())
130             {
131                 toNodes.add(next.getToNode());
132             }
133             lsr = lsr.getLeft();
134         }
135         lsr = this.getRight();
136         while (lsr != null)
137         {
138             for (LaneStructureRecord next : lsr.getNext())
139             {
140                 toNodes.add(next.getToNode());
141             }
142             lsr = lsr.getRight();
143         }
144         return toNodes.size() > 1;
145     }
146 
147     /**
148      * @return whether the link to which this lane belongs merges, i.e. some of the parallel, connected lanes follow from a
149      *         different origin than others
150      */
151     public final boolean isLinkMerge()
152     {
153         if (isCutOffStart())
154         {
155             // if the start is a merge, it's out of range
156             return false;
157         }
158         Set<Node> fromNodes = new HashSet<>();
159         LaneStructureRecord lsr = this;
160         while (lsr != null)
161         {
162             for (LaneStructureRecord prev : lsr.getPrev())
163             {
164                 fromNodes.add(prev.getFromNode());
165             }
166             lsr = lsr.getLeft();
167         }
168         lsr = this.getRight();
169         while (lsr != null)
170         {
171             for (LaneStructureRecord prev : lsr.getPrev())
172             {
173                 fromNodes.add(prev.getFromNode());
174             }
175             lsr = lsr.getRight();
176         }
177         return fromNodes.size() > 1;
178     }
179 
180     /**
181      * Returns whether this lane allows the route to be followed.
182      * @param route Route; the route to follow
183      * @param gtuType GTUType; gtu type
184      * @return whether this lane allows the route to be followed
185      * @throws NetworkException if no destination node
186      */
187     public final boolean allowsRoute(final Route route, final GTUType gtuType) throws NetworkException
188     {
189         return allowsRoute(route, gtuType, false);
190     }
191 
192     /**
193      * Returns whether the end of this lane allows the route to be followed.
194      * @param route Route; the route to follow
195      * @param gtuType GTUType; gtu type
196      * @return whether the end of this lane allows the route to be followed
197      * @throws NetworkException if no destination node
198      */
199     public final boolean allowsRouteAtEnd(final Route route, final GTUType gtuType) throws NetworkException
200     {
201         return allowsRoute(route, gtuType, true);
202     }
203 
204     /**
205      * Returns whether (the end of) this lane allows the route to be followed.
206      * @param route Route; the route to follow
207      * @param gtuType GTUType; gtu type
208      * @param end boolean; whether to consider the end (or otherwise the lane itself, i.e. allow lane change from this lane)
209      * @return whether the end of this lane allows the route to be followed
210      * @throws NetworkException if no destination node
211      */
212     private boolean allowsRoute(final Route route, final GTUType gtuType, final boolean end) throws NetworkException
213     {
214 
215         // driving without route
216         if (route == null)
217         {
218             return true;
219         }
220 
221         // start with simple check
222         int from = route.indexOf(this.getFromNode());
223         int to = route.indexOf(this.getToNode());
224         if (from == -1 || to == -1 || from != to - 1)
225         {
226             return leadsToRoute(route, gtuType, null);
227         }
228 
229         // link is on the route, but lane markings may still prevent the route from being followed
230         Set<LaneStructureRecord> currentSet = new HashSet<>();
231         Set<LaneStructureRecord> nextSet = new HashSet<>();
232         currentSet.add(this);
233 
234         boolean firstLoop = true;
235         while (!currentSet.isEmpty())
236         {
237 
238             if (!firstLoop || end)
239             {
240                 // move longitudinal
241                 for (LaneStructureRecord laneRecord : currentSet)
242                 {
243                     for (LaneStructureRecord next : laneRecord.getNext())
244                     {
245                         if (next.getToNode().equals(route.destinationNode()))
246                         {
247                             // reached destination, by definition ok
248                             return true;
249                         }
250                         if (route.contains(next.getToNode()))
251                         {
252                             nextSet.add(next);
253                         }
254                     }
255                 }
256                 currentSet = nextSet;
257                 nextSet = new HashSet<>();
258             }
259             firstLoop = false;
260 
261             // move lateral
262             nextSet.addAll(currentSet);
263             for (LaneStructureRecord laneRecord : currentSet)
264             {
265                 while (laneRecord.getLeft() != null && !nextSet.contains(laneRecord.getLeft()))
266                 {
267                     nextSet.add(laneRecord.getLeft());
268                     laneRecord = laneRecord.getLeft();
269                 }
270             }
271             for (LaneStructureRecord laneRecord : currentSet)
272             {
273                 while (laneRecord.getRight() != null && !nextSet.contains(laneRecord.getRight()))
274                 {
275                     nextSet.add(laneRecord.getRight());
276                     laneRecord = laneRecord.getRight();
277                 }
278             }
279 
280             // none of the next lanes was on the route
281             if (nextSet.isEmpty())
282             {
283                 return false;
284             }
285 
286             // reached a link on the route where all lanes can be reached?
287             int nLanesOnNextLink = 0;
288             LaneStructureRecord nextRecord = nextSet.iterator().next();
289             for (Lane l : nextRecord.getLane().getParentLink().getLanes())
290             {
291                 if (l.getDirectionality(gtuType).equals(LongitudinalDirectionality.DIR_BOTH)
292                         || ((l.getDirectionality(gtuType).isForward() && nextRecord.getDirection().isPlus())
293                                 || (l.getDirectionality(gtuType).isBackward() && nextRecord.getDirection().isMinus())))
294                 {
295                     nLanesOnNextLink++;
296                 }
297             }
298             if (nextSet.size() == nLanesOnNextLink)
299             {
300                 // in this case we don't need to look further, anything is possible again
301                 return true;
302             }
303 
304             currentSet = nextSet;
305             nextSet = new HashSet<>();
306 
307         }
308 
309         // never reached our destination or a link with all lanes accessible
310         return false;
311     }
312 
313     /**
314      * Returns whether continuing on this lane will allow the route to be followed, while the lane itself is not on the route.
315      * @param route Route; the route to follow
316      * @param gtuType GTUType; gtu type
317      * @param source LaneStructureRecord; source record, should be {@code null} to prevent loop recognition on first iteration
318      * @return whether continuing on this lane will allow the route to be followed
319      * @throws NetworkException if no destination node
320      */
321     private boolean leadsToRoute(final Route route, final GTUType gtuType, final LaneStructureRecord source)
322             throws NetworkException
323     {
324         if (source == this)
325         {
326             return false; // stop loop
327         }
328         if (source != null && allowsRoute(route, gtuType))
329         {
330             return true;
331         }
332         // move downstream until we are at the route
333         for (LaneStructureRecord record : getNext())
334         {
335             boolean leadsTo = record.leadsToRoute(route, gtuType, source == null ? this : source);
336             if (leadsTo)
337             {
338                 return true;
339             }
340         }
341         return false;
342     }
343 
344     /**
345      * @return the left LSR or null if not available. Left and right are relative to the <b>driving</b> direction.
346      */
347     public final LaneStructureRecord getLeft()
348     {
349         return this.left;
350     }
351 
352     /**
353      * @param left set the left LSR or null if not available. Left and right are relative to the <b>driving</b> direction.
354      */
355     public final void setLeft(final LaneStructureRecord left)
356     {
357         this.left = left;
358     }
359 
360     /**
361      * @return the right LSR or null if not available. Left and right are relative to the <b>driving</b> direction
362      */
363     public final LaneStructureRecord getRight()
364     {
365         return this.right;
366     }
367 
368     /**
369      * @param right set the right LSR or null if not available. Left and right are relative to the <b>driving</b> direction
370      */
371     public final void setRight(final LaneStructureRecord right)
372     {
373         this.right = right;
374     }
375 
376     /**
377      * @return the next LSRs. The list is empty if no LSRs are available. Next is relative to the driving direction, not to the
378      *         design line direction.
379      */
380     public final List<LaneStructureRecord> getNext()
381     {
382         return this.nextList;
383     }
384 
385     /**
386      * @param nextList set the next LSRs. The list is empty if no LSRs are available. Next is relative to the driving direction,
387      *            not to the design line direction.
388      * @throws GTUException if the records is cut-off at the end
389      */
390     public final void setNextList(final List<LaneStructureRecord> nextList) throws GTUException
391     {
392         Throw.when(this.cutOffEnd != null && !nextList.isEmpty(), GTUException.class,
393                 "Cannot set next records to a record that was cut-off at the end.");
394         this.nextList = nextList;
395     }
396 
397     /**
398      * @param next a next LSRs to add. Next is relative to the driving direction, not to the design line direction.
399      * @throws GTUException if the records is cut-off at the end
400      */
401     public final void addNext(final LaneStructureRecord next) throws GTUException
402     {
403         Throw.when(this.cutOffEnd != null, GTUException.class,
404                 "Cannot add next records to a record that was cut-off at the end.");
405         this.nextList.add(next);
406     }
407 
408     /**
409      * @return the previous LSRs. The list is empty if no LSRs are available. Previous is relative to the driving direction, not
410      *         to the design line direction.
411      */
412     public final List<LaneStructureRecord> getPrev()
413     {
414         return this.prevList;
415     }
416 
417     /**
418      * @param prevList set the next LSRs. The list is empty if no LSRs are available. Previous is relative to the driving
419      *            direction, not to the design line direction.
420      * @throws GTUException if the records is cut-off at the start
421      */
422     public final void setPrevList(final List<LaneStructureRecord> prevList) throws GTUException
423     {
424         Throw.when(this.cutOffStart != null && !prevList.isEmpty(), GTUException.class,
425                 "Cannot set previous records to a record that was cut-off at the start.");
426         this.prevList = prevList;
427     }
428 
429     /**
430      * @param prev a previous LSRs to add. Previous is relative to the driving direction, not to the design line direction.
431      * @throws GTUException if the records is cut-off at the start
432      */
433     public final void addPrev(final LaneStructureRecord prev) throws GTUException
434     {
435         Throw.when(this.cutOffStart != null, GTUException.class,
436                 "Cannot add previous records to a record that was cut-off at the start.");
437         this.prevList.add(prev);
438     }
439 
440     /**
441      * Sets this record as being cut-off, i.e. there are no next records due to cut-off.
442      * @param cutOffEnd where this lane was cut-off (in the driving direction) resulting in no prev lanes
443      * @throws GTUException if there are next records
444      */
445     public final void setCutOffEnd(final Length cutOffEnd) throws GTUException
446     {
447         Throw.when(!this.nextList.isEmpty(), GTUException.class,
448                 "Setting lane record with cut-off end, but there are next records.");
449         this.cutOffEnd = cutOffEnd;
450     }
451 
452     /**
453      * Sets this record as being cut-off, i.e. there are no previous records due to cut-off.
454      * @param cutOffStart where this lane was cut-off (in the driving direction) resulting in no next lanes
455      * @throws GTUException if there are previous records
456      */
457     public final void setCutOffStart(final Length cutOffStart) throws GTUException
458     {
459         Throw.when(!this.prevList.isEmpty(), GTUException.class,
460                 "Setting lane record with cut-off start, but there are previous records.");
461         this.cutOffStart = cutOffStart;
462     }
463 
464     /**
465      * Returns whether this lane has no next records as the lane structure was cut-off.
466      * @return whether this lane has no next records as the lane structure was cut-off
467      */
468     public final boolean isCutOffEnd()
469     {
470         return this.cutOffEnd != null;
471     }
472 
473     /**
474      * Returns whether this lane has no previous records as the lane structure was cut-off.
475      * @return whether this lane has no previous records as the lane structure was cut-off
476      */
477     public final boolean isCutOffStart()
478     {
479         return this.cutOffStart != null;
480     }
481 
482     /**
483      * Returns distance where the structure was cut-off.
484      * @return distance where the structure was cut-off
485      */
486     public final Length getCutOffEnd()
487     {
488         return this.cutOffEnd;
489     }
490 
491     /**
492      * Returns distance where the structure was cut-off.
493      * @return distance where the structure was cut-off
494      */
495     public final Length getCutOffStart()
496     {
497         return this.cutOffStart;
498     }
499 
500     /**
501      * Returns whether the record forms a dead-end.
502      * @return whether the record forms a dead-end
503      */
504     public final boolean isDeadEnd()
505     {
506         return this.cutOffEnd == null && this.nextList.isEmpty();
507     }
508 
509     /**
510      * @return the lane of the LSR
511      */
512     public final Lane getLane()
513     {
514         return this.lane;
515     }
516 
517     /**
518      * @return the direction in which we process this lane
519      */
520     public final GTUDirectionality getDirection()
521     {
522         return this.gtuDirectionality;
523     }
524 
525     /**
526      * @return distance to start in the driving direction, from the reference position
527      */
528     public final Length getStartDistance()
529     {
530         return this.startDistance;
531     }
532 
533     /** {@inheritDoc} */
534     @Override
535     public final String toString()
536     {
537         // left and right may cause stack overflow
538         return "LaneStructureRecord [lane=" + this.lane + ", direction=" + this.gtuDirectionality + "]";
539     }
540 
541 }