MATSIM
NetworkSimplifier.java
Go to the documentation of this file.
1 /* *********************************************************************** *
2  * project: org.matsim.*
3  * NetworkCleaner.java
4  * *
5  * *********************************************************************** *
6  * *
7  * copyright : (C) 2007 by the members listed in the COPYING, *
8  * LICENSE and WARRANTY file. *
9  * email : info at matsim dot org *
10  * *
11  * *********************************************************************** *
12  * *
13  * This program is free software; you can redistribute it and/or modify *
14  * it under the terms of the GNU General Public License as published by *
15  * the Free Software Foundation; either version 2 of the License, or *
16  * (at your option) any later version. *
17  * See also COPYING, LICENSE and WARRANTY file *
18  * *
19  * *********************************************************************** */
20 
21 package org.matsim.core.network.algorithms;
22 
23 import org.apache.logging.log4j.LogManager;
24 import org.apache.logging.log4j.Logger;
25 import org.matsim.api.core.v01.Id;
26 import org.matsim.api.core.v01.Scenario;
36 
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collection;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.TreeSet;
46 import java.util.function.BiConsumer;
47 import java.util.function.BiPredicate;
48 
63 public final class NetworkSimplifier {
64 
65  private static final Logger log = LogManager.getLogger(NetworkSimplifier.class);
66  private boolean mergeLinksWithDifferentAttributes = false;
67  private Collection<Integer> nodeTopoToMerge = Arrays.asList( NetworkCalcTopoType.PASS1WAY , NetworkCalcTopoType.PASS2WAY );
68 
69  private Set<Id<Node>> nodesNotToMerge = new HashSet<>();
70 
71  private final Map<Id<Link>,List<Node>> mergedLinksToIntermediateNodes = new HashMap<>();
72 
73  public static final BiPredicate<Link, Link> DEFAULT_IS_MERGEABLE_PREDICATE = (inLink, outLink) -> true;
74  public static final BiConsumer<Tuple<Link, Link>, Link> DEFAULT_TRANSFER_ATTRIBUTES_CONSUMER = (inOutLinks,
75  newLink) -> {
76  // do nothing
77  };
78 
83  public void run(final Network network){
84  run(network, Double.POSITIVE_INFINITY, ThresholdExceeded.EITHER);
85  }
86 
96  public void run(final Network network, final BiPredicate<Link, Link> isMergeable,
97  final BiConsumer<Tuple<Link, Link>, Link> transferAttributes) {
98  run(network, Double.POSITIVE_INFINITY, ThresholdExceeded.EITHER, isMergeable, transferAttributes);
99  }
100 
112  @Deprecated
113  public void run(final Network network, double thresholdLength){
114  run(network, thresholdLength, ThresholdExceeded.BOTH);
115  run(network, thresholdLength, ThresholdExceeded.EITHER);
116  }
117 
124  public void setNodesNotToMerge(Set<Long> nodeIDs){
125  for(Long l : nodeIDs){
126  this.nodesNotToMerge.add(Id.createNodeId(l));
127  }
128  }
129 
130  private void run(final Network network, double thresholdLength, ThresholdExceeded type) {
131  run(network, thresholdLength, type, DEFAULT_IS_MERGEABLE_PREDICATE, DEFAULT_TRANSFER_ATTRIBUTES_CONSUMER);
132  }
133 
134  private void run(final Network network, double thresholdLength, ThresholdExceeded type,
135  final BiPredicate<Link, Link> isMergeable, final BiConsumer<Tuple<Link, Link>, Link> transferAttributes) {
136 
137  if(this.nodeTopoToMerge.size() == 0){
138  throw new RuntimeException("No types of node specified. Please use setNodesToMerge to specify which nodes should be merged");
139  }
140 
141  log.info("running " + this.getClass().getName() + " algorithm...");
142 
143  NetworkCalcTopoType nodeTopo = new NetworkCalcTopoType();
144  nodeTopo.run(network);
145 
146  for (Node node : network.getNodes().values()) {
147 
148  if(this.nodeTopoToMerge.contains(nodeTopo.getTopoType(node)) && (!this.nodesNotToMerge.contains(node.getId())) ){
149 
150  List<Link> removedLinks = new ArrayList<>();
151 
152  List<Link> iLinks = new ArrayList<>(node.getInLinks().values());
153  List<Link> oLinks = new ArrayList<>(node.getOutLinks().values());
154 
155  for (Link inLink : iLinks) {
156  for (Link outLink : oLinks) {
157 
158  if(areLinksMergeable(inLink, outLink) && isMergeable.test(inLink, outLink)) {
159  if(this.mergeLinksWithDifferentAttributes){
160 
161  // Only merge if threshold criteria is met.
162  boolean criteria = false;
163  switch (type) {
164  case BOTH:
165  criteria = bothLinksAreShorterThanThreshold(inLink, outLink, thresholdLength);
166  break;
167  case EITHER:
168  criteria = eitherLinkIsShorterThanThreshold(inLink, outLink, thresholdLength);
169  break;
170  default:
171  break;
172  }
173 
174  // yyyy The approach here depends on the sequence in which this goes through the nodes:
175  // * in the "EITHER" situation, a long link may gobble up short neighboring links
176  // until it hits another long link doing the same.
177  // * In the "BOTH" situation, something like going through nodes randomly will often merge
178  // the neighboring links, while going through the nodes along some path will mean that it will
179  // gobble up until the threshold is met.
180  // I would strongly advise against setting thresholdLength to anything other than POSITIVE_INFINITY.
181  // kai, feb'18
182 
183  if(criteria){
184  // Try to merge both links by guessing the resulting links attributes
185  Link link = network.getFactory().createLink(
186  Id.create(inLink.getId() + "-" + outLink.getId(), Link.class),
187  inLink.getFromNode(),
188  outLink.getToNode());
189 
190  // length can be summed up
191  link.setLength(inLink.getLength() + outLink.getLength());
192 
193  // freespeed depends on total length and time needed for inLink and outLink
194  link.setFreespeed(
195  (inLink.getLength() + outLink.getLength()) /
197  );
198 
199  // the capacity and the new links end is important, thus it will be set to the minimum
200  link.setCapacity(Math.min(inLink.getCapacity(), outLink.getCapacity()));
201 
202  // number of lanes can be derived from the storage capacity of both links
203  link.setNumberOfLanes((inLink.getLength() * inLink.getNumberOfLanes()
204  + outLink.getLength() * outLink.getNumberOfLanes())
205  / (inLink.getLength() + outLink.getLength())
206  );
207  if (NetworkUtils.getOrigId(inLink) != null || NetworkUtils.getOrigId(outLink) != null) {
208  NetworkUtils.setOrigId(link, NetworkUtils.getOrigId(inLink) + "-" + NetworkUtils.getOrigId(outLink));
209  }
210  network.addLink(link);
211  transferAttributes.accept(Tuple.of(inLink, outLink), link);
212  network.removeLink(inLink.getId());
213  network.removeLink(outLink.getId());
214  removedLinks.add(inLink);
215  removedLinks.add(outLink);
216  collectMergedLinkNodeInfo(inLink, outLink, link.getId());
217  }
218  } else {
219 
220  // Only merge links with same attributes
221  if(bothLinksHaveSameLinkStats(inLink, outLink)){
222 
223  // Only merge if threshold criteria is met.
224  boolean isHavingShortLinks = false;
225  switch (type) {
226  case BOTH:
227  isHavingShortLinks = bothLinksAreShorterThanThreshold(inLink, outLink, thresholdLength);
228  break;
229  case EITHER:
230  isHavingShortLinks = eitherLinkIsShorterThanThreshold(inLink, outLink, thresholdLength);
231  break;
232  default:
233  break;
234  }
235 
236  if(isHavingShortLinks){
237  Link newLink = NetworkUtils.createAndAddLink(network,Id.create(inLink.getId() + "-" + outLink.getId(), Link.class), inLink.getFromNode(), outLink.getToNode(), inLink.getLength() + outLink.getLength(), inLink.getFreespeed(), inLink.getCapacity(), inLink.getNumberOfLanes());
238  if (NetworkUtils.getOrigId(inLink) != null || NetworkUtils.getOrigId(outLink) != null) {
239  NetworkUtils.setOrigId(newLink, NetworkUtils.getOrigId(inLink) + "-" + NetworkUtils.getOrigId(outLink));
240  }
241 
242  newLink.setAllowedModes(inLink.getAllowedModes());
243 
244  transferAttributes.accept(Tuple.of(inLink, outLink), newLink);
245  network.removeLink(inLink.getId());
246  network.removeLink(outLink.getId());
247  removedLinks.add(inLink);
248  removedLinks.add(outLink);
249  collectMergedLinkNodeInfo(inLink, outLink, newLink.getId());
250  }
251  }
252  }
253  }
254  }
255  }
256  for (Link removedLink : removedLinks) {
257  this.mergedLinksToIntermediateNodes.remove(removedLink.getId());
258  }
259  }
260  }
261 
262  log.info(" resulting network contains " + network.getNodes().size() + " nodes and " +
263  network.getLinks().size() + " links.");
264  log.info("done.");
265 
266  // writes stats as a side effect
267  nodeTopo = new NetworkCalcTopoType();
268  nodeTopo.run(network);
269  }
270 
271  private boolean areLinksMergeable(Link inLink, Link outLink) {
272  Set<Node> fromNodes = new HashSet<>();
273  List<Node> tmp = this.mergedLinksToIntermediateNodes.get(inLink.getId());
274  if (tmp != null) fromNodes.addAll(tmp);
275  fromNodes.add(inLink.getFromNode());
276 
277  Set<Node> toNodes = new HashSet<>();
278  tmp = this.mergedLinksToIntermediateNodes.get(outLink.getId());
279  if (tmp != null) toNodes.addAll(tmp);
280  toNodes.add(outLink.getToNode());
281 
282  // build intersection of from-nodes and to-nodes
283  fromNodes.retainAll(toNodes);
284  // there should be no intersection in order to merge the links
285  return fromNodes.isEmpty();
286  }
287 
288  private void collectMergedLinkNodeInfo(Link inLink, Link outLink, Id<Link> mergedLinkId) {
289  List<Node> nodes = new ArrayList<>();
290  List<Node> tmp = this.mergedLinksToIntermediateNodes.get(inLink.getId());
291  if (tmp != null) {
292  nodes.addAll(tmp);
293  }
294  tmp = this.mergedLinksToIntermediateNodes.get(outLink.getId());
295  if (tmp != null) {
296  nodes.addAll(tmp);
297  }
298  nodes.add(inLink.getToNode());
299 
300  this.mergedLinksToIntermediateNodes.put(mergedLinkId, nodes);
301  }
302 
303 
310  public void setNodesToMerge(Set<Integer> nodeTypesToMerge){
311  this.nodeTopoToMerge.addAll(nodeTypesToMerge);
312  }
313 
320  public void setMergeLinkStats(boolean mergeLinksWithDifferentAttributes){
321  this.mergeLinksWithDifferentAttributes = mergeLinksWithDifferentAttributes;
322  }
323 
324  // helper
325 
335  private boolean bothLinksAreShorterThanThreshold(Link linkA, Link linkB, double thresholdLength){
336  boolean hasTwoShortLinks = false;
337  if(linkA.getLength() < thresholdLength && linkB.getLength() < thresholdLength){
338  hasTwoShortLinks = true;
339  }
340  return hasTwoShortLinks;
341  }
342 
352  private boolean eitherLinkIsShorterThanThreshold(Link linkA, Link linkB, double thresholdLength){
353  boolean hasShortLink = false;
354  if(linkA.getLength() < thresholdLength || linkB.getLength() < thresholdLength){
355  hasShortLink = true;
356  }
357  return hasShortLink;
358  }
359 
363  private boolean bothLinksHaveSameLinkStats(Link linkA, Link linkB){
364 
365  boolean bothLinksHaveSameLinkStats = true;
366 
367  if(!linkA.getAllowedModes().equals(linkB.getAllowedModes())){ bothLinksHaveSameLinkStats = false; }
368 
369  if(linkA.getFreespeed() != linkB.getFreespeed()){ bothLinksHaveSameLinkStats = false; }
370 
371  if(linkA.getCapacity() != linkB.getCapacity()){ bothLinksHaveSameLinkStats = false; }
372 
373  if(linkA.getNumberOfLanes() != linkB.getNumberOfLanes()){ bothLinksHaveSameLinkStats = false; }
374 
376  }
377 
378  public static void main(String[] args) {
379  final String inNetworkFile = args[ 0 ];
380  final String outNetworkFile = args[ 1 ];
381 
382  Set<Integer> nodeTypesToMerge = new TreeSet<>();
383  nodeTypesToMerge.add(4);
384  nodeTypesToMerge.add(5);
385 
387  final Network network = scenario.getNetwork();
388  new MatsimNetworkReader(scenario.getNetwork()).readFile( inNetworkFile );
389 
390  NetworkSimplifier nsimply = new NetworkSimplifier();
391  nsimply.setNodesToMerge(nodeTypesToMerge);
392 // nsimply.setMergeLinkStats(true);
393  nsimply.run(network, Double.NEGATIVE_INFINITY);
394 
395  new NetworkWriter(network).write( outNetworkFile );
396 
397  }
398 
399  private enum ThresholdExceeded {
400  EITHER, BOTH
401  }
402 }
void run(final Network network, double thresholdLength, ThresholdExceeded type, final BiPredicate< Link, Link > isMergeable, final BiConsumer< Tuple< Link, Link >, Link > transferAttributes)
static void setOrigId(final Node node, final String id)
static final BiConsumer< Tuple< Link, Link >, Link > DEFAULT_TRANSFER_ATTRIBUTES_CONSUMER
Map< Id< Node >, ? extends Node > getNodes()
void setNodesToMerge(Set< Integer > nodeTypesToMerge)
Link removeLink(final Id< Link > linkId)
void run(final Network network, double thresholdLength, ThresholdExceeded type)
boolean areLinksMergeable(Link inLink, Link outLink)
void write(final String filename)
void collectMergedLinkNodeInfo(Link inLink, Link outLink, Id< Link > mergedLinkId)
static String getOrigId(Node node)
boolean bothLinksHaveSameLinkStats(Link linkA, Link linkB)
static< T > Id< T > create(final long key, final Class< T > type)
Definition: Id.java:68
boolean eitherLinkIsShorterThanThreshold(Link linkA, Link linkB, double thresholdLength)
Link createLink(final Id< Link > id, final Node fromNode, final Node toNode)
void setMergeLinkStats(boolean mergeLinksWithDifferentAttributes)
Map< Id< Link >, ? extends Link > getLinks()
static double getFreespeedTravelTime(Link link)
static< A, B > Tuple< A, B > of(final A first, final B second)
Definition: Tuple.java:39
static final BiPredicate< Link, Link > DEFAULT_IS_MERGEABLE_PREDICATE
static Link createAndAddLink(Network network, final Id< Link > id, final Node fromNode, final Node toNode, final double length, final double freespeed, final double capacity, final double numLanes)
boolean bothLinksAreShorterThanThreshold(Link linkA, Link linkB, double thresholdLength)
final Map< Id< Link >, List< Node > > mergedLinksToIntermediateNodes
void run(final Network network, double thresholdLength)
static Scenario createScenario(final Config config)
void run(final Network network, final BiPredicate< Link, Link > isMergeable, final BiConsumer< Tuple< Link, Link >, Link > transferAttributes)
static Id< Node > createNodeId(final long key)
Definition: Id.java:211
static Config createConfig(final String context)