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 }