1 package org.opentrafficsim.road.gtu.lane.perception;
2
3 import java.awt.BasicStroke;
4 import java.awt.Color;
5 import java.awt.Graphics2D;
6 import java.awt.geom.Path2D;
7 import java.awt.image.ImageObserver;
8 import java.io.Serializable;
9 import java.lang.reflect.Field;
10 import java.rmi.RemoteException;
11 import java.util.HashMap;
12 import java.util.HashSet;
13 import java.util.Iterator;
14 import java.util.LinkedHashMap;
15 import java.util.LinkedHashSet;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.Set;
19 import java.util.SortedSet;
20 import java.util.TreeMap;
21 import java.util.TreeSet;
22
23 import javax.media.j3d.BoundingBox;
24 import javax.media.j3d.Bounds;
25 import javax.naming.NamingException;
26 import javax.vecmath.Point3d;
27
28 import org.djunits.value.vdouble.scalar.Length;
29 import org.djunits.value.vdouble.scalar.Time;
30 import org.opentrafficsim.core.geometry.OTSGeometryException;
31 import org.opentrafficsim.core.geometry.OTSLine3D;
32 import org.opentrafficsim.core.geometry.OTSPoint3D;
33 import org.opentrafficsim.core.gtu.GTU;
34 import org.opentrafficsim.core.gtu.GTUDirectionality;
35 import org.opentrafficsim.core.gtu.GTUException;
36 import org.opentrafficsim.core.gtu.GTUType;
37 import org.opentrafficsim.core.gtu.RelativePosition;
38 import org.opentrafficsim.core.gtu.Try;
39 import org.opentrafficsim.core.network.LateralDirectionality;
40 import org.opentrafficsim.core.network.Node;
41 import org.opentrafficsim.core.network.route.Route;
42 import org.opentrafficsim.core.perception.Historical;
43 import org.opentrafficsim.core.perception.HistoricalValue;
44 import org.opentrafficsim.core.perception.HistoryManager;
45 import org.opentrafficsim.road.gtu.lane.LaneBasedGTU;
46 import org.opentrafficsim.road.gtu.lane.perception.RollingLaneStructureRecord.RecordLink;
47 import org.opentrafficsim.road.network.lane.CrossSectionLink;
48 import org.opentrafficsim.road.network.lane.DirectedLanePosition;
49 import org.opentrafficsim.road.network.lane.Lane;
50 import org.opentrafficsim.road.network.lane.LaneDirection;
51 import org.opentrafficsim.road.network.lane.object.LaneBasedObject;
52
53 import nl.tudelft.simulation.dsol.animation.Locatable;
54 import nl.tudelft.simulation.dsol.animation.D2.Renderable2D;
55 import nl.tudelft.simulation.language.Throw;
56 import nl.tudelft.simulation.language.d3.DirectedPoint;
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123 public class RollingLaneStructure implements LaneStructure, Serializable
124 {
125
126 private static final long serialVersionUID = 20160400L;
127
128
129 private final Historical<RollingLaneStructureRecord> root;
130
131
132 private Length lookAhead;
133
134
135 private Route previousRoute;
136
137
138 private TreeMap<RelativeLane, RollingLaneStructureRecord> crossSectionRecords = new TreeMap<>();
139
140
141 private TreeMap<RelativeLane, RollingLaneStructureRecord> firstRecords = new TreeMap<>();
142
143
144 private Map<RelativeLane, Set<RollingLaneStructureRecord>> relativeLaneMap = new HashMap<>();
145
146
147 private Map<LaneStructureRecord, RelativeLane> relativeLanes = new HashMap<>();
148
149
150 private final Set<Lane> ignoreSet = new HashSet<>();
151
152
153 private final Set<RollingLaneStructureRecord> upstreamEdge = new LinkedHashSet<>();
154
155
156 private final Set<RollingLaneStructureRecord> downstreamEdge = new LinkedHashSet<>();
157
158
159 private final Length down;
160
161
162 private final Length up;
163
164
165 private final Length downSplit;
166
167
168 private final Length upMerge;
169
170
171 private final LaneBasedGTU containingGtu;
172
173
174
175
176
177
178
179
180
181
182 public RollingLaneStructure(final Length lookAhead, final Length down, final Length up, final Length downSplit,
183 final Length upMerge, final LaneBasedGTU gtu)
184 {
185 this.root = new HistoricalValue<>(HistoryManager.get(gtu.getSimulator()));
186 this.lookAhead = lookAhead;
187 this.down = down;
188 this.up = up;
189 this.downSplit = downSplit;
190 this.upMerge = upMerge;
191 this.containingGtu = gtu;
192
193
194
195
196 }
197
198
199
200
201
202
203
204
205 public final void update(final DirectedLanePosition pos, final Route route, final GTUType gtuType) throws GTUException
206 {
207
208
209
210
211
212
213
214
215
216
217
218 Lane lane = pos.getLane();
219 GTUDirectionality direction = pos.getGtuDirection();
220 Length position = pos.getPosition();
221 double fracPos = direction.isPlus() ? position.si / lane.getLength().si : 1.0 - position.si / lane.getLength().si;
222
223
224 if (this.previousRoute != route || this.root.get() == null)
225 {
226
227 this.previousRoute = route;
228
229
230 this.upstreamEdge.clear();
231 this.downstreamEdge.clear();
232 this.crossSectionRecords.clear();
233 this.relativeLanes.clear();
234
235
236 RollingLaneStructureRecord newRoot = constructRecord(lane, direction, null, RecordLink.CROSS, RelativeLane.CURRENT);
237 this.root.set(newRoot);
238 this.crossSectionRecords.put(RelativeLane.CURRENT, newRoot);
239 for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
240 LateralDirectionality.RIGHT })
241 {
242 RollingLaneStructureRecord current = newRoot;
243 RelativeLane relativeLane = RelativeLane.CURRENT;
244 Set<Lane> adjacentLanes =
245 current.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, current.getDirection());
246 while (!adjacentLanes.isEmpty())
247 {
248 Throw.when(adjacentLanes.size() > 1, RuntimeException.class,
249 "Multiple adjacent lanes encountered during construction of lane map.");
250 relativeLane = latDirection.isLeft() ? relativeLane.getLeft() : relativeLane.getRight();
251 Lane adjacentLane = adjacentLanes.iterator().next();
252 RollingLaneStructureRecord adjacentRecord =
253 constructRecord(adjacentLane, direction, current, RecordLink.CROSS, relativeLane);
254 this.crossSectionRecords.put(relativeLane, adjacentRecord);
255 if (latDirection.isLeft())
256 {
257 if (adjacentLane
258 .accessibleAdjacentLanesPhysical(LateralDirectionality.RIGHT, gtuType, current.getDirection())
259 .contains(current.getLane()))
260 {
261 adjacentRecord.setRight(current, gtuType);
262 }
263 current.setLeft(adjacentRecord, gtuType);
264 }
265 else
266 {
267 if (adjacentLane
268 .accessibleAdjacentLanesPhysical(LateralDirectionality.LEFT, gtuType, current.getDirection())
269 .contains(current.getLane()))
270 {
271 adjacentRecord.setLeft(current, gtuType);
272 }
273 current.setRight(adjacentRecord, gtuType);
274 }
275 current = adjacentRecord;
276 adjacentLanes =
277 current.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, current.getDirection());
278 }
279 }
280 this.upstreamEdge.addAll(this.crossSectionRecords.values());
281 this.downstreamEdge.addAll(this.crossSectionRecords.values());
282 this.firstRecords.putAll(this.crossSectionRecords);
283
284
285 newRoot.updateStartDistance(fracPos, this);
286
287
288 expandUpstreamEdge(gtuType, fracPos);
289
290
291 deriveFirstRecords();
292 }
293 else
294 {
295
296 RollingLaneStructureRecord newRoot = this.root.get();
297 if (!lane.equals(newRoot.getLane()))
298 {
299
300 newRoot = null;
301 RelativeLane lateralMove = null;
302 for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
303 {
304 if (newRoot != null)
305 {
306 break;
307 }
308 for (RollingLaneStructureRecord record : this.relativeLaneMap.get(relativeLane))
309 {
310 if (record.getLane().equals(lane))
311 {
312 newRoot = record;
313 lateralMove = relativeLane;
314 break;
315 }
316 }
317 }
318
319 this.root.set(newRoot);
320
321
322 updateStartDistanceSources();
323
324
325 if (!lateralMove.isCurrent())
326 {
327 RelativeLane delta =
328 new RelativeLane(lateralMove.getLateralDirectionality().flip(), lateralMove.getNumLanes());
329
330 TreeMap<RelativeLane, Set<RollingLaneStructureRecord>> newRelativeLaneMap = new TreeMap<>();
331 for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
332 {
333 RelativeLane newRelativeLane = relativeLane.add(delta);
334 newRelativeLaneMap.put(newRelativeLane, this.relativeLaneMap.get(relativeLane));
335 }
336 this.relativeLaneMap = newRelativeLaneMap;
337
338 Map<LaneStructureRecord, RelativeLane> newRelativeLanes = new HashMap<>();
339 for (LaneStructureRecord record : this.relativeLanes.keySet())
340 {
341 newRelativeLanes.put(record, this.relativeLanes.get(record).add(delta));
342 }
343 this.relativeLanes = newRelativeLanes;
344 }
345
346 this.crossSectionRecords.clear();
347 this.crossSectionRecords.put(RelativeLane.CURRENT, newRoot);
348 for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
349 LateralDirectionality.RIGHT })
350 {
351 RollingLaneStructureRecord record = newRoot;
352 RollingLaneStructureRecord next = newRoot;
353 RelativeLane delta = new RelativeLane(latDirection, 1);
354 RelativeLane relLane = RelativeLane.CURRENT;
355 while (next != null)
356 {
357 next = latDirection.isLeft() ? record.getLeft() : record.getRight();
358 if (next != null)
359 {
360 next.changeStartDistanceSource(record, RecordLink.CROSS);
361 relLane = relLane.add(delta);
362 this.crossSectionRecords.put(relLane, next);
363 record = next;
364 }
365 }
366 }
367
368 }
369 newRoot.updateStartDistance(fracPos, this);
370
371
372 retreatUpstreamEdge();
373
374
375 deriveFirstRecords();
376
377 }
378
379
380 expandDownstreamEdge(gtuType, fracPos, route);
381
382 }
383
384
385
386
387 private void deriveFirstRecords()
388 {
389 this.firstRecords.clear();
390 this.firstRecords.putAll(this.crossSectionRecords);
391 for (RelativeLane lane : this.relativeLaneMap.keySet())
392 {
393 getFirstRecord(lane);
394 }
395 }
396
397
398
399
400
401
402
403
404 private void updateStartDistanceSources()
405 {
406
407
408 Set<RollingLaneStructureRecord> set = new HashSet<>();
409 RollingLaneStructureRecord rootRecord = this.root.get();
410 set.add(rootRecord);
411 rootRecord.changeStartDistanceSource(null, RecordLink.CROSS);
412 RollingLaneStructureRecord prev = rootRecord;
413 RollingLaneStructureRecord next = prev.getLeft();
414 while (next != null)
415 {
416 set.add(next);
417 next.changeStartDistanceSource(prev, RecordLink.CROSS);
418 prev = next;
419 next = next.getLeft();
420 }
421 prev = rootRecord;
422 next = prev.getRight();
423 while (next != null)
424 {
425 set.add(next);
426 next.changeStartDistanceSource(prev, RecordLink.CROSS);
427 prev = next;
428 next = next.getRight();
429 }
430
431 while (!set.isEmpty())
432 {
433
434 Set<RollingLaneStructureRecord> newSet = new HashSet<>();
435 for (RollingLaneStructureRecord record : set)
436 {
437 for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
438 LateralDirectionality.RIGHT })
439 {
440 prev = record;
441 next = latDirection.isLeft() ? record.getLeft() : record.getRight();
442 while (next != null && !set.contains(next))
443 {
444 next.changeStartDistanceSource(prev, RecordLink.LATERAL_END);
445 removeDownstream(next, latDirection.flip());
446 newSet.add(next);
447 prev = next;
448 next = latDirection.isLeft() ? next.getLeft() : next.getRight();
449 }
450 }
451 }
452 set.addAll(newSet);
453
454
455 newSet.clear();
456 for (RollingLaneStructureRecord record : set)
457 {
458 for (RollingLaneStructureRecord prevRecord : record.getPrev())
459 {
460 Iterator<RollingLaneStructureRecord> it = prevRecord.getNext().iterator();
461 while (it.hasNext())
462 {
463 RollingLaneStructureRecord otherDown = it.next();
464 if (!otherDown.getLane().getParentLink().equals(record.getLane().getParentLink()))
465 {
466
467 otherDown.changeStartDistanceSource(null, null);
468
469 removeDownstream(otherDown, LateralDirectionality.NONE);
470 removeRecord(otherDown);
471 it.remove();
472 }
473 }
474 LaneStructureRecord source = prevRecord.getStartDistanceSource();
475 if (source == null || (source != null && !source.equals(record)))
476 {
477 prevRecord.changeStartDistanceSource(record, RecordLink.UP);
478 newSet.add(prevRecord);
479 }
480 }
481 }
482
483
484 set = newSet;
485 }
486 }
487
488
489
490
491
492
493 private void removeDownstream(final RollingLaneStructureRecord record, final LateralDirectionality lat)
494 {
495 for (RollingLaneStructureRecord next : record.getNext())
496 {
497 RollingLaneStructureRecord adj = lat.isLeft() ? next.getLeft() : lat.isRight() ? next.getRight() : null;
498 if (adj == null)
499 {
500 next.changeStartDistanceSource(null, null);
501 removeDownstream(next, lat);
502 removeRecord(next);
503 }
504 }
505 record.clearNextList();
506 }
507
508
509
510
511
512 private void removeRecord(final RollingLaneStructureRecord record)
513 {
514 RelativeLane lane = this.relativeLanes.get(record);
515 if (lane != null)
516 {
517 this.relativeLanes.remove(record);
518 }
519 record.changeStartDistanceSource(null, null);
520
521 }
522
523
524
525
526
527
528
529 private void expandUpstreamEdge(final GTUType gtuType, final double fractionalPosition) throws GTUException
530 {
531 this.ignoreSet.clear();
532 for (LaneStructureRecord record : this.upstreamEdge)
533 {
534 this.ignoreSet.add(record.getLane());
535 }
536 Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
537 boolean expand = true;
538 while (expand)
539 {
540 expand = false;
541
542
543 Iterator<RollingLaneStructureRecord> iterator = this.upstreamEdge.iterator();
544 while (iterator.hasNext())
545 {
546 RollingLaneStructureRecord prev = iterator.next();
547 Map<Lane, GTUDirectionality> nexts = prev.getLane().upstreamLanes(prev.getDirection(), gtuType);
548 if (prev.getStartDistance().si < this.up.si)
549 {
550
551 prev.setCutOffStart(this.up.minus(prev.getStartDistance()));
552 for (Lane prevLane : nexts.keySet())
553 {
554 this.ignoreSet.add(prevLane);
555 }
556 }
557 else
558 {
559
560 prev.clearCutOffStart();
561 iterator.remove();
562 if (!nexts.isEmpty())
563 {
564 for (Lane prevLane : nexts.keySet())
565 {
566 RelativeLane relativeLane = this.relativeLanes.get(prev);
567 RollingLaneStructureRecord next =
568 constructRecord(prevLane, nexts.get(prevLane), prev, RecordLink.UP, relativeLane);
569 this.ignoreSet.add(prevLane);
570 next.updateStartDistance(fractionalPosition, this);
571 connectLaterally(next, gtuType, nextSet);
572 next.addNext(prev);
573 prev.addPrev(next);
574 nextSet.add(next);
575 }
576 }
577 }
578 }
579 this.upstreamEdge.addAll(nextSet);
580 expand |= !nextSet.isEmpty();
581 nextSet.clear();
582
583
584 Set<RollingLaneStructureRecord> lateralSet =
585 expandLateral(this.upstreamEdge, RecordLink.LATERAL_END, gtuType, fractionalPosition);
586 nextSet.addAll(lateralSet);
587
588
589 this.upstreamEdge.addAll(nextSet);
590 expand |= !nextSet.isEmpty();
591 nextSet.clear();
592 }
593 }
594
595
596
597
598
599
600
601
602
603
604 private Set<RollingLaneStructureRecord> expandLateral(final Set<RollingLaneStructureRecord> edge,
605 final RecordLink recordLink, final GTUType gtuType, final double fractionalPosition)
606 {
607 Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
608 Set<Lane> laneSet = new HashSet<>();
609 for (LaneStructureRecord record : edge)
610 {
611 laneSet.add(record.getLane());
612 }
613 Iterator<RollingLaneStructureRecord> iterator = edge.iterator();
614 while (iterator.hasNext())
615 {
616 RollingLaneStructureRecord record = iterator.next();
617 for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
618 LateralDirectionality.RIGHT })
619 {
620 RelativeLane relativeLane = this.relativeLanes.get(record);
621 RollingLaneStructureRecord prev = record;
622 Set<Lane> adjacentLanes =
623 prev.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, prev.getDirection());
624 while (!adjacentLanes.isEmpty())
625 {
626 Throw.when(adjacentLanes.size() > 1, RuntimeException.class,
627 "Multiple adjacent lanes encountered during construction of lane map.");
628 relativeLane = latDirection.isLeft() ? relativeLane.getLeft() : relativeLane.getRight();
629 Lane nextLane = adjacentLanes.iterator().next();
630 if (!laneSet.contains(nextLane) && !this.ignoreSet.contains(nextLane))
631 {
632 RollingLaneStructureRecord next =
633 constructRecord(nextLane, record.getDirection(), prev, recordLink, relativeLane);
634 this.ignoreSet.add(nextLane);
635 next.updateStartDistance(fractionalPosition, this);
636 nextSet.add(next);
637 laneSet.add(nextLane);
638 if (latDirection.isLeft())
639 {
640 prev.setLeft(next, gtuType);
641 if (nextLane
642 .accessibleAdjacentLanesPhysical(LateralDirectionality.RIGHT, gtuType, prev.getDirection())
643 .contains(prev.getLane()))
644 {
645 next.setRight(prev, gtuType);
646 }
647 for (RollingLaneStructureRecord edgeRecord : edge)
648 {
649 if (!edgeRecord.equals(prev)
650 && edgeRecord.getLane().getParentLink().equals(next.getLane().getParentLink()))
651 {
652 for (Lane adjLane : edgeRecord.getLane().accessibleAdjacentLanesPhysical(
653 LateralDirectionality.RIGHT, gtuType, edgeRecord.getDirection()))
654 {
655 if (adjLane.equals(next.getLane()))
656 {
657 edgeRecord.setRight(next, gtuType);
658 next.setLeft(edgeRecord, gtuType);
659 }
660 }
661 }
662 }
663 }
664 else
665 {
666 prev.setRight(next, gtuType);
667 if (nextLane
668 .accessibleAdjacentLanesPhysical(LateralDirectionality.LEFT, gtuType, prev.getDirection())
669 .contains(prev.getLane()))
670 {
671 next.setLeft(prev, gtuType);
672 }
673 for (RollingLaneStructureRecord edgeRecord : edge)
674 {
675 if (!edgeRecord.equals(prev)
676 && edgeRecord.getLane().getParentLink().equals(next.getLane().getParentLink()))
677 {
678 for (Lane adjLane : edgeRecord.getLane().accessibleAdjacentLanesPhysical(
679 LateralDirectionality.LEFT, gtuType, edgeRecord.getDirection()))
680 {
681 if (adjLane.equals(next.getLane()))
682 {
683 edgeRecord.setLeft(next, gtuType);
684 next.setRight(edgeRecord, gtuType);
685 }
686 }
687 }
688 }
689 }
690 prev = next;
691 adjacentLanes =
692 prev.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType, prev.getDirection());
693 }
694 else
695 {
696 break;
697 }
698 }
699 }
700 }
701 return nextSet;
702 }
703
704
705
706
707
708 private void retreatUpstreamEdge() throws GTUException
709 {
710 boolean moved = true;
711 Set<RollingLaneStructureRecord> nexts = new LinkedHashSet<>();
712 while (moved)
713 {
714 moved = false;
715 nexts.clear();
716 Iterator<RollingLaneStructureRecord> iterator = this.upstreamEdge.iterator();
717 while (iterator.hasNext())
718 {
719 RollingLaneStructureRecord prev = iterator.next();
720
721 if (!nexts.contains(prev) && prev.getStartDistance().si + prev.getLane().getLength().si < this.up.si)
722 {
723 for (RollingLaneStructureRecord next : prev.getNext())
724 {
725 next.clearPrevList();
726 next.setCutOffStart(this.up.minus(next.getStartDistance()));
727 moved = true;
728 nexts.add(next);
729 RollingLaneStructureRecord lat = next.getLeft();
730 while (lat != null && lat.getPrev().isEmpty())
731 {
732 nexts.add(lat);
733 lat = lat.getLeft();
734 }
735 lat = next.getRight();
736 while (lat != null && lat.getPrev().isEmpty())
737 {
738 nexts.add(lat);
739 lat = lat.getRight();
740 }
741 }
742 prev.clearNextList();
743 removeRecord(prev);
744 iterator.remove();
745 }
746 else
747 {
748 Length cutOff = this.up.minus(prev.getStartDistance());
749 if (cutOff.si > 0)
750 {
751 prev.setCutOffStart(cutOff);
752 }
753 }
754 }
755 this.upstreamEdge.addAll(nexts);
756
757
758 for (RollingLaneStructureRecord record : nexts)
759 {
760 RollingLaneStructureRecord prev = record;
761 for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
762 LateralDirectionality.RIGHT })
763 {
764 while (prev != null)
765 {
766 RollingLaneStructureRecord next = latDirection.isLeft() ? prev.getLeft() : prev.getRight();
767 if (next != null && !this.upstreamEdge.contains(next))
768 {
769 moved |= findUpstreamEdge(next);
770 }
771 prev = next;
772 }
773 }
774 }
775 }
776 }
777
778
779
780
781
782
783
784
785
786 private boolean findUpstreamEdge(final RollingLaneStructureRecord record) throws GTUException
787 {
788 Length cutOff = this.up.minus(record.getStartDistance());
789 boolean moved = false;
790 if (cutOff.gt0())
791 {
792 if (cutOff.lt(record.getLane().getLength()))
793 {
794 record.clearPrevList();
795 record.setCutOffStart(cutOff);
796 this.upstreamEdge.add(record);
797 moved = true;
798 }
799 else
800 {
801 if (this.relativeLanes.containsKey(record))
802 {
803
804 removeRecord(record);
805 }
806 for (RollingLaneStructureRecord next : record.getNext())
807 {
808 moved |= findUpstreamEdge(next);
809 }
810 }
811 }
812 return moved;
813 }
814
815
816
817
818
819
820
821
822 private void expandDownstreamEdge(final GTUType gtuType, final double fractionalPosition, final Route route)
823 throws GTUException
824 {
825 this.ignoreSet.clear();
826 for (LaneStructureRecord record : this.downstreamEdge)
827 {
828 this.ignoreSet.add(record.getLane());
829 }
830 Set<RollingLaneStructureRecord> nextSet = new LinkedHashSet<>();
831 Set<RollingLaneStructureRecord> splitSet = new LinkedHashSet<>();
832 boolean expand = true;
833 while (expand)
834 {
835 expand = false;
836
837
838 Iterator<RollingLaneStructureRecord> iterator = this.downstreamEdge.iterator();
839 while (iterator.hasNext())
840 {
841 RollingLaneStructureRecord record = iterator.next();
842 Map<Lane, GTUDirectionality> nexts = record.getLane().downstreamLanes(record.getDirection(), gtuType);
843 if (record.getStartDistance().si + record.getLane().getLength().si > this.down.si)
844 {
845
846 record.setCutOffEnd(this.down.minus(record.getStartDistance()));
847 for (Lane nextLane : nexts.keySet())
848 {
849 this.ignoreSet.add(nextLane);
850 }
851 }
852 else
853 {
854
855
856
857 LaneDirection nextLaneDirection =
858 new LaneDirection(record.getLane(), record.getDirection()).getNextLaneDirection(this.containingGtu);
859
860 record.clearCutOffEnd();
861 iterator.remove();
862 for (Lane nextLane : nexts.keySet())
863 {
864 if (nextLaneDirection != null
865 && nextLane.getParentLink().equals(nextLaneDirection.getLane().getParentLink())
866 && !nextLane.equals(nextLaneDirection.getLane()))
867 {
868
869 continue;
870 }
871 RelativeLane relativeLane = this.relativeLanes.get(record);
872 GTUDirectionality dir = nexts.get(nextLane);
873 RollingLaneStructureRecord next = constructRecord(nextLane, dir, record, RecordLink.DOWN, relativeLane);
874 this.ignoreSet.add(nextLane);
875 next.updateStartDistance(fractionalPosition, this);
876 record.addNext(next);
877 next.addPrev(record);
878 connectLaterally(next, gtuType, nextSet);
879
880 int from = route == null ? 0 : route.indexOf(next.getFromNode());
881 int to = route == null ? 1 : route.indexOf(next.getToNode());
882 if (to < 0 || to - from != 1)
883 {
884
885 splitSet.add(next);
886 }
887 else
888 {
889
890 nextSet.add(next);
891 }
892
893 Set<RollingLaneStructureRecord> set = new HashSet<>();
894 set.add(next);
895 expandUpstreamMerge(set, gtuType, fractionalPosition, route);
896 }
897 }
898 }
899 this.downstreamEdge.addAll(nextSet);
900 expand |= !nextSet.isEmpty();
901 nextSet.clear();
902
903
904 expandDownstreamSplit(splitSet, gtuType, fractionalPosition, route);
905 splitSet.clear();
906
907
908 Set<RollingLaneStructureRecord> lateralSet =
909 expandLateral(this.downstreamEdge, RecordLink.LATERAL_END, gtuType, fractionalPosition);
910 nextSet.addAll(lateralSet);
911 expandUpstreamMerge(lateralSet, gtuType, fractionalPosition, route);
912
913
914 this.downstreamEdge.addAll(nextSet);
915 expand |= !nextSet.isEmpty();
916 nextSet.clear();
917 }
918 }
919
920
921
922
923
924
925
926
927
928 private void expandDownstreamSplit(final Set<RollingLaneStructureRecord> set, final GTUType gtuType,
929 final double fractionalPosition, final Route route) throws GTUException
930 {
931 Map<RollingLaneStructureRecord, Length> prevs = new LinkedHashMap<>();
932 Map<RollingLaneStructureRecord, Length> nexts = new LinkedHashMap<>();
933 for (RollingLaneStructureRecord record : set)
934 {
935 prevs.put(record, record.getStartDistance().plus(this.downSplit));
936 }
937 while (!prevs.isEmpty())
938 {
939 for (RollingLaneStructureRecord prev : prevs.keySet())
940 {
941 Map<Lane, GTUDirectionality> nextLanes = prev.getLane().downstreamLanes(prev.getDirection(), gtuType);
942 RelativeLane relativeLane = this.relativeLanes.get(prev);
943 for (Lane nextLane : nextLanes.keySet())
944 {
945 GTUDirectionality dir = nextLanes.get(nextLane);
946 Node fromNode =
947 dir.isPlus() ? nextLane.getParentLink().getStartNode() : nextLane.getParentLink().getEndNode();
948 Node toNode =
949 dir.isPlus() ? nextLane.getParentLink().getEndNode() : nextLane.getParentLink().getStartNode();
950 int from = route.indexOf(fromNode);
951 int to = route.indexOf(toNode);
952 if (from == -1 || to == -1 || to - from != 1)
953 {
954 RollingLaneStructureRecord next = constructRecord(nextLane, dir, prev, RecordLink.DOWN, relativeLane);
955 next.updateStartDistance(fractionalPosition, this);
956 next.addPrev(prev);
957 prev.addNext(next);
958 connectLaterally(next, gtuType, nexts.keySet());
959 Length downHere = prevs.get(prev);
960 if (next.getStartDistance().si > downHere.si)
961 {
962 next.setCutOffEnd(downHere.minus(next.getStartDistance()));
963 }
964 else
965 {
966 nexts.put(next, downHere);
967 }
968 }
969 }
970 }
971 prevs = nexts;
972 nexts = new LinkedHashMap<>();
973 }
974 }
975
976
977
978
979
980
981
982
983
984 private void expandUpstreamMerge(final Set<RollingLaneStructureRecord> set, final GTUType gtuType,
985 final double fractionalPosition, final Route route) throws GTUException
986 {
987 Map<RollingLaneStructureRecord, Length> prevs = new LinkedHashMap<>();
988 Map<RollingLaneStructureRecord, Length> nexts = new LinkedHashMap<>();
989 for (RollingLaneStructureRecord record : set)
990 {
991 prevs.put(record, record.getStartDistance().plus(this.upMerge));
992 }
993 while (!prevs.isEmpty())
994 {
995 for (RollingLaneStructureRecord prev : prevs.keySet())
996 {
997 Map<Lane, GTUDirectionality> nextLanes = prev.getLane().upstreamLanes(prev.getDirection(), gtuType);
998 boolean anyAdded = false;
999 for (Lane nextLane : nextLanes.keySet())
1000 {
1001 GTUDirectionality dir = nextLanes.get(nextLane);
1002 Node fromNode =
1003 dir.isPlus() ? nextLane.getParentLink().getStartNode() : nextLane.getParentLink().getEndNode();
1004 Node toNode =
1005 dir.isPlus() ? nextLane.getParentLink().getEndNode() : nextLane.getParentLink().getStartNode();
1006 int from = route == null ? 0 : route.indexOf(fromNode);
1007 int to = route == null ? 1 : route.indexOf(toNode);
1008
1009
1010 if (from == -1 || to == -1 || to - from != 1)
1011 {
1012 anyAdded = true;
1013 RelativeLane relativeLane = this.relativeLanes.get(prev);
1014 RollingLaneStructureRecord next =
1015 constructRecord(nextLane, nextLanes.get(nextLane), prev, RecordLink.UP, relativeLane);
1016 next.updateStartDistance(fractionalPosition, this);
1017 next.addNext(prev);
1018 prev.addPrev(next);
1019 connectLaterally(next, gtuType, nexts.keySet());
1020 Length upHere = prevs.get(prev);
1021 if (next.getStartDistance().si < upHere.si)
1022 {
1023 next.setCutOffStart(upHere.minus(next.getStartDistance()));
1024 this.upstreamEdge.add(next);
1025 }
1026 else
1027 {
1028 nexts.put(next, upHere);
1029 }
1030 }
1031 }
1032 if (!anyAdded && !set.contains(prev))
1033 {
1034 this.upstreamEdge.add(prev);
1035 }
1036 }
1037 prevs = nexts;
1038 nexts = new LinkedHashMap<>();
1039 }
1040 }
1041
1042
1043
1044
1045
1046
1047
1048 private void connectLaterally(final RollingLaneStructureRecord record, final GTUType gtuType,
1049 final Set<RollingLaneStructureRecord> nextSet)
1050 {
1051 for (RollingLaneStructureRecord other : nextSet)
1052 {
1053 for (LateralDirectionality latDirection : new LateralDirectionality[] { LateralDirectionality.LEFT,
1054 LateralDirectionality.RIGHT })
1055 {
1056 if ((latDirection.isLeft() ? other.getLeft() : other.getRight()) == null)
1057 {
1058 for (Lane otherLane : other.getLane().accessibleAdjacentLanesPhysical(latDirection, gtuType,
1059 other.getDirection()))
1060 {
1061 if (otherLane.equals(record.getLane()))
1062 {
1063 if (latDirection.isLeft())
1064 {
1065 other.setLeft(record, gtuType);
1066 record.setRight(other, gtuType);
1067 }
1068 else
1069 {
1070 other.setRight(record, gtuType);
1071 record.setLeft(other, gtuType);
1072 }
1073 }
1074 }
1075 }
1076 }
1077 }
1078 }
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089 private RollingLaneStructureRecord constructRecord(final Lane lane, final GTUDirectionality direction,
1090 final RollingLaneStructureRecord startDistanceSource, final RecordLink recordLink, final RelativeLane relativeLane)
1091 {
1092 RollingLaneStructureRecord record = new RollingLaneStructureRecord(lane, direction, startDistanceSource, recordLink);
1093 if (!this.relativeLaneMap.containsKey(relativeLane))
1094 {
1095 this.relativeLaneMap.put(relativeLane, new LinkedHashSet<>());
1096 }
1097 this.relativeLaneMap.get(relativeLane).add(record);
1098 this.relativeLanes.put(record, relativeLane);
1099 return record;
1100 }
1101
1102
1103 @Override
1104 public final LaneStructureRecord getRootRecord()
1105 {
1106 return this.root.get();
1107 }
1108
1109
1110
1111
1112
1113 public final LaneStructureRecord getRootRecord(final Time time)
1114 {
1115 return this.root.get(time);
1116 }
1117
1118
1119 @Override
1120 public final SortedSet<RelativeLane> getExtendedCrossSection()
1121 {
1122 return this.firstRecords.navigableKeySet();
1123 }
1124
1125
1126
1127
1128
1129
1130
1131 public final RollingLaneStructureRecord getFirstRecord(final RelativeLane lane)
1132 {
1133 if (this.firstRecords.containsKey(lane))
1134 {
1135 return this.firstRecords.get(lane);
1136 }
1137
1138 RelativeLane rel = RelativeLane.CURRENT;
1139 int dMin = Integer.MAX_VALUE;
1140 for (RelativeLane relLane : this.crossSectionRecords.keySet())
1141 {
1142 if (relLane.getLateralDirectionality().equals(lane.getLateralDirectionality()))
1143 {
1144 int d = lane.getNumLanes() - relLane.getNumLanes();
1145 if (d < dMin)
1146 {
1147 rel = relLane;
1148 d = dMin;
1149 }
1150 }
1151 }
1152 RollingLaneStructureRecord record = this.crossSectionRecords.get(rel);
1153
1154 while (rel.getNumLanes() < lane.getNumLanes())
1155 {
1156 RollingLaneStructureRecord adj = lane.getLateralDirectionality().isLeft() ? record.getLeft() : record.getRight();
1157 if (adj != null)
1158 {
1159 rel = lane.getLateralDirectionality().isLeft() ? rel.getLeft() : rel.getRight();
1160 record = adj;
1161 }
1162 else if (!record.getNext().isEmpty())
1163 {
1164 LaneDirection laneDir =
1165 new LaneDirection(record.getLane(), record.getDirection()).getNextLaneDirection(this.containingGtu);
1166 if (laneDir == null)
1167 {
1168 record = null;
1169 break;
1170 }
1171 RollingLaneStructureRecord chosenNext = null;
1172 for (RollingLaneStructureRecord next : record.getNext())
1173 {
1174 if (next.getLane().equals(laneDir.getLane()))
1175 {
1176 chosenNext = next;
1177 break;
1178 }
1179 }
1180 Throw.when(chosenNext == null, RuntimeException.class,
1181 "Unexpected exception while deriving first record not on the cross-section.");
1182 record = chosenNext;
1183 }
1184 else
1185 {
1186
1187 record = null;
1188 break;
1189 }
1190 }
1191 if (record != null)
1192 {
1193
1194 while (record.getPrev().size() == 1 && record.getStartDistance().gt0())
1195 {
1196 record = record.getPrev().get(0);
1197 }
1198 this.firstRecords.put(lane, record);
1199 }
1200 return record;
1201 }
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213 public final <T extends LaneBasedObject> Map<RelativeLane, SortedSet<Entry<T>>> getDownstreamObjects(final Class<T> clazz,
1214 final LaneBasedGTU gtu, final RelativePosition.TYPE pos) throws GTUException
1215 {
1216 Map<RelativeLane, SortedSet<Entry<T>>> out = new HashMap<>();
1217 for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
1218 {
1219 out.put(relativeLane, getDownstreamObjects(relativeLane, clazz, gtu, pos));
1220 }
1221 return out;
1222 }
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235 @SuppressWarnings("unchecked")
1236 public final <T extends LaneBasedObject> SortedSet<Entry<T>> getDownstreamObjects(final RelativeLane lane,
1237 final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos) throws GTUException
1238 {
1239 LaneStructureRecord record = getFirstRecord(lane);
1240 SortedSet<Entry<T>> set = new TreeSet<>();
1241 if (record != null)
1242 {
1243 double ds = gtu.getRelativePositions().get(pos).getDx().si - gtu.getReference().getDx().si;
1244 if (record.isDownstreamBranch())
1245 {
1246
1247 Length minimumPosition;
1248 Length maximumPosition;
1249 if (record.getDirection().isPlus())
1250 {
1251 minimumPosition = Length.createSI(ds - record.getStartDistance().si);
1252 maximumPosition = Length.createSI(record.getLane().getLength().si);
1253 }
1254 else
1255 {
1256 minimumPosition = Length.ZERO;
1257 maximumPosition = Length.createSI(record.getLane().getLength().si + record.getStartDistance().si - ds);
1258 }
1259
1260 for (LaneBasedObject object : record.getLane().getLaneBasedObjects(minimumPosition, maximumPosition))
1261 {
1262 if (clazz.isAssignableFrom(object.getClass())
1263 && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
1264 || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
1265 {
1266
1267 double distance = record.getDistanceToPosition(object.getLongitudinalPosition()).si - ds;
1268 if (distance <= this.lookAhead.si)
1269 {
1270 set.add(new Entry<>(Length.createSI(distance), (T) object));
1271 }
1272 }
1273 }
1274 }
1275 getDownstreamObjectsRecursive(set, record, clazz, ds);
1276 }
1277 return set;
1278 }
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292 public final <T extends LaneBasedObject> SortedSet<Entry<T>> getDownstreamObjectsOnRoute(final RelativeLane lane,
1293 final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos, final Route route)
1294 throws GTUException
1295 {
1296 SortedSet<Entry<T>> set = getDownstreamObjects(lane, clazz, gtu, pos);
1297 if (route != null)
1298 {
1299 Iterator<Entry<T>> iterator = set.iterator();
1300 while (iterator.hasNext())
1301 {
1302 Entry<T> entry = iterator.next();
1303 CrossSectionLink link = entry.getLaneBasedObject().getLane().getParentLink();
1304 if (!route.contains(link.getStartNode()) || !route.contains(link.getEndNode())
1305 || Math.abs(route.indexOf(link.getStartNode()) - route.indexOf(link.getEndNode())) != 1)
1306 {
1307 iterator.remove();
1308 }
1309 }
1310 }
1311 return set;
1312 }
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322 @SuppressWarnings("unchecked")
1323 private <T extends LaneBasedObject> void getDownstreamObjectsRecursive(final SortedSet<Entry<T>> set,
1324 final LaneStructureRecord record, final Class<T> clazz, final double ds)
1325 {
1326 if (record.getNext().isEmpty() || record.getNext().get(0).getStartDistance().gt(this.lookAhead))
1327 {
1328 return;
1329 }
1330 for (LaneStructureRecord next : record.getNext())
1331 {
1332 if (next.isDownstreamBranch())
1333 {
1334 List<LaneBasedObject> list = next.getLane().getLaneBasedObjects();
1335 int iStart, di;
1336 if (record.getDirection().isPlus())
1337 {
1338 iStart = 0;
1339 di = 1;
1340 }
1341 else
1342 {
1343 iStart = list.size() - 1;
1344 di = -1;
1345 }
1346 for (int i = iStart; i >= 0 & i < list.size(); i += di)
1347 {
1348 LaneBasedObject object = list.get(i);
1349 if (clazz.isAssignableFrom(object.getClass())
1350 && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
1351 || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
1352 {
1353
1354 double distance = next.getDistanceToPosition(object.getLongitudinalPosition()).si - ds;
1355 if (distance <= this.lookAhead.si)
1356 {
1357 set.add(new Entry<>(Length.createSI(distance), (T) object));
1358 }
1359 else
1360 {
1361 return;
1362 }
1363 }
1364 }
1365 }
1366 getDownstreamObjectsRecursive(set, next, clazz, ds);
1367 }
1368 }
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381 public final <T extends LaneBasedObject> Map<RelativeLane, SortedSet<Entry<T>>> getDownstreamObjectsOnRoute(
1382 final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos, final Route route)
1383 throws GTUException
1384 {
1385 Map<RelativeLane, SortedSet<Entry<T>>> out = new HashMap<>();
1386 for (RelativeLane relativeLane : this.relativeLaneMap.keySet())
1387 {
1388 out.put(relativeLane, getDownstreamObjectsOnRoute(relativeLane, clazz, gtu, pos, route));
1389 }
1390 return out;
1391 }
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404 @SuppressWarnings("unchecked")
1405 public final <T extends LaneBasedObject> SortedSet<Entry<T>> getUpstreamObjects(final RelativeLane lane,
1406 final Class<T> clazz, final LaneBasedGTU gtu, final RelativePosition.TYPE pos) throws GTUException
1407 {
1408 SortedSet<Entry<T>> set = new TreeSet<>();
1409 LaneStructureRecord record = this.getFirstRecord(lane);
1410 if (record.getStartDistance().gt0())
1411 {
1412 return set;
1413 }
1414 Length ds = gtu.getReference().getDx().minus(gtu.getRelativePositions().get(pos).getDx());
1415
1416 Length minimumPosition;
1417 Length maximumPosition;
1418 if (record.getDirection().isPlus())
1419 {
1420 minimumPosition = Length.ZERO;
1421 maximumPosition = record.getStartDistance().neg().minus(ds);
1422 }
1423 else
1424 {
1425 minimumPosition = record.getLane().getLength().plus(record.getStartDistance()).plus(ds);
1426 maximumPosition = record.getLane().getLength();
1427 }
1428 Length distance;
1429 for (LaneBasedObject object : record.getLane().getLaneBasedObjects(minimumPosition, maximumPosition))
1430 {
1431 if (clazz.isAssignableFrom(object.getClass())
1432 && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
1433 || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
1434 {
1435 distance = record.getDistanceToPosition(object.getLongitudinalPosition()).neg().minus(ds);
1436
1437 set.add(new Entry<>(distance, (T) object));
1438 }
1439 }
1440 getUpstreamObjectsRecursive(set, record, clazz, ds);
1441 return set;
1442 }
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452 @SuppressWarnings("unchecked")
1453 private <T extends LaneBasedObject> void getUpstreamObjectsRecursive(final SortedSet<Entry<T>> set,
1454 final LaneStructureRecord record, final Class<T> clazz, final Length ds)
1455 {
1456 for (LaneStructureRecord prev : record.getPrev())
1457 {
1458 Length distance;
1459 for (LaneBasedObject object : prev.getLane().getLaneBasedObjects())
1460 {
1461 if (clazz.isAssignableFrom(object.getClass())
1462 && ((record.getDirection().isPlus() && object.getDirection().isForwardOrBoth())
1463 || (record.getDirection().isMinus() && object.getDirection().isBackwardOrBoth())))
1464 {
1465 distance = prev.getDistanceToPosition(object.getLongitudinalPosition()).neg().minus(ds);
1466
1467 set.add(new Entry<>(distance, (T) object));
1468 }
1469 }
1470 getUpstreamObjectsRecursive(set, prev, clazz, ds);
1471 }
1472 }
1473
1474
1475 @Override
1476 public final String toString()
1477 {
1478 return "LaneStructure [rootLSR=" + this.root + "]";
1479 }
1480
1481
1482
1483
1484
1485 public final void visualize(final GTU gtu)
1486 {
1487
1488
1489
1490 class LaneStructureLocatable implements Locatable
1491 {
1492
1493 @Override
1494 public DirectedPoint getLocation() throws RemoteException
1495 {
1496 LaneStructureRecord rt = getRootRecord();
1497 if (rt == null)
1498 {
1499 return gtu.getLocation();
1500 }
1501 Length position = rt.getDirection().isPlus() ? rt.getStartDistance().neg()
1502 : rt.getLane().getLength().plus(rt.getStartDistance());
1503 position = position.lt0() ? Length.ZERO : position;
1504 try
1505 {
1506 return rt.getLane().getCenterLine().getLocation(position);
1507 }
1508 catch (OTSGeometryException exception)
1509 {
1510 throw new RuntimeException("Unable to return location.", exception);
1511 }
1512 }
1513
1514
1515 @Override
1516 public Bounds getBounds() throws RemoteException
1517 {
1518 Point3d p1 = new Point3d(-1000000, -1000000, 0.0);
1519 Point3d p2 = new Point3d(1000000, 1000000, 0.0);
1520 return new BoundingBox(p1, p2);
1521 }
1522 }
1523
1524
1525
1526 class LaneStructureAnimation extends Renderable2D<LaneStructureLocatable>
1527 {
1528
1529
1530 private boolean isDestroyed = false;
1531
1532
1533
1534
1535
1536
1537 LaneStructureAnimation(final LaneStructureLocatable source) throws NamingException, RemoteException
1538 {
1539 super(source, gtu.getSimulator());
1540 this.setFlip(false);
1541 this.setRotate(false);
1542 }
1543
1544
1545 @Override
1546 public void paint(final Graphics2D graphics, final ImageObserver observer) throws RemoteException
1547 {
1548 if (!this.isDestroyed)
1549 {
1550 if (gtu.isDestroyed())
1551 {
1552 this.isDestroyed = true;
1553 Try.execute(() -> destroy(), "Exception during deletion of LaneStructureAnimation.");
1554 return;
1555 }
1556 else
1557 {
1558 LaneStructureRecord rt = getRootRecord();
1559 if (rt != null)
1560 {
1561 paintRecord(rt, graphics);
1562 }
1563 }
1564 }
1565 }
1566
1567
1568
1569
1570
1571 @SuppressWarnings({ "unchecked", "synthetic-access" })
1572 private void paintRecord(final LaneStructureRecord lsr, final Graphics2D graphics)
1573 {
1574
1575 DirectedPoint loc = Try.assign(() -> getSource().getLocation(), "Unable to return location.");
1576 graphics.setStroke(
1577 new BasicStroke(RollingLaneStructure.this.crossSectionRecords.containsValue(lsr) ? 1.0f : 0.5f));
1578 graphics.setColor(RollingLaneStructure.this.crossSectionRecords.containsValue(lsr) ? Color.PINK
1579 : RollingLaneStructure.this.upstreamEdge.contains(lsr) ? Color.MAGENTA
1580 : RollingLaneStructure.this.downstreamEdge.contains(lsr) ? Color.GREEN : Color.CYAN);
1581 OTSLine3D line = Try.assign(() -> lsr.getLane().getCenterLine().extractFractional(0.1, 0.9),
1582 "Exception while painting LaneStructures");
1583 Path2D.Double path = new Path2D.Double();
1584 boolean start = true;
1585 for (OTSPoint3D point : line.getPoints())
1586 {
1587 if (start)
1588 {
1589 path.moveTo(point.x - loc.x, -(point.y - loc.y));
1590 start = false;
1591 }
1592 else
1593 {
1594 path.lineTo(point.x - loc.x, -(point.y - loc.y));
1595 }
1596 }
1597 graphics.draw(path);
1598
1599 Field sourceField = Try.assign(() -> RollingLaneStructureRecord.class.getDeclaredField("source"),
1600 "Exception while painting LaneStructure");
1601 sourceField.setAccessible(true);
1602 LaneStructureRecord src =
1603 Try.assign(() -> (LaneStructureRecord) sourceField.get(lsr), "Exception while painting LaneStructure");
1604 if (src != null)
1605 {
1606 Field sourceLinkField = Try.assign(() -> RollingLaneStructureRecord.class.getDeclaredField("sourceLink"),
1607 "Exception while painting LaneStructure");
1608 sourceLinkField.setAccessible(true);
1609 RecordLink link =
1610 (RecordLink) Try.assign(() -> sourceLinkField.get(lsr), "Exception while painting LaneStructure");
1611 float f1 = link.equals(RecordLink.DOWN) ? 0.9f : link.equals(RecordLink.UP) ? 0.1f : 0.5f;
1612 float f2 = link.equals(RecordLink.DOWN) ? 0.0f : link.equals(RecordLink.UP) ? 1.0f : 0.5f;
1613 f1 = src.getDirection().isPlus() ? f1 : 1.0f - f1;
1614 f2 = lsr.getDirection().isPlus() ? f2 : 1.0f - f2;
1615 float f3 = f1;
1616 float f4 = f2;
1617 DirectedPoint p1 = Try.assign(() -> src.getLane().getCenterLine().getLocationFraction(f3),
1618 "Exception while painting LaneStructure");
1619 DirectedPoint p2 = Try.assign(() -> line.getLocationFraction(f4), "Exception while painting LaneStructure");
1620 path = new Path2D.Double();
1621 path.moveTo(p1.x - loc.x, -(p1.y - loc.y));
1622 path.lineTo(p2.x - loc.x, -(p2.y - loc.y));
1623 graphics.setStroke(new BasicStroke(0.15f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f,
1624 new float[] { .3f, 1.2f }, 0f));
1625 graphics.setColor(Color.DARK_GRAY);
1626 graphics.draw(path);
1627 }
1628
1629 paintLateralConnection(lsr, lsr.getLeft(), Color.RED, graphics, loc);
1630 paintLateralConnection(lsr, lsr.getRight(), Color.BLUE, graphics, loc);
1631
1632 Field dependentField = Try.assign(() -> RollingLaneStructureRecord.class.getDeclaredField("dependentRecords"),
1633 "Exception while painting LaneStructure");
1634 dependentField.setAccessible(true);
1635 Set<LaneStructureRecord> dependables = (Set<LaneStructureRecord>) Try.assign(() -> dependentField.get(lsr),
1636 "Exception while painting LaneStructure");
1637 if (dependables != null)
1638 {
1639 for (LaneStructureRecord dependable : new LinkedHashSet<>(dependables))
1640 {
1641 paintRecord(dependable, graphics);
1642 }
1643 }
1644 }
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654 private void paintLateralConnection(final LaneStructureRecord main, final LaneStructureRecord adj,
1655 final Color color, final Graphics2D graphics, final DirectedPoint loc)
1656 {
1657 if (adj == null)
1658 {
1659 return;
1660 }
1661 float f1 = main.getDirection().isPlus() ? 0.45f : 0.55f;
1662 float f2 = adj.getDirection().isPlus() ? 0.55f : 0.45f;
1663 DirectedPoint p1 = Try.assign(() -> main.getLane().getCenterLine().getLocationFraction(f1),
1664 "Exception while painting LaneStructure");
1665 DirectedPoint p2 = Try.assign(() -> adj.getLane().getCenterLine().getLocationFraction(f2),
1666 "Exception while painting LaneStructure");
1667 Path2D.Double path = new Path2D.Double();
1668 path.moveTo(p1.x - loc.x, -(p1.y - loc.y));
1669 path.lineTo(p2.x - loc.x, -(p2.y - loc.y));
1670 graphics.setStroke(new BasicStroke(0.05f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f,
1671 new float[] { .15f, 0.6f }, 0f));
1672 graphics.setColor(color);
1673 graphics.draw(path);
1674 }
1675 }
1676 Try.execute(() -> new LaneStructureAnimation(new LaneStructureLocatable()), "Could not create animation.");
1677 }
1678
1679 }