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
23
24
25
26
27
28
29
30
31
32
33
34
35 public class LaneStructureRecord implements Serializable
36 {
37
38 private static final long serialVersionUID = 20160400L;
39
40
41 private final Lane lane;
42
43
44 private final GTUDirectionality gtuDirectionality;
45
46
47 private LaneStructureRecord left;
48
49
50 private LaneStructureRecord right;
51
52
53 private Length cutOffEnd = null;
54
55
56 private Length cutOffStart = null;
57
58
59 private final Length startDistance;
60
61
62
63
64
65 private List<LaneStructureRecord> nextList = new ArrayList<>();
66
67
68
69
70
71 private List<LaneStructureRecord> prevList = new ArrayList<>();
72
73
74
75
76
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
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
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
105
106
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
116
117
118 public final boolean isLinkSplit()
119 {
120 if (isCutOffEnd())
121 {
122
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
149
150
151 public final boolean isLinkMerge()
152 {
153 if (isCutOffStart())
154 {
155
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
182
183
184
185
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
194
195
196
197
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
206
207
208
209
210
211
212 private boolean allowsRoute(final Route route, final GTUType gtuType, final boolean end) throws NetworkException
213 {
214
215
216 if (route == null)
217 {
218 return true;
219 }
220
221
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
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
241 for (LaneStructureRecord laneRecord : currentSet)
242 {
243 for (LaneStructureRecord next : laneRecord.getNext())
244 {
245 if (next.getToNode().equals(route.destinationNode()))
246 {
247
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
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
281 if (nextSet.isEmpty())
282 {
283 return false;
284 }
285
286
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
301 return true;
302 }
303
304 currentSet = nextSet;
305 nextSet = new HashSet<>();
306
307 }
308
309
310 return false;
311 }
312
313
314
315
316
317
318
319
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;
327 }
328 if (source != null && allowsRoute(route, gtuType))
329 {
330 return true;
331 }
332
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
346
347 public final LaneStructureRecord getLeft()
348 {
349 return this.left;
350 }
351
352
353
354
355 public final void setLeft(final LaneStructureRecord left)
356 {
357 this.left = left;
358 }
359
360
361
362
363 public final LaneStructureRecord getRight()
364 {
365 return this.right;
366 }
367
368
369
370
371 public final void setRight(final LaneStructureRecord right)
372 {
373 this.right = right;
374 }
375
376
377
378
379
380 public final List<LaneStructureRecord> getNext()
381 {
382 return this.nextList;
383 }
384
385
386
387
388
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
399
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
410
411
412 public final List<LaneStructureRecord> getPrev()
413 {
414 return this.prevList;
415 }
416
417
418
419
420
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
431
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
442
443
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
454
455
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
466
467
468 public final boolean isCutOffEnd()
469 {
470 return this.cutOffEnd != null;
471 }
472
473
474
475
476
477 public final boolean isCutOffStart()
478 {
479 return this.cutOffStart != null;
480 }
481
482
483
484
485
486 public final Length getCutOffEnd()
487 {
488 return this.cutOffEnd;
489 }
490
491
492
493
494
495 public final Length getCutOffStart()
496 {
497 return this.cutOffStart;
498 }
499
500
501
502
503
504 public final boolean isDeadEnd()
505 {
506 return this.cutOffEnd == null && this.nextList.isEmpty();
507 }
508
509
510
511
512 public final Lane getLane()
513 {
514 return this.lane;
515 }
516
517
518
519
520 public final GTUDirectionality getDirection()
521 {
522 return this.gtuDirectionality;
523 }
524
525
526
527
528 public final Length getStartDistance()
529 {
530 return this.startDistance;
531 }
532
533
534 @Override
535 public final String toString()
536 {
537
538 return "LaneStructureRecord [lane=" + this.lane + ", direction=" + this.gtuDirectionality + "]";
539 }
540
541 }