1 package org.opentrafficsim.road.gtu.lane.perception.structure;
2
3 import java.util.Collection;
4 import java.util.Collections;
5 import java.util.Deque;
6 import java.util.Iterator;
7 import java.util.LinkedHashMap;
8 import java.util.LinkedHashSet;
9 import java.util.LinkedList;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Set;
13 import java.util.SortedSet;
14 import java.util.TreeSet;
15 import java.util.function.Function;
16
17 import org.djunits.value.vdouble.scalar.Length;
18 import org.djunits.value.vdouble.scalar.Time;
19 import org.djutils.exceptions.Try;
20 import org.opentrafficsim.core.gtu.RelativePosition;
21 import org.opentrafficsim.core.network.LateralDirectionality;
22 import org.opentrafficsim.core.network.Link;
23 import org.opentrafficsim.core.network.route.Route;
24 import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
25 import org.opentrafficsim.road.gtu.lane.perception.RelativeLane;
26 import org.opentrafficsim.road.gtu.lane.perception.structure.NavigatingIterable.Entry;
27 import org.opentrafficsim.road.network.lane.Lane;
28 import org.opentrafficsim.road.network.lane.LanePosition;
29 import org.opentrafficsim.road.network.lane.object.LaneBasedObject;
30
31
32
33
34
35
36
37
38
39 public class LaneStructure
40 {
41
42
43 private final LaneBasedGtu egoGtu;
44
45
46 private Length upstream;
47
48
49 private Length downstream;
50
51
52 private Time updated = null;
53
54
55 private final Map<RelativeLane, Set<LaneRecord>> crossSection = new LinkedHashMap<>();
56
57
58 private final Map<RelativeLane, LaneRecord> rootCrossSection = new LinkedHashMap<>();
59
60
61
62
63
64
65
66 public LaneStructure(final LaneBasedGtu gtu, final Length upstream, final Length downstream)
67 {
68 this.egoGtu = gtu;
69 this.upstream = upstream;
70 this.downstream = downstream;
71 }
72
73
74
75
76
77
78
79
80
81
82
83
84 public <T extends LaneBasedObject> Iterable<Entry<T>> getDownstreamObjects(final RelativeLane relativeLane,
85 final Class<T> clazz, final RelativePosition.Type position, final boolean onRoute)
86 {
87 update();
88 Length dx = LaneStructure.this.egoGtu.getRelativePositions().get(position).dx();
89 return new NavigatingIterable<>(clazz, this.downstream,
90 start((record) -> startDownstream(record, position), relativeLane), (record) ->
91 {
92
93 Set<LaneRecord> set = record.getNext();
94 set.removeIf((r) -> onRoute && !r.isOnRoute(LaneStructure.this.egoGtu.getStrategicalPlanner().getRoute()));
95 return set;
96 }, (record) ->
97 {
98
99 List<LaneBasedObject> list = record.getLane().getLaneBasedObjects();
100 if (list.isEmpty())
101 {
102 return list;
103 }
104 Length pos = record.getStartDistance().neg().plus(dx);
105 int from = 0;
106 while (from < list.size()
107 && list.get(from).getLongitudinalPosition().plus(list.get(from).getLength()).lt(pos))
108 {
109 from++;
110 }
111 if (from > list.size() - 1)
112 {
113 return Collections.emptyList();
114 }
115 return list.subList(from, list.size());
116 }, (t, r) -> r.getStartDistance().plus(t.getLongitudinalPosition()).minus(dx));
117 }
118
119
120
121
122
123
124
125
126
127
128
129 public <T extends LaneBasedObject> Iterable<Entry<T>> getUpstreamObjects(final RelativeLane relativeLane,
130 final Class<T> clazz, final RelativePosition.Type position)
131 {
132 update();
133 Length dx = LaneStructure.this.egoGtu.getRelativePositions().get(position).dx();
134 return new NavigatingIterable<>(clazz, this.upstream, start((record) -> startUpstream(record, position), relativeLane),
135 (record) ->
136 {
137
138 Set<LaneRecord> set = new LinkedHashSet<>(record.getPrev());
139 set.addAll(record.lateral());
140 return set;
141 }, (record) ->
142 {
143
144 List<LaneBasedObject> list = record.getLane().getLaneBasedObjects();
145 if (list.isEmpty())
146 {
147 return list;
148 }
149 Length pos = record.getStartDistance().neg().plus(dx);
150 int to = list.size();
151 while (to >= 0 && list.get(to).getLongitudinalPosition().gt(pos))
152 {
153 to--;
154 }
155 if (to < 0)
156 {
157 return Collections.emptyList();
158 }
159 list = list.subList(0, to);
160 Collections.reverse(list);
161 return list;
162 }, (t, r) -> r.getStartDistance().plus(t.getLongitudinalPosition()).plus(dx).neg());
163 }
164
165
166
167
168
169
170
171
172
173
174
175
176
177 public Iterable<Entry<LaneBasedGtu>> getDownstreamGtus(final RelativeLane relativeLane,
178 final RelativePosition.Type egoPosition, final RelativePosition.Type otherPosition,
179 final RelativePosition.Type egoDistancePosition, final RelativePosition.Type otherDistancePosition)
180 {
181 update();
182 Length dx = LaneStructure.this.egoGtu.getRelativePositions().get(egoPosition).dx();
183 Length dxDistance = LaneStructure.this.egoGtu.getRelativePositions().get(egoDistancePosition).dx();
184 Route route = LaneStructure.this.egoGtu.getStrategicalPlanner().getRoute();
185 return new NavigatingIterable<>(LaneBasedGtu.class, this.downstream,
186 start((record) -> startDownstream(record, egoPosition), relativeLane), (record) ->
187 {
188
189 Set<LaneRecord> next = new LinkedHashSet<>(record.getNext());
190 if (route != null && record.getLane().getLink().getEndNode().getLinks().size() > 2
191 && route.containsLink(record.getLane().getLink()))
192 {
193 Iterator<LaneRecord> it = next.iterator();
194 while (it.hasNext())
195 {
196 LaneRecord down = it.next();
197 if (!route.containsLink(down.getLane().getLink()))
198 {
199 it.remove();
200 }
201 }
202 }
203 return next;
204 }, (record) ->
205 {
206
207 List<LaneBasedGtu> gtus = record.getLane().getGtuList().toList();
208 if (gtus.isEmpty())
209 {
210 return gtus;
211 }
212 int from = 0;
213 Length pos = Length.max(record.getStartDistance().neg().plus(dx), Length.ZERO);
214 while (from < gtus.size() && (position(gtus.get(from), record, otherPosition).lt(pos)
215 || gtus.get(from).getId().equals(this.egoGtu.getId())))
216 {
217 from++;
218 }
219 int to = gtus.size() - 1;
220 while (to >= 0 && (position(gtus.get(to), record, otherPosition).gt(record.getLength())
221 || gtus.get(to).getId().equals(this.egoGtu.getId())))
222 {
223 to--;
224 }
225 if (from > to)
226 {
227 return Collections.emptyList();
228 }
229 if (from > 0 || to < gtus.size() - 1)
230 {
231 gtus = gtus.subList(from, to + 1);
232 }
233 return gtus;
234 }, (t, r) -> r.getStartDistance().plus(position(t, r, otherDistancePosition)).minus(dxDistance));
235 }
236
237
238
239
240
241
242
243
244
245
246
247
248 public Iterable<Entry<LaneBasedGtu>> getUpstreamGtus(final RelativeLane relativeLane,
249 final RelativePosition.Type egoPosition, final RelativePosition.Type otherPosition,
250 final RelativePosition.Type egoDistancePosition, final RelativePosition.Type otherDistancePosition)
251 {
252 update();
253 Length dx = LaneStructure.this.egoGtu.getRelativePositions().get(egoPosition).dx();
254 Length dxDistance = LaneStructure.this.egoGtu.getRelativePositions().get(egoDistancePosition).dx();
255 return new NavigatingIterable<>(LaneBasedGtu.class, this.upstream,
256 start((record) -> startUpstream(record, egoPosition), relativeLane), (record) ->
257 {
258
259 Set<LaneRecord> set = new LinkedHashSet<>(record.getPrev());
260 set.addAll(record.lateral());
261 return set;
262 }, (record) ->
263 {
264
265 List<LaneBasedGtu> gtus = record.getLane().getGtuList().toList();
266 if (gtus.isEmpty())
267 {
268 return gtus;
269 }
270 int from = 0;
271 while (from < gtus.size() && (position(gtus.get(from), record, otherPosition).lt0()
272 || gtus.get(from).getId().equals(this.egoGtu.getId())))
273 {
274 from++;
275 }
276 int to = gtus.size() - 1;
277 Length pos = Length.min(record.getStartDistance().neg().plus(dx), record.getLength());
278 while (to >= 0 && (position(gtus.get(to), record, otherPosition).gt(pos)
279 || gtus.get(to).getId().equals(this.egoGtu.getId())))
280 {
281 to--;
282 }
283 if (from > to)
284 {
285 return Collections.emptyList();
286 }
287 if (from > 0 || to < gtus.size() - 1)
288 {
289 gtus = gtus.subList(from, to + 1);
290 }
291 Collections.reverse(gtus);
292 return gtus;
293 }, (t, r) -> dxDistance.minus(r.getStartDistance().plus(position(t, r, otherDistancePosition))));
294 }
295
296
297
298
299
300
301
302
303
304
305
306
307
308 public Iterable<Entry<LaneBasedGtu>> getFirstDownstreamGtus(final RelativeLane relativeLane,
309 final RelativePosition.Type egoPosition, final RelativePosition.Type otherPosition,
310 final RelativePosition.Type egoDistancePosition, final RelativePosition.Type otherDistancePosition)
311 {
312 update();
313 Length dx = LaneStructure.this.egoGtu.getRelativePositions().get(egoPosition).dx();
314 Length dxDistance = LaneStructure.this.egoGtu.getRelativePositions().get(egoDistancePosition).dx();
315 return new NavigatingIterable<>(LaneBasedGtu.class, this.downstream,
316 start((record) -> startDownstream(record, egoPosition), relativeLane), (record) ->
317 {
318
319 return Try.assign(
320 () -> record.getLane().getGtuAhead(record.getStartDistance().neg().plus(dx), otherPosition,
321 record.getLane().getNetwork().getSimulator().getSimulatorAbsTime()),
322 "Problem with GTU") == null ? record.getNext() : new LinkedHashSet<>();
323 }, (record) ->
324 {
325
326 LaneBasedGtu down =
327 Try.assign(
328 () -> record.getLane().getGtuAhead(record.getStartDistance().neg().plus(dx), otherPosition,
329 record.getLane().getNetwork().getSimulator().getSimulatorAbsTime()),
330 "Problem with GTU");
331 return down == null ? Collections.emptyList() : List.of(down);
332 }, (t, r) -> r.getStartDistance().plus(position(t, r, otherDistancePosition)).minus(dxDistance));
333 }
334
335
336
337
338
339
340
341
342
343
344
345
346
347 public Iterable<Entry<LaneBasedGtu>> getFirstUpstreamGtus(final RelativeLane relativeLane,
348 final RelativePosition.Type egoPosition, final RelativePosition.Type otherPosition,
349 final RelativePosition.Type egoDistancePosition, final RelativePosition.Type otherDistancePosition)
350 {
351 update();
352 Length dx = LaneStructure.this.egoGtu.getRelativePositions().get(egoPosition).dx();
353 Length dxDistance = LaneStructure.this.egoGtu.getRelativePositions().get(egoDistancePosition).dx();
354 return new NavigatingIterable<>(LaneBasedGtu.class, this.upstream,
355 start((record) -> startUpstream(record, egoPosition), relativeLane), (record) ->
356 {
357
358
359 LaneBasedGtu gtu =
360 Try.assign(
361 () -> record.getLane().getGtuBehind(record.getStartDistance().neg().plus(dx), otherPosition,
362 record.getLane().getNetwork().getSimulator().getSimulatorAbsTime()),
363 "Problem with GTU");
364 Set<LaneRecord> set = new LinkedHashSet<>();
365 if (gtu == null)
366 {
367 set.addAll(record.getPrev());
368 set.addAll(record.lateral());
369 }
370 return set;
371 }, (record) ->
372 {
373
374 LaneBasedGtu up =
375 Try.assign(
376 () -> record.getLane().getGtuBehind(record.getStartDistance().neg().plus(dx), otherPosition,
377 record.getLane().getNetwork().getSimulator().getSimulatorAbsTime()),
378 "Problem with GTU");
379 return up == null ? Collections.emptyList() : List.of(up);
380 }, (t, r) -> dxDistance.minus(r.getStartDistance().plus(position(t, r, otherDistancePosition))));
381 }
382
383
384
385
386
387
388
389 private Collection<LaneRecord> start(final Function<LaneRecord, Collection<LaneRecord>> starter,
390 final RelativeLane relativeLane)
391 {
392 Collection<LaneRecord> collection = new LinkedHashSet<>();
393 for (LaneRecord record : LaneStructure.this.crossSection.computeIfAbsent(relativeLane, (l) -> new LinkedHashSet<>()))
394 {
395 for (LaneRecord start : starter.apply(record))
396 {
397 collection.add(start);
398 }
399 }
400 return collection;
401 }
402
403
404
405
406
407
408
409
410 private Collection<LaneRecord> startDownstream(final LaneRecord record, final RelativePosition.Type position)
411 {
412 if (position(LaneStructure.this.egoGtu, record, position).ge0())
413 {
414 return Set.of(record);
415 }
416 Set<LaneRecord> set = new LinkedHashSet<>();
417 for (LaneRecord up : record.getPrev())
418 {
419 set.addAll(startDownstream(up, position));
420 }
421 return set;
422 }
423
424
425
426
427
428
429
430
431 private Collection<LaneRecord> startUpstream(final LaneRecord record, final RelativePosition.Type position)
432 {
433 if (position(LaneStructure.this.egoGtu, record, position).lt(record.getLane().getLength()))
434 {
435 return Set.of(record);
436 }
437 Set<LaneRecord> set = new LinkedHashSet<>();
438 for (LaneRecord down : record.getNext())
439 {
440 set.addAll(startUpstream(down, position));
441 }
442 return set;
443 }
444
445
446
447
448
449
450
451
452 private Length position(final LaneBasedGtu gtu, final LaneRecordInterface<?> record,
453 final RelativePosition.Type positionType)
454 {
455 if (gtu.equals(LaneStructure.this.egoGtu))
456 {
457 return record.getStartDistance().neg().plus(gtu.getRelativePositions().get(positionType).dx());
458 }
459 return Try.assign(() -> gtu.position(record.getLane(), gtu.getRelativePositions().get(positionType)),
460 "Unable to obtain position %s of GTU.", positionType);
461 }
462
463
464
465
466 private synchronized void update()
467 {
468 if (this.updated != null && this.updated.equals(this.egoGtu.getSimulator().getSimulatorAbsTime()))
469 {
470 return;
471 }
472
473 this.crossSection.clear();
474 this.rootCrossSection.clear();
475 Set<Lane> visited = new LinkedHashSet<>();
476 Deque<LaneRecord> downQueue = new LinkedList<>();
477 Deque<LaneRecord> upQueue = new LinkedList<>();
478 Deque<LaneRecord> latDownQueue = new LinkedList<>();
479 Deque<LaneRecord> latUpQueue = new LinkedList<>();
480 LanePosition position = Try.assign(() -> this.egoGtu.getReferencePosition(), "GTU does not have a reference position.");
481 LaneRecord root = new LaneRecord(position.lane(), RelativeLane.CURRENT, position.position().neg(), Length.ZERO);
482 visited.add(position.lane());
483 addToCrossSection(root);
484 downQueue.add(root);
485 upQueue.add(root);
486 latDownQueue.add(root);
487 this.rootCrossSection.put(root.getRelativeLane(), root);
488 while (!downQueue.isEmpty() || !upQueue.isEmpty() || !latDownQueue.isEmpty() || !latUpQueue.isEmpty())
489 {
490 if (!downQueue.isEmpty())
491 {
492 nextDown(visited, downQueue, latDownQueue);
493 }
494 else if (!upQueue.isEmpty())
495 {
496 nextUp(visited, upQueue, latUpQueue);
497 }
498 else
499 {
500 nextLateral(visited, downQueue, upQueue, latDownQueue, latUpQueue);
501 }
502 }
503 this.updated = this.egoGtu.getSimulator().getSimulatorAbsTime();
504 }
505
506
507
508
509
510
511
512 private void nextDown(final Set<Lane> visited, final Deque<LaneRecord> downQueue, final Deque<LaneRecord> latDownQueue)
513 {
514 LaneRecord record = downQueue.poll();
515 Set<Lane> downstreamLanes = record.getLane().nextLanes(null);
516 if (!record.getLane().getType().isCompatible(this.egoGtu.getType()))
517 {
518
519
520
521
522 downstreamLanes = new LinkedHashSet<>(downstreamLanes);
523 downstreamLanes.removeAll(record.getLane().nextLanes(this.egoGtu.getType()));
524 }
525 for (Lane lane : downstreamLanes)
526 {
527 LaneRecord down = new LaneRecord(lane, record.getRelativeLane(), record.getEndDistance(), Length.ZERO);
528 record.addNext(down);
529 down.addPrev(record);
530 visited.add(lane);
531 addToCrossSection(down);
532 if (down.getEndDistance().lt(this.downstream))
533 {
534 downQueue.add(down);
535 }
536 latDownQueue.add(down);
537 }
538 }
539
540
541
542
543
544
545
546 private void nextUp(final Set<Lane> visited, final Deque<LaneRecord> upQueue, final Deque<LaneRecord> latUpQueue)
547 {
548 LaneRecord record = upQueue.poll();
549 for (Lane lane : record.getLane().prevLanes(null))
550 {
551
552
553
554
555 if (!visited.contains(lane) || record.getMergeDistance().eq0())
556 {
557 LaneRecord up = new LaneRecord(lane, record.getRelativeLane(),
558 record.getStartDistance().minus(lane.getLength()), record.getMergeDistance());
559 record.addPrev(up);
560 up.addNext(record);
561 visited.add(lane);
562 addToCrossSection(up);
563 if (up.getStartDistance().neg().plus(up.getMergeDistance()).lt(this.upstream))
564 {
565 upQueue.add(up);
566 }
567 latUpQueue.add(up);
568 }
569 }
570 }
571
572
573
574
575
576
577
578
579
580 private void nextLateral(final Set<Lane> visited, final Deque<LaneRecord> downQueue, final Deque<LaneRecord> upQueue,
581 final Deque<LaneRecord> latDownQueue, final Deque<LaneRecord> latUpQueue)
582 {
583 boolean down;
584 Deque<LaneRecord> latQueue;
585 if (!latDownQueue.isEmpty())
586 {
587 down = true;
588 latQueue = latDownQueue;
589 }
590 else
591 {
592 down = false;
593 latQueue = latUpQueue;
594 }
595 LaneRecord record = latQueue.poll();
596 for (LateralDirectionality latDirection : new LateralDirectionality[] {LateralDirectionality.LEFT,
597 LateralDirectionality.RIGHT})
598 {
599 for (Lane lane : record.getLane().accessibleAdjacentLanesPhysical(latDirection, null))
600 {
601 if (!visited.contains(lane))
602 {
603
604
605
606
607
608
609
610 RelativeLane relativeLane = !down ? record.getRelativeLane() : (latDirection.isLeft()
611 ? record.getRelativeLane().getLeft() : record.getRelativeLane().getRight());
612
613
614
615
616
617 Length startDistance;
618 if (record.getStartDistance().lt0() && record.getEndDistance().gt0())
619 {
620 startDistance = lane.getLength()
621 .times(record.getStartDistance().neg().si / record.getLane().getLength().si).neg();
622 }
623 else if (down)
624 {
625 startDistance = record.getStartDistance();
626 }
627 else
628 {
629 startDistance = record.getEndDistance().minus(lane.getLength());
630 }
631
632
633
634
635
636 Length mergeDistance;
637 if (down && record.getStartDistance().gt0())
638 {
639 if (!getUpstreamLinks(lane).equals(getUpstreamLinks(record.getLane())))
640 {
641 mergeDistance = record.getStartDistance();
642 }
643 else
644 {
645 mergeDistance = Length.ZERO;
646 }
647 }
648 else
649 {
650 mergeDistance = record.getMergeDistance();
651 }
652 LaneRecord lat = new LaneRecord(lane, relativeLane, startDistance, mergeDistance);
653 if (!down)
654 {
655 record.addLateral(lat);
656 }
657 visited.add(lane);
658 addToCrossSection(lat);
659
660 if (this.rootCrossSection.containsValue(record))
661 {
662 this.rootCrossSection.put(lat.getRelativeLane(), lat);
663 latDownQueue.add(lat);
664 if (lat.getEndDistance().lt(this.downstream))
665 {
666 downQueue.add(lat);
667 }
668 if (lat.getStartDistance().neg().lt(this.upstream))
669 {
670 upQueue.add(lat);
671 }
672 }
673 else if (down)
674 {
675 latDownQueue.add(lat);
676 if (lat.getEndDistance().lt(this.downstream))
677 {
678 downQueue.add(lat);
679 if (mergeDistance.gt0())
680 {
681 upQueue.add(lat);
682 }
683 }
684 }
685 else
686 {
687 latUpQueue.add(lat);
688 if (lat.getStartDistance().neg().plus(lat.getMergeDistance()).lt(this.upstream))
689 {
690 upQueue.add(lat);
691 }
692 }
693 }
694 }
695 }
696 }
697
698
699
700
701
702
703 private void addToCrossSection(final LaneRecord record)
704 {
705 if (record.getStartDistance().le0() && record.getEndDistance().gt0())
706 {
707 this.crossSection.computeIfAbsent(record.getRelativeLane(), (r) -> new LinkedHashSet<>()).add(record);
708 }
709 }
710
711
712
713
714
715
716 private Set<Link> getUpstreamLinks(final Lane lane)
717 {
718 Set<Link> set = new LinkedHashSet<>();
719 for (Lane prev : lane.prevLanes(null))
720 {
721 set.add(prev.getLink());
722 }
723 return set;
724 }
725
726
727
728
729
730 public SortedSet<RelativeLane> getRootCrossSection()
731 {
732 update();
733 return new TreeSet<>(this.rootCrossSection.keySet());
734 }
735
736
737
738
739
740
741 public boolean exists(final RelativeLane lane)
742 {
743 update();
744 return this.crossSection.containsKey(lane);
745 }
746
747
748
749
750
751
752 public LaneRecord getRootRecord(final RelativeLane lane)
753 {
754 update();
755 return this.rootCrossSection.get(lane);
756 }
757
758
759
760
761
762
763 public Set<LaneRecord> getCrossSectionRecords(final RelativeLane lane)
764 {
765 return new LinkedHashSet<>(this.crossSection.get(lane));
766 }
767
768 }