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
26
27
28
29
30
31
32
33
34
35
36
37
38 public class RollingLaneStructureRecord implements LaneStructureRecord, Serializable
39 {
40
41 private static final long serialVersionUID = 20160400L;
42
43
44
45 private static NestedCache<Boolean> allowsRouteCache =
46 new NestedCache<>(Lane.class, Route.class, GTUType.class, Boolean.class);
47
48
49 private final Lane lane;
50
51
52 private final GTUDirectionality gtuDirectionality;
53
54
55 private RollingLaneStructureRecord left;
56
57
58 private boolean mayChangeLeft;
59
60
61 private RollingLaneStructureRecord right;
62
63
64 private boolean mayChangeRight;
65
66
67 private Length cutOffEnd = null;
68
69
70 private Length cutOffStart = null;
71
72
73 private Length startDistance;
74
75
76
77
78
79 private List<RollingLaneStructureRecord> nextList = new ArrayList<>();
80
81
82
83
84
85 private List<RollingLaneStructureRecord> prevList = new ArrayList<>();
86
87
88 private RollingLaneStructureRecord source;
89
90
91 private RecordLink sourceLink;
92
93
94 private final Set<RollingLaneStructureRecord> dependentRecords = new LinkedHashSet<>();
95
96
97
98
99
100
101
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
118
119
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
132 @Override
133 public Length getLength()
134 {
135 return getLane().getLength();
136 }
137
138
139
140
141
142
143 final void changeStartDistanceSource(final RollingLaneStructureRecord startDistanceSource, final RecordLink recordLink)
144 {
145
146 if (this.source != null)
147 {
148 this.source.dependentRecords.remove(this);
149 }
150
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
161
162
163
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
176
177
178 final RollingLaneStructureRecord getStartDistanceSource()
179 {
180 return this.source;
181 }
182
183
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
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
201
202
203 @Deprecated
204 public final boolean isLinkSplit()
205 {
206 if (isCutOffEnd())
207 {
208
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
235
236
237 public final boolean isLinkMerge()
238 {
239 if (isCutOffStart())
240 {
241
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
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
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
282
283
284
285
286
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
296
297
298
299
300
301
302 private boolean allowsRoute0(final Route route, final GTUType gtuType, final boolean end) throws NetworkException
303 {
304
305
306 if (route == null)
307 {
308 return true;
309 }
310
311
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
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
331 for (LaneStructureRecord laneRecord : currentSet)
332 {
333 to = route.indexOf(laneRecord.getToNode());
334 if (to == route.getNodes().size() - 2)
335 {
336
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
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
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
389 if (nextSet.isEmpty())
390 {
391 return false;
392 }
393
394
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
407 return true;
408 }
409
410 currentSet = nextSet;
411 nextSet = new LinkedHashSet<>();
412
413 }
414
415
416 return false;
417 }
418
419
420
421
422
423
424
425
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;
433 }
434 if (original != null && allowsRoute(route, gtuType))
435 {
436 return true;
437 }
438
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
452 @Override
453 public final RollingLaneStructureRecord getLeft()
454 {
455 return this.left;
456 }
457
458
459
460
461
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
475 @Override
476 public final boolean legalLeft()
477 {
478 return this.mayChangeLeft;
479 }
480
481
482 @Override
483 public final boolean physicalLeft()
484 {
485 return this.left != null;
486 }
487
488
489 @Override
490 public final RollingLaneStructureRecord getRight()
491 {
492 return this.right;
493 }
494
495
496
497
498
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
509 @Override
510 public final boolean legalRight()
511 {
512 return this.mayChangeRight;
513 }
514
515
516 @Override
517 public final boolean physicalRight()
518 {
519 return this.right != null;
520 }
521
522
523 @Override
524 public final List<RollingLaneStructureRecord> getNext()
525 {
526 return this.nextList;
527 }
528
529
530
531
532 final void clearNextList()
533 {
534 this.nextList.clear();
535 }
536
537
538
539
540
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
550 @Override
551 public final List<RollingLaneStructureRecord> getPrev()
552 {
553 return this.prevList;
554 }
555
556
557
558
559 final void clearPrevList()
560 {
561 this.prevList.clear();
562 }
563
564
565
566
567
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
578
579
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
590
591
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
601 @Override
602 public final boolean isCutOffEnd()
603 {
604 return this.cutOffEnd != null;
605 }
606
607
608 @Override
609 public final boolean isCutOffStart()
610 {
611 return this.cutOffStart != null;
612 }
613
614
615
616
617
618 public final Length getCutOffEnd()
619 {
620 return this.cutOffEnd;
621 }
622
623
624
625
626
627 public final Length getCutOffStart()
628 {
629 return this.cutOffStart;
630 }
631
632
633
634
635 public final void clearCutOffEnd()
636 {
637 this.cutOffEnd = null;
638 }
639
640
641
642
643 public final void clearCutOffStart()
644 {
645 this.cutOffStart = null;
646 }
647
648
649 @Override
650 public final boolean isDeadEnd()
651 {
652 return this.cutOffEnd == null && this.nextList.isEmpty();
653 }
654
655
656 @Override
657 public final Lane getLane()
658 {
659 return this.lane;
660 }
661
662
663 @Override
664 public final GTUDirectionality getDirection()
665 {
666 return this.gtuDirectionality;
667 }
668
669
670 @Override
671 public final Length getStartDistance()
672 {
673 return this.startDistance;
674 }
675
676
677 @Override
678 public boolean isDownstreamBranch()
679 {
680
681 return !RecordLink.UP.equals(this.sourceLink) && !RecordLink.LATERAL_END.equals(this.sourceLink);
682 }
683
684
685 @Override
686 public final String toString()
687 {
688
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
719
720
721
722
723
724
725
726
727
728
729 public enum RecordLink
730 {
731
732
733 UP
734 {
735
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
745 DOWN
746 {
747
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
757 LATERAL_END
758 {
759
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
770 LATERAL_START
771 {
772
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
782 CROSS
783 {
784
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
795
796
797
798
799
800 public abstract Length calculateStartDistance(RollingLaneStructureRecord startDistanceSource,
801 RollingLaneStructureRecord self, double fractionalPosition);
802
803 }
804
805 }