/* *********************************************************************** * * project: org.matsim.* * *********************************************************************** * * * * copyright : (C) 2016 by the members listed in the COPYING, * * LICENSE and WARRANTY file. * * email : info at matsim dot org * * * * *********************************************************************** * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * See also COPYING, LICENSE and WARRANTY file * * * * *********************************************************************** */ package org.matsim.pt2matsim.tools; import org.apache.log4j.Logger; import org.matsim.api.core.v01.Coord; import org.matsim.api.core.v01.Id; import org.matsim.api.core.v01.TransportMode; import org.matsim.api.core.v01.network.Link; import org.matsim.api.core.v01.network.Network; import org.matsim.api.core.v01.network.NetworkFactory; import org.matsim.api.core.v01.network.Node; import org.matsim.core.network.NetworkUtils; import org.matsim.core.network.algorithms.NetworkTransform; import org.matsim.core.network.filter.NetworkFilterManager; import org.matsim.core.network.filter.NetworkLinkFilter; import org.matsim.core.network.io.MatsimNetworkReader; import org.matsim.core.network.io.NetworkWriter; import org.matsim.core.utils.collections.MapUtils; import org.matsim.core.utils.geometry.CoordUtils; import org.matsim.core.utils.geometry.transformations.TransformationFactory; import org.matsim.pt.transitSchedule.api.TransitLine; import org.matsim.pt.transitSchedule.api.TransitRoute; import org.matsim.pt.transitSchedule.api.TransitSchedule; import org.matsim.pt2matsim.config.PublicTransitMappingConfigGroup; import org.matsim.pt2matsim.mapping.networkRouter.ScheduleRouters; import org.matsim.pt2matsim.mapping.networkRouter.ScheduleRoutersFactory; import org.matsim.pt2matsim.mapping.networkRouter.ScheduleRoutersStandard; import java.util.*; import static org.matsim.pt2matsim.tools.ScheduleTools.getTransitRouteLinkIds; /** * Provides Tools for analysing and manipulating networks. * * @author polettif */ public final class NetworkTools { protected static Logger log = Logger.getLogger(NetworkTools.class); private NetworkTools() {} /** * Reads and returns a network */ public static Network readNetwork(String fileName) { Network network = NetworkUtils.createNetwork(); new MatsimNetworkReader(network).readFile(fileName); return network; } public static void writeNetwork(Network network, String fileName) { new NetworkWriter(network).write(fileName); } public static Network createNetwork() { return NetworkUtils.createNetwork(); } public static void transformNetwork(Network network, String fromCoordinateSystem, String toCoordinateSystem) { new NetworkTransform(TransformationFactory.getCoordinateTransformation(fromCoordinateSystem, toCoordinateSystem)).run(network); } public static void transformNetworkFile(String networkFile, String fromCoordinateSystem, String toCoordinateSystem) { log.info("... Transformig network from " + fromCoordinateSystem + " to " + toCoordinateSystem); Network network = readNetwork(networkFile); transformNetwork(network, fromCoordinateSystem, toCoordinateSystem); writeNetwork(network, networkFile); } /** * Returns the nearest link for the given coordinate. * Looks for nodes within search radius of coord (using {@link NetworkUtils#getNearestNodes}, * fetches all in- and outlinks returns the link with the smallest distance * to the given coordinate. If there are two opposite links, the link with * the coordinate on its right side is returned.<p/> * * NOTE: In contrast to {@link NetworkUtils#getNearestLink}, this method looks for the * nearest link passing the coordinate from a reasonable set of nearby nodes instead * of the closest link originating from or ending in the closest node. * * @param network network * @param coord the coordinate * @param nodeSearchRadius links from/to nodes within this radius are considered */ public static Link getNearestLink(Network network, Coord coord, double nodeSearchRadius) { Link closestLink = null; double minDistance = Double.MAX_VALUE; Collection<Node> nearestNodes = NetworkUtils.getNearestNodes(network, coord, nodeSearchRadius); while(nearestNodes.size() == 0) { nodeSearchRadius *= 2; nearestNodes = NetworkUtils.getNearestNodes(network, coord, nodeSearchRadius); } // check every in- and outlink of each node for(Node node : nearestNodes) { Set<Link> links = new HashSet<>(node.getOutLinks().values()); links.addAll(node.getInLinks().values()); double lineSegmentDistance; for(Link link : links) { // only use links with a viable network transport mode lineSegmentDistance = CoordUtils.distancePointLinesegment(link.getFromNode().getCoord(), link.getToNode().getCoord(), coord); if(lineSegmentDistance < minDistance) { minDistance = lineSegmentDistance; closestLink = link; } } } // check for opposite link Link oppositeLink = getOppositeLink(closestLink); if(oppositeLink != null && !coordIsOnRightSideOfLink(coord, closestLink)) { return oppositeLink; } else { return closestLink; } } /** * Looks for nodes within search radius of <tt>coord</tt> (using {@link NetworkUtils#getNearestNodes}, * fetches all in- and outlinks and sorts them ascending by their * distance to the coordinates given. A map with the distance as key and a set as value is used * to (1) return the already calculated distance to the coord and (2) store two opposite links under * the same distance. * * @param network The network * @param coord the coordinate from which the closest links are * to be searched * @param nodeSearchRadius Only links from and to nodes within this radius are considered. * @param allowedTransportModes Only links with at least one of these transport modes are considered. All links are considered if <tt>null</tt>. */ public static Map<Double, Set<Link>> findClosestLinks(Network network, Coord coord, double nodeSearchRadius, Set<String> allowedTransportModes) { Collection<Node> nearestNodes = NetworkUtils.getNearestNodes(network, coord, nodeSearchRadius); SortedMap<Double, Set<Link>> closestLinksSortedByDistance = new TreeMap<>(); if(nearestNodes.size() != 0) { // fetch every in- and outlink of each node HashSet<Link> links = new HashSet<>(); for(Node node : nearestNodes) { links.addAll(node.getOutLinks().values()); links.addAll(node.getInLinks().values()); } // calculate lineSegmentDistance for all links for(Link link : links) { // only use links with a viable network transport mode if(allowedTransportModes == null || MiscUtils.collectionsShareMinOneStringEntry(link.getAllowedModes(), allowedTransportModes)) { double lineSegmentDistance = CoordUtils.distancePointLinesegment(link.getFromNode().getCoord(), link.getToNode().getCoord(), coord); MapUtils.getSet(lineSegmentDistance, closestLinksSortedByDistance).add(link); } } } return closestLinksSortedByDistance; } /** * See {@link #findClosestLinks}. Returns a list ordered ascending by distance to the coord. * For opposite links, the link which has the coordinate on its right side is sorted "closer" to the coordinate. * If more than two links have the exact same distance, links are sorted by distance to their respective closest node. * After that, behaviour is undefined. */ public static List<Link> findClosestLinksSorted(Network network, Coord coord, double nodeSearchRadius, Set<String> allowedTransportModes) { List<Link> links = new ArrayList<>(); Map<Double, Set<Link>> sortedLinks = findClosestLinks(network, coord, nodeSearchRadius, allowedTransportModes); for(Set<Link> set : sortedLinks.values()) { List<Link> list = new ArrayList<>(set); if(list.size() == 1) { links.add(list.get(0)); } else if(list.size() == 2) { if(coordIsOnRightSideOfLink(coord, list.get(0))) { links.add(list.get(0)); links.add(list.get(1)); } else { links.add(list.get(1)); links.add(list.get(0)); } } else { Map<Double, Link> tmp = new HashMap<>(); for(Link l : list) { double fromNodeDist = CoordUtils.calcEuclideanDistance(l.getFromNode().getCoord(), coord); double toNodeDist = CoordUtils.calcEuclideanDistance(l.getFromNode().getCoord(), coord); double nodeDist = fromNodeDist < toNodeDist ? fromNodeDist : toNodeDist; double d = nodeDist + (coordIsOnRightSideOfLink(coord, l) ? 1 : 100); while(tmp.putIfAbsent(d, l) == null) { d += 0.01; } } links.addAll(tmp.values()); } } return links; } /** * Creates and returns a mode filtered network. * * @param network the input network, is not modified * @param transportModes Links of the input network that share at least one network mode * with this set are added to the new network. The returned network * is empty if <tt>null</tt>. * @return the filtered new network */ public static Network createFilteredNetworkByLinkMode(Network network, Set<String> transportModes) { NetworkFilterManager filterManager = new NetworkFilterManager(network); filterManager.addLinkFilter(new LinkFilter(transportModes)); Network newNetwork = filterManager.applyFilters(); removeNotUsedNodes(newNetwork); return newNetwork; } public static Network createFilteredNetworkExceptLinkMode(Network network, Set<String> transportModes) { NetworkFilterManager filterManager = new NetworkFilterManager(network); filterManager.addLinkFilter(new InverseLinkFilter(transportModes)); return filterManager.applyFilters(); } public static void cutNetwork(Network network, Coord SW, Coord NE) { for(Node n : new HashSet<>(network.getNodes().values())) { if(!CoordTools.isInArea(n.getCoord(), SW, NE)) { network.removeNode(n.getId()); } } } /** * @return the opposite direction link. <tt>null</tt> if there is no opposite link. */ public static Link getOppositeLink(Link link) { if(link == null) { return null; } Link oppositeDirectionLink = null; Map<Id<Link>, ? extends Link> inLinks = link.getFromNode().getInLinks(); if(inLinks != null) { for(Link inLink : inLinks.values()) { if(inLink.getFromNode().equals(link.getToNode())) { oppositeDirectionLink = inLink; } } } return oppositeDirectionLink; } /** * @return true if the coordinate is on the right hand side of the link (or on the link). */ public static boolean coordIsOnRightSideOfLink(Coord coord, Link link) { return CoordTools.coordIsOnRightSideOfLine(coord, link.getFromNode().getCoord(), link.getToNode().getCoord()); } /** * Checks if a link sequence has loops (i.e. the same link is passed twice). */ public static boolean linkSequenceHasDuplicateLink(List<Link> linkSequence) { Set tmpSet = new HashSet<>(linkSequence); return tmpSet.size() < linkSequence.size(); } /** * Checks if a link sequence has u-turns (i.e. the opposite direction link is * passed immediately after a link). */ public static boolean linkSequenceHasUTurns(List<Link> links) { for(int i = 1; i < links.size(); i++) { if(links.get(i).getToNode().equals(links.get(i - 1).getFromNode())) { return true; } } return false; } /** * A debug method to assign weights to network links as number of lanes. * The network is changed permanently, so this should really only be used for * debugging. */ public static void visualizeWeightsAsLanes(Network network, Map<Id<Link>, Double> weightMap) { for(Map.Entry<Id<Link>, Double> w : weightMap.entrySet()) { network.getLinks().get(w.getKey()).setNumberOfLanes(w.getValue()); } } /** * @return the network links from a given list of link ids */ public static List<Link> getLinksFromIds(Network network, List<Id<Link>> linkIds) { Map<Id<Link>, ? extends Link> links = network.getLinks(); List<Link> list = new ArrayList<>(); for(Id<Link> linkId : linkIds) { list.add(links.get(linkId)); } return list; } /** * Merges all network into baseNetworks. If a link id already * exists in the base network, the link is not added to it. * * @param baseNetwork the network in which all other networks are integrated * @param networks collection of networks to merge into the base network */ public static void mergeNetworks(Network baseNetwork, Collection<Network> networks) { log.info("Merging networks..."); int numberOfLinksBefore = baseNetwork.getLinks().size(); int numberOfNodesBefore = baseNetwork.getNodes().size(); for(Network currentNetwork : networks) { integrateNetwork(baseNetwork, currentNetwork, true); } log.info("... Total number of links added to network: " + (baseNetwork.getLinks().size() - numberOfLinksBefore)); log.info("... Total number of nodes added to network: " + (baseNetwork.getNodes().size() - numberOfNodesBefore)); log.info("Merging networks... done."); } /** * Integrates <tt>network B</tt> into <tt>network A</tt>. Network * A contains all links and nodes of both networks * after integration. */ public static void integrateNetwork(final Network networkA, final Network networkB, boolean mergeModes) { final NetworkFactory factory = networkA.getFactory(); // Nodes for(Node node : networkB.getNodes().values()) { Id<Node> nodeId = Id.create(node.getId().toString(), Node.class); if(!networkA.getNodes().containsKey(nodeId)) { Node newNode = factory.createNode(nodeId, node.getCoord()); networkA.addNode(newNode); } } // Links double capacityFactor = networkA.getCapacityPeriod() / networkB.getCapacityPeriod(); for(Link link : networkB.getLinks().values()) { Id<Link> linkId = Id.create(link.getId().toString(), Link.class); if(!networkA.getLinks().containsKey(linkId)) { Id<Node> fromNodeId = Id.create(link.getFromNode().getId().toString(), Node.class); Id<Node> toNodeId = Id.create(link.getToNode().getId().toString(), Node.class); Link newLink = factory.createLink(linkId, networkA.getNodes().get(fromNodeId), networkA.getNodes().get(toNodeId)); newLink.setAllowedModes(link.getAllowedModes()); newLink.setCapacity(link.getCapacity() * capacityFactor); newLink.setFreespeed(link.getFreespeed()); newLink.setLength(link.getLength()); newLink.setNumberOfLanes(link.getNumberOfLanes()); networkA.addLink(newLink); } else if (mergeModes) { Link existingLink = networkA.getLinks().get(linkId); Set<String> allowedModes = new HashSet<>(); allowedModes.addAll(existingLink.getAllowedModes()); allowedModes.addAll(link.getAllowedModes()); existingLink.setAllowedModes(allowedModes); if (link.getCapacity() * capacityFactor != existingLink.getCapacity()) { throw new IllegalStateException("Capacity must be equal for integration"); } if (link.getFreespeed() != existingLink.getFreespeed()) { throw new IllegalStateException("Freespeed must be equal for integration"); } if (link.getLength() != existingLink.getLength()) { throw new IllegalStateException("Length must be equal for integration"); } if (link.getNumberOfLanes() != existingLink.getNumberOfLanes()) { throw new IllegalStateException("Number of lanes must be equal for integration"); } } } } public static void shortenLink(Link link, Node toNode) { link.setToNode(toNode); link.setLength(CoordUtils.calcEuclideanDistance(link.getFromNode().getCoord(), toNode.getCoord())); } public static void shortenLink(Node fromNode, Link link) { link.setFromNode(fromNode); link.setLength(CoordUtils.calcEuclideanDistance(link.getFromNode().getCoord(), fromNode.getCoord())); } /** * Sets the free speed of all links with the networkMode to the * defined value. */ public static void setFreeSpeedOfLinks(Network network, String networkMode, double freespeedValue) { for(Link link : network.getLinks().values()) { if(link.getAllowedModes().contains(networkMode)) { link.setFreespeed(freespeedValue); } } } /** * Resets the link length of all links with the given link Mode */ public static void resetLinkLength(Network network, String networkMode) { for(Link link : network.getLinks().values()) { if(link.getAllowedModes().contains(networkMode)) { double l = CoordUtils.calcEuclideanDistance(link.getFromNode().getCoord(), link.getToNode().getCoord()); link.setLength(l > 0 ? l : 1); } } } /** * Creates mode dependent routers based on the actual network modes used. */ public static ScheduleRoutersFactory guessRouters(TransitSchedule schedule, Network network) { // for each schedule modes, look which network modes are used Map<String, Set<String>> modeAssignments = new HashMap<>(); for(TransitLine transitLine : schedule.getTransitLines().values()) { for(TransitRoute transitRoute : transitLine.getRoutes().values()) { Set<String> usedNetworkModes = MapUtils.getSet(transitRoute.getTransportMode(), modeAssignments); List<Link> links = getLinksFromIds(network, getTransitRouteLinkIds(transitRoute)); for(Link link : links) { usedNetworkModes.addAll(link.getAllowedModes()); } } } // setup config PublicTransitMappingConfigGroup config = new PublicTransitMappingConfigGroup(); for(Map.Entry<String, Set<String>> entry : modeAssignments.entrySet()) { PublicTransitMappingConfigGroup.TransportModeAssignment mra = new PublicTransitMappingConfigGroup.TransportModeAssignment(entry.getKey()); mra.setNetworkModes(entry.getValue()); config.addParameterSet(mra); } return new ScheduleRoutersStandard.Factory(schedule, network, modeAssignments, PublicTransitMappingConfigGroup.TravelCostType.linkLength, true); } /** * Replaces all non-car link modes with "pt" */ public static void replaceNonCarModesWithPT(Network network) { log.info("... Replacing all non-car link modes with \"pt\""); Set<String> modesCar = Collections.singleton(TransportMode.car); Set<String> modesCarPt = new HashSet<>(); modesCarPt.add(TransportMode.car); modesCarPt.add(TransportMode.pt); Set<String> modesPt = new HashSet<>(); modesPt.add(TransportMode.pt); for(Link link : network.getLinks().values()) { if(link.getAllowedModes().size() == 0 && link.getAllowedModes().contains(TransportMode.car)) { link.setAllowedModes(modesCar); } if(link.getAllowedModes().size() > 0 && link.getAllowedModes().contains(TransportMode.car)) { link.setAllowedModes(modesCarPt); } else if(!link.getAllowedModes().contains(TransportMode.car)) { link.setAllowedModes(modesPt); } } } /** * @return only links that have the same allowed modes set */ public static Set<Link> filterLinkSetExactlyByModes(Collection<? extends Link> links, Set<String> transportModes) { Set<Link> returnSet = new HashSet<>(); for(Link l : links) { if(l.getAllowedModes().equals(transportModes)) { returnSet.add(l); } } return returnSet; } /** * Removes all nodes with no in- or outgoing links from a network */ public static void removeNotUsedNodes(Network network) { for(Node n : new HashSet<>(network.getNodes().values())) { if(n.getInLinks().size() == 0 && n.getOutLinks().size() == 0) { network.removeNode(n.getId()); } } } /** * Removes all nodes that are not specified from the network */ public static void cutNetwork(Network network, Collection<Node> nodesToKeep) { for(Node n : new HashSet<>(network.getNodes().values())) { if(!nodesToKeep.contains(n)) { network.removeNode(n.getId()); } } } /** * Links that only have one preceding and succeeding link (ignoring opposite links) * are removed except the link in that sequence that is closest to the coordinate */ public static void reduceSequencedLinks(Collection<? extends Link> links, Coord coord) { Map<Link, Link> linkSequence = new HashMap<>(); // key points to succeeding link Set<Link> originLinks = new HashSet<>(); // links from which a sequence of single file links originates Map<Link, Link> linksToKeep = new HashMap<>(); // links from single file sequence that are closest to the coord // get all single file links Set<Link> singleFileLinks = new HashSet<>(); for(Link l : links) { if(getSingleFilePrecedingLink(l) != null || getSingleFileSucceedingLink(l) != null) { singleFileLinks.add(l); } } // find origin links Set<Link> found = new HashSet<>(); Set<Link> visited = new HashSet<>(); for(Link currentLink : singleFileLinks) { if(!found.contains(currentLink)) { Link actual = currentLink; Link precedingLink; visited.clear(); do { found.add(actual); visited.add(actual); precedingLink = getSingleFilePrecedingLink(actual); if(precedingLink != null && links.contains(precedingLink)) { linkSequence.put(precedingLink, actual); actual = precedingLink; if (visited.contains(actual)) { // We found a closed loop and arrived back at the starting point. break; } } } while(precedingLink != null && links.contains(precedingLink)); originLinks.add(actual); actual = currentLink; Link succeedingLink; visited.clear(); do { found.add(actual); visited.add(actual); succeedingLink = getSingleFileSucceedingLink(actual); if(succeedingLink != null && links.contains(succeedingLink)) { linkSequence.put(actual, succeedingLink); actual = succeedingLink; if (visited.contains(actual)) { // We found a closed loop and arrived back at the starting point. break; } } } while(succeedingLink != null && links.contains(succeedingLink)); } } // find closest link for each single link set if(originLinks.size() != 0) { for(Link originLink : originLinks) { Set<Link> consideredLinks = new HashSet<>(); double minDist = Double.MAX_VALUE; Link actual = originLink; do { double dist = CoordUtils.distancePointLinesegment(actual.getFromNode().getCoord(), actual.getToNode().getCoord(), coord); if(dist < minDist) { minDist = dist; linksToKeep.put(originLink, actual); } actual = linkSequence.get(actual); if(!consideredLinks.add(actual)) { linkSequence.put(actual, null); // loop found, abort } } while(actual != null); } // remove links (all succedingLinks are up for removal except the ones closest to the stop) for(Link link : singleFileLinks) { if(!linksToKeep.containsValue(link)) { links.remove(link); } } } // NetworkTools.writeNetwork(NetworkTools.createNetworkFromLinks(new HashSet<>(singleFileLinks)), "singleFileLinks.xml"); // NetworkTools.writeNetwork(NetworkTools.createNetworkFromLinks(new HashSet<>(originLinks)), "originLinks.xml"); // NetworkTools.writeNetwork(NetworkTools.createNetworkFromLinks(new HashSet<>(linksToKeep.values())), "linksToKeep.xml"); // NetworkTools.writeNetwork(NetworkTools.createNetworkFromLinks(new HashSet<>(links)), "remainingLinks.xml"); } /** * @return The preceding link if its the given link's only preceding link (ignoring opposite links) * Returns null if there are multiple succeeding links. */ /*pckg*/ static Link getSingleFilePrecedingLink(Link link) { Link oppositeLink = getOppositeLink(link); if((link.getFromNode().getInLinks().values().size() == 2 && oppositeLink != null) || (link.getFromNode().getInLinks().values().size() == 1 && oppositeLink == null)) { for(Link fromInLink : link.getFromNode().getInLinks().values()) { if(fromInLink != oppositeLink) { return fromInLink; } } } return null; } /** * @return The succeeding link if its the given link's only succeding link (ignoring opposite links). * Returns null if there are multiple succeeding links. */ /*pckg*/ static Link getSingleFileSucceedingLink(Link link) { Link oppositeLink = getOppositeLink(link); if((link.getToNode().getOutLinks().values().size() == 2 && oppositeLink != null) || (link.getToNode().getOutLinks().values().size() == 1 && oppositeLink == null)) { for(Link toOutLink : link.getToNode().getOutLinks().values()) { if(toOutLink != oppositeLink) { return toOutLink; } } } return null; } /** * Creates a new network from a collection of links. For debugging. */ public static Network createNetworkFromLinks(Collection<? extends Link> links) { Network network = NetworkTools.createNetwork(); Set<Node> nodes = new HashSet<>(); for(Link l : links) { nodes.add(l.getFromNode()); nodes.add(l.getToNode()); } for(Node n : nodes) { NetworkUtils.createAndAddNode(network, n.getId(), n.getCoord()); } for(Link l : links) { network.addLink(network.getFactory().createLink(l.getId(), network.getNodes().get(l.getFromNode().getId()), network.getNodes().get(l.getToNode().getId()))); } return network; } /** * Calculates the length of a link sequence * @param euclidian uses the sum of all link lengths if <tt>false</tt> */ public static double calcRouteLength(List<Link> links, boolean euclidian) { double length = 0; Link debugPrev = null; for(Link link : links) { if(debugPrev != null && !debugPrev.getToNode().getOutLinks().values().contains(link)) { throw new NoSuchElementException("Links not connected!"); } if(euclidian) { length += CoordUtils.calcEuclideanDistance(link.getFromNode().getCoord(), link.getToNode().getCoord()); } else { length += link.getLength(); } debugPrev = link; } return length; } public static double calcRouteLength(Network network, TransitRoute transitRoute, boolean euclidian) { return calcRouteLength(NetworkUtils.getLinks(network, ScheduleTools.getTransitRouteLinkIds(transitRoute)), euclidian); } /** * Link filters by mode */ private static class LinkFilter implements NetworkLinkFilter { private final Set<String> modes; public LinkFilter(Set<String> modes) { this.modes = modes; } @Override public boolean judgeLink(Link l) { return MiscUtils.collectionsShareMinOneStringEntry(l.getAllowedModes(), modes); } } private static class InverseLinkFilter implements NetworkLinkFilter { private final Set<String> modes; public InverseLinkFilter(Set<String> modes) { this.modes = modes; } @Override public boolean judgeLink(Link l) { return !MiscUtils.collectionsShareMinOneStringEntry(l.getAllowedModes(), modes); } } }