1 package org.opentrafficsim.road.network;
2
3 import java.util.Iterator;
4 import java.util.LinkedHashMap;
5 import java.util.List;
6 import java.util.Map;
7 import java.util.Set;
8 import java.util.SortedSet;
9 import java.util.TreeSet;
10
11 import org.djunits.value.vdouble.scalar.Length;
12 import org.djutils.base.Identifiable;
13 import org.djutils.exceptions.Throw;
14 import org.djutils.immutablecollections.ImmutableSortedSet;
15 import org.djutils.immutablecollections.ImmutableTreeSet;
16 import org.djutils.multikeymap.MultiKeyMap;
17 import org.jgrapht.GraphPath;
18 import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
19 import org.jgrapht.graph.SimpleDirectedWeightedGraph;
20 import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
21 import org.opentrafficsim.core.gtu.GtuType;
22 import org.opentrafficsim.core.network.LateralDirectionality;
23 import org.opentrafficsim.core.network.Link;
24 import org.opentrafficsim.core.network.Network;
25 import org.opentrafficsim.core.network.NetworkException;
26 import org.opentrafficsim.core.network.Node;
27 import org.opentrafficsim.core.network.route.Route;
28 import org.opentrafficsim.road.network.lane.CrossSectionLink;
29 import org.opentrafficsim.road.network.lane.Lane;
30
31
32
33
34
35
36
37
38
39
40 public class RoadNetwork extends Network
41 {
42
43 private static final long serialVersionUID = 1L;
44
45
46 private Map<GtuType, RouteWeightedGraph> legalLaneGraph = new LinkedHashMap<>();
47
48
49 private RouteWeightedGraph physicalLaneGraph = null;
50
51
52 private MultiKeyMap<SortedSet<LaneChangeInfo>> legalLaneChangeInfoCache =
53 new MultiKeyMap<>(GtuType.class, Route.class, Lane.class);
54
55
56 private MultiKeyMap<SortedSet<LaneChangeInfo>> physicalLaneChangeInfoCache = new MultiKeyMap<>(Route.class, Lane.class);
57
58
59
60
61
62
63 public RoadNetwork(final String id, final OtsSimulatorInterface simulator)
64 {
65 super(id, simulator);
66 }
67
68
69
70
71
72
73
74
75
76
77
78
79
80 public ImmutableSortedSet<LaneChangeInfo> getLaneChangeInfo(final Lane lane, final Route route, final GtuType gtuType,
81 final Length range, final LaneAccessLaw laneAccessLaw)
82 {
83 Throw.whenNull(lane, "Lane may not be null.");
84 Throw.whenNull(route, "Route may not be null.");
85 Throw.whenNull(gtuType, "GTU type may not be null.");
86 Throw.whenNull(range, "Range may not be null.");
87 Throw.whenNull(laneAccessLaw, "Lane access law may not be null.");
88 Throw.when(range.le0(), IllegalArgumentException.class, "Range should be a positive value.");
89
90
91 SortedSet<LaneChangeInfo> info = getCompleteLaneChangeInfo(lane, route, gtuType, laneAccessLaw);
92 if (info == null)
93 {
94 return null;
95 }
96
97
98 LaneChangeInfo lcInfoBeyondHorizon = null;
99 Iterator<LaneChangeInfo> iterator = info.iterator();
100 while (lcInfoBeyondHorizon == null && iterator.hasNext())
101 {
102 LaneChangeInfo lcInfo = iterator.next();
103 if (lcInfo.remainingDistance().gt(range))
104 {
105 lcInfoBeyondHorizon = lcInfo;
106 }
107 }
108
109
110 if (lcInfoBeyondHorizon != null)
111 {
112 return new ImmutableTreeSet<>(info.headSet(lcInfoBeyondHorizon));
113 }
114 return new ImmutableTreeSet<>(info);
115 }
116
117
118
119
120
121
122
123
124
125
126
127 private SortedSet<LaneChangeInfo> getCompleteLaneChangeInfo(final Lane lane, final Route route, final GtuType gtuType,
128 final LaneAccessLaw laneAccessLaw)
129 {
130
131 SortedSet<LaneChangeInfo> outputLaneChangeInfo;
132 if (laneAccessLaw.equals(LaneAccessLaw.LEGAL))
133 {
134 outputLaneChangeInfo = this.legalLaneChangeInfoCache.get(gtuType, route, lane);
135
136 if (outputLaneChangeInfo == null)
137 {
138
139 RouteWeightedGraph graph = this.legalLaneGraph.get(gtuType);
140 if (graph == null)
141 {
142 graph = new RouteWeightedGraph();
143 this.legalLaneGraph.put(gtuType, graph);
144 buildGraph(graph, gtuType, laneAccessLaw);
145 }
146 List<LaneChangeInfoEdge> path = findPath(lane, graph, gtuType, route);
147
148 if (path != null)
149 {
150
151 boolean originalPath = true;
152 while (!path.isEmpty())
153 {
154 SortedSet<LaneChangeInfo> laneChangeInfo = extractLaneChangeInfo(path);
155 if (originalPath)
156 {
157 outputLaneChangeInfo = laneChangeInfo;
158 originalPath = false;
159 }
160 this.legalLaneChangeInfoCache.put(laneChangeInfo, gtuType, route, path.get(0).fromLane());
161 path.remove(0);
162 }
163 }
164 }
165 }
166 else if (laneAccessLaw.equals(LaneAccessLaw.PHYSICAL))
167 {
168 outputLaneChangeInfo = this.physicalLaneChangeInfoCache.get(route, lane);
169
170 if (outputLaneChangeInfo == null)
171 {
172
173 if (this.physicalLaneGraph == null)
174 {
175 this.physicalLaneGraph = new RouteWeightedGraph();
176
177 buildGraph(this.physicalLaneGraph, gtuType, laneAccessLaw);
178 }
179 List<LaneChangeInfoEdge> path = findPath(lane, this.physicalLaneGraph, gtuType, route);
180
181 if (path != null)
182 {
183
184 boolean originalPath = true;
185 while (!path.isEmpty())
186 {
187 SortedSet<LaneChangeInfo> laneChangeInfo = extractLaneChangeInfo(path);
188 if (originalPath)
189 {
190 outputLaneChangeInfo = laneChangeInfo;
191 originalPath = false;
192 }
193 this.physicalLaneChangeInfoCache.put(laneChangeInfo, route, path.get(0).fromLane());
194 path.remove(0);
195 }
196 }
197 }
198 }
199 else
200 {
201
202 throw new RuntimeException(String.format("Unknown LaneChangeLaw %s", laneAccessLaw));
203 }
204 return outputLaneChangeInfo;
205 }
206
207
208
209
210
211
212
213 private void buildGraph(final RouteWeightedGraph graph, final GtuType gtuType, final LaneAccessLaw laneChangeLaw)
214 {
215
216 boolean legal = laneChangeLaw.equals(LaneAccessLaw.LEGAL);
217 for (Link link : this.getLinkMap().values())
218 {
219 for (Lane lane : legal ? ((CrossSectionLink) link).getLanes() : ((CrossSectionLink) link).getLanesAndShoulders())
220 {
221 graph.addVertex(lane);
222 }
223
224 graph.addVertex(link.getEndNode());
225 }
226
227
228 for (Link link : this.getLinkMap().values())
229 {
230 if (link instanceof CrossSectionLink cLink)
231 {
232 for (Lane lane : legal ? cLink.getLanes() : cLink.getLanesAndShoulders())
233 {
234
235 for (LateralDirectionality lat : List.of(LateralDirectionality.LEFT, LateralDirectionality.RIGHT))
236 {
237 Set<Lane> adjacentLanes;
238 if (legal)
239 {
240 adjacentLanes = lane.accessibleAdjacentLanesLegal(lat, gtuType);
241 }
242 else
243 {
244 adjacentLanes = lane.accessibleAdjacentLanesPhysical(lat, gtuType);
245 }
246 for (Lane adjacentLane : adjacentLanes)
247 {
248 LaneChangeInfoEdgeType type = lat.equals(LateralDirectionality.LEFT) ? LaneChangeInfoEdgeType.LEFT
249 : LaneChangeInfoEdgeType.RIGHT;
250
251 LaneChangeInfoEdge edge = new LaneChangeInfoEdge(lane, type, null);
252 graph.addEdge(lane, adjacentLane, edge);
253 }
254 }
255
256 Set<Lane> nextLanes = lane.nextLanes(legal ? gtuType : null);
257 for (Lane nextLane : nextLanes)
258 {
259 LaneChangeInfoEdge edge =
260 new LaneChangeInfoEdge(lane, LaneChangeInfoEdgeType.DOWNSTREAM, nextLane.getLink());
261 graph.addEdge(lane, nextLane, edge);
262 }
263
264 LaneChangeInfoEdge edge = new LaneChangeInfoEdge(lane, LaneChangeInfoEdgeType.DOWNSTREAM, null);
265 graph.addEdge(lane, lane.getLink().getEndNode(), edge);
266 }
267 }
268 }
269 }
270
271
272
273
274
275
276
277
278
279 private List<LaneChangeInfoEdge> findPath(final Lane lane, final RouteWeightedGraph graph, final GtuType gtuType,
280 final Route route)
281 {
282
283 Node destination = null;
284 Route routeForWeights = route;
285 if (route == null)
286 {
287 destination = graph.getNoRouteDestinationNode(gtuType);
288 try
289 {
290 routeForWeights = getShortestRouteBetween(gtuType, lane.getLink().getStartNode(), destination);
291 }
292 catch (NetworkException exception)
293 {
294
295 throw new RuntimeException("Could not find route to destination.", exception);
296 }
297 }
298 else
299 {
300
301 List<Node> nodes = route.getNodes();
302 for (int i = nodes.size() - 1; i > 0; i--)
303 {
304 Link link = getLink(nodes.get(i - 1), nodes.get(i));
305 if (link instanceof CrossSectionLink && !((CrossSectionLink) link).getLanes().isEmpty())
306 {
307 destination = nodes.get(i);
308 break;
309 }
310 }
311 Throw.whenNull(destination, "Route has no links with lanes, "
312 + "unable to find a suitable destination node regarding lane change information.");
313 }
314
315
316 graph.setRoute(routeForWeights);
317
318
319 GraphPath<Identifiable, LaneChangeInfoEdge> path = DijkstraShortestPath.findPathBetween(graph, lane, destination);
320 return path == null ? null : path.getEdgeList();
321 }
322
323
324
325
326
327
328 private SortedSet<LaneChangeInfo> extractLaneChangeInfo(final List<LaneChangeInfoEdge> path)
329 {
330 SortedSet<LaneChangeInfo> info = new TreeSet<>();
331 Length x = Length.ZERO;
332 int n = 0;
333 boolean inLateralState = false;
334 for (LaneChangeInfoEdge edge : path)
335 {
336 LaneChangeInfoEdgeType lcType = edge.laneChangeInfoEdgeType();
337 int lat = lcType.equals(LaneChangeInfoEdgeType.LEFT) ? -1 : (lcType.equals(LaneChangeInfoEdgeType.RIGHT) ? 1 : 0);
338
339
340 if (n * lat < 0)
341 {
342
343
344
345
346
347
348 break;
349 }
350
351
352 if (lat == 0)
353 {
354
355 if (inLateralState)
356 {
357
358 boolean isDeadEnd = false;
359 info.add(new LaneChangeInfo(Math.abs(n), x, isDeadEnd,
360 n < 0 ? LateralDirectionality.LEFT : LateralDirectionality.RIGHT));
361 inLateralState = false;
362
363 }
364 else
365 {
366
367 x = x.plus(edge.fromLane().getLength());
368 }
369 }
370 else
371 {
372
373 if (!inLateralState)
374 {
375 x = x.plus(edge.fromLane().getLength());
376 inLateralState = true;
377 }
378
379 n += lat;
380 }
381 }
382 return info;
383 }
384
385
386
387
388
389 public void clearLaneChangeInfoCache()
390 {
391 this.legalLaneGraph.clear();
392 this.physicalLaneGraph = null;
393 this.legalLaneChangeInfoCache = new MultiKeyMap<>(GtuType.class, Route.class, Lane.class);
394 this.physicalLaneChangeInfoCache = new MultiKeyMap<>(Route.class, Lane.class);
395 }
396
397
398
399
400
401
402
403
404
405
406
407
408 private class RouteWeightedGraph extends SimpleDirectedWeightedGraph<Identifiable, LaneChangeInfoEdge>
409 {
410
411
412 private static final long serialVersionUID = 20220923L;
413
414
415 private Route route;
416
417
418 private Node noRouteDestination = null;
419
420
421
422
423 RouteWeightedGraph()
424 {
425 super(LaneChangeInfoEdge.class);
426 }
427
428
429
430
431
432 public void setRoute(final Route route)
433 {
434 Throw.whenNull(route, "Route may not be null for lane change information.");
435 this.route = route;
436 }
437
438
439
440
441
442
443
444
445
446 @Override
447 public double getEdgeWeight(final LaneChangeInfoEdge e)
448 {
449 if (e.laneChangeInfoEdgeType().equals(LaneChangeInfoEdgeType.LEFT)
450 || e.laneChangeInfoEdgeType().equals(LaneChangeInfoEdgeType.RIGHT))
451 {
452 int indexEndNode = this.route.indexOf(e.fromLane().getLink().getEndNode());
453 return 1.0 + 1.0 / indexEndNode;
454 }
455 Link toLink = e.toLink();
456 if (toLink == null)
457 {
458 return 0.0;
459 }
460 if (this.route.contains(toLink.getEndNode())
461 && this.route.indexOf(toLink.getEndNode()) == this.route.indexOf(toLink.getStartNode()) + 1)
462 {
463 return 1.0;
464 }
465 return Double.POSITIVE_INFINITY;
466 }
467
468
469
470
471
472
473 public Node getNoRouteDestinationNode(final GtuType gtuType)
474 {
475 if (this.noRouteDestination == null)
476 {
477
478 Lane lane = null;
479 Iterator<Identifiable> iterator = this.vertexSet().iterator();
480 while (lane == null && iterator.hasNext())
481 {
482 Identifiable next = iterator.next();
483 if (next instanceof Lane)
484 {
485 lane = (Lane) next;
486 }
487 }
488 Throw.when(lane == null, RuntimeException.class, "Requesting destination node on network without lanes.");
489
490 try
491 {
492 Link link = lane.getLink();
493 Set<Link> downstreamLinks = link.getEndNode().nextLinks(gtuType, link);
494 while (downstreamLinks.size() == 1)
495 {
496 link = downstreamLinks.iterator().next();
497 downstreamLinks = link.getEndNode().nextLinks(gtuType, link);
498 }
499 Throw.when(downstreamLinks.size() > 1, RuntimeException.class, "Using null route on network with split. "
500 + "Unable to find a destination to find lane change info towards.");
501 this.noRouteDestination = link.getEndNode();
502 }
503 catch (NetworkException ne)
504 {
505 throw new RuntimeException("Requesting lane change info from link that does not allow the GTU type.", ne);
506 }
507 }
508 return this.noRouteDestination;
509 }
510 }
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526 private static record LaneChangeInfoEdge(Lane fromLane, LaneChangeInfoEdgeType laneChangeInfoEdgeType, Link toLink)
527 {
528 }
529
530
531
532
533
534
535
536
537
538
539 private enum LaneChangeInfoEdgeType
540 {
541
542 LEFT,
543
544
545 RIGHT,
546
547
548 DOWNSTREAM;
549 }
550
551 }