Path: blob/main/tools/import/visum/visum_convertXMLRoutes.py
185787 views
#!/usr/bin/env python1# -*- coding: utf-8 -*-2# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo3# Copyright (C) 2015-2025 German Aerospace Center (DLR) and others.4# This program and the accompanying materials are made available under the5# terms of the Eclipse Public License 2.0 which is available at6# https://www.eclipse.org/legal/epl-2.0/7# This Source Code may also be made available under the following Secondary8# Licenses when the conditions for such availability set forth in the Eclipse9# Public License 2.0 are satisfied: GNU General Public License, version 210# or later which is available at11# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html12# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later1314# @file visum_convertXMLRoutes.py15# @author Jakob Erdmann16# @date Nov 17 20251718"""19Parses a VISUM xml-route file and writes a SUMO route file20"""2122from __future__ import print_function23from __future__ import absolute_import24import os25import sys26try:27from functools import cache28except ImportError:29# python < 3.930from functools import lru_cache as cache3132if 'SUMO_HOME' in os.environ:33sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools'))34import sumolib # noqa35from sumolib.miscutils import openz # noqa3637MSG_CACHE = set()383940def get_options(args=None):41op = sumolib.options.ArgumentParser(description="Import VISUM route file")42# input43op.add_argument("-n", "--net-file", category="input", dest="netfile", required=True, type=op.net_file,44help="define the net file (mandatory)")45op.add_argument("-r", "--route-file", category="input", dest="routefile", required=True, type=op.route_file,46help="define the net file (mandatory)")47# output48op.add_argument("-o", "--output-trip-file", category="output", dest="outfile", required=True, type=op.route_file,49help="define the output route file")50# processing51op.add_argument("--scale", metavar="FLOAT", type=float, default=1,52help="Scale volume by the given value (i.e. 24 when volume denotes hourly rather than daily traffic)") # noqa53op.add_argument("--vclass", help="Only include routes for the given vclass")54op.add_argument("-a", "--attributes", default="",55help="additional flow attributes.")5657options = op.parse_args(args=args)58return options596061def append_no_duplicate(edges, e):62if edges and edges[-1] == e:63return64edges.append(e)656667@cache68def repairEdgeEdge(net, prevEdge, edge, vClass):69if edge in prevEdge.getAllowedOutgoing(vClass):70return [prevEdge, edge], None71return net.getFastestPath(prevEdge, edge, vClass=vClass)727374@cache75def repairEdgeNode(net, prevEdge, node, vClass):76bestPath = None77bestCost = 1e40078for edge in node.getIncoming():79path, cost = net.getFastestPath(prevEdge, edge, vClass=vClass)80if path and cost < bestCost:81bestPath = path82bestCost = cost83return bestPath, bestCost848586@cache87def repairNodeNode(net, prevNode, node, vClass):88bestPath = None89bestCost = 1e40090for prevEdge in prevNode.getOutgoing():91for edge in node.getIncoming():92path, cost = net.getFastestPath(prevEdge, edge, vClass=vClass)93if path and cost < bestCost:94bestPath = path95bestCost = cost96return bestPath, bestCost979899@cache100def repairNodeEdge(net, prevNode, edge, vClass):101bestPath = None102bestCost = 1e400103for prevEdge in prevNode.getOutgoing():104path, cost = net.getFastestPath(prevEdge, edge, vClass=vClass)105if path and cost < bestCost:106bestPath = path107bestCost = cost108return bestPath, bestCost109110111def getValidNodes(net, nodes):112"""obtain a stream of nodes that exist in the network"""113for nodeID in nodes:114if ' ' in nodeID:115nodeID, id2 = nodeID.split()116if id2[-1] == 'B':117continue118elif id2[-1] == 'A':119if net.hasNode(nodeID):120yield net.getNode(nodeID)121edgeID = id2[:-1]122if net.hasEdge(edgeID):123yield net.getEdge(edgeID).getToNode()124else:125if net.hasNode(nodeID):126yield net.getNode(nodeID)127if net.hasNode(id2):128yield net.getNode(id2)129else:130if net.hasNode(nodeID):131yield net.getNode(nodeID)132133134def getValidEdgesNodes(edgedict, validNodes):135"""obtain stream of edges intermixed with nodes that could not be assigned to edges"""136singleNodes = []137for node in validNodes:138if singleNodes:139e = edgedict.get((singleNodes[-1], node))140if e is not None:141singleNodes.pop()142yield singleNodes, e143singleNodes = []144else:145singleNodes.append(node)146else:147singleNodes.append(node)148if singleNodes:149yield singleNodes, None150151152def msgOnce(msg, key, file):153if key not in MSG_CACHE:154print(msg, file=file)155MSG_CACHE.add(key)156157158def getConnectedEdges(net, validEdgesNodes, vClass, routeID):159"""obtain a sequence of connected edges"""160result = []161lastEdge = None162lastNode = None163msgSuccess = "Route %s:" % routeID + " Repaired path between %s, length %s, cost %s"164msgFail = "Route %s:" % routeID + " Found no path between %s"165for singleNodes, edge in validEdgesNodes:166for n in singleNodes:167if lastEdge:168path, cost = repairEdgeNode(net, lastEdge, n, vClass)169between = "edge %s and node %s" % (lastEdge.getID(), n.getID())170if path:171if len(path) > 2:172msgOnce(msgSuccess % (between, len(path), cost), between, sys.stderr)173result += path[1:]174lastEdge = path[-1]175else:176print(msgFail % between, file=sys.stderr)177return None178elif lastNode:179path, cost = repairNodeNode(net, lastNode, n, vClass)180between = "node %s and node %s" % (lastNode.getID(), n.getID())181if path:182msgOnce(msgSuccess % (between, len(path), cost), between, sys.stderr)183result += path184lastEdge = path[-1]185else:186print(msgFail % between, file=sys.stderr)187return None188else:189lastNode = n190if edge is not None:191if lastEdge:192path, cost = repairEdgeEdge(net, lastEdge, edge, vClass)193between = "edge %s and edge %s" % (lastEdge.getID(), edge.getID())194if path:195if len(path) > 2:196msgOnce(msgSuccess % (between, len(path), cost), between, sys.stderr)197result += path[1:]198lastEdge = path[-1]199else:200print(msgFail % between, file=sys.stderr)201return None202elif lastNode:203path, cost = repairNodeEdge(net, lastNode, edge, vClass)204between = "node %s and edge %s" % (lastNode.getID(), edge.getID())205if path:206msgOnce(msgSuccess % (between, len(path), cost), between, sys.stderr)207result += path208lastEdge = path[-1]209else:210print(msgFail % between, file=sys.stderr)211return None212else:213result.append(edge)214lastEdge = edge215216return result217218219def main(options):220vTypes = dict()221nSkipped = 0222nBroken = 0223nDisallowed = 0224nZeroFlows = 0225nZeroRoutes = 0226net = sumolib.net.readNet(options.netfile)227allowed = set()228if options.vclass:229allowed.add(None)230for e in net.getEdges():231if e.allows(options.vclass):232allowed.add(e)233234edgedict = {} # (from,to) -> edge235for e in net.getEdges():236edgedict[e.getFromNode(), e.getToNode()] = e237238with openz(options.outfile, 'w') as fout:239sumolib.writeXMLHeader(fout, "$Id$", "routes", options=options)240for vtype in sumolib.xml.parse_fast(options.routefile, 'VEHTYPETI',241['INDEX', 'FROMTIME', 'TOTIME', 'VEHTYPEID']):242vTypes[vtype.INDEX] = (vtype.VEHTYPEID, vtype.FROMTIME, vtype.TOTIME)243fout.write(' <vType id="%s"/>\n' % vtype.VEHTYPEID)244245nested = {246'ITEM': ['NODE'],247'DEMAND': ['VTI', 'VOLUME']}248for route in sumolib.xml.parse_fast_structured(options.routefile, 'ROUTE', ['INDEX'], nested):249nodes = [i.NODE for i in route.ITEM]250validNodes = list(getValidNodes(net, nodes))251if len(validNodes) < 2:252nSkipped += 1253continue254validEdgesNodes = list(getValidEdgesNodes(edgedict, validNodes))255if options.vclass:256if any([e not in allowed for s, e in validEdgesNodes]):257nDisallowed += 1258continue259edges = getConnectedEdges(net, validEdgesNodes, options.vclass, route.INDEX)260if not edges:261nBroken += 1262continue263264totalVolume = 0265for demand in route.DEMAND:266totalVolume += float(demand.VOLUME)267if totalVolume == 0:268nZeroRoutes += 1269continue270271edgeIDs = [e.getID() for e in edges]272273fout.write(' <route id="%s" edges="%s"/>\n' % (route.INDEX, ' '.join(edgeIDs)))274for demand in route.DEMAND:275flowID = "%s_%s" % (route.INDEX, demand.VTI)276vtype, begin, end = vTypes[demand.VTI]277# assume VOLUME is per day278rate = float(demand.VOLUME) * options.scale / (3600 * 24)279if rate > 0:280attrs = ""281if options.attributes:282attrs = " " + options.attributes283fout.write(' <flow id="%s" route="%s" type="%s" begin="%s" end="%s" period="exp(%s)"%s/>\n' % (284flowID, route.INDEX, vtype, begin, end, rate, attrs))285else:286nZeroFlows += 1287fout.write("</routes>\n")288289if nSkipped > 0:290print("Warning: Skipped %s routes because they were defined with a single node" % nSkipped)291if nBroken > 0:292print("Warning: Skipped %s routes because they could not be repaired" % nBroken)293if nZeroRoutes > 0:294print("Warning: Skipped %s routes because they have no volume" % nZeroRoutes)295if nZeroFlows > 0:296print("Warning: Skipped %s flows because they have no volume" % nZeroFlows)297if nDisallowed > 0:298print("Warning: Ignored %s routes because they have edges that are not allowed for %s " % (299nDisallowed, options.vclass))300301302if __name__ == "__main__":303main(get_options())304305306