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