MATSIM
TurnRestrictionsContext.java
Go to the documentation of this file.
1 package org.matsim.core.network.turnRestrictions;
2 
3 import org.matsim.api.core.v01.Id;
8 
9 import java.util.*;
10 
16 public final class TurnRestrictionsContext {
17 
18  private int nodeCount;
19  private int linkCount;
20 
21  public final Network network;
22 
23  public Map<Id<Link>, ColoredLink> replacedLinks = new HashMap<>();
24  public List<ColoredNode> coloredNodes = new ArrayList<>();
25  public List<ColoredLink> coloredLinks = new ArrayList<>();
26  public Map<Id<Link>, List<ColoredLink>> coloredLinksPerLinkMap = new HashMap<>();
27 
28  private TurnRestrictionsContext(Network network) {
29  this.network = network;
30  this.nodeCount = Id.getNumberOfIds(Node.class);
31  this.linkCount = Id.getNumberOfIds(Link.class);
32  }
33 
34  public static final class ColoredLink {
35  public final int index;
36  public final Link link;
38  public final Node fromNode;
40  public Node toNode;
41 
42  private ColoredLink(
43  int index,
44  Link link,
45  ColoredNode fromColoredNode,
46  Node fromNode,
47  ColoredNode toColoredNode,
48  Node toNode
49  ) {
50  this.index = index;
51  this.link = link;
52  this.fromColoredNode = fromColoredNode;
53  this.fromNode = fromNode;
54  this.toColoredNode = toColoredNode;
55  this.toNode = toNode;
56  }
57  }
58 
59  public record ColoredNode(
60  int index,
61  Node node,
62  List<ColoredLink> outLinks,
63  List<ColoredLink> inLinks
64  ) {
65 
66  @Override
67  public int index() {
68  return index;
69  }
70 
71  @Override
72  public Node node() {
73  return node;
74  }
75 
76  @Override
77  public int hashCode() {
78  return index;
79  }
80  }
81 
82  public static TurnRestrictionsContext build(Network network) {
83  /*
84  * The implementation follows the algorithm developed by
85  * Marcel Rieser (Simunto) and Hannes Rewald (Volkswagen Group)
86  * in October 2023 during the MATSim Code Sprint week in Berlin,
87  * and documented at https://github.com/matsim-org/matsim-code-examples/wiki/turn-restrictions.
88  *
89  * TL;DR:
90  * Main idea of algorithm: for each link with turn-restrictions, create a sub-graph of the network
91  * containing all links required to model all allowed paths, but exclude the last link of turn restrictions'
92  * link sequence to enforce the "disallow" along that route.
93  *
94  *
95  * Implementation details:
96  * - The easiest solution would be to make a copy of the original network, then start modifying it
97  * according to the algorithm above (e.g. add and delete links and nodes), and then to convert the
98  * resulting network into a graph. This would require substantial amount of memory for duplicating
99  * the complete network, and might pose problems as we will have multiple links with the same id.
100  * - Given the assumption that turn restrictions apply only to a small amount of the full network,
101  * we keep the original network intact. Instead, we keep all modifications in separate data-structures
102  * so they can be used to create the routing-graph.
103  * - If the network is already filtered for a specific mode, it might be that links referenced
104  * in a turn restriction are missing. The implementation must be able to deal with such cases,
105  * prevent NullPointerExceptions.
106  * - As turn restrictions are mode-specific, the algorithm needs to know for which mode the
107  * turn restriction need to be considered.
108  */
109 
110  TurnRestrictionsContext context = new TurnRestrictionsContext(network);
111 
112  for (Link startingLink : network.getLinks().values()) {
113  DisallowedNextLinks disallowedNextLinks = NetworkUtils.getDisallowedNextLinks(startingLink);
114  if (disallowedNextLinks == null) {
115  continue;
116  }
117  Collection<List<Id<Link>>> turnRestrictions = disallowedNextLinks.getMergedDisallowedLinkSequences();
118  if (turnRestrictions == null || turnRestrictions.isEmpty()) {
119  continue;
120  }
121 
122  // steps 1 to 5:
123  ColoredLink coloredStartingLink = context.applyTurnRestriction(context, turnRestrictions, startingLink);
124 
125  // step 6: turn restrictions have to be applied separately to existing colored links as well.
126  // see if there are already colored link copies available for this starting link
127  List<ColoredLink> coloredLinks = context.coloredLinksPerLinkMap.get(startingLink.getId());
128  if (coloredLinks != null) {
129  for (ColoredLink coloredLink : coloredLinks) {
130  // optimization: re-point toNode instead of re-applying full turn restrictions
131  if (coloredLink.toColoredNode == null) {
132  coloredLink.toColoredNode = coloredStartingLink.toColoredNode;
133  coloredLink.toNode = null;
134  } else {
135  context.applyTurnRestriction(context, turnRestrictions, coloredLink);
136  }
137  }
138  }
139  }
140 
141  return context;
142  }
143 
144  private ColoredLink applyTurnRestriction(TurnRestrictionsContext context, Collection<List<Id<Link>>> restrictions, Link startingLink) {
145  return this.applyTurnRestriction(context, restrictions, startingLink, null);
146  }
147 
148  private void applyTurnRestriction(TurnRestrictionsContext context, Collection<List<Id<Link>>> restrictions, ColoredLink startingLink) {
149  this.applyTurnRestriction(context, restrictions, null, startingLink);
150  }
151 
152  private ColoredLink applyTurnRestriction(TurnRestrictionsContext context, Collection<List<Id<Link>>> restrictions, Link startingLink, ColoredLink coloredStartingLink) {
153  Set<Node> affectedNodes = new HashSet<>();
154  Set<ColoredNode> affectedColoredNodes = new HashSet<>();
155  Set<Link> affectedLinks = new HashSet<>();
156  Set<ColoredLink> affectedColoredLinks = new HashSet<>();
157  Set<Id<Link>> endLinkIds = new HashSet<>();
158 
159  // step 1 and 2: collect end-links, affected-links and affected-nodes
160  for (List<Id<Link>> restriction : restrictions) {
161 
162  Link currentLink;
163  ColoredLink currentColoredLink;
164  Node currentNode = startingLink == null ? null : startingLink.getToNode();
165  // due to the optimization in step 6, every colored starting link leads to a colored to-node
166  ColoredNode currentColoredNode = coloredStartingLink == null ? null : coloredStartingLink.toColoredNode;
167 
168  // walk along the restricted path, collect affectedLinks, affectedNodes and endLink
169  for (Id<Link> linkId : restriction) {
170  if (currentNode != null) {
171  // handle regular node
172  affectedNodes.add(currentNode);
173  currentLink = null;
174  currentColoredLink = null;
175  for (Link outLink : currentNode.getOutLinks().values()) {
176  if (outLink.getId() == linkId) {
177  currentColoredLink = context.replacedLinks.get(linkId);
178  if (currentColoredLink == null) {
179  currentLink = outLink;
180  }
181  break;
182  }
183  }
184 
185  if (currentLink != null) {
186  affectedLinks.add(currentLink);
187  currentNode = currentLink.getToNode();
188  currentColoredNode = null;
189  }
190  if (currentColoredLink != null) {
191  affectedColoredLinks.add(currentColoredLink);
192  currentNode = currentColoredLink.toNode;
193  currentColoredNode = currentColoredLink.toColoredNode;
194  }
195  if (currentLink == null && currentColoredLink == null) {
196  // link of restriction is no longer part of the network, maybe we are in a sub-graph
197  break;
198  }
199  } else if (currentColoredNode != null) {
200  // handle colored node
201  affectedColoredNodes.add(currentColoredNode);
202  currentLink = null;
203  currentColoredLink = null;
204  for (ColoredLink outLink : currentColoredNode.outLinks) {
205  if (outLink.link.getId() == linkId) {
206  currentColoredLink = outLink;
207  break;
208  }
209  }
210  if (currentColoredLink != null) {
211  affectedColoredLinks.add(currentColoredLink);
212  currentNode = currentColoredLink.toNode;
213  currentColoredNode = currentColoredLink.toColoredNode;
214  }
215  if (currentColoredLink == null) {
216  // link of restriction is no longer part of the network, maybe we are in a sub-graph
217  break;
218  }
219  }
220  }
221  endLinkIds.add(restriction.get(restriction.size() - 1));
222  }
223 
224  // step 3: create colored copies of nodes
225  Map<Id<Node>, ColoredNode> newlyColoredNodes = new HashMap<>();
226  for (Node affectedNode : affectedNodes) {
227  int nodeIndex = context.nodeCount;
228  context.nodeCount++;
229  ColoredNode newlyColoredNode = new ColoredNode(nodeIndex, affectedNode, new ArrayList<>(), new ArrayList<>());
230  newlyColoredNodes.put(affectedNode.getId(), newlyColoredNode);
231  context.coloredNodes.add(newlyColoredNode);
232  }
233  for (ColoredNode affectedColoredNode : affectedColoredNodes) {
234  int nodeIndex = context.nodeCount;
235  context.nodeCount++;
236  ColoredNode newlyColoredNode = new ColoredNode(nodeIndex, affectedColoredNode.node, new ArrayList<>(), new ArrayList<>());
237  newlyColoredNodes.put(affectedColoredNode.node.getId(), newlyColoredNode);
238  context.coloredNodes.add(newlyColoredNode);
239  }
240 
241  // step 4: create colored copies of links
242  for (Node affectedNode : affectedNodes) {
243  for (Link outLink : affectedNode.getOutLinks().values()) {
244  if (endLinkIds.contains(outLink.getId())) {
245  continue;
246  }
247  ColoredLink replacedOutLink = context.replacedLinks.get(outLink.getId());
248  int linkIndex = context.linkCount;
249  context.linkCount++;
250  ColoredLink newlyColoredLink;
251  ColoredNode fromNode = newlyColoredNodes.get(outLink.getFromNode().getId());
252  if (affectedLinks.contains(outLink) || (replacedOutLink != null && affectedColoredLinks.contains(replacedOutLink))) {
253  ColoredNode toNode = newlyColoredNodes.get(outLink.getToNode().getId());
254  newlyColoredLink = new ColoredLink(linkIndex, outLink, fromNode, null, toNode, null);
255  } else {
256  Node toNode = outLink.getToNode();
257  newlyColoredLink = new ColoredLink(linkIndex, outLink, fromNode, null, null, toNode);
258  }
259  fromNode.outLinks.add(newlyColoredLink);
260  context.coloredLinks.add(newlyColoredLink);
261  context.coloredLinksPerLinkMap.computeIfAbsent(outLink.getId(), id -> new ArrayList<>(3)).add(newlyColoredLink);
262  }
263  }
264  for (ColoredNode affectedNode : affectedColoredNodes) {
265  for (ColoredLink outLink : affectedNode.outLinks) {
266  if (endLinkIds.contains(outLink.link.getId())) {
267  continue;
268  }
269  int linkIndex = context.linkCount;
270  context.linkCount++;
271  ColoredLink newlyColoredLink;
272  ColoredNode fromNode = newlyColoredNodes.get(outLink.link.getFromNode().getId());
273  if (affectedColoredLinks.contains(outLink)) {
274  ColoredNode toNode = newlyColoredNodes.get(outLink.link.getToNode().getId());
275  newlyColoredLink = new ColoredLink(linkIndex, outLink.link, fromNode, null, toNode, null);
276  } else {
277  newlyColoredLink = new ColoredLink(linkIndex, outLink.link, fromNode, null, outLink.toColoredNode, outLink.toNode);
278  }
279  fromNode.outLinks.add(newlyColoredLink);
280  context.coloredLinks.add(newlyColoredLink);
281  context.coloredLinksPerLinkMap.computeIfAbsent(outLink.link.getId(), id -> new ArrayList<>(3)).add(newlyColoredLink);
282  }
283  }
284 
285  // step 5: replace starting link
286  if (startingLink != null) {
287  ColoredNode toNode = newlyColoredNodes.get(startingLink.getToNode().getId());
288  int linkIndex = startingLink.getId().index(); // re-use the index
289  ColoredLink newlyColoredStartingLink = new ColoredLink(linkIndex, startingLink, null, startingLink.getFromNode(), toNode, null);
290  context.coloredLinks.add(newlyColoredStartingLink);
291  context.replacedLinks.put(startingLink.getId(), newlyColoredStartingLink);
292 
293  return newlyColoredStartingLink;
294  }
295  if (coloredStartingLink != null) {
296  // don't really replace the colored started link, but re-point it to the newly colored node
297  coloredStartingLink.toColoredNode = newlyColoredNodes.get(coloredStartingLink.link.getToNode().getId());
298  return null;
299 
300  }
301  throw new IllegalArgumentException("either startingLink or coloredStartingLink must be set");
302  }
303 
304 
305  public int getNodeCount() {
306  return nodeCount;
307  }
308 
309  public int getLinkCount() {
310  return linkCount;
311  }
312 }
static< T > Id< T > get(int index, final Class< T > type)
Definition: Id.java:112
static< T > int getNumberOfIds(final Class< T > type)
Definition: Id.java:122
static DisallowedNextLinks getDisallowedNextLinks(Link link)
record ColoredNode(int index, Node node, List< ColoredLink > outLinks, List< ColoredLink > inLinks)
void applyTurnRestriction(TurnRestrictionsContext context, Collection< List< Id< Link >>> restrictions, ColoredLink startingLink)
ColoredLink applyTurnRestriction(TurnRestrictionsContext context, Collection< List< Id< Link >>> restrictions, Link startingLink, ColoredLink coloredStartingLink)
Map< Id< Link >, ? extends Link > getLinks()
ColoredLink applyTurnRestriction(TurnRestrictionsContext context, Collection< List< Id< Link >>> restrictions, Link startingLink)
Map< Id< Link >, ? extends Link > getOutLinks()