sumolib.net

This file contains a content handler for parsing sumo network xml files. It uses other classes from this module to represent the road network.

   1# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
   2# Copyright (C) 2008-2026 German Aerospace Center (DLR) and others.
   3# This program and the accompanying materials are made available under the
   4# terms of the Eclipse Public License 2.0 which is available at
   5# https://www.eclipse.org/legal/epl-2.0/
   6# This Source Code may also be made available under the following Secondary
   7# Licenses when the conditions for such availability set forth in the Eclipse
   8# Public License 2.0 are satisfied: GNU General Public License, version 2
   9# or later which is available at
  10# https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
  11# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
  12
  13# @file    __init__.py
  14# @author  Daniel Krajzewicz
  15# @author  Laura Bieker
  16# @author  Karol Stosiek
  17# @author  Michael Behrisch
  18# @author  Jakob Erdmann
  19# @author  Robert Hilbrich
  20# @author  Mirko Barthauer
  21# @date    2008-03-27
  22
  23"""
  24This file contains a content handler for parsing sumo network xml files.
  25It uses other classes from this module to represent the road network.
  26"""
  27
  28from __future__ import print_function
  29from __future__ import absolute_import
  30import sys
  31import math
  32import heapq
  33import gzip
  34import warnings
  35import io
  36from xml.sax import handler, parse
  37from copy import copy
  38from collections import defaultdict
  39from itertools import chain
  40try:
  41    from functools import lru_cache
  42    HAVE_LRU_CACHE = True
  43except ImportError:
  44    HAVE_LRU_CACHE = False
  45
  46try:
  47    import lxml.etree
  48    import pathlib
  49    HAVE_LXML = True
  50except ImportError:
  51    HAVE_LXML = False
  52
  53try:
  54    import pyproj  # noqa
  55    HAVE_PYPROJ = True
  56except ImportError:
  57    HAVE_PYPROJ = False
  58
  59import sumolib
  60from . import lane, edge, netshiftadaptor, node, connection, roundabout  # noqa
  61from .connection import Connection
  62from sumolib.miscutils import intIfPossible, openz, PRACTICAL_INFINITY
  63
  64
  65class TLS:
  66
  67    """Traffic Light Signal for a sumo network"""
  68
  69    def __init__(self, id):
  70        self._id = id
  71        self._connections = []
  72        self._maxConnectionNo = -1
  73        self._programs = {}
  74
  75    def addConnection(self, inLane, outLane, linkNo):
  76        self._connections.append([inLane, outLane, linkNo])
  77        if linkNo > self._maxConnectionNo:
  78            self._maxConnectionNo = linkNo
  79
  80    def getConnections(self):
  81        return self._connections
  82
  83    def getID(self):
  84        return self._id
  85
  86    def getLinks(self):
  87        links = {}
  88        for the_connection in self._connections:
  89            if the_connection[2] not in links:
  90                links[the_connection[2]] = []
  91            links[the_connection[2]].append(the_connection)
  92        return links
  93
  94    def getEdges(self):
  95        edges = set()
  96        for c in self._connections:
  97            edges.add(c[0].getEdge())
  98        return edges
  99
 100    def addProgram(self, program):
 101        self._programs[program._id] = program
 102
 103    def removePrograms(self):
 104        self._programs.clear()
 105
 106    def toXML(self):
 107        ret = ""
 108        for p in self._programs:
 109            ret = ret + self._programs[p].toXML(self._id)
 110        return ret
 111
 112    def getPrograms(self):
 113        return self._programs
 114
 115
 116class Phase:
 117
 118    def __init__(self, duration, state, minDur=None, maxDur=None, next=tuple(), name="", earlyTarget=""):
 119        """
 120        Constructs a traffic light phase
 121        duration (float): the duration of the phase in seconds
 122        state (string): the state codes for each controlled link
 123        minDur (float): the minimum duration (ignored by static tls)
 124        maxDur (float): the maximum duration (ignored by static tls)
 125        next (intList): possible succesor phase (optional)
 126        name (string): the name of the phase
 127        earlyTarget (string): early switching to phase with the given index(es)
 128        """
 129        self.duration = duration
 130        self.state = state
 131        # minimum and maximum duration (only for actuated tls)
 132        self.minDur = minDur if minDur is not None else duration
 133        self.maxDur = maxDur if maxDur is not None else duration
 134        self.next = next
 135        self.name = name
 136        self.earlyTarget = earlyTarget
 137
 138    def __repr__(self):
 139        name = (", name='%s'" % self.name) if self.name else ""
 140        next = (", next='%s'" % str(self.next)) if self.next else ""
 141        earlyTarget = (", earlyTarget='%s'" % self.earlyTarget) if self.earlyTarget else ""
 142        return ("Phase(duration=%s, state='%s', minDur=%s, maxDur=%s%s%s%s)"
 143                % (self.duration, self.state, self.minDur, self.maxDur, name, next, earlyTarget))
 144
 145
 146class TLSProgram:
 147
 148    def __init__(self, id, offset, type):
 149        self._id = id
 150        self._type = type
 151        self._offset = offset
 152        self._phases = []
 153        self._params = {}
 154        self._conditions = {}
 155
 156    def addPhase(self, state, duration, minDur=-1, maxDur=-1, next=None, name="", earlyTarget=""):
 157        self._phases.append(Phase(duration, state, minDur, maxDur, next, name, earlyTarget))
 158
 159    def addCondition(self, id, value):
 160        self._conditions[id] = value
 161
 162    def toXML(self, tlsID):
 163        ret = '  <tlLogic id="%s" type="%s" programID="%s" offset="%s">\n' % (
 164            tlsID, self._type, self._id, self._offset)
 165        for p in self._phases:
 166            minDur = '' if p.minDur < 0 else ' minDur="%s"' % p.minDur
 167            maxDur = '' if p.maxDur < 0 else ' maxDur="%s"' % p.maxDur
 168            name = '' if p.name == '' else ' name="%s"' % p.name
 169            next = '' if len(p.next) == 0 else ' next="%s"' % ' '.join(map(str, p.next))
 170            earlyTarget = '' if p.earlyTarget == '' else ' earlyTarget="%s"' % p.earlyTarget
 171            ret += '    <phase duration="%s" state="%s"%s%s%s%s%s/>\n' % (
 172                p.duration, p.state, minDur, maxDur, name, next, earlyTarget)
 173        for k, v in self._params.items():
 174            ret += '    <param key="%s" value="%s"/>\n' % (k, v)
 175        for i, v in self._conditions.items():
 176            ret += '    <condition id="%s" value="%s"/>\n' % (i, v)
 177        ret += '  </tlLogic>\n'
 178        return ret
 179
 180    def getPhases(self):
 181        return self._phases
 182
 183    def getType(self):
 184        return self._type
 185
 186    def setParam(self, key, value):
 187        self._params[key] = value
 188
 189    def getParam(self, key, default=None):
 190        return self._params.get(key, default)
 191
 192    def getParams(self):
 193        return self._params
 194
 195    def getStages(self):
 196        stages = dict()
 197        for idx, phase in enumerate(self.getPhases()):
 198            if phase not in stages.values():
 199                if 'G' in phase.state and 'y' not in phase.state and phase.name:
 200                    stages[idx] = phase
 201        return stages
 202
 203    def getOffset(self):
 204        return self._offset
 205
 206
 207class EdgeType:
 208    def __init__(self, id, allow, disallow):
 209        self.id = id
 210        self.allow = allow
 211        self.disallow = disallow
 212
 213
 214class Net:
 215
 216    """The whole sumo network."""
 217
 218    def __init__(self):
 219        self._location = {}
 220        self._id2node = {}
 221        self._id2edge = {}
 222        self._crossings_and_walkingAreas = set()
 223        self._macroConnectors = set()
 224        self._id2tls = {}
 225        self._nodes = []
 226        self._edges = []
 227        self._tlss = []
 228        self._ranges = [[sys.float_info.max, -sys.float_info.max], [sys.float_info.max, -sys.float_info.max]]
 229        self._roundabouts = []
 230        self._rtreeEdges = {True: None, False: None}  # different rTrees depending on includeJunctions
 231        self._rtreeLanes = {True: None, False: None}  # different rTrees depending on includeJunctions
 232        self._allLanes = []
 233        self._origIdx = None
 234        self._proj = None
 235        self.hasInternal = False
 236        self.hasWalkingArea = False
 237        # store dijsktra heap for reuse if the same origin is used repeatedly
 238        self._shortestPathCache = None
 239        self._version = None
 240        self._edgeTypes = defaultdict(lambda: EdgeType("DEFAULT_EDGETYPE", "", ""))
 241        self._routingCache = None
 242        self._edgeSuccessorCache = None
 243        self._edgeCostCache = None
 244
 245    def initRoutingCache(self, maxsize=1000):
 246        self._routingCache = lru_cache(maxsize=maxsize)(lambda fromEdge, fastest, vClass, ignoreDirection, reversalPenalty, preferences: {})  # noqa
 247
 248    def getVersion(self):
 249        return self._version
 250
 251    def getEdgeType(self, typeID):
 252        return self._edgeTypes[typeID]
 253
 254    def setLocation(self, netOffset, convBoundary, origBoundary, projParameter):
 255        self._location["netOffset"] = netOffset
 256        self._location["convBoundary"] = convBoundary
 257        self._location["origBoundary"] = origBoundary
 258        self._location["projParameter"] = projParameter
 259
 260    def loadSelection(self, selectionFile):
 261        self.resetSelection()
 262        with io.open(selectionFile, "r", encoding="utf-8") as f:
 263            for line in f:
 264                line = line.strip()
 265                if line.startswith("edge:"):
 266                    edgeID = line[5:]
 267                    if edgeID in self._id2edge:
 268                        self.getEdge(edgeID).select()
 269                elif line.startswith("junction:"):
 270                    nodeID = line[9:]
 271                    if nodeID in self._id2node:
 272                        self.getNode(nodeID).select()
 273
 274    def resetSelection(self):
 275        for n in self._nodes:
 276            n.select(False)
 277        for e in self._edges:
 278            e.select(False)
 279
 280    def addNode(self, id, type=None, coord=None, incLanes=None, intLanes=None):
 281        if id is None:
 282            return None
 283        if id not in self._id2node:
 284            n = node.Node(id, type, coord, incLanes, intLanes)
 285            self._nodes.append(n)
 286            self._id2node[id] = n
 287        self.setAdditionalNodeInfo(
 288            self._id2node[id], type, coord, incLanes, intLanes)
 289        return self._id2node[id]
 290
 291    def setAdditionalNodeInfo(self, node, type, coord, incLanes, intLanes=None):
 292        if coord is not None and node._coord is None:
 293            node._coord = coord
 294            self._ranges[0][0] = min(self._ranges[0][0], coord[0])
 295            self._ranges[0][1] = max(self._ranges[0][1], coord[0])
 296            self._ranges[1][0] = min(self._ranges[1][0], coord[1])
 297            self._ranges[1][1] = max(self._ranges[1][1], coord[1])
 298        if incLanes is not None and node._incLanes is None:
 299            node._incLanes = incLanes
 300        if intLanes is not None and node._intLanes is None:
 301            node._intLanes = intLanes
 302        if type is not None and node._type is None:
 303            node._type = type
 304
 305    def addEdge(self, id, fromID, toID, prio, function, name, edgeType='', routingType=''):
 306        if id not in self._id2edge:
 307            fromN = self.addNode(fromID)
 308            toN = self.addNode(toID)
 309            e = edge.Edge(id, fromN, toN, prio, function, name, edgeType, routingType)
 310            self._edges.append(e)
 311            self._id2edge[id] = e
 312            if function:
 313                self.hasInternal = True
 314                if function == "walkingarea":
 315                    self.hasWalkingArea = True
 316        return self._id2edge[id]
 317
 318    def addLane(self, edge, speed, length, width, allow=None, disallow=None, acceleration=False):
 319        return lane.Lane(edge, speed, length, width, allow, disallow, acceleration)
 320
 321    def addRoundabout(self, nodes, edges=None):
 322        r = roundabout.Roundabout(nodes, edges)
 323        self._roundabouts.append(r)
 324        return r
 325
 326    def addConnection(self, fromEdge, toEdge, fromlane, tolane, direction,
 327                      tls, tllink, tllink2, allow, disallow, state, viaLaneID=None):
 328        conn = connection.Connection(
 329            fromEdge, toEdge, fromlane, tolane, direction,
 330            tls, tllink, tllink2, allow, disallow, state, viaLaneID)
 331        fromEdge.addOutgoing(conn)
 332        fromlane.addOutgoing(conn)
 333        toEdge._addIncoming(conn)
 334        if viaLaneID:
 335            try:
 336                # internal lanes are only available when building with option withInternal=True
 337                viaLane = self.getLane(viaLaneID)
 338                viaEdge = viaLane.getEdge()
 339                viaEdge._addIncoming(connection.Connection(
 340                    fromEdge, viaEdge, fromlane, viaLane, direction, tls,
 341                    tllink, tllink2, allow, disallow, state, ''))
 342            except Exception:
 343                pass
 344        return conn
 345
 346    def getEdges(self, withInternal=True):
 347        if not withInternal:
 348            return [e for e in self._edges if e.getFunction() == '']
 349        else:
 350            return self._edges
 351
 352    def getRoundabouts(self):
 353        return self._roundabouts
 354
 355    def hasEdge(self, id):
 356        return id in self._id2edge
 357
 358    def getEdge(self, id):
 359        return self._id2edge[id]
 360
 361    def getLane(self, laneID):
 362        edge_id, lane_index = laneID.rsplit("_", 1)
 363        return self.getEdge(edge_id).getLane(int(lane_index))
 364
 365    def _initRTree(self, shapeList, includeJunctions=True):
 366        import rtree  # noqa
 367        result = rtree.index.Index()
 368        result.interleaved = True
 369        for ri, shape in enumerate(shapeList):
 370            result.add(ri, shape.getBoundingBox(includeJunctions))
 371        return result
 372
 373    # Please be aware that the resulting list of edges is NOT sorted
 374    def getNeighboringEdges(self, x, y, r=0.1, includeJunctions=True, allowFallback=True):
 375        edges = []
 376        rtree = self._rtreeEdges[includeJunctions]
 377        try:
 378            if rtree is None:
 379                rtree = self._initRTree(self._edges, includeJunctions)
 380                self._rtreeEdges[includeJunctions] = rtree
 381            for i in rtree.intersection((x - r, y - r, x + r, y + r)):
 382                e = self._edges[i]
 383                d = sumolib.geomhelper.distancePointToPolygon(
 384                    (x, y), e.getShape(includeJunctions))
 385                if d < r:
 386                    edges.append((e, d))
 387        except ImportError:
 388            if not allowFallback:
 389                raise
 390            warnings.warn("Module 'rtree' not available. Using brute-force fallback.")
 391            for the_edge in self._edges:
 392                d = sumolib.geomhelper.distancePointToPolygon((x, y), the_edge.getShape(includeJunctions))
 393                if d < r:
 394                    edges.append((the_edge, d))
 395        return edges
 396
 397    def getNeighboringLanes(self, x, y, r=0.1, includeJunctions=True, allowFallback=True):
 398        lanes = []
 399        rtree = self._rtreeLanes[includeJunctions]
 400        try:
 401            if rtree is None:
 402                for the_edge in self._edges:
 403                    self._allLanes += the_edge.getLanes()
 404                rtree = self._initRTree(self._allLanes, includeJunctions)
 405                self._rtreeLanes[includeJunctions] = rtree
 406            for i in rtree.intersection((x - r, y - r, x + r, y + r)):
 407                the_lane = self._allLanes[i]
 408                d = sumolib.geomhelper.distancePointToPolygon((x, y), the_lane.getShape(includeJunctions))
 409                if d < r:
 410                    lanes.append((the_lane, d))
 411        except ImportError:
 412            if not allowFallback:
 413                raise
 414            warnings.warn("Module 'rtree' not available. Using brute-force fallback.")
 415            for the_edge in self._edges:
 416                for the_lane in the_edge.getLanes():
 417                    d = sumolib.geomhelper.distancePointToPolygon((x, y), the_lane.getShape(includeJunctions))
 418                    if d < r:
 419                        lanes.append((the_lane, d))
 420        return lanes
 421
 422    def getNearestLane(self, x, y, r=0.1, vClass=None, isGeo=False, allowFallback=True):
 423        """Return (lane, lanePos, dist) for the nearest lane at a distance less than r,
 424        or None if no lane matches. Both lanePos and dist are in meters.
 425
 426        If vClass is given, only lanes allowing that vehicle class are considered.
 427        If isGeo is True, x and y are longitude and latitude, respectively, and
 428        the network's geo-projection is used (requires pyproj).
 429        Distances are measured to the lane shape without junction extensions.
 430        Internal lanes are considered if the network was read withInternal=True.
 431        """
 432        if isGeo:
 433            x, y = self.convertLonLat2XY(x, y)
 434        candidates = [(lane, dist) for lane, dist in
 435                      self.getNeighboringLanes(x, y, r, includeJunctions=False, allowFallback=allowFallback)
 436                      if lane.allows(vClass)]
 437        if not candidates:
 438            return None
 439        lane, dist = min(candidates, key=lambda candidate: candidate[1])
 440        lanePos, dist = lane.getClosestLanePosAndDist((x, y))
 441        return lane, lanePos, dist
 442
 443    def hasNode(self, id):
 444        return id in self._id2node
 445
 446    def getNode(self, id):
 447        return self._id2node[id]
 448
 449    def getNodes(self):
 450        return self._nodes
 451
 452    def getTLS(self, tlid):
 453        return self._id2tls[tlid]
 454
 455    def getTLSSecure(self, tlid):
 456        if tlid in self._id2tls:
 457            tls = self._id2tls[tlid]
 458        else:
 459            tls = TLS(tlid)
 460            self._id2tls[tlid] = tls
 461            self._tlss.append(tls)
 462        return tls
 463
 464    def getTrafficLights(self):
 465        return self._tlss
 466
 467    def addTLS(self, tlid, inLane, outLane, linkNo):
 468        tls = self.getTLSSecure(tlid)
 469        tls.addConnection(inLane, outLane, linkNo)
 470        return tls
 471
 472    def addTLSProgram(self, tlid, programID, offset, type, removeOthers):
 473        tls = self.getTLSSecure(tlid)
 474        program = TLSProgram(programID, offset, type)
 475        if removeOthers:
 476            tls.removePrograms()
 477        tls.addProgram(program)
 478        return program
 479
 480    def setFoes(self, junctionID, index, foes, prohibits):
 481        self._id2node[junctionID].setFoes(index, foes, prohibits)
 482
 483    def forbids(self, possProhibitor, possProhibited):
 484        return possProhibitor.getFrom().getToNode().forbids(possProhibitor, possProhibited)
 485
 486    def getUpstreamEdges(self, edge, distance, stopOnTLS, stopOnTurnaround):
 487        """return a list of lists of the form
 488           [[firstEdge, pos, [edge_0, edge_1, ..., edge_k], aborted], ...]
 489           where
 490             firstEdge: is the upstream edge furthest away from the intersection,
 491             [edge_0, ..., edge_k]: is the list of edges from the intersection upstream to firstEdge
 492             pos: is the position on firstEdge with distance to the end of the input edge
 493             aborted: a flag indicating whether the downstream
 494                 search stopped at a TLS or a node without incoming edges before reaching the distance threshold
 495        """
 496        ret = []
 497        seen = set()
 498        toProc = []
 499        toProc.append([edge, 0, []])
 500        while not len(toProc) == 0:
 501            ie = toProc.pop()
 502            if ie[0] in seen:
 503                continue
 504            seen.add(ie[0])
 505            if ie[1] + ie[0].getLength() >= distance:
 506                ret.append(
 507                    [ie[0], ie[0].getLength() + ie[1] - distance, ie[2], False])
 508                continue
 509            if len(ie[0]._incoming) == 0:
 510                ret.append([ie[0], ie[0].getLength() + ie[1], ie[2], True])
 511                continue
 512            mn = []
 513            stop = False
 514            for ci in ie[0]._incoming:
 515                if ci not in seen:
 516                    prev = copy(ie[2])
 517                    if stopOnTLS and ci._tls and ci != edge and not stop:
 518                        ret.append([ie[0], ie[1], prev, True])
 519                        stop = True
 520                    elif (stopOnTurnaround and ie[0]._incoming[ci][0].getDirection() == Connection.LINKDIR_TURN and
 521                          not stop):
 522                        ret.append([ie[0], ie[1], prev, True])
 523                        stop = True
 524                    else:
 525                        prev.append(ie[0])
 526                        mn.append([ci, ie[0].getLength() + ie[1], prev])
 527            if not stop:
 528                toProc.extend(mn)
 529        return ret
 530
 531    def getEdgesByOrigID(self, origID):
 532        if self._origIdx is None:
 533            self._origIdx = defaultdict(set)
 534            for the_edge in self._edges:
 535                for the_lane in the_edge.getLanes():
 536                    for oID in the_lane.getParam("origId", "").split():
 537                        self._origIdx[oID].add(the_edge)
 538        return self._origIdx[origID]
 539
 540    def getGeometries(self, useLanes, includeJunctions=False):
 541        for e in self._edges:
 542            if useLanes:
 543                for the_lane in e.getLanes():
 544                    yield the_lane.getID(), the_lane.getShape(), the_lane.getWidth()
 545            else:
 546                yield e.getID(), e.getShape(includeJunctions), sum([the_lane.getWidth() for the_lane in e.getLanes()])
 547
 548    def getBBoxXY(self):
 549        """
 550        Get the bounding box (bottom left and top right coordinates) for a net;
 551        Coordinates are in X and Y (not Lat and Lon)
 552
 553        :return [(bottom_left_X, bottom_left_Y), (top_right_X, top_right_Y)]
 554        """
 555        return [(self._ranges[0][0], self._ranges[1][0]),
 556                (self._ranges[0][1], self._ranges[1][1])]
 557
 558    # the diagonal of the bounding box of all nodes
 559    def getBBoxDiameter(self):
 560        return math.sqrt(
 561            (self._ranges[0][0] - self._ranges[0][1]) ** 2 +
 562            (self._ranges[1][0] - self._ranges[1][1]) ** 2)
 563
 564    def hasGeoProj(self):
 565        projString = self._location["projParameter"]
 566        return projString != "!"
 567
 568    def getGeoProj(self):
 569        if not self.hasGeoProj() or not HAVE_PYPROJ:
 570            raise RuntimeError("Network does not provide geo-projection or pyproj not installed.")
 571        if self._proj is None:
 572            try:
 573                self._proj = pyproj.Proj(projparams=self._location["projParameter"])
 574            except RuntimeError:
 575                if hasattr(pyproj.datadir, 'set_data_dir'):
 576                    pyproj.datadir.set_data_dir('/usr/share/proj')
 577                    self._proj = pyproj.Proj(projparams=self._location["projParameter"])
 578                raise
 579        return self._proj
 580
 581    def getLocationOffset(self):
 582        """ offset to be added after converting from geo-coordinates to UTM"""
 583        return list(map(float, self._location["netOffset"].split(",")))
 584
 585    def getBoundary(self):
 586        """ return xmin,ymin,xmax,ymax network coordinates"""
 587        return list(map(float, self._location["convBoundary"].split(",")))
 588
 589    def convertLonLat2XY(self, lon, lat, rawUTM=False):
 590        x, y = self.getGeoProj()(lon, lat)
 591        if rawUTM:
 592            return x, y
 593        else:
 594            x_off, y_off = self.getLocationOffset()
 595            return x + x_off, y + y_off
 596
 597    def convertXY2LonLat(self, x, y, rawUTM=False):
 598        if not rawUTM:
 599            x_off, y_off = self.getLocationOffset()
 600            x -= x_off
 601            y -= y_off
 602        return self.getGeoProj()(x, y, inverse=True)
 603
 604    def move(self, dx, dy, dz=0):
 605        for n in self._nodes:
 606            n._coord = (n._coord[0] + dx, n._coord[1] + dy, n._coord[2] + dz)
 607        for e in self._edges:
 608            for _lane in e.getLanes():
 609                _lane.setShape([(p[0] + dx, p[1] + dy, p[2] + dz) for p in _lane.getShape3D()])
 610            e.rebuildShape()
 611
 612    def getInternalPath(self, conn, fastest=False):
 613        minInternalCost = PRACTICAL_INFINITY
 614        minPath = None
 615        for c in conn:
 616            if c.getViaLaneID() != "":
 617                viaCost = 0
 618                viaID = c.getViaLaneID()
 619                viaPath = []
 620                while viaID != "":
 621                    viaLane = self.getLane(viaID)
 622                    viaCost += viaLane.getLength() if not fastest else viaLane.getLength() / viaLane.getSpeed()
 623                    viaID = viaLane.getOutgoing()[0].getViaLaneID()
 624                    viaPath.append(viaLane.getEdge())
 625                if viaCost < minInternalCost:
 626                    minInternalCost = viaCost
 627                    minPath = viaPath
 628        return minPath, minInternalCost
 629
 630    def getOptimalPath(self, fromEdge, toEdge, fastest=False, maxCost=PRACTICAL_INFINITY, vClass=None,
 631                       reversalPenalty=0, includeFromToCost=True, withInternal=False, ignoreDirection=False,
 632                       fromPos=None, toPos=None, preferences={}):
 633        """
 634        Finds the optimal (shortest or fastest) path for vClass from fromEdge to toEdge
 635        by using using Dijkstra's algorithm.
 636        It returns a pair of a tuple of edges and the cost.
 637        If no path is found the first element is None.
 638        The cost for the returned path is equal to the sum of all edge costs in the path,
 639        including the internal connectors, if they are present in the network.
 640        The path itself does not include internal edges except for the case
 641        when the start or end edge are internal edges.
 642        The search may be limited using the given threshold.
 643        The preferences declare a mapping from the 'routingType' of each edge to divisor
 644        that is applied to the computed cost of that edge (i.e. a preference of 2 reduces cost by a factor of 0.5).
 645        """
 646
 647        if preferences:
 648            if fastest:
 649                def speedFunc(edge):
 650                    return preferences.get(edge.getRoutingType(), 1.0) * edge.getSpeed()
 651            else:
 652                def speedFunc(edge):
 653                    return preferences.get(edge.getRoutingType(), 1.0)
 654        elif fastest:
 655            def speedFunc(edge):
 656                return edge.getSpeed()
 657        else:
 658            def speedFunc(edge):
 659                return 1.0
 660
 661        def remainder(edge, pos):
 662            if pos < 0:
 663                return min(-pos, edge.getLength())
 664            return max(0., edge.getLength() - pos)
 665
 666        def getToNormalIncoming(edge):
 667            if edge.getFunction() == '':
 668                return [(e, None) for e in edge.getToNode().getIncoming() if e.getFunction() == '']
 669            else:
 670                return []
 671
 672        if self._edgeSuccessorCache != (vClass, ignoreDirection):
 673            self._edgeSuccessorCache = (vClass, ignoreDirection)
 674            for e1 in self.getEdges():
 675                e1._succCacche = tuple(chain(e1.getAllowedOutgoing(vClass).items(),
 676                                       e1.getIncoming().items() if ignoreDirection else [],
 677                                       getToNormalIncoming(e1) if ignoreDirection and not self.hasWalkingArea else []))
 678
 679        if self._edgeCostCache != (fastest, preferences):
 680            self._edgeCostCache = (fastest, preferences)
 681            for e1 in self.getEdges():
 682                e1._costCache = e1.getLength() / speedFunc(e1)
 683
 684        if self.hasInternal:
 685            appendix = []
 686            appendixCost = 0.
 687            while toEdge.getFunction() == "internal":
 688                appendix = [toEdge] + appendix
 689                appendixCost += toEdge._costCache
 690                toEdge = list(toEdge.getIncoming().keys())[0]
 691
 692        def finalizeCost(cost, path):
 693            if includeFromToCost:
 694                # add costs for (part of) the first edge, still needs to be fixed for wrong direction travel
 695                remainFrom = fromEdge.getLength() if fromPos is None else remainder(fromEdge, fromPos)
 696                cost += remainFrom / speedFunc(fromEdge)
 697                # remove costs for (part of) the last edge, still needs to be fixed for wrong direction travel
 698                removeTo = 0. if toPos is None else remainder(toEdge, toPos)
 699            else:
 700                removeTo = toEdge.getLength() if len(path) > 1 else 0.
 701            cost -= removeTo / speedFunc(toEdge)
 702            return cost
 703
 704        def constructPath(dist):
 705            # destination was already reached in a previous query
 706            cost, pred = dist[toEdge]
 707            path = [toEdge]
 708            while pred is not None:
 709                if self.hasInternal and withInternal:
 710                    viaPath, minInternalCost = self.getInternalPath(pred.getAllowedOutgoing(vClass).get(path[-1], []),
 711                                                                    fastest=fastest)
 712                    if viaPath is not None:
 713                        path += reversed(viaPath)
 714                path.append(pred)
 715                _, pred = dist[pred]
 716
 717            path.reverse()
 718            cost = finalizeCost(cost, path)
 719            assert cost >= 0
 720            if self.hasInternal:
 721                if appendix:
 722                    return tuple(path + appendix), cost + appendixCost
 723                elif ignoreDirection and self.hasWalkingArea and not withInternal:
 724                    return [e for e in path if e.getFunction() == ''], cost
 725            return tuple(path), cost
 726
 727        needLoop = (fromEdge == toEdge
 728                    and fromPos is not None
 729                    and toPos is not None
 730                    and fromPos > toPos
 731                    and not ignoreDirection)
 732
 733        seen = set()
 734        dist = {}
 735        q = []
 736        if self._routingCache is not None:
 737            if needLoop:
 738                # use cached results from all follower edges:
 739                bestCost = maxCost
 740                bestPath = None
 741                for e2, conn in fromEdge.getAllowedOutgoing(vClass).items():
 742                    path, cost = self.getOptimalPath(e2, toEdge, fastest=fastest, maxCost=maxCost, vClass=vClass,
 743                                                     reversalPenalty=reversalPenalty,
 744                                                     includeFromToCost=includeFromToCost,
 745                                                     withInternal=withInternal, fromPos=0, toPos=toPos,
 746                                                     preferences=preferences)
 747                    if path is not None and cost < bestCost:
 748                        bestPath = path
 749                        bestCost = cost
 750                if bestPath is not None:
 751                    path = [fromEdge]
 752                    if self.hasInternal and withInternal:
 753                        viaPath, minInternalCost = self.getInternalPath(
 754                            fromEdge.getAllowedOutgoing(vClass).get(path[0], []), fastest=fastest)
 755                        if viaPath is not None:
 756                            path += viaPath
 757                            bestCost += minInternalCost
 758                    path += list(bestPath)
 759                    if includeFromToCost:
 760                        bestCost += remainder(fromEdge, fromPos) / speedFunc(fromEdge)
 761                    return tuple(path), bestCost
 762                else:
 763                    return None, PRACTICAL_INFINITY
 764
 765            dist = self._routingCache(fromEdge, fastest, vClass, ignoreDirection, reversalPenalty,
 766                                      tuple(preferences.items()))
 767            if toEdge in dist:
 768                return constructPath(dist)
 769            else:
 770                # initialize heap from previous query
 771                q = []
 772                frontier = set(dist.keys())
 773                for cost, prev in dist.values():
 774                    frontier.discard(prev)
 775                for e in frontier:
 776                    cost, prev = dist[e]
 777                    heapq.heappush(q, (cost, e, prev))
 778        elif needLoop:
 779            # start search on successors of fromEdge
 780            for e2, conn in fromEdge.getAllowedOutgoing(vClass).items():
 781                q.append((e2._costCache, e2, fromEdge))
 782
 783        if len(dist) == 0:
 784            dist[fromEdge] = (0., None)
 785            if not needLoop:
 786                q.append((0., fromEdge, None))
 787
 788        while q:
 789            cost, e1, prev = heapq.heappop(q)
 790            if e1 in seen:
 791                continue
 792            seen.add(e1)
 793            if e1 == toEdge:
 794                return constructPath(dist)
 795            if cost > maxCost:
 796                return None, cost
 797
 798            for e2, conn in e1._succCacche:
 799                if e2 not in seen:
 800                    newCost = cost + e2._costCache
 801                    #  print(cost, newCost, e2.getID(), speedFunc(e2))
 802                    if e2 == e1.getBidi():
 803                        newCost += reversalPenalty
 804                    if self.hasInternal and conn is not None:
 805                        viaPath, minInternalCost = self.getInternalPath(conn, fastest=fastest)
 806                        if viaPath is not None:
 807                            newCost += minInternalCost
 808                    if e2 not in dist or newCost < dist[e2][0]:
 809                        dist[e2] = (newCost, e1)
 810                        heapq.heappush(q, (newCost, e2, e1))
 811        return None, PRACTICAL_INFINITY
 812
 813    def getShortestPath(self, fromEdge, toEdge, maxCost=PRACTICAL_INFINITY, vClass=None, reversalPenalty=0,
 814                        includeFromToCost=True, withInternal=False, ignoreDirection=False,
 815                        fromPos=None, toPos=None, preferences={}):
 816        """
 817        Finds the shortest path from fromEdge to toEdge respecting vClass, using Dijkstra's algorithm.
 818        It returns a pair of a tuple of edges and the cost. If no path is found the first element is None.
 819        The cost for the returned path is equal to the sum of all edge lengths in the path,
 820        including the internal connectors, if they are present in the network.
 821        The path itself does not include internal edges except for the case
 822        when the start or end edge are internal edges.
 823        The search may be limited using the given threshold.
 824        The preferences declare a mapping from the 'routingType' of each edge to divisor
 825        that is applied to the lenght of that edge
 826        (i.e. a preference of 2 reduces reduces effective length by a factor of 0.5).
 827        """
 828
 829        return self.getOptimalPath(fromEdge, toEdge, False, maxCost, vClass, reversalPenalty,
 830                                   includeFromToCost, withInternal, ignoreDirection, fromPos, toPos,
 831                                   preferences)
 832
 833    def getFastestPath(self, fromEdge, toEdge, maxCost=PRACTICAL_INFINITY, vClass=None, reversalPenalty=0,
 834                       includeFromToCost=True, withInternal=False, ignoreDirection=False,
 835                       fromPos=None, toPos=None, preferences={}):
 836        """
 837        Finds the fastest path from fromEdge to toEdge respecting vClass, using Dijkstra's algorithm.
 838        It returns a pair of a tuple of edges and the cost. If no path is found the first element is None.
 839        The cost for the returned path is equal to the sum of all edge costs in the path,
 840        including the internal connectors, if they are present in the network.
 841        The path itself does not include internal edges except for the case
 842        when the start or end edge are internal edges.
 843        The search may be limited using the given threshold.
 844        The preferences declare a mapping from the 'routingType' of each edge to divisor
 845        that is applied to the computed traveltime of that edge
 846        (i.e. a preference of 2 reduces effective traveltime by a factor of 0.5).
 847        """
 848
 849        return self.getOptimalPath(fromEdge, toEdge, True, maxCost, vClass, reversalPenalty,
 850                                   includeFromToCost, withInternal, ignoreDirection, fromPos, toPos,
 851                                   preferences)
 852
 853    def getReachable(self, source, vclass=None, useIncoming=False, cache=None):
 854        if vclass is not None and not source.allows(vclass):
 855            raise RuntimeError("'{}' does not allow {}".format(source.getID(), vclass))
 856        fringe = [source]
 857        found = set()
 858        found.add(source)
 859        while len(fringe) > 0:
 860            new_fringe = []
 861            for e in fringe:
 862                if vclass == "pedestrian":
 863                    cands = chain(chain(*e.getIncoming().values()), chain(*e.getOutgoing().values()))
 864                else:
 865                    cands = chain(*(e.getIncoming().values() if useIncoming else e.getOutgoing().values()))
 866                # print("\n".join(map(str, list(cands))))
 867                for conn in cands:
 868                    if vclass is None or (
 869                            conn.getFromLane().allows(vclass)
 870                            and conn.getToLane().allows(vclass)):
 871                        for reachable in [conn.getTo(), conn.getFrom()]:
 872                            if reachable not in found:
 873                                # print("added %s via %s" % (reachable, conn))
 874                                if cache and reachable in cache:
 875                                    found.update(cache[reachable])
 876                                else:
 877                                    found.add(reachable)
 878                                    new_fringe.append(reachable)
 879            fringe = new_fringe
 880        if cache is not None:
 881            cache[source] = tuple(found)
 882        return found
 883
 884
 885class NetReader(handler.ContentHandler):
 886
 887    """Reads a network, storing the edge geometries, lane numbers and max. speeds"""
 888
 889    def __init__(self, **others):
 890        self._net = others.get('net', Net())
 891        self._currentEdge = None
 892        self._currentNode = None
 893        self._currentConnection = None
 894        self._currentLane = None
 895        self._crossingID2edgeIDs = {}
 896        self._withPhases = others.get('withPrograms', False)
 897        self._latestProgram = others.get('withLatestPrograms', False)
 898        if self._latestProgram:
 899            self._withPhases = True
 900        self._withConnections = others.get('withConnections', True)
 901        self._withFoes = others.get('withFoes', True)
 902        self._withPedestrianConnections = others.get('withPedestrianConnections', False)
 903        self._withMacroConnectors = others.get('withMacroConnectors', False)
 904        self._withInternal = others.get('withInternal', self._withPedestrianConnections)
 905        if self._withPedestrianConnections and not self._withInternal:
 906            sys.stderr.write("Warning: Option withPedestrianConnections requires withInternal\n")
 907            self._withInternal = True
 908        self._bidiEdgeIDs = {}
 909
 910    def startElement(self, name, attrs):
 911        if name == 'net':
 912            parts = attrs["version"].split('.', 1)
 913            self._net._version = (int(parts[0]), float(parts[1]))
 914        elif name == 'location':
 915            self._net.setLocation(attrs["netOffset"], attrs["convBoundary"], attrs[
 916                                  "origBoundary"], attrs["projParameter"])
 917        elif name == 'type':
 918            self._net._edgeTypes[attrs['id']] = EdgeType(attrs['id'], attrs.get('allow'), attrs.get('disallow'))
 919        elif name == 'edge':
 920            function = attrs.get('function', '')
 921            if (function == ''
 922                    or (self._withInternal and function in ['internal', 'crossing', 'walkingarea'])
 923                    or (self._withMacroConnectors and function == 'connector')):
 924                prio = -1
 925                if 'priority' in attrs:
 926                    prio = int(attrs['priority'])
 927
 928                # get the  ids
 929                edgeID = attrs['id']
 930                fromNodeID = attrs.get('from', None)
 931                toNodeID = attrs.get('to', None)
 932
 933                # for internal junctions use the junction's id for from and to node
 934                if function == 'internal' or function == 'crossing' or function == 'walkingarea':
 935                    fromNodeID = toNodeID = edgeID[1:edgeID.rfind('_')]
 936
 937                # remember edges crossed by pedestrians to link them later to the crossing objects
 938                if function == 'crossing':
 939                    self._crossingID2edgeIDs[edgeID] = attrs.get('crossingEdges').split(' ')
 940
 941                self._currentEdge = self._net.addEdge(edgeID, fromNodeID, toNodeID, prio, function,
 942                                                      attrs.get('name', ''), attrs.get('type', ''),
 943                                                      attrs.get('routingType', ''))
 944
 945                self._currentEdge.setRawShape(convertShape(attrs.get('shape', '')))
 946
 947                bidi = attrs.get('bidi', '')
 948                if bidi:
 949                    self._bidiEdgeIDs[edgeID] = bidi
 950            else:
 951                if function in ['crossing', 'walkingarea']:
 952                    self._net._crossings_and_walkingAreas.add(attrs['id'])
 953                elif function == 'connector':
 954                    self._net._macroConnectors.add(attrs['id'])
 955                self._currentEdge = None
 956        elif name == 'lane' and self._currentEdge is not None:
 957            self._currentLane = self._net.addLane(
 958                self._currentEdge,
 959                float(attrs['speed']),
 960                float(attrs['length']),
 961                float(attrs.get('width', 3.2)),
 962                attrs.get('allow'),
 963                attrs.get('disallow'),
 964                attrs.get('acceleration') == "1")
 965            self._currentLane.setShape(convertShape(attrs.get('shape', '')))
 966        elif name == 'neigh' and self._currentLane is not None:
 967            self._currentLane.setNeigh(attrs['lane'])
 968        elif name == 'junction':
 969            if attrs['id'][0] != ':':
 970                intLanes = None
 971                if self._withInternal:
 972                    intLanes = attrs["intLanes"].split(" ")
 973                self._currentNode = self._net.addNode(attrs['id'], attrs['type'],
 974                                                      tuple(
 975                                                          map(float, [attrs['x'], attrs['y'],
 976                                                                      attrs['z'] if 'z' in attrs else '0'])),
 977                                                      attrs['incLanes'].split(" "), intLanes)
 978                self._currentNode.setShape(
 979                    convertShape(attrs.get('shape', '')))
 980                if 'fringe' in attrs:
 981                    self._currentNode._fringe = attrs['fringe']
 982
 983        elif name == 'succ' and self._withConnections:  # deprecated
 984            if attrs['edge'][0] != ':':
 985                self._currentEdge = self._net.getEdge(attrs['edge'])
 986                self._currentLane = attrs['lane']
 987                self._currentLane = int(
 988                    self._currentLane[self._currentLane.rfind('_') + 1:])
 989            else:
 990                self._currentEdge = None
 991        elif name == 'succlane' and self._withConnections:  # deprecated
 992            lid = attrs['lane']
 993            if lid[0] != ':' and lid != "SUMO_NO_DESTINATION" and self._currentEdge:
 994                connected = self._net.getEdge(lid[:lid.rfind('_')])
 995                tolane = int(lid[lid.rfind('_') + 1:])
 996                if 'tl' in attrs and attrs['tl'] != "":
 997                    tl = attrs['tl']
 998                    tllink = int(attrs['linkIdx'])
 999                    tlid = attrs['tl']
1000                    toEdge = self._net.getEdge(lid[:lid.rfind('_')])
1001                    tolane2 = toEdge._lanes[tolane]
1002                    tls = self._net.addTLS(
1003                        tlid, self._currentEdge._lanes[self._currentLane], tolane2, tllink)
1004                    self._currentEdge.setTLS(tls)
1005                else:
1006                    tl = ""
1007                    tllink = -1
1008                toEdge = self._net.getEdge(lid[:lid.rfind('_')])
1009                tolane = toEdge._lanes[tolane]
1010                viaLaneID = attrs['via']
1011                self._net.addConnection(self._currentEdge, connected, self._currentEdge._lanes[
1012                                        self._currentLane], tolane,
1013                                        attrs['dir'], tl, tllink, -1,
1014                                        attrs.get('allow'), attrs.get('disallow'), attrs['state'], viaLaneID)
1015        elif name == 'connection' and self._withConnections and (attrs['from'][0] != ":" or self._withInternal):
1016            fromEdgeID = attrs['from']
1017            toEdgeID = attrs['to']
1018            if ((self._withPedestrianConnections or not (fromEdgeID in self._net._crossings_and_walkingAreas or
1019                                                         toEdgeID in self._net._crossings_and_walkingAreas))
1020                and (self._withMacroConnectors or not (fromEdgeID in self._net._macroConnectors or toEdgeID in
1021                                                       self._net._macroConnectors))):
1022                fromEdge = self._net.getEdge(fromEdgeID)
1023                toEdge = self._net.getEdge(toEdgeID)
1024                fromLane = fromEdge.getLane(int(attrs['fromLane']))
1025                toLane = toEdge.getLane(int(attrs['toLane']))
1026                if 'tl' in attrs and attrs['tl'] != "":
1027                    tl = attrs['tl']
1028                    tllink = int(attrs['linkIndex'])
1029                    tllink2 = int(attrs.get('linkIndex2', -1))
1030                    tls = self._net.addTLS(tl, fromLane, toLane, tllink)
1031                    fromEdge.setTLS(tls)
1032                else:
1033                    tl = ""
1034                    tllink = -1
1035                    tllink2 = -1
1036                try:
1037                    viaLaneID = attrs['via']
1038                except KeyError:
1039                    viaLaneID = ''
1040
1041                self._currentConnection = self._net.addConnection(
1042                    fromEdge, toEdge, fromLane, toLane, attrs['dir'], tl,
1043                    tllink, tllink2, attrs.get('allow'), attrs.get('disallow'), attrs['state'], viaLaneID)
1044
1045        # 'row-logic' is deprecated!!!
1046        elif self._withFoes and name == 'ROWLogic':
1047            self._currentNode = attrs['id']
1048        elif name == 'logicitem' and self._withFoes:  # deprecated
1049            self._net.setFoes(
1050                self._currentNode, int(attrs['request']), attrs["foes"], attrs["response"])
1051        elif name == 'request' and self._withFoes:
1052            self._currentNode.setFoes(
1053                int(attrs['index']), attrs["foes"], attrs["response"])
1054        # tl-logic is deprecated!!! NOTE: nevertheless, this is still used by
1055        # netconvert... (Leo)
1056        elif self._withPhases and name == 'tlLogic':
1057            self._currentProgram = self._net.addTLSProgram(
1058                attrs['id'], attrs['programID'],
1059                intIfPossible(float(attrs['offset'])), attrs['type'], self._latestProgram)
1060        elif self._withPhases and name == 'phase':
1061            self._currentProgram.addPhase(
1062                attrs['state'],
1063                intIfPossible(float(attrs['duration'])),
1064                intIfPossible(float(attrs['minDur'])) if 'minDur' in attrs else -1,
1065                intIfPossible(float(attrs['maxDur'])) if 'maxDur' in attrs else -1,
1066                list(map(int, attrs['next'].split())) if 'next' in attrs else [],
1067                attrs['name'] if 'name' in attrs else ""
1068            )
1069        elif name == 'roundabout':
1070            self._net.addRoundabout(
1071                attrs['nodes'].split(), attrs['edges'].split())
1072        elif name == 'param':
1073            if self._currentLane is not None:
1074                self._currentLane.setParam(attrs['key'], attrs['value'])
1075            elif self._currentEdge is not None:
1076                self._currentEdge.setParam(attrs['key'], attrs['value'])
1077            elif self._currentNode is not None:
1078                self._currentNode.setParam(attrs['key'], attrs['value'])
1079            elif self._currentConnection is not None:
1080                self._currentConnection.setParam(attrs['key'], attrs['value'])
1081            elif self._withPhases and self._currentProgram is not None:
1082                self._currentProgram.setParam(attrs['key'], attrs['value'])
1083
1084    def endElement(self, name):
1085        if name == 'lane':
1086            self._currentLane = None
1087        elif name == 'edge':
1088            self._currentEdge = None
1089        elif name == 'junction':
1090            self._currentNode = None
1091        elif name == 'connection':
1092            self._currentConnection = None
1093        # 'row-logic' is deprecated!!!
1094        elif name == 'ROWLogic' or name == 'row-logic':
1095            self._haveROWLogic = False
1096        # tl-logic is deprecated!!!
1097        elif self._withPhases and (name == 'tlLogic' or name == 'tl-logic'):
1098            self._currentProgram = None
1099        elif name == 'net':
1100            for edgeID, bidiID in self._bidiEdgeIDs.items():
1101                self._net.getEdge(edgeID)._bidi = self._net.getEdge(bidiID)
1102
1103    def endDocument(self):
1104        # set crossed edges of pedestrian crossings
1105        for crossingID, crossedEdgeIDs in self._crossingID2edgeIDs.items():
1106            pedCrossing = self._net.getEdge(crossingID)
1107            for crossedEdgeID in crossedEdgeIDs:
1108                pedCrossing._addCrossingEdge(self._net.getEdge(crossedEdgeID))
1109
1110    def getNet(self):
1111        return self._net
1112
1113
1114def convertShape(shapeString):
1115    """ Convert xml shape string into float tuples.
1116
1117    This method converts the 2d or 3d shape string from SUMO's xml file
1118    into a list containing 3d float-tuples. Non existent z coordinates default
1119    to zero. If shapeString is empty, an empty list will be returned.
1120    """
1121
1122    cshape = []
1123    for pointString in shapeString.split():
1124        p = [float(e) for e in pointString.split(",")]
1125        if len(p) == 2:
1126            cshape.append((p[0], p[1], 0.))
1127        elif len(p) == 3:
1128            cshape.append(tuple(p))
1129        else:
1130            raise ValueError(
1131                'Invalid shape point "%s", should be either 2d or 3d' % pointString)
1132    return cshape
1133
1134
1135def lane2edge(laneID):
1136    return laneID[:laneID.rfind("_")]
1137
1138
1139def lane2index(laneID):
1140    return int(laneID[laneID.rfind("_") + 1:])
1141
1142
1143def hasInternal(filename):
1144    with openz(filename) as f:
1145        for line in f:
1146            if '<edge id="' in line:
1147                if '<edge id=":' in line:
1148                    return True
1149                else:
1150                    return False
1151    return False
1152
1153
1154def readNet(filename, **others):
1155    """ load a .net.xml file
1156    The following named options are supported:
1157
1158        'net' : initialize data structures with an existing net object (default Net())
1159        'withPrograms' : import all traffic light programs (default False)
1160        'withLatestPrograms' : import only the last program for each traffic light.
1161                               This is the program that would be active in sumo by default.
1162                               (default False)
1163        'withConnections' : import all connections (default True)
1164        'withFoes' : import right-of-way information (default True)
1165        'withInternal' : import internal edges and lanes (default False)
1166        'withPedestrianConnections' : import connections between sidewalks, crossings (default False)
1167        'lxml' : set to False to use the xml.sax parser instead of the lxml parser
1168        'maxcache' : set maximum cache size (default 1000) or 0 to disable optimal route caching
1169    """
1170    netreader = NetReader(**others)
1171    try:
1172        source = gzip.open(filename)
1173        source.read(10)
1174        source.seek(0)
1175    except IOError:
1176        source = filename
1177    if HAVE_LXML and others.get("lxml", True):
1178        if isinstance(source, pathlib.Path):
1179            source = str(source)
1180        for event, v in lxml.etree.iterparse(source, events=("start", "end")):
1181            if event == "start":
1182                netreader.startElement(v.tag, v.attrib)
1183            elif event == "end":
1184                netreader.endElement(v.tag)
1185            v.clear()  # reduce memory footprint
1186    else:
1187        parse(source, netreader)
1188    net = netreader.getNet()
1189    maxcache = others.get('maxcache', 1000)
1190    if HAVE_LRU_CACHE and maxcache is not None and maxcache > 0:
1191        net.initRoutingCache(maxcache)
1192    return net
class TLS:
 66class TLS:
 67
 68    """Traffic Light Signal for a sumo network"""
 69
 70    def __init__(self, id):
 71        self._id = id
 72        self._connections = []
 73        self._maxConnectionNo = -1
 74        self._programs = {}
 75
 76    def addConnection(self, inLane, outLane, linkNo):
 77        self._connections.append([inLane, outLane, linkNo])
 78        if linkNo > self._maxConnectionNo:
 79            self._maxConnectionNo = linkNo
 80
 81    def getConnections(self):
 82        return self._connections
 83
 84    def getID(self):
 85        return self._id
 86
 87    def getLinks(self):
 88        links = {}
 89        for the_connection in self._connections:
 90            if the_connection[2] not in links:
 91                links[the_connection[2]] = []
 92            links[the_connection[2]].append(the_connection)
 93        return links
 94
 95    def getEdges(self):
 96        edges = set()
 97        for c in self._connections:
 98            edges.add(c[0].getEdge())
 99        return edges
100
101    def addProgram(self, program):
102        self._programs[program._id] = program
103
104    def removePrograms(self):
105        self._programs.clear()
106
107    def toXML(self):
108        ret = ""
109        for p in self._programs:
110            ret = ret + self._programs[p].toXML(self._id)
111        return ret
112
113    def getPrograms(self):
114        return self._programs

Traffic Light Signal for a sumo network

TLS(id)
70    def __init__(self, id):
71        self._id = id
72        self._connections = []
73        self._maxConnectionNo = -1
74        self._programs = {}
def addConnection(self, inLane, outLane, linkNo):
76    def addConnection(self, inLane, outLane, linkNo):
77        self._connections.append([inLane, outLane, linkNo])
78        if linkNo > self._maxConnectionNo:
79            self._maxConnectionNo = linkNo
def getConnections(self):
81    def getConnections(self):
82        return self._connections
def getID(self):
84    def getID(self):
85        return self._id
def getEdges(self):
95    def getEdges(self):
96        edges = set()
97        for c in self._connections:
98            edges.add(c[0].getEdge())
99        return edges
def addProgram(self, program):
101    def addProgram(self, program):
102        self._programs[program._id] = program
def removePrograms(self):
104    def removePrograms(self):
105        self._programs.clear()
def toXML(self):
107    def toXML(self):
108        ret = ""
109        for p in self._programs:
110            ret = ret + self._programs[p].toXML(self._id)
111        return ret
def getPrograms(self):
113    def getPrograms(self):
114        return self._programs
class Phase:
117class Phase:
118
119    def __init__(self, duration, state, minDur=None, maxDur=None, next=tuple(), name="", earlyTarget=""):
120        """
121        Constructs a traffic light phase
122        duration (float): the duration of the phase in seconds
123        state (string): the state codes for each controlled link
124        minDur (float): the minimum duration (ignored by static tls)
125        maxDur (float): the maximum duration (ignored by static tls)
126        next (intList): possible succesor phase (optional)
127        name (string): the name of the phase
128        earlyTarget (string): early switching to phase with the given index(es)
129        """
130        self.duration = duration
131        self.state = state
132        # minimum and maximum duration (only for actuated tls)
133        self.minDur = minDur if minDur is not None else duration
134        self.maxDur = maxDur if maxDur is not None else duration
135        self.next = next
136        self.name = name
137        self.earlyTarget = earlyTarget
138
139    def __repr__(self):
140        name = (", name='%s'" % self.name) if self.name else ""
141        next = (", next='%s'" % str(self.next)) if self.next else ""
142        earlyTarget = (", earlyTarget='%s'" % self.earlyTarget) if self.earlyTarget else ""
143        return ("Phase(duration=%s, state='%s', minDur=%s, maxDur=%s%s%s%s)"
144                % (self.duration, self.state, self.minDur, self.maxDur, name, next, earlyTarget))
Phase( duration, state, minDur=None, maxDur=None, next=(), name='', earlyTarget='')
119    def __init__(self, duration, state, minDur=None, maxDur=None, next=tuple(), name="", earlyTarget=""):
120        """
121        Constructs a traffic light phase
122        duration (float): the duration of the phase in seconds
123        state (string): the state codes for each controlled link
124        minDur (float): the minimum duration (ignored by static tls)
125        maxDur (float): the maximum duration (ignored by static tls)
126        next (intList): possible succesor phase (optional)
127        name (string): the name of the phase
128        earlyTarget (string): early switching to phase with the given index(es)
129        """
130        self.duration = duration
131        self.state = state
132        # minimum and maximum duration (only for actuated tls)
133        self.minDur = minDur if minDur is not None else duration
134        self.maxDur = maxDur if maxDur is not None else duration
135        self.next = next
136        self.name = name
137        self.earlyTarget = earlyTarget

Constructs a traffic light phase duration (float): the duration of the phase in seconds state (string): the state codes for each controlled link minDur (float): the minimum duration (ignored by static tls) maxDur (float): the maximum duration (ignored by static tls) next (intList): possible succesor phase (optional) name (string): the name of the phase earlyTarget (string): early switching to phase with the given index(es)

duration
state
minDur
maxDur
next
name
earlyTarget
class TLSProgram:
147class TLSProgram:
148
149    def __init__(self, id, offset, type):
150        self._id = id
151        self._type = type
152        self._offset = offset
153        self._phases = []
154        self._params = {}
155        self._conditions = {}
156
157    def addPhase(self, state, duration, minDur=-1, maxDur=-1, next=None, name="", earlyTarget=""):
158        self._phases.append(Phase(duration, state, minDur, maxDur, next, name, earlyTarget))
159
160    def addCondition(self, id, value):
161        self._conditions[id] = value
162
163    def toXML(self, tlsID):
164        ret = '  <tlLogic id="%s" type="%s" programID="%s" offset="%s">\n' % (
165            tlsID, self._type, self._id, self._offset)
166        for p in self._phases:
167            minDur = '' if p.minDur < 0 else ' minDur="%s"' % p.minDur
168            maxDur = '' if p.maxDur < 0 else ' maxDur="%s"' % p.maxDur
169            name = '' if p.name == '' else ' name="%s"' % p.name
170            next = '' if len(p.next) == 0 else ' next="%s"' % ' '.join(map(str, p.next))
171            earlyTarget = '' if p.earlyTarget == '' else ' earlyTarget="%s"' % p.earlyTarget
172            ret += '    <phase duration="%s" state="%s"%s%s%s%s%s/>\n' % (
173                p.duration, p.state, minDur, maxDur, name, next, earlyTarget)
174        for k, v in self._params.items():
175            ret += '    <param key="%s" value="%s"/>\n' % (k, v)
176        for i, v in self._conditions.items():
177            ret += '    <condition id="%s" value="%s"/>\n' % (i, v)
178        ret += '  </tlLogic>\n'
179        return ret
180
181    def getPhases(self):
182        return self._phases
183
184    def getType(self):
185        return self._type
186
187    def setParam(self, key, value):
188        self._params[key] = value
189
190    def getParam(self, key, default=None):
191        return self._params.get(key, default)
192
193    def getParams(self):
194        return self._params
195
196    def getStages(self):
197        stages = dict()
198        for idx, phase in enumerate(self.getPhases()):
199            if phase not in stages.values():
200                if 'G' in phase.state and 'y' not in phase.state and phase.name:
201                    stages[idx] = phase
202        return stages
203
204    def getOffset(self):
205        return self._offset
TLSProgram(id, offset, type)
149    def __init__(self, id, offset, type):
150        self._id = id
151        self._type = type
152        self._offset = offset
153        self._phases = []
154        self._params = {}
155        self._conditions = {}
def addPhase( self, state, duration, minDur=-1, maxDur=-1, next=None, name='', earlyTarget=''):
157    def addPhase(self, state, duration, minDur=-1, maxDur=-1, next=None, name="", earlyTarget=""):
158        self._phases.append(Phase(duration, state, minDur, maxDur, next, name, earlyTarget))
def addCondition(self, id, value):
160    def addCondition(self, id, value):
161        self._conditions[id] = value
def toXML(self, tlsID):
163    def toXML(self, tlsID):
164        ret = '  <tlLogic id="%s" type="%s" programID="%s" offset="%s">\n' % (
165            tlsID, self._type, self._id, self._offset)
166        for p in self._phases:
167            minDur = '' if p.minDur < 0 else ' minDur="%s"' % p.minDur
168            maxDur = '' if p.maxDur < 0 else ' maxDur="%s"' % p.maxDur
169            name = '' if p.name == '' else ' name="%s"' % p.name
170            next = '' if len(p.next) == 0 else ' next="%s"' % ' '.join(map(str, p.next))
171            earlyTarget = '' if p.earlyTarget == '' else ' earlyTarget="%s"' % p.earlyTarget
172            ret += '    <phase duration="%s" state="%s"%s%s%s%s%s/>\n' % (
173                p.duration, p.state, minDur, maxDur, name, next, earlyTarget)
174        for k, v in self._params.items():
175            ret += '    <param key="%s" value="%s"/>\n' % (k, v)
176        for i, v in self._conditions.items():
177            ret += '    <condition id="%s" value="%s"/>\n' % (i, v)
178        ret += '  </tlLogic>\n'
179        return ret
def getPhases(self):
181    def getPhases(self):
182        return self._phases
def getType(self):
184    def getType(self):
185        return self._type
def setParam(self, key, value):
187    def setParam(self, key, value):
188        self._params[key] = value
def getParam(self, key, default=None):
190    def getParam(self, key, default=None):
191        return self._params.get(key, default)
def getParams(self):
193    def getParams(self):
194        return self._params
def getStages(self):
196    def getStages(self):
197        stages = dict()
198        for idx, phase in enumerate(self.getPhases()):
199            if phase not in stages.values():
200                if 'G' in phase.state and 'y' not in phase.state and phase.name:
201                    stages[idx] = phase
202        return stages
def getOffset(self):
204    def getOffset(self):
205        return self._offset
class EdgeType:
208class EdgeType:
209    def __init__(self, id, allow, disallow):
210        self.id = id
211        self.allow = allow
212        self.disallow = disallow
EdgeType(id, allow, disallow)
209    def __init__(self, id, allow, disallow):
210        self.id = id
211        self.allow = allow
212        self.disallow = disallow
id
allow
disallow
class Net:
215class Net:
216
217    """The whole sumo network."""
218
219    def __init__(self):
220        self._location = {}
221        self._id2node = {}
222        self._id2edge = {}
223        self._crossings_and_walkingAreas = set()
224        self._macroConnectors = set()
225        self._id2tls = {}
226        self._nodes = []
227        self._edges = []
228        self._tlss = []
229        self._ranges = [[sys.float_info.max, -sys.float_info.max], [sys.float_info.max, -sys.float_info.max]]
230        self._roundabouts = []
231        self._rtreeEdges = {True: None, False: None}  # different rTrees depending on includeJunctions
232        self._rtreeLanes = {True: None, False: None}  # different rTrees depending on includeJunctions
233        self._allLanes = []
234        self._origIdx = None
235        self._proj = None
236        self.hasInternal = False
237        self.hasWalkingArea = False
238        # store dijsktra heap for reuse if the same origin is used repeatedly
239        self._shortestPathCache = None
240        self._version = None
241        self._edgeTypes = defaultdict(lambda: EdgeType("DEFAULT_EDGETYPE", "", ""))
242        self._routingCache = None
243        self._edgeSuccessorCache = None
244        self._edgeCostCache = None
245
246    def initRoutingCache(self, maxsize=1000):
247        self._routingCache = lru_cache(maxsize=maxsize)(lambda fromEdge, fastest, vClass, ignoreDirection, reversalPenalty, preferences: {})  # noqa
248
249    def getVersion(self):
250        return self._version
251
252    def getEdgeType(self, typeID):
253        return self._edgeTypes[typeID]
254
255    def setLocation(self, netOffset, convBoundary, origBoundary, projParameter):
256        self._location["netOffset"] = netOffset
257        self._location["convBoundary"] = convBoundary
258        self._location["origBoundary"] = origBoundary
259        self._location["projParameter"] = projParameter
260
261    def loadSelection(self, selectionFile):
262        self.resetSelection()
263        with io.open(selectionFile, "r", encoding="utf-8") as f:
264            for line in f:
265                line = line.strip()
266                if line.startswith("edge:"):
267                    edgeID = line[5:]
268                    if edgeID in self._id2edge:
269                        self.getEdge(edgeID).select()
270                elif line.startswith("junction:"):
271                    nodeID = line[9:]
272                    if nodeID in self._id2node:
273                        self.getNode(nodeID).select()
274
275    def resetSelection(self):
276        for n in self._nodes:
277            n.select(False)
278        for e in self._edges:
279            e.select(False)
280
281    def addNode(self, id, type=None, coord=None, incLanes=None, intLanes=None):
282        if id is None:
283            return None
284        if id not in self._id2node:
285            n = node.Node(id, type, coord, incLanes, intLanes)
286            self._nodes.append(n)
287            self._id2node[id] = n
288        self.setAdditionalNodeInfo(
289            self._id2node[id], type, coord, incLanes, intLanes)
290        return self._id2node[id]
291
292    def setAdditionalNodeInfo(self, node, type, coord, incLanes, intLanes=None):
293        if coord is not None and node._coord is None:
294            node._coord = coord
295            self._ranges[0][0] = min(self._ranges[0][0], coord[0])
296            self._ranges[0][1] = max(self._ranges[0][1], coord[0])
297            self._ranges[1][0] = min(self._ranges[1][0], coord[1])
298            self._ranges[1][1] = max(self._ranges[1][1], coord[1])
299        if incLanes is not None and node._incLanes is None:
300            node._incLanes = incLanes
301        if intLanes is not None and node._intLanes is None:
302            node._intLanes = intLanes
303        if type is not None and node._type is None:
304            node._type = type
305
306    def addEdge(self, id, fromID, toID, prio, function, name, edgeType='', routingType=''):
307        if id not in self._id2edge:
308            fromN = self.addNode(fromID)
309            toN = self.addNode(toID)
310            e = edge.Edge(id, fromN, toN, prio, function, name, edgeType, routingType)
311            self._edges.append(e)
312            self._id2edge[id] = e
313            if function:
314                self.hasInternal = True
315                if function == "walkingarea":
316                    self.hasWalkingArea = True
317        return self._id2edge[id]
318
319    def addLane(self, edge, speed, length, width, allow=None, disallow=None, acceleration=False):
320        return lane.Lane(edge, speed, length, width, allow, disallow, acceleration)
321
322    def addRoundabout(self, nodes, edges=None):
323        r = roundabout.Roundabout(nodes, edges)
324        self._roundabouts.append(r)
325        return r
326
327    def addConnection(self, fromEdge, toEdge, fromlane, tolane, direction,
328                      tls, tllink, tllink2, allow, disallow, state, viaLaneID=None):
329        conn = connection.Connection(
330            fromEdge, toEdge, fromlane, tolane, direction,
331            tls, tllink, tllink2, allow, disallow, state, viaLaneID)
332        fromEdge.addOutgoing(conn)
333        fromlane.addOutgoing(conn)
334        toEdge._addIncoming(conn)
335        if viaLaneID:
336            try:
337                # internal lanes are only available when building with option withInternal=True
338                viaLane = self.getLane(viaLaneID)
339                viaEdge = viaLane.getEdge()
340                viaEdge._addIncoming(connection.Connection(
341                    fromEdge, viaEdge, fromlane, viaLane, direction, tls,
342                    tllink, tllink2, allow, disallow, state, ''))
343            except Exception:
344                pass
345        return conn
346
347    def getEdges(self, withInternal=True):
348        if not withInternal:
349            return [e for e in self._edges if e.getFunction() == '']
350        else:
351            return self._edges
352
353    def getRoundabouts(self):
354        return self._roundabouts
355
356    def hasEdge(self, id):
357        return id in self._id2edge
358
359    def getEdge(self, id):
360        return self._id2edge[id]
361
362    def getLane(self, laneID):
363        edge_id, lane_index = laneID.rsplit("_", 1)
364        return self.getEdge(edge_id).getLane(int(lane_index))
365
366    def _initRTree(self, shapeList, includeJunctions=True):
367        import rtree  # noqa
368        result = rtree.index.Index()
369        result.interleaved = True
370        for ri, shape in enumerate(shapeList):
371            result.add(ri, shape.getBoundingBox(includeJunctions))
372        return result
373
374    # Please be aware that the resulting list of edges is NOT sorted
375    def getNeighboringEdges(self, x, y, r=0.1, includeJunctions=True, allowFallback=True):
376        edges = []
377        rtree = self._rtreeEdges[includeJunctions]
378        try:
379            if rtree is None:
380                rtree = self._initRTree(self._edges, includeJunctions)
381                self._rtreeEdges[includeJunctions] = rtree
382            for i in rtree.intersection((x - r, y - r, x + r, y + r)):
383                e = self._edges[i]
384                d = sumolib.geomhelper.distancePointToPolygon(
385                    (x, y), e.getShape(includeJunctions))
386                if d < r:
387                    edges.append((e, d))
388        except ImportError:
389            if not allowFallback:
390                raise
391            warnings.warn("Module 'rtree' not available. Using brute-force fallback.")
392            for the_edge in self._edges:
393                d = sumolib.geomhelper.distancePointToPolygon((x, y), the_edge.getShape(includeJunctions))
394                if d < r:
395                    edges.append((the_edge, d))
396        return edges
397
398    def getNeighboringLanes(self, x, y, r=0.1, includeJunctions=True, allowFallback=True):
399        lanes = []
400        rtree = self._rtreeLanes[includeJunctions]
401        try:
402            if rtree is None:
403                for the_edge in self._edges:
404                    self._allLanes += the_edge.getLanes()
405                rtree = self._initRTree(self._allLanes, includeJunctions)
406                self._rtreeLanes[includeJunctions] = rtree
407            for i in rtree.intersection((x - r, y - r, x + r, y + r)):
408                the_lane = self._allLanes[i]
409                d = sumolib.geomhelper.distancePointToPolygon((x, y), the_lane.getShape(includeJunctions))
410                if d < r:
411                    lanes.append((the_lane, d))
412        except ImportError:
413            if not allowFallback:
414                raise
415            warnings.warn("Module 'rtree' not available. Using brute-force fallback.")
416            for the_edge in self._edges:
417                for the_lane in the_edge.getLanes():
418                    d = sumolib.geomhelper.distancePointToPolygon((x, y), the_lane.getShape(includeJunctions))
419                    if d < r:
420                        lanes.append((the_lane, d))
421        return lanes
422
423    def getNearestLane(self, x, y, r=0.1, vClass=None, isGeo=False, allowFallback=True):
424        """Return (lane, lanePos, dist) for the nearest lane at a distance less than r,
425        or None if no lane matches. Both lanePos and dist are in meters.
426
427        If vClass is given, only lanes allowing that vehicle class are considered.
428        If isGeo is True, x and y are longitude and latitude, respectively, and
429        the network's geo-projection is used (requires pyproj).
430        Distances are measured to the lane shape without junction extensions.
431        Internal lanes are considered if the network was read withInternal=True.
432        """
433        if isGeo:
434            x, y = self.convertLonLat2XY(x, y)
435        candidates = [(lane, dist) for lane, dist in
436                      self.getNeighboringLanes(x, y, r, includeJunctions=False, allowFallback=allowFallback)
437                      if lane.allows(vClass)]
438        if not candidates:
439            return None
440        lane, dist = min(candidates, key=lambda candidate: candidate[1])
441        lanePos, dist = lane.getClosestLanePosAndDist((x, y))
442        return lane, lanePos, dist
443
444    def hasNode(self, id):
445        return id in self._id2node
446
447    def getNode(self, id):
448        return self._id2node[id]
449
450    def getNodes(self):
451        return self._nodes
452
453    def getTLS(self, tlid):
454        return self._id2tls[tlid]
455
456    def getTLSSecure(self, tlid):
457        if tlid in self._id2tls:
458            tls = self._id2tls[tlid]
459        else:
460            tls = TLS(tlid)
461            self._id2tls[tlid] = tls
462            self._tlss.append(tls)
463        return tls
464
465    def getTrafficLights(self):
466        return self._tlss
467
468    def addTLS(self, tlid, inLane, outLane, linkNo):
469        tls = self.getTLSSecure(tlid)
470        tls.addConnection(inLane, outLane, linkNo)
471        return tls
472
473    def addTLSProgram(self, tlid, programID, offset, type, removeOthers):
474        tls = self.getTLSSecure(tlid)
475        program = TLSProgram(programID, offset, type)
476        if removeOthers:
477            tls.removePrograms()
478        tls.addProgram(program)
479        return program
480
481    def setFoes(self, junctionID, index, foes, prohibits):
482        self._id2node[junctionID].setFoes(index, foes, prohibits)
483
484    def forbids(self, possProhibitor, possProhibited):
485        return possProhibitor.getFrom().getToNode().forbids(possProhibitor, possProhibited)
486
487    def getUpstreamEdges(self, edge, distance, stopOnTLS, stopOnTurnaround):
488        """return a list of lists of the form
489           [[firstEdge, pos, [edge_0, edge_1, ..., edge_k], aborted], ...]
490           where
491             firstEdge: is the upstream edge furthest away from the intersection,
492             [edge_0, ..., edge_k]: is the list of edges from the intersection upstream to firstEdge
493             pos: is the position on firstEdge with distance to the end of the input edge
494             aborted: a flag indicating whether the downstream
495                 search stopped at a TLS or a node without incoming edges before reaching the distance threshold
496        """
497        ret = []
498        seen = set()
499        toProc = []
500        toProc.append([edge, 0, []])
501        while not len(toProc) == 0:
502            ie = toProc.pop()
503            if ie[0] in seen:
504                continue
505            seen.add(ie[0])
506            if ie[1] + ie[0].getLength() >= distance:
507                ret.append(
508                    [ie[0], ie[0].getLength() + ie[1] - distance, ie[2], False])
509                continue
510            if len(ie[0]._incoming) == 0:
511                ret.append([ie[0], ie[0].getLength() + ie[1], ie[2], True])
512                continue
513            mn = []
514            stop = False
515            for ci in ie[0]._incoming:
516                if ci not in seen:
517                    prev = copy(ie[2])
518                    if stopOnTLS and ci._tls and ci != edge and not stop:
519                        ret.append([ie[0], ie[1], prev, True])
520                        stop = True
521                    elif (stopOnTurnaround and ie[0]._incoming[ci][0].getDirection() == Connection.LINKDIR_TURN and
522                          not stop):
523                        ret.append([ie[0], ie[1], prev, True])
524                        stop = True
525                    else:
526                        prev.append(ie[0])
527                        mn.append([ci, ie[0].getLength() + ie[1], prev])
528            if not stop:
529                toProc.extend(mn)
530        return ret
531
532    def getEdgesByOrigID(self, origID):
533        if self._origIdx is None:
534            self._origIdx = defaultdict(set)
535            for the_edge in self._edges:
536                for the_lane in the_edge.getLanes():
537                    for oID in the_lane.getParam("origId", "").split():
538                        self._origIdx[oID].add(the_edge)
539        return self._origIdx[origID]
540
541    def getGeometries(self, useLanes, includeJunctions=False):
542        for e in self._edges:
543            if useLanes:
544                for the_lane in e.getLanes():
545                    yield the_lane.getID(), the_lane.getShape(), the_lane.getWidth()
546            else:
547                yield e.getID(), e.getShape(includeJunctions), sum([the_lane.getWidth() for the_lane in e.getLanes()])
548
549    def getBBoxXY(self):
550        """
551        Get the bounding box (bottom left and top right coordinates) for a net;
552        Coordinates are in X and Y (not Lat and Lon)
553
554        :return [(bottom_left_X, bottom_left_Y), (top_right_X, top_right_Y)]
555        """
556        return [(self._ranges[0][0], self._ranges[1][0]),
557                (self._ranges[0][1], self._ranges[1][1])]
558
559    # the diagonal of the bounding box of all nodes
560    def getBBoxDiameter(self):
561        return math.sqrt(
562            (self._ranges[0][0] - self._ranges[0][1]) ** 2 +
563            (self._ranges[1][0] - self._ranges[1][1]) ** 2)
564
565    def hasGeoProj(self):
566        projString = self._location["projParameter"]
567        return projString != "!"
568
569    def getGeoProj(self):
570        if not self.hasGeoProj() or not HAVE_PYPROJ:
571            raise RuntimeError("Network does not provide geo-projection or pyproj not installed.")
572        if self._proj is None:
573            try:
574                self._proj = pyproj.Proj(projparams=self._location["projParameter"])
575            except RuntimeError:
576                if hasattr(pyproj.datadir, 'set_data_dir'):
577                    pyproj.datadir.set_data_dir('/usr/share/proj')
578                    self._proj = pyproj.Proj(projparams=self._location["projParameter"])
579                raise
580        return self._proj
581
582    def getLocationOffset(self):
583        """ offset to be added after converting from geo-coordinates to UTM"""
584        return list(map(float, self._location["netOffset"].split(",")))
585
586    def getBoundary(self):
587        """ return xmin,ymin,xmax,ymax network coordinates"""
588        return list(map(float, self._location["convBoundary"].split(",")))
589
590    def convertLonLat2XY(self, lon, lat, rawUTM=False):
591        x, y = self.getGeoProj()(lon, lat)
592        if rawUTM:
593            return x, y
594        else:
595            x_off, y_off = self.getLocationOffset()
596            return x + x_off, y + y_off
597
598    def convertXY2LonLat(self, x, y, rawUTM=False):
599        if not rawUTM:
600            x_off, y_off = self.getLocationOffset()
601            x -= x_off
602            y -= y_off
603        return self.getGeoProj()(x, y, inverse=True)
604
605    def move(self, dx, dy, dz=0):
606        for n in self._nodes:
607            n._coord = (n._coord[0] + dx, n._coord[1] + dy, n._coord[2] + dz)
608        for e in self._edges:
609            for _lane in e.getLanes():
610                _lane.setShape([(p[0] + dx, p[1] + dy, p[2] + dz) for p in _lane.getShape3D()])
611            e.rebuildShape()
612
613    def getInternalPath(self, conn, fastest=False):
614        minInternalCost = PRACTICAL_INFINITY
615        minPath = None
616        for c in conn:
617            if c.getViaLaneID() != "":
618                viaCost = 0
619                viaID = c.getViaLaneID()
620                viaPath = []
621                while viaID != "":
622                    viaLane = self.getLane(viaID)
623                    viaCost += viaLane.getLength() if not fastest else viaLane.getLength() / viaLane.getSpeed()
624                    viaID = viaLane.getOutgoing()[0].getViaLaneID()
625                    viaPath.append(viaLane.getEdge())
626                if viaCost < minInternalCost:
627                    minInternalCost = viaCost
628                    minPath = viaPath
629        return minPath, minInternalCost
630
631    def getOptimalPath(self, fromEdge, toEdge, fastest=False, maxCost=PRACTICAL_INFINITY, vClass=None,
632                       reversalPenalty=0, includeFromToCost=True, withInternal=False, ignoreDirection=False,
633                       fromPos=None, toPos=None, preferences={}):
634        """
635        Finds the optimal (shortest or fastest) path for vClass from fromEdge to toEdge
636        by using using Dijkstra's algorithm.
637        It returns a pair of a tuple of edges and the cost.
638        If no path is found the first element is None.
639        The cost for the returned path is equal to the sum of all edge costs in the path,
640        including the internal connectors, if they are present in the network.
641        The path itself does not include internal edges except for the case
642        when the start or end edge are internal edges.
643        The search may be limited using the given threshold.
644        The preferences declare a mapping from the 'routingType' of each edge to divisor
645        that is applied to the computed cost of that edge (i.e. a preference of 2 reduces cost by a factor of 0.5).
646        """
647
648        if preferences:
649            if fastest:
650                def speedFunc(edge):
651                    return preferences.get(edge.getRoutingType(), 1.0) * edge.getSpeed()
652            else:
653                def speedFunc(edge):
654                    return preferences.get(edge.getRoutingType(), 1.0)
655        elif fastest:
656            def speedFunc(edge):
657                return edge.getSpeed()
658        else:
659            def speedFunc(edge):
660                return 1.0
661
662        def remainder(edge, pos):
663            if pos < 0:
664                return min(-pos, edge.getLength())
665            return max(0., edge.getLength() - pos)
666
667        def getToNormalIncoming(edge):
668            if edge.getFunction() == '':
669                return [(e, None) for e in edge.getToNode().getIncoming() if e.getFunction() == '']
670            else:
671                return []
672
673        if self._edgeSuccessorCache != (vClass, ignoreDirection):
674            self._edgeSuccessorCache = (vClass, ignoreDirection)
675            for e1 in self.getEdges():
676                e1._succCacche = tuple(chain(e1.getAllowedOutgoing(vClass).items(),
677                                       e1.getIncoming().items() if ignoreDirection else [],
678                                       getToNormalIncoming(e1) if ignoreDirection and not self.hasWalkingArea else []))
679
680        if self._edgeCostCache != (fastest, preferences):
681            self._edgeCostCache = (fastest, preferences)
682            for e1 in self.getEdges():
683                e1._costCache = e1.getLength() / speedFunc(e1)
684
685        if self.hasInternal:
686            appendix = []
687            appendixCost = 0.
688            while toEdge.getFunction() == "internal":
689                appendix = [toEdge] + appendix
690                appendixCost += toEdge._costCache
691                toEdge = list(toEdge.getIncoming().keys())[0]
692
693        def finalizeCost(cost, path):
694            if includeFromToCost:
695                # add costs for (part of) the first edge, still needs to be fixed for wrong direction travel
696                remainFrom = fromEdge.getLength() if fromPos is None else remainder(fromEdge, fromPos)
697                cost += remainFrom / speedFunc(fromEdge)
698                # remove costs for (part of) the last edge, still needs to be fixed for wrong direction travel
699                removeTo = 0. if toPos is None else remainder(toEdge, toPos)
700            else:
701                removeTo = toEdge.getLength() if len(path) > 1 else 0.
702            cost -= removeTo / speedFunc(toEdge)
703            return cost
704
705        def constructPath(dist):
706            # destination was already reached in a previous query
707            cost, pred = dist[toEdge]
708            path = [toEdge]
709            while pred is not None:
710                if self.hasInternal and withInternal:
711                    viaPath, minInternalCost = self.getInternalPath(pred.getAllowedOutgoing(vClass).get(path[-1], []),
712                                                                    fastest=fastest)
713                    if viaPath is not None:
714                        path += reversed(viaPath)
715                path.append(pred)
716                _, pred = dist[pred]
717
718            path.reverse()
719            cost = finalizeCost(cost, path)
720            assert cost >= 0
721            if self.hasInternal:
722                if appendix:
723                    return tuple(path + appendix), cost + appendixCost
724                elif ignoreDirection and self.hasWalkingArea and not withInternal:
725                    return [e for e in path if e.getFunction() == ''], cost
726            return tuple(path), cost
727
728        needLoop = (fromEdge == toEdge
729                    and fromPos is not None
730                    and toPos is not None
731                    and fromPos > toPos
732                    and not ignoreDirection)
733
734        seen = set()
735        dist = {}
736        q = []
737        if self._routingCache is not None:
738            if needLoop:
739                # use cached results from all follower edges:
740                bestCost = maxCost
741                bestPath = None
742                for e2, conn in fromEdge.getAllowedOutgoing(vClass).items():
743                    path, cost = self.getOptimalPath(e2, toEdge, fastest=fastest, maxCost=maxCost, vClass=vClass,
744                                                     reversalPenalty=reversalPenalty,
745                                                     includeFromToCost=includeFromToCost,
746                                                     withInternal=withInternal, fromPos=0, toPos=toPos,
747                                                     preferences=preferences)
748                    if path is not None and cost < bestCost:
749                        bestPath = path
750                        bestCost = cost
751                if bestPath is not None:
752                    path = [fromEdge]
753                    if self.hasInternal and withInternal:
754                        viaPath, minInternalCost = self.getInternalPath(
755                            fromEdge.getAllowedOutgoing(vClass).get(path[0], []), fastest=fastest)
756                        if viaPath is not None:
757                            path += viaPath
758                            bestCost += minInternalCost
759                    path += list(bestPath)
760                    if includeFromToCost:
761                        bestCost += remainder(fromEdge, fromPos) / speedFunc(fromEdge)
762                    return tuple(path), bestCost
763                else:
764                    return None, PRACTICAL_INFINITY
765
766            dist = self._routingCache(fromEdge, fastest, vClass, ignoreDirection, reversalPenalty,
767                                      tuple(preferences.items()))
768            if toEdge in dist:
769                return constructPath(dist)
770            else:
771                # initialize heap from previous query
772                q = []
773                frontier = set(dist.keys())
774                for cost, prev in dist.values():
775                    frontier.discard(prev)
776                for e in frontier:
777                    cost, prev = dist[e]
778                    heapq.heappush(q, (cost, e, prev))
779        elif needLoop:
780            # start search on successors of fromEdge
781            for e2, conn in fromEdge.getAllowedOutgoing(vClass).items():
782                q.append((e2._costCache, e2, fromEdge))
783
784        if len(dist) == 0:
785            dist[fromEdge] = (0., None)
786            if not needLoop:
787                q.append((0., fromEdge, None))
788
789        while q:
790            cost, e1, prev = heapq.heappop(q)
791            if e1 in seen:
792                continue
793            seen.add(e1)
794            if e1 == toEdge:
795                return constructPath(dist)
796            if cost > maxCost:
797                return None, cost
798
799            for e2, conn in e1._succCacche:
800                if e2 not in seen:
801                    newCost = cost + e2._costCache
802                    #  print(cost, newCost, e2.getID(), speedFunc(e2))
803                    if e2 == e1.getBidi():
804                        newCost += reversalPenalty
805                    if self.hasInternal and conn is not None:
806                        viaPath, minInternalCost = self.getInternalPath(conn, fastest=fastest)
807                        if viaPath is not None:
808                            newCost += minInternalCost
809                    if e2 not in dist or newCost < dist[e2][0]:
810                        dist[e2] = (newCost, e1)
811                        heapq.heappush(q, (newCost, e2, e1))
812        return None, PRACTICAL_INFINITY
813
814    def getShortestPath(self, fromEdge, toEdge, maxCost=PRACTICAL_INFINITY, vClass=None, reversalPenalty=0,
815                        includeFromToCost=True, withInternal=False, ignoreDirection=False,
816                        fromPos=None, toPos=None, preferences={}):
817        """
818        Finds the shortest path from fromEdge to toEdge respecting vClass, using Dijkstra's algorithm.
819        It returns a pair of a tuple of edges and the cost. If no path is found the first element is None.
820        The cost for the returned path is equal to the sum of all edge lengths in the path,
821        including the internal connectors, if they are present in the network.
822        The path itself does not include internal edges except for the case
823        when the start or end edge are internal edges.
824        The search may be limited using the given threshold.
825        The preferences declare a mapping from the 'routingType' of each edge to divisor
826        that is applied to the lenght of that edge
827        (i.e. a preference of 2 reduces reduces effective length by a factor of 0.5).
828        """
829
830        return self.getOptimalPath(fromEdge, toEdge, False, maxCost, vClass, reversalPenalty,
831                                   includeFromToCost, withInternal, ignoreDirection, fromPos, toPos,
832                                   preferences)
833
834    def getFastestPath(self, fromEdge, toEdge, maxCost=PRACTICAL_INFINITY, vClass=None, reversalPenalty=0,
835                       includeFromToCost=True, withInternal=False, ignoreDirection=False,
836                       fromPos=None, toPos=None, preferences={}):
837        """
838        Finds the fastest path from fromEdge to toEdge respecting vClass, using Dijkstra's algorithm.
839        It returns a pair of a tuple of edges and the cost. If no path is found the first element is None.
840        The cost for the returned path is equal to the sum of all edge costs in the path,
841        including the internal connectors, if they are present in the network.
842        The path itself does not include internal edges except for the case
843        when the start or end edge are internal edges.
844        The search may be limited using the given threshold.
845        The preferences declare a mapping from the 'routingType' of each edge to divisor
846        that is applied to the computed traveltime of that edge
847        (i.e. a preference of 2 reduces effective traveltime by a factor of 0.5).
848        """
849
850        return self.getOptimalPath(fromEdge, toEdge, True, maxCost, vClass, reversalPenalty,
851                                   includeFromToCost, withInternal, ignoreDirection, fromPos, toPos,
852                                   preferences)
853
854    def getReachable(self, source, vclass=None, useIncoming=False, cache=None):
855        if vclass is not None and not source.allows(vclass):
856            raise RuntimeError("'{}' does not allow {}".format(source.getID(), vclass))
857        fringe = [source]
858        found = set()
859        found.add(source)
860        while len(fringe) > 0:
861            new_fringe = []
862            for e in fringe:
863                if vclass == "pedestrian":
864                    cands = chain(chain(*e.getIncoming().values()), chain(*e.getOutgoing().values()))
865                else:
866                    cands = chain(*(e.getIncoming().values() if useIncoming else e.getOutgoing().values()))
867                # print("\n".join(map(str, list(cands))))
868                for conn in cands:
869                    if vclass is None or (
870                            conn.getFromLane().allows(vclass)
871                            and conn.getToLane().allows(vclass)):
872                        for reachable in [conn.getTo(), conn.getFrom()]:
873                            if reachable not in found:
874                                # print("added %s via %s" % (reachable, conn))
875                                if cache and reachable in cache:
876                                    found.update(cache[reachable])
877                                else:
878                                    found.add(reachable)
879                                    new_fringe.append(reachable)
880            fringe = new_fringe
881        if cache is not None:
882            cache[source] = tuple(found)
883        return found

The whole sumo network.

hasInternal
hasWalkingArea
def initRoutingCache(self, maxsize=1000):
246    def initRoutingCache(self, maxsize=1000):
247        self._routingCache = lru_cache(maxsize=maxsize)(lambda fromEdge, fastest, vClass, ignoreDirection, reversalPenalty, preferences: {})  # noqa
def getVersion(self):
249    def getVersion(self):
250        return self._version
def getEdgeType(self, typeID):
252    def getEdgeType(self, typeID):
253        return self._edgeTypes[typeID]
def setLocation(self, netOffset, convBoundary, origBoundary, projParameter):
255    def setLocation(self, netOffset, convBoundary, origBoundary, projParameter):
256        self._location["netOffset"] = netOffset
257        self._location["convBoundary"] = convBoundary
258        self._location["origBoundary"] = origBoundary
259        self._location["projParameter"] = projParameter
def loadSelection(self, selectionFile):
261    def loadSelection(self, selectionFile):
262        self.resetSelection()
263        with io.open(selectionFile, "r", encoding="utf-8") as f:
264            for line in f:
265                line = line.strip()
266                if line.startswith("edge:"):
267                    edgeID = line[5:]
268                    if edgeID in self._id2edge:
269                        self.getEdge(edgeID).select()
270                elif line.startswith("junction:"):
271                    nodeID = line[9:]
272                    if nodeID in self._id2node:
273                        self.getNode(nodeID).select()
def resetSelection(self):
275    def resetSelection(self):
276        for n in self._nodes:
277            n.select(False)
278        for e in self._edges:
279            e.select(False)
def addNode(self, id, type=None, coord=None, incLanes=None, intLanes=None):
281    def addNode(self, id, type=None, coord=None, incLanes=None, intLanes=None):
282        if id is None:
283            return None
284        if id not in self._id2node:
285            n = node.Node(id, type, coord, incLanes, intLanes)
286            self._nodes.append(n)
287            self._id2node[id] = n
288        self.setAdditionalNodeInfo(
289            self._id2node[id], type, coord, incLanes, intLanes)
290        return self._id2node[id]
def setAdditionalNodeInfo(self, node, type, coord, incLanes, intLanes=None):
292    def setAdditionalNodeInfo(self, node, type, coord, incLanes, intLanes=None):
293        if coord is not None and node._coord is None:
294            node._coord = coord
295            self._ranges[0][0] = min(self._ranges[0][0], coord[0])
296            self._ranges[0][1] = max(self._ranges[0][1], coord[0])
297            self._ranges[1][0] = min(self._ranges[1][0], coord[1])
298            self._ranges[1][1] = max(self._ranges[1][1], coord[1])
299        if incLanes is not None and node._incLanes is None:
300            node._incLanes = incLanes
301        if intLanes is not None and node._intLanes is None:
302            node._intLanes = intLanes
303        if type is not None and node._type is None:
304            node._type = type
def addEdge( self, id, fromID, toID, prio, function, name, edgeType='', routingType=''):
306    def addEdge(self, id, fromID, toID, prio, function, name, edgeType='', routingType=''):
307        if id not in self._id2edge:
308            fromN = self.addNode(fromID)
309            toN = self.addNode(toID)
310            e = edge.Edge(id, fromN, toN, prio, function, name, edgeType, routingType)
311            self._edges.append(e)
312            self._id2edge[id] = e
313            if function:
314                self.hasInternal = True
315                if function == "walkingarea":
316                    self.hasWalkingArea = True
317        return self._id2edge[id]
def addLane( self, edge, speed, length, width, allow=None, disallow=None, acceleration=False):
319    def addLane(self, edge, speed, length, width, allow=None, disallow=None, acceleration=False):
320        return lane.Lane(edge, speed, length, width, allow, disallow, acceleration)
def addRoundabout(self, nodes, edges=None):
322    def addRoundabout(self, nodes, edges=None):
323        r = roundabout.Roundabout(nodes, edges)
324        self._roundabouts.append(r)
325        return r
def addConnection( self, fromEdge, toEdge, fromlane, tolane, direction, tls, tllink, tllink2, allow, disallow, state, viaLaneID=None):
327    def addConnection(self, fromEdge, toEdge, fromlane, tolane, direction,
328                      tls, tllink, tllink2, allow, disallow, state, viaLaneID=None):
329        conn = connection.Connection(
330            fromEdge, toEdge, fromlane, tolane, direction,
331            tls, tllink, tllink2, allow, disallow, state, viaLaneID)
332        fromEdge.addOutgoing(conn)
333        fromlane.addOutgoing(conn)
334        toEdge._addIncoming(conn)
335        if viaLaneID:
336            try:
337                # internal lanes are only available when building with option withInternal=True
338                viaLane = self.getLane(viaLaneID)
339                viaEdge = viaLane.getEdge()
340                viaEdge._addIncoming(connection.Connection(
341                    fromEdge, viaEdge, fromlane, viaLane, direction, tls,
342                    tllink, tllink2, allow, disallow, state, ''))
343            except Exception:
344                pass
345        return conn
def getEdges(self, withInternal=True):
347    def getEdges(self, withInternal=True):
348        if not withInternal:
349            return [e for e in self._edges if e.getFunction() == '']
350        else:
351            return self._edges
def getRoundabouts(self):
353    def getRoundabouts(self):
354        return self._roundabouts
def hasEdge(self, id):
356    def hasEdge(self, id):
357        return id in self._id2edge
def getEdge(self, id):
359    def getEdge(self, id):
360        return self._id2edge[id]
def getLane(self, laneID):
362    def getLane(self, laneID):
363        edge_id, lane_index = laneID.rsplit("_", 1)
364        return self.getEdge(edge_id).getLane(int(lane_index))
def getNeighboringEdges(self, x, y, r=0.1, includeJunctions=True, allowFallback=True):
375    def getNeighboringEdges(self, x, y, r=0.1, includeJunctions=True, allowFallback=True):
376        edges = []
377        rtree = self._rtreeEdges[includeJunctions]
378        try:
379            if rtree is None:
380                rtree = self._initRTree(self._edges, includeJunctions)
381                self._rtreeEdges[includeJunctions] = rtree
382            for i in rtree.intersection((x - r, y - r, x + r, y + r)):
383                e = self._edges[i]
384                d = sumolib.geomhelper.distancePointToPolygon(
385                    (x, y), e.getShape(includeJunctions))
386                if d < r:
387                    edges.append((e, d))
388        except ImportError:
389            if not allowFallback:
390                raise
391            warnings.warn("Module 'rtree' not available. Using brute-force fallback.")
392            for the_edge in self._edges:
393                d = sumolib.geomhelper.distancePointToPolygon((x, y), the_edge.getShape(includeJunctions))
394                if d < r:
395                    edges.append((the_edge, d))
396        return edges
def getNeighboringLanes(self, x, y, r=0.1, includeJunctions=True, allowFallback=True):
398    def getNeighboringLanes(self, x, y, r=0.1, includeJunctions=True, allowFallback=True):
399        lanes = []
400        rtree = self._rtreeLanes[includeJunctions]
401        try:
402            if rtree is None:
403                for the_edge in self._edges:
404                    self._allLanes += the_edge.getLanes()
405                rtree = self._initRTree(self._allLanes, includeJunctions)
406                self._rtreeLanes[includeJunctions] = rtree
407            for i in rtree.intersection((x - r, y - r, x + r, y + r)):
408                the_lane = self._allLanes[i]
409                d = sumolib.geomhelper.distancePointToPolygon((x, y), the_lane.getShape(includeJunctions))
410                if d < r:
411                    lanes.append((the_lane, d))
412        except ImportError:
413            if not allowFallback:
414                raise
415            warnings.warn("Module 'rtree' not available. Using brute-force fallback.")
416            for the_edge in self._edges:
417                for the_lane in the_edge.getLanes():
418                    d = sumolib.geomhelper.distancePointToPolygon((x, y), the_lane.getShape(includeJunctions))
419                    if d < r:
420                        lanes.append((the_lane, d))
421        return lanes
def getNearestLane(self, x, y, r=0.1, vClass=None, isGeo=False, allowFallback=True):
423    def getNearestLane(self, x, y, r=0.1, vClass=None, isGeo=False, allowFallback=True):
424        """Return (lane, lanePos, dist) for the nearest lane at a distance less than r,
425        or None if no lane matches. Both lanePos and dist are in meters.
426
427        If vClass is given, only lanes allowing that vehicle class are considered.
428        If isGeo is True, x and y are longitude and latitude, respectively, and
429        the network's geo-projection is used (requires pyproj).
430        Distances are measured to the lane shape without junction extensions.
431        Internal lanes are considered if the network was read withInternal=True.
432        """
433        if isGeo:
434            x, y = self.convertLonLat2XY(x, y)
435        candidates = [(lane, dist) for lane, dist in
436                      self.getNeighboringLanes(x, y, r, includeJunctions=False, allowFallback=allowFallback)
437                      if lane.allows(vClass)]
438        if not candidates:
439            return None
440        lane, dist = min(candidates, key=lambda candidate: candidate[1])
441        lanePos, dist = lane.getClosestLanePosAndDist((x, y))
442        return lane, lanePos, dist

Return (lane, lanePos, dist) for the nearest lane at a distance less than r, or None if no lane matches. Both lanePos and dist are in meters.

If vClass is given, only lanes allowing that vehicle class are considered. If isGeo is True, x and y are longitude and latitude, respectively, and the network's geo-projection is used (requires pyproj). Distances are measured to the lane shape without junction extensions. Internal lanes are considered if the network was read withInternal=True.

def hasNode(self, id):
444    def hasNode(self, id):
445        return id in self._id2node
def getNode(self, id):
447    def getNode(self, id):
448        return self._id2node[id]
def getNodes(self):
450    def getNodes(self):
451        return self._nodes
def getTLS(self, tlid):
453    def getTLS(self, tlid):
454        return self._id2tls[tlid]
def getTLSSecure(self, tlid):
456    def getTLSSecure(self, tlid):
457        if tlid in self._id2tls:
458            tls = self._id2tls[tlid]
459        else:
460            tls = TLS(tlid)
461            self._id2tls[tlid] = tls
462            self._tlss.append(tls)
463        return tls
def getTrafficLights(self):
465    def getTrafficLights(self):
466        return self._tlss
def addTLS(self, tlid, inLane, outLane, linkNo):
468    def addTLS(self, tlid, inLane, outLane, linkNo):
469        tls = self.getTLSSecure(tlid)
470        tls.addConnection(inLane, outLane, linkNo)
471        return tls
def addTLSProgram(self, tlid, programID, offset, type, removeOthers):
473    def addTLSProgram(self, tlid, programID, offset, type, removeOthers):
474        tls = self.getTLSSecure(tlid)
475        program = TLSProgram(programID, offset, type)
476        if removeOthers:
477            tls.removePrograms()
478        tls.addProgram(program)
479        return program
def setFoes(self, junctionID, index, foes, prohibits):
481    def setFoes(self, junctionID, index, foes, prohibits):
482        self._id2node[junctionID].setFoes(index, foes, prohibits)
def forbids(self, possProhibitor, possProhibited):
484    def forbids(self, possProhibitor, possProhibited):
485        return possProhibitor.getFrom().getToNode().forbids(possProhibitor, possProhibited)
def getUpstreamEdges(self, edge, distance, stopOnTLS, stopOnTurnaround):
487    def getUpstreamEdges(self, edge, distance, stopOnTLS, stopOnTurnaround):
488        """return a list of lists of the form
489           [[firstEdge, pos, [edge_0, edge_1, ..., edge_k], aborted], ...]
490           where
491             firstEdge: is the upstream edge furthest away from the intersection,
492             [edge_0, ..., edge_k]: is the list of edges from the intersection upstream to firstEdge
493             pos: is the position on firstEdge with distance to the end of the input edge
494             aborted: a flag indicating whether the downstream
495                 search stopped at a TLS or a node without incoming edges before reaching the distance threshold
496        """
497        ret = []
498        seen = set()
499        toProc = []
500        toProc.append([edge, 0, []])
501        while not len(toProc) == 0:
502            ie = toProc.pop()
503            if ie[0] in seen:
504                continue
505            seen.add(ie[0])
506            if ie[1] + ie[0].getLength() >= distance:
507                ret.append(
508                    [ie[0], ie[0].getLength() + ie[1] - distance, ie[2], False])
509                continue
510            if len(ie[0]._incoming) == 0:
511                ret.append([ie[0], ie[0].getLength() + ie[1], ie[2], True])
512                continue
513            mn = []
514            stop = False
515            for ci in ie[0]._incoming:
516                if ci not in seen:
517                    prev = copy(ie[2])
518                    if stopOnTLS and ci._tls and ci != edge and not stop:
519                        ret.append([ie[0], ie[1], prev, True])
520                        stop = True
521                    elif (stopOnTurnaround and ie[0]._incoming[ci][0].getDirection() == Connection.LINKDIR_TURN and
522                          not stop):
523                        ret.append([ie[0], ie[1], prev, True])
524                        stop = True
525                    else:
526                        prev.append(ie[0])
527                        mn.append([ci, ie[0].getLength() + ie[1], prev])
528            if not stop:
529                toProc.extend(mn)
530        return ret

return a list of lists of the form [[firstEdge, pos, [edge_0, edge_1, ..., edge_k], aborted], ...] where firstEdge: is the upstream edge furthest away from the intersection, pos: is the position on firstEdge with distance to the end of the input edge aborted: a flag indicating whether the downstream search stopped at a TLS or a node without incoming edges before reaching the distance threshold

def getEdgesByOrigID(self, origID):
532    def getEdgesByOrigID(self, origID):
533        if self._origIdx is None:
534            self._origIdx = defaultdict(set)
535            for the_edge in self._edges:
536                for the_lane in the_edge.getLanes():
537                    for oID in the_lane.getParam("origId", "").split():
538                        self._origIdx[oID].add(the_edge)
539        return self._origIdx[origID]
def getGeometries(self, useLanes, includeJunctions=False):
541    def getGeometries(self, useLanes, includeJunctions=False):
542        for e in self._edges:
543            if useLanes:
544                for the_lane in e.getLanes():
545                    yield the_lane.getID(), the_lane.getShape(), the_lane.getWidth()
546            else:
547                yield e.getID(), e.getShape(includeJunctions), sum([the_lane.getWidth() for the_lane in e.getLanes()])
def getBBoxXY(self):
549    def getBBoxXY(self):
550        """
551        Get the bounding box (bottom left and top right coordinates) for a net;
552        Coordinates are in X and Y (not Lat and Lon)
553
554        :return [(bottom_left_X, bottom_left_Y), (top_right_X, top_right_Y)]
555        """
556        return [(self._ranges[0][0], self._ranges[1][0]),
557                (self._ranges[0][1], self._ranges[1][1])]

Get the bounding box (bottom left and top right coordinates) for a net; Coordinates are in X and Y (not Lat and Lon)

:return [(bottom_left_X, bottom_left_Y), (top_right_X, top_right_Y)]

def getBBoxDiameter(self):
560    def getBBoxDiameter(self):
561        return math.sqrt(
562            (self._ranges[0][0] - self._ranges[0][1]) ** 2 +
563            (self._ranges[1][0] - self._ranges[1][1]) ** 2)
def hasGeoProj(self):
565    def hasGeoProj(self):
566        projString = self._location["projParameter"]
567        return projString != "!"
def getGeoProj(self):
569    def getGeoProj(self):
570        if not self.hasGeoProj() or not HAVE_PYPROJ:
571            raise RuntimeError("Network does not provide geo-projection or pyproj not installed.")
572        if self._proj is None:
573            try:
574                self._proj = pyproj.Proj(projparams=self._location["projParameter"])
575            except RuntimeError:
576                if hasattr(pyproj.datadir, 'set_data_dir'):
577                    pyproj.datadir.set_data_dir('/usr/share/proj')
578                    self._proj = pyproj.Proj(projparams=self._location["projParameter"])
579                raise
580        return self._proj
def getLocationOffset(self):
582    def getLocationOffset(self):
583        """ offset to be added after converting from geo-coordinates to UTM"""
584        return list(map(float, self._location["netOffset"].split(",")))

offset to be added after converting from geo-coordinates to UTM

def getBoundary(self):
586    def getBoundary(self):
587        """ return xmin,ymin,xmax,ymax network coordinates"""
588        return list(map(float, self._location["convBoundary"].split(",")))

return xmin,ymin,xmax,ymax network coordinates

def convertLonLat2XY(self, lon, lat, rawUTM=False):
590    def convertLonLat2XY(self, lon, lat, rawUTM=False):
591        x, y = self.getGeoProj()(lon, lat)
592        if rawUTM:
593            return x, y
594        else:
595            x_off, y_off = self.getLocationOffset()
596            return x + x_off, y + y_off
def convertXY2LonLat(self, x, y, rawUTM=False):
598    def convertXY2LonLat(self, x, y, rawUTM=False):
599        if not rawUTM:
600            x_off, y_off = self.getLocationOffset()
601            x -= x_off
602            y -= y_off
603        return self.getGeoProj()(x, y, inverse=True)
def move(self, dx, dy, dz=0):
605    def move(self, dx, dy, dz=0):
606        for n in self._nodes:
607            n._coord = (n._coord[0] + dx, n._coord[1] + dy, n._coord[2] + dz)
608        for e in self._edges:
609            for _lane in e.getLanes():
610                _lane.setShape([(p[0] + dx, p[1] + dy, p[2] + dz) for p in _lane.getShape3D()])
611            e.rebuildShape()
def getInternalPath(self, conn, fastest=False):
613    def getInternalPath(self, conn, fastest=False):
614        minInternalCost = PRACTICAL_INFINITY
615        minPath = None
616        for c in conn:
617            if c.getViaLaneID() != "":
618                viaCost = 0
619                viaID = c.getViaLaneID()
620                viaPath = []
621                while viaID != "":
622                    viaLane = self.getLane(viaID)
623                    viaCost += viaLane.getLength() if not fastest else viaLane.getLength() / viaLane.getSpeed()
624                    viaID = viaLane.getOutgoing()[0].getViaLaneID()
625                    viaPath.append(viaLane.getEdge())
626                if viaCost < minInternalCost:
627                    minInternalCost = viaCost
628                    minPath = viaPath
629        return minPath, minInternalCost
def getOptimalPath( self, fromEdge, toEdge, fastest=False, maxCost=inf, vClass=None, reversalPenalty=0, includeFromToCost=True, withInternal=False, ignoreDirection=False, fromPos=None, toPos=None, preferences={}):
631    def getOptimalPath(self, fromEdge, toEdge, fastest=False, maxCost=PRACTICAL_INFINITY, vClass=None,
632                       reversalPenalty=0, includeFromToCost=True, withInternal=False, ignoreDirection=False,
633                       fromPos=None, toPos=None, preferences={}):
634        """
635        Finds the optimal (shortest or fastest) path for vClass from fromEdge to toEdge
636        by using using Dijkstra's algorithm.
637        It returns a pair of a tuple of edges and the cost.
638        If no path is found the first element is None.
639        The cost for the returned path is equal to the sum of all edge costs in the path,
640        including the internal connectors, if they are present in the network.
641        The path itself does not include internal edges except for the case
642        when the start or end edge are internal edges.
643        The search may be limited using the given threshold.
644        The preferences declare a mapping from the 'routingType' of each edge to divisor
645        that is applied to the computed cost of that edge (i.e. a preference of 2 reduces cost by a factor of 0.5).
646        """
647
648        if preferences:
649            if fastest:
650                def speedFunc(edge):
651                    return preferences.get(edge.getRoutingType(), 1.0) * edge.getSpeed()
652            else:
653                def speedFunc(edge):
654                    return preferences.get(edge.getRoutingType(), 1.0)
655        elif fastest:
656            def speedFunc(edge):
657                return edge.getSpeed()
658        else:
659            def speedFunc(edge):
660                return 1.0
661
662        def remainder(edge, pos):
663            if pos < 0:
664                return min(-pos, edge.getLength())
665            return max(0., edge.getLength() - pos)
666
667        def getToNormalIncoming(edge):
668            if edge.getFunction() == '':
669                return [(e, None) for e in edge.getToNode().getIncoming() if e.getFunction() == '']
670            else:
671                return []
672
673        if self._edgeSuccessorCache != (vClass, ignoreDirection):
674            self._edgeSuccessorCache = (vClass, ignoreDirection)
675            for e1 in self.getEdges():
676                e1._succCacche = tuple(chain(e1.getAllowedOutgoing(vClass).items(),
677                                       e1.getIncoming().items() if ignoreDirection else [],
678                                       getToNormalIncoming(e1) if ignoreDirection and not self.hasWalkingArea else []))
679
680        if self._edgeCostCache != (fastest, preferences):
681            self._edgeCostCache = (fastest, preferences)
682            for e1 in self.getEdges():
683                e1._costCache = e1.getLength() / speedFunc(e1)
684
685        if self.hasInternal:
686            appendix = []
687            appendixCost = 0.
688            while toEdge.getFunction() == "internal":
689                appendix = [toEdge] + appendix
690                appendixCost += toEdge._costCache
691                toEdge = list(toEdge.getIncoming().keys())[0]
692
693        def finalizeCost(cost, path):
694            if includeFromToCost:
695                # add costs for (part of) the first edge, still needs to be fixed for wrong direction travel
696                remainFrom = fromEdge.getLength() if fromPos is None else remainder(fromEdge, fromPos)
697                cost += remainFrom / speedFunc(fromEdge)
698                # remove costs for (part of) the last edge, still needs to be fixed for wrong direction travel
699                removeTo = 0. if toPos is None else remainder(toEdge, toPos)
700            else:
701                removeTo = toEdge.getLength() if len(path) > 1 else 0.
702            cost -= removeTo / speedFunc(toEdge)
703            return cost
704
705        def constructPath(dist):
706            # destination was already reached in a previous query
707            cost, pred = dist[toEdge]
708            path = [toEdge]
709            while pred is not None:
710                if self.hasInternal and withInternal:
711                    viaPath, minInternalCost = self.getInternalPath(pred.getAllowedOutgoing(vClass).get(path[-1], []),
712                                                                    fastest=fastest)
713                    if viaPath is not None:
714                        path += reversed(viaPath)
715                path.append(pred)
716                _, pred = dist[pred]
717
718            path.reverse()
719            cost = finalizeCost(cost, path)
720            assert cost >= 0
721            if self.hasInternal:
722                if appendix:
723                    return tuple(path + appendix), cost + appendixCost
724                elif ignoreDirection and self.hasWalkingArea and not withInternal:
725                    return [e for e in path if e.getFunction() == ''], cost
726            return tuple(path), cost
727
728        needLoop = (fromEdge == toEdge
729                    and fromPos is not None
730                    and toPos is not None
731                    and fromPos > toPos
732                    and not ignoreDirection)
733
734        seen = set()
735        dist = {}
736        q = []
737        if self._routingCache is not None:
738            if needLoop:
739                # use cached results from all follower edges:
740                bestCost = maxCost
741                bestPath = None
742                for e2, conn in fromEdge.getAllowedOutgoing(vClass).items():
743                    path, cost = self.getOptimalPath(e2, toEdge, fastest=fastest, maxCost=maxCost, vClass=vClass,
744                                                     reversalPenalty=reversalPenalty,
745                                                     includeFromToCost=includeFromToCost,
746                                                     withInternal=withInternal, fromPos=0, toPos=toPos,
747                                                     preferences=preferences)
748                    if path is not None and cost < bestCost:
749                        bestPath = path
750                        bestCost = cost
751                if bestPath is not None:
752                    path = [fromEdge]
753                    if self.hasInternal and withInternal:
754                        viaPath, minInternalCost = self.getInternalPath(
755                            fromEdge.getAllowedOutgoing(vClass).get(path[0], []), fastest=fastest)
756                        if viaPath is not None:
757                            path += viaPath
758                            bestCost += minInternalCost
759                    path += list(bestPath)
760                    if includeFromToCost:
761                        bestCost += remainder(fromEdge, fromPos) / speedFunc(fromEdge)
762                    return tuple(path), bestCost
763                else:
764                    return None, PRACTICAL_INFINITY
765
766            dist = self._routingCache(fromEdge, fastest, vClass, ignoreDirection, reversalPenalty,
767                                      tuple(preferences.items()))
768            if toEdge in dist:
769                return constructPath(dist)
770            else:
771                # initialize heap from previous query
772                q = []
773                frontier = set(dist.keys())
774                for cost, prev in dist.values():
775                    frontier.discard(prev)
776                for e in frontier:
777                    cost, prev = dist[e]
778                    heapq.heappush(q, (cost, e, prev))
779        elif needLoop:
780            # start search on successors of fromEdge
781            for e2, conn in fromEdge.getAllowedOutgoing(vClass).items():
782                q.append((e2._costCache, e2, fromEdge))
783
784        if len(dist) == 0:
785            dist[fromEdge] = (0., None)
786            if not needLoop:
787                q.append((0., fromEdge, None))
788
789        while q:
790            cost, e1, prev = heapq.heappop(q)
791            if e1 in seen:
792                continue
793            seen.add(e1)
794            if e1 == toEdge:
795                return constructPath(dist)
796            if cost > maxCost:
797                return None, cost
798
799            for e2, conn in e1._succCacche:
800                if e2 not in seen:
801                    newCost = cost + e2._costCache
802                    #  print(cost, newCost, e2.getID(), speedFunc(e2))
803                    if e2 == e1.getBidi():
804                        newCost += reversalPenalty
805                    if self.hasInternal and conn is not None:
806                        viaPath, minInternalCost = self.getInternalPath(conn, fastest=fastest)
807                        if viaPath is not None:
808                            newCost += minInternalCost
809                    if e2 not in dist or newCost < dist[e2][0]:
810                        dist[e2] = (newCost, e1)
811                        heapq.heappush(q, (newCost, e2, e1))
812        return None, PRACTICAL_INFINITY

Finds the optimal (shortest or fastest) path for vClass from fromEdge to toEdge by using using Dijkstra's algorithm. It returns a pair of a tuple of edges and the cost. If no path is found the first element is None. The cost for the returned path is equal to the sum of all edge costs in the path, including the internal connectors, if they are present in the network. The path itself does not include internal edges except for the case when the start or end edge are internal edges. The search may be limited using the given threshold. The preferences declare a mapping from the 'routingType' of each edge to divisor that is applied to the computed cost of that edge (i.e. a preference of 2 reduces cost by a factor of 0.5).

def getShortestPath( self, fromEdge, toEdge, maxCost=inf, vClass=None, reversalPenalty=0, includeFromToCost=True, withInternal=False, ignoreDirection=False, fromPos=None, toPos=None, preferences={}):
814    def getShortestPath(self, fromEdge, toEdge, maxCost=PRACTICAL_INFINITY, vClass=None, reversalPenalty=0,
815                        includeFromToCost=True, withInternal=False, ignoreDirection=False,
816                        fromPos=None, toPos=None, preferences={}):
817        """
818        Finds the shortest path from fromEdge to toEdge respecting vClass, using Dijkstra's algorithm.
819        It returns a pair of a tuple of edges and the cost. If no path is found the first element is None.
820        The cost for the returned path is equal to the sum of all edge lengths in the path,
821        including the internal connectors, if they are present in the network.
822        The path itself does not include internal edges except for the case
823        when the start or end edge are internal edges.
824        The search may be limited using the given threshold.
825        The preferences declare a mapping from the 'routingType' of each edge to divisor
826        that is applied to the lenght of that edge
827        (i.e. a preference of 2 reduces reduces effective length by a factor of 0.5).
828        """
829
830        return self.getOptimalPath(fromEdge, toEdge, False, maxCost, vClass, reversalPenalty,
831                                   includeFromToCost, withInternal, ignoreDirection, fromPos, toPos,
832                                   preferences)

Finds the shortest path from fromEdge to toEdge respecting vClass, using Dijkstra's algorithm. It returns a pair of a tuple of edges and the cost. If no path is found the first element is None. The cost for the returned path is equal to the sum of all edge lengths in the path, including the internal connectors, if they are present in the network. The path itself does not include internal edges except for the case when the start or end edge are internal edges. The search may be limited using the given threshold. The preferences declare a mapping from the 'routingType' of each edge to divisor that is applied to the lenght of that edge (i.e. a preference of 2 reduces reduces effective length by a factor of 0.5).

def getFastestPath( self, fromEdge, toEdge, maxCost=inf, vClass=None, reversalPenalty=0, includeFromToCost=True, withInternal=False, ignoreDirection=False, fromPos=None, toPos=None, preferences={}):
834    def getFastestPath(self, fromEdge, toEdge, maxCost=PRACTICAL_INFINITY, vClass=None, reversalPenalty=0,
835                       includeFromToCost=True, withInternal=False, ignoreDirection=False,
836                       fromPos=None, toPos=None, preferences={}):
837        """
838        Finds the fastest path from fromEdge to toEdge respecting vClass, using Dijkstra's algorithm.
839        It returns a pair of a tuple of edges and the cost. If no path is found the first element is None.
840        The cost for the returned path is equal to the sum of all edge costs in the path,
841        including the internal connectors, if they are present in the network.
842        The path itself does not include internal edges except for the case
843        when the start or end edge are internal edges.
844        The search may be limited using the given threshold.
845        The preferences declare a mapping from the 'routingType' of each edge to divisor
846        that is applied to the computed traveltime of that edge
847        (i.e. a preference of 2 reduces effective traveltime by a factor of 0.5).
848        """
849
850        return self.getOptimalPath(fromEdge, toEdge, True, maxCost, vClass, reversalPenalty,
851                                   includeFromToCost, withInternal, ignoreDirection, fromPos, toPos,
852                                   preferences)

Finds the fastest path from fromEdge to toEdge respecting vClass, using Dijkstra's algorithm. It returns a pair of a tuple of edges and the cost. If no path is found the first element is None. The cost for the returned path is equal to the sum of all edge costs in the path, including the internal connectors, if they are present in the network. The path itself does not include internal edges except for the case when the start or end edge are internal edges. The search may be limited using the given threshold. The preferences declare a mapping from the 'routingType' of each edge to divisor that is applied to the computed traveltime of that edge (i.e. a preference of 2 reduces effective traveltime by a factor of 0.5).

def getReachable(self, source, vclass=None, useIncoming=False, cache=None):
854    def getReachable(self, source, vclass=None, useIncoming=False, cache=None):
855        if vclass is not None and not source.allows(vclass):
856            raise RuntimeError("'{}' does not allow {}".format(source.getID(), vclass))
857        fringe = [source]
858        found = set()
859        found.add(source)
860        while len(fringe) > 0:
861            new_fringe = []
862            for e in fringe:
863                if vclass == "pedestrian":
864                    cands = chain(chain(*e.getIncoming().values()), chain(*e.getOutgoing().values()))
865                else:
866                    cands = chain(*(e.getIncoming().values() if useIncoming else e.getOutgoing().values()))
867                # print("\n".join(map(str, list(cands))))
868                for conn in cands:
869                    if vclass is None or (
870                            conn.getFromLane().allows(vclass)
871                            and conn.getToLane().allows(vclass)):
872                        for reachable in [conn.getTo(), conn.getFrom()]:
873                            if reachable not in found:
874                                # print("added %s via %s" % (reachable, conn))
875                                if cache and reachable in cache:
876                                    found.update(cache[reachable])
877                                else:
878                                    found.add(reachable)
879                                    new_fringe.append(reachable)
880            fringe = new_fringe
881        if cache is not None:
882            cache[source] = tuple(found)
883        return found
class NetReader(xml.sax.handler.ContentHandler):
 886class NetReader(handler.ContentHandler):
 887
 888    """Reads a network, storing the edge geometries, lane numbers and max. speeds"""
 889
 890    def __init__(self, **others):
 891        self._net = others.get('net', Net())
 892        self._currentEdge = None
 893        self._currentNode = None
 894        self._currentConnection = None
 895        self._currentLane = None
 896        self._crossingID2edgeIDs = {}
 897        self._withPhases = others.get('withPrograms', False)
 898        self._latestProgram = others.get('withLatestPrograms', False)
 899        if self._latestProgram:
 900            self._withPhases = True
 901        self._withConnections = others.get('withConnections', True)
 902        self._withFoes = others.get('withFoes', True)
 903        self._withPedestrianConnections = others.get('withPedestrianConnections', False)
 904        self._withMacroConnectors = others.get('withMacroConnectors', False)
 905        self._withInternal = others.get('withInternal', self._withPedestrianConnections)
 906        if self._withPedestrianConnections and not self._withInternal:
 907            sys.stderr.write("Warning: Option withPedestrianConnections requires withInternal\n")
 908            self._withInternal = True
 909        self._bidiEdgeIDs = {}
 910
 911    def startElement(self, name, attrs):
 912        if name == 'net':
 913            parts = attrs["version"].split('.', 1)
 914            self._net._version = (int(parts[0]), float(parts[1]))
 915        elif name == 'location':
 916            self._net.setLocation(attrs["netOffset"], attrs["convBoundary"], attrs[
 917                                  "origBoundary"], attrs["projParameter"])
 918        elif name == 'type':
 919            self._net._edgeTypes[attrs['id']] = EdgeType(attrs['id'], attrs.get('allow'), attrs.get('disallow'))
 920        elif name == 'edge':
 921            function = attrs.get('function', '')
 922            if (function == ''
 923                    or (self._withInternal and function in ['internal', 'crossing', 'walkingarea'])
 924                    or (self._withMacroConnectors and function == 'connector')):
 925                prio = -1
 926                if 'priority' in attrs:
 927                    prio = int(attrs['priority'])
 928
 929                # get the  ids
 930                edgeID = attrs['id']
 931                fromNodeID = attrs.get('from', None)
 932                toNodeID = attrs.get('to', None)
 933
 934                # for internal junctions use the junction's id for from and to node
 935                if function == 'internal' or function == 'crossing' or function == 'walkingarea':
 936                    fromNodeID = toNodeID = edgeID[1:edgeID.rfind('_')]
 937
 938                # remember edges crossed by pedestrians to link them later to the crossing objects
 939                if function == 'crossing':
 940                    self._crossingID2edgeIDs[edgeID] = attrs.get('crossingEdges').split(' ')
 941
 942                self._currentEdge = self._net.addEdge(edgeID, fromNodeID, toNodeID, prio, function,
 943                                                      attrs.get('name', ''), attrs.get('type', ''),
 944                                                      attrs.get('routingType', ''))
 945
 946                self._currentEdge.setRawShape(convertShape(attrs.get('shape', '')))
 947
 948                bidi = attrs.get('bidi', '')
 949                if bidi:
 950                    self._bidiEdgeIDs[edgeID] = bidi
 951            else:
 952                if function in ['crossing', 'walkingarea']:
 953                    self._net._crossings_and_walkingAreas.add(attrs['id'])
 954                elif function == 'connector':
 955                    self._net._macroConnectors.add(attrs['id'])
 956                self._currentEdge = None
 957        elif name == 'lane' and self._currentEdge is not None:
 958            self._currentLane = self._net.addLane(
 959                self._currentEdge,
 960                float(attrs['speed']),
 961                float(attrs['length']),
 962                float(attrs.get('width', 3.2)),
 963                attrs.get('allow'),
 964                attrs.get('disallow'),
 965                attrs.get('acceleration') == "1")
 966            self._currentLane.setShape(convertShape(attrs.get('shape', '')))
 967        elif name == 'neigh' and self._currentLane is not None:
 968            self._currentLane.setNeigh(attrs['lane'])
 969        elif name == 'junction':
 970            if attrs['id'][0] != ':':
 971                intLanes = None
 972                if self._withInternal:
 973                    intLanes = attrs["intLanes"].split(" ")
 974                self._currentNode = self._net.addNode(attrs['id'], attrs['type'],
 975                                                      tuple(
 976                                                          map(float, [attrs['x'], attrs['y'],
 977                                                                      attrs['z'] if 'z' in attrs else '0'])),
 978                                                      attrs['incLanes'].split(" "), intLanes)
 979                self._currentNode.setShape(
 980                    convertShape(attrs.get('shape', '')))
 981                if 'fringe' in attrs:
 982                    self._currentNode._fringe = attrs['fringe']
 983
 984        elif name == 'succ' and self._withConnections:  # deprecated
 985            if attrs['edge'][0] != ':':
 986                self._currentEdge = self._net.getEdge(attrs['edge'])
 987                self._currentLane = attrs['lane']
 988                self._currentLane = int(
 989                    self._currentLane[self._currentLane.rfind('_') + 1:])
 990            else:
 991                self._currentEdge = None
 992        elif name == 'succlane' and self._withConnections:  # deprecated
 993            lid = attrs['lane']
 994            if lid[0] != ':' and lid != "SUMO_NO_DESTINATION" and self._currentEdge:
 995                connected = self._net.getEdge(lid[:lid.rfind('_')])
 996                tolane = int(lid[lid.rfind('_') + 1:])
 997                if 'tl' in attrs and attrs['tl'] != "":
 998                    tl = attrs['tl']
 999                    tllink = int(attrs['linkIdx'])
1000                    tlid = attrs['tl']
1001                    toEdge = self._net.getEdge(lid[:lid.rfind('_')])
1002                    tolane2 = toEdge._lanes[tolane]
1003                    tls = self._net.addTLS(
1004                        tlid, self._currentEdge._lanes[self._currentLane], tolane2, tllink)
1005                    self._currentEdge.setTLS(tls)
1006                else:
1007                    tl = ""
1008                    tllink = -1
1009                toEdge = self._net.getEdge(lid[:lid.rfind('_')])
1010                tolane = toEdge._lanes[tolane]
1011                viaLaneID = attrs['via']
1012                self._net.addConnection(self._currentEdge, connected, self._currentEdge._lanes[
1013                                        self._currentLane], tolane,
1014                                        attrs['dir'], tl, tllink, -1,
1015                                        attrs.get('allow'), attrs.get('disallow'), attrs['state'], viaLaneID)
1016        elif name == 'connection' and self._withConnections and (attrs['from'][0] != ":" or self._withInternal):
1017            fromEdgeID = attrs['from']
1018            toEdgeID = attrs['to']
1019            if ((self._withPedestrianConnections or not (fromEdgeID in self._net._crossings_and_walkingAreas or
1020                                                         toEdgeID in self._net._crossings_and_walkingAreas))
1021                and (self._withMacroConnectors or not (fromEdgeID in self._net._macroConnectors or toEdgeID in
1022                                                       self._net._macroConnectors))):
1023                fromEdge = self._net.getEdge(fromEdgeID)
1024                toEdge = self._net.getEdge(toEdgeID)
1025                fromLane = fromEdge.getLane(int(attrs['fromLane']))
1026                toLane = toEdge.getLane(int(attrs['toLane']))
1027                if 'tl' in attrs and attrs['tl'] != "":
1028                    tl = attrs['tl']
1029                    tllink = int(attrs['linkIndex'])
1030                    tllink2 = int(attrs.get('linkIndex2', -1))
1031                    tls = self._net.addTLS(tl, fromLane, toLane, tllink)
1032                    fromEdge.setTLS(tls)
1033                else:
1034                    tl = ""
1035                    tllink = -1
1036                    tllink2 = -1
1037                try:
1038                    viaLaneID = attrs['via']
1039                except KeyError:
1040                    viaLaneID = ''
1041
1042                self._currentConnection = self._net.addConnection(
1043                    fromEdge, toEdge, fromLane, toLane, attrs['dir'], tl,
1044                    tllink, tllink2, attrs.get('allow'), attrs.get('disallow'), attrs['state'], viaLaneID)
1045
1046        # 'row-logic' is deprecated!!!
1047        elif self._withFoes and name == 'ROWLogic':
1048            self._currentNode = attrs['id']
1049        elif name == 'logicitem' and self._withFoes:  # deprecated
1050            self._net.setFoes(
1051                self._currentNode, int(attrs['request']), attrs["foes"], attrs["response"])
1052        elif name == 'request' and self._withFoes:
1053            self._currentNode.setFoes(
1054                int(attrs['index']), attrs["foes"], attrs["response"])
1055        # tl-logic is deprecated!!! NOTE: nevertheless, this is still used by
1056        # netconvert... (Leo)
1057        elif self._withPhases and name == 'tlLogic':
1058            self._currentProgram = self._net.addTLSProgram(
1059                attrs['id'], attrs['programID'],
1060                intIfPossible(float(attrs['offset'])), attrs['type'], self._latestProgram)
1061        elif self._withPhases and name == 'phase':
1062            self._currentProgram.addPhase(
1063                attrs['state'],
1064                intIfPossible(float(attrs['duration'])),
1065                intIfPossible(float(attrs['minDur'])) if 'minDur' in attrs else -1,
1066                intIfPossible(float(attrs['maxDur'])) if 'maxDur' in attrs else -1,
1067                list(map(int, attrs['next'].split())) if 'next' in attrs else [],
1068                attrs['name'] if 'name' in attrs else ""
1069            )
1070        elif name == 'roundabout':
1071            self._net.addRoundabout(
1072                attrs['nodes'].split(), attrs['edges'].split())
1073        elif name == 'param':
1074            if self._currentLane is not None:
1075                self._currentLane.setParam(attrs['key'], attrs['value'])
1076            elif self._currentEdge is not None:
1077                self._currentEdge.setParam(attrs['key'], attrs['value'])
1078            elif self._currentNode is not None:
1079                self._currentNode.setParam(attrs['key'], attrs['value'])
1080            elif self._currentConnection is not None:
1081                self._currentConnection.setParam(attrs['key'], attrs['value'])
1082            elif self._withPhases and self._currentProgram is not None:
1083                self._currentProgram.setParam(attrs['key'], attrs['value'])
1084
1085    def endElement(self, name):
1086        if name == 'lane':
1087            self._currentLane = None
1088        elif name == 'edge':
1089            self._currentEdge = None
1090        elif name == 'junction':
1091            self._currentNode = None
1092        elif name == 'connection':
1093            self._currentConnection = None
1094        # 'row-logic' is deprecated!!!
1095        elif name == 'ROWLogic' or name == 'row-logic':
1096            self._haveROWLogic = False
1097        # tl-logic is deprecated!!!
1098        elif self._withPhases and (name == 'tlLogic' or name == 'tl-logic'):
1099            self._currentProgram = None
1100        elif name == 'net':
1101            for edgeID, bidiID in self._bidiEdgeIDs.items():
1102                self._net.getEdge(edgeID)._bidi = self._net.getEdge(bidiID)
1103
1104    def endDocument(self):
1105        # set crossed edges of pedestrian crossings
1106        for crossingID, crossedEdgeIDs in self._crossingID2edgeIDs.items():
1107            pedCrossing = self._net.getEdge(crossingID)
1108            for crossedEdgeID in crossedEdgeIDs:
1109                pedCrossing._addCrossingEdge(self._net.getEdge(crossedEdgeID))
1110
1111    def getNet(self):
1112        return self._net

Reads a network, storing the edge geometries, lane numbers and max. speeds

NetReader(**others)
890    def __init__(self, **others):
891        self._net = others.get('net', Net())
892        self._currentEdge = None
893        self._currentNode = None
894        self._currentConnection = None
895        self._currentLane = None
896        self._crossingID2edgeIDs = {}
897        self._withPhases = others.get('withPrograms', False)
898        self._latestProgram = others.get('withLatestPrograms', False)
899        if self._latestProgram:
900            self._withPhases = True
901        self._withConnections = others.get('withConnections', True)
902        self._withFoes = others.get('withFoes', True)
903        self._withPedestrianConnections = others.get('withPedestrianConnections', False)
904        self._withMacroConnectors = others.get('withMacroConnectors', False)
905        self._withInternal = others.get('withInternal', self._withPedestrianConnections)
906        if self._withPedestrianConnections and not self._withInternal:
907            sys.stderr.write("Warning: Option withPedestrianConnections requires withInternal\n")
908            self._withInternal = True
909        self._bidiEdgeIDs = {}
def startElement(self, name, attrs):
 911    def startElement(self, name, attrs):
 912        if name == 'net':
 913            parts = attrs["version"].split('.', 1)
 914            self._net._version = (int(parts[0]), float(parts[1]))
 915        elif name == 'location':
 916            self._net.setLocation(attrs["netOffset"], attrs["convBoundary"], attrs[
 917                                  "origBoundary"], attrs["projParameter"])
 918        elif name == 'type':
 919            self._net._edgeTypes[attrs['id']] = EdgeType(attrs['id'], attrs.get('allow'), attrs.get('disallow'))
 920        elif name == 'edge':
 921            function = attrs.get('function', '')
 922            if (function == ''
 923                    or (self._withInternal and function in ['internal', 'crossing', 'walkingarea'])
 924                    or (self._withMacroConnectors and function == 'connector')):
 925                prio = -1
 926                if 'priority' in attrs:
 927                    prio = int(attrs['priority'])
 928
 929                # get the  ids
 930                edgeID = attrs['id']
 931                fromNodeID = attrs.get('from', None)
 932                toNodeID = attrs.get('to', None)
 933
 934                # for internal junctions use the junction's id for from and to node
 935                if function == 'internal' or function == 'crossing' or function == 'walkingarea':
 936                    fromNodeID = toNodeID = edgeID[1:edgeID.rfind('_')]
 937
 938                # remember edges crossed by pedestrians to link them later to the crossing objects
 939                if function == 'crossing':
 940                    self._crossingID2edgeIDs[edgeID] = attrs.get('crossingEdges').split(' ')
 941
 942                self._currentEdge = self._net.addEdge(edgeID, fromNodeID, toNodeID, prio, function,
 943                                                      attrs.get('name', ''), attrs.get('type', ''),
 944                                                      attrs.get('routingType', ''))
 945
 946                self._currentEdge.setRawShape(convertShape(attrs.get('shape', '')))
 947
 948                bidi = attrs.get('bidi', '')
 949                if bidi:
 950                    self._bidiEdgeIDs[edgeID] = bidi
 951            else:
 952                if function in ['crossing', 'walkingarea']:
 953                    self._net._crossings_and_walkingAreas.add(attrs['id'])
 954                elif function == 'connector':
 955                    self._net._macroConnectors.add(attrs['id'])
 956                self._currentEdge = None
 957        elif name == 'lane' and self._currentEdge is not None:
 958            self._currentLane = self._net.addLane(
 959                self._currentEdge,
 960                float(attrs['speed']),
 961                float(attrs['length']),
 962                float(attrs.get('width', 3.2)),
 963                attrs.get('allow'),
 964                attrs.get('disallow'),
 965                attrs.get('acceleration') == "1")
 966            self._currentLane.setShape(convertShape(attrs.get('shape', '')))
 967        elif name == 'neigh' and self._currentLane is not None:
 968            self._currentLane.setNeigh(attrs['lane'])
 969        elif name == 'junction':
 970            if attrs['id'][0] != ':':
 971                intLanes = None
 972                if self._withInternal:
 973                    intLanes = attrs["intLanes"].split(" ")
 974                self._currentNode = self._net.addNode(attrs['id'], attrs['type'],
 975                                                      tuple(
 976                                                          map(float, [attrs['x'], attrs['y'],
 977                                                                      attrs['z'] if 'z' in attrs else '0'])),
 978                                                      attrs['incLanes'].split(" "), intLanes)
 979                self._currentNode.setShape(
 980                    convertShape(attrs.get('shape', '')))
 981                if 'fringe' in attrs:
 982                    self._currentNode._fringe = attrs['fringe']
 983
 984        elif name == 'succ' and self._withConnections:  # deprecated
 985            if attrs['edge'][0] != ':':
 986                self._currentEdge = self._net.getEdge(attrs['edge'])
 987                self._currentLane = attrs['lane']
 988                self._currentLane = int(
 989                    self._currentLane[self._currentLane.rfind('_') + 1:])
 990            else:
 991                self._currentEdge = None
 992        elif name == 'succlane' and self._withConnections:  # deprecated
 993            lid = attrs['lane']
 994            if lid[0] != ':' and lid != "SUMO_NO_DESTINATION" and self._currentEdge:
 995                connected = self._net.getEdge(lid[:lid.rfind('_')])
 996                tolane = int(lid[lid.rfind('_') + 1:])
 997                if 'tl' in attrs and attrs['tl'] != "":
 998                    tl = attrs['tl']
 999                    tllink = int(attrs['linkIdx'])
1000                    tlid = attrs['tl']
1001                    toEdge = self._net.getEdge(lid[:lid.rfind('_')])
1002                    tolane2 = toEdge._lanes[tolane]
1003                    tls = self._net.addTLS(
1004                        tlid, self._currentEdge._lanes[self._currentLane], tolane2, tllink)
1005                    self._currentEdge.setTLS(tls)
1006                else:
1007                    tl = ""
1008                    tllink = -1
1009                toEdge = self._net.getEdge(lid[:lid.rfind('_')])
1010                tolane = toEdge._lanes[tolane]
1011                viaLaneID = attrs['via']
1012                self._net.addConnection(self._currentEdge, connected, self._currentEdge._lanes[
1013                                        self._currentLane], tolane,
1014                                        attrs['dir'], tl, tllink, -1,
1015                                        attrs.get('allow'), attrs.get('disallow'), attrs['state'], viaLaneID)
1016        elif name == 'connection' and self._withConnections and (attrs['from'][0] != ":" or self._withInternal):
1017            fromEdgeID = attrs['from']
1018            toEdgeID = attrs['to']
1019            if ((self._withPedestrianConnections or not (fromEdgeID in self._net._crossings_and_walkingAreas or
1020                                                         toEdgeID in self._net._crossings_and_walkingAreas))
1021                and (self._withMacroConnectors or not (fromEdgeID in self._net._macroConnectors or toEdgeID in
1022                                                       self._net._macroConnectors))):
1023                fromEdge = self._net.getEdge(fromEdgeID)
1024                toEdge = self._net.getEdge(toEdgeID)
1025                fromLane = fromEdge.getLane(int(attrs['fromLane']))
1026                toLane = toEdge.getLane(int(attrs['toLane']))
1027                if 'tl' in attrs and attrs['tl'] != "":
1028                    tl = attrs['tl']
1029                    tllink = int(attrs['linkIndex'])
1030                    tllink2 = int(attrs.get('linkIndex2', -1))
1031                    tls = self._net.addTLS(tl, fromLane, toLane, tllink)
1032                    fromEdge.setTLS(tls)
1033                else:
1034                    tl = ""
1035                    tllink = -1
1036                    tllink2 = -1
1037                try:
1038                    viaLaneID = attrs['via']
1039                except KeyError:
1040                    viaLaneID = ''
1041
1042                self._currentConnection = self._net.addConnection(
1043                    fromEdge, toEdge, fromLane, toLane, attrs['dir'], tl,
1044                    tllink, tllink2, attrs.get('allow'), attrs.get('disallow'), attrs['state'], viaLaneID)
1045
1046        # 'row-logic' is deprecated!!!
1047        elif self._withFoes and name == 'ROWLogic':
1048            self._currentNode = attrs['id']
1049        elif name == 'logicitem' and self._withFoes:  # deprecated
1050            self._net.setFoes(
1051                self._currentNode, int(attrs['request']), attrs["foes"], attrs["response"])
1052        elif name == 'request' and self._withFoes:
1053            self._currentNode.setFoes(
1054                int(attrs['index']), attrs["foes"], attrs["response"])
1055        # tl-logic is deprecated!!! NOTE: nevertheless, this is still used by
1056        # netconvert... (Leo)
1057        elif self._withPhases and name == 'tlLogic':
1058            self._currentProgram = self._net.addTLSProgram(
1059                attrs['id'], attrs['programID'],
1060                intIfPossible(float(attrs['offset'])), attrs['type'], self._latestProgram)
1061        elif self._withPhases and name == 'phase':
1062            self._currentProgram.addPhase(
1063                attrs['state'],
1064                intIfPossible(float(attrs['duration'])),
1065                intIfPossible(float(attrs['minDur'])) if 'minDur' in attrs else -1,
1066                intIfPossible(float(attrs['maxDur'])) if 'maxDur' in attrs else -1,
1067                list(map(int, attrs['next'].split())) if 'next' in attrs else [],
1068                attrs['name'] if 'name' in attrs else ""
1069            )
1070        elif name == 'roundabout':
1071            self._net.addRoundabout(
1072                attrs['nodes'].split(), attrs['edges'].split())
1073        elif name == 'param':
1074            if self._currentLane is not None:
1075                self._currentLane.setParam(attrs['key'], attrs['value'])
1076            elif self._currentEdge is not None:
1077                self._currentEdge.setParam(attrs['key'], attrs['value'])
1078            elif self._currentNode is not None:
1079                self._currentNode.setParam(attrs['key'], attrs['value'])
1080            elif self._currentConnection is not None:
1081                self._currentConnection.setParam(attrs['key'], attrs['value'])
1082            elif self._withPhases and self._currentProgram is not None:
1083                self._currentProgram.setParam(attrs['key'], attrs['value'])

Signals the start of an element in non-namespace mode.

The name parameter contains the raw XML 1.0 name of the element type as a string and the attrs parameter holds an instance of the Attributes class containing the attributes of the element.

def endElement(self, name):
1085    def endElement(self, name):
1086        if name == 'lane':
1087            self._currentLane = None
1088        elif name == 'edge':
1089            self._currentEdge = None
1090        elif name == 'junction':
1091            self._currentNode = None
1092        elif name == 'connection':
1093            self._currentConnection = None
1094        # 'row-logic' is deprecated!!!
1095        elif name == 'ROWLogic' or name == 'row-logic':
1096            self._haveROWLogic = False
1097        # tl-logic is deprecated!!!
1098        elif self._withPhases and (name == 'tlLogic' or name == 'tl-logic'):
1099            self._currentProgram = None
1100        elif name == 'net':
1101            for edgeID, bidiID in self._bidiEdgeIDs.items():
1102                self._net.getEdge(edgeID)._bidi = self._net.getEdge(bidiID)

Signals the end of an element in non-namespace mode.

The name parameter contains the name of the element type, just as with the startElement event.

def endDocument(self):
1104    def endDocument(self):
1105        # set crossed edges of pedestrian crossings
1106        for crossingID, crossedEdgeIDs in self._crossingID2edgeIDs.items():
1107            pedCrossing = self._net.getEdge(crossingID)
1108            for crossedEdgeID in crossedEdgeIDs:
1109                pedCrossing._addCrossingEdge(self._net.getEdge(crossedEdgeID))

Receive notification of the end of a document.

The SAX parser will invoke this method only once, and it will be the last method invoked during the parse. The parser shall not invoke this method until it has either abandoned parsing (because of an unrecoverable error) or reached the end of input.

def getNet(self):
1111    def getNet(self):
1112        return self._net
def convertShape(shapeString):
1115def convertShape(shapeString):
1116    """ Convert xml shape string into float tuples.
1117
1118    This method converts the 2d or 3d shape string from SUMO's xml file
1119    into a list containing 3d float-tuples. Non existent z coordinates default
1120    to zero. If shapeString is empty, an empty list will be returned.
1121    """
1122
1123    cshape = []
1124    for pointString in shapeString.split():
1125        p = [float(e) for e in pointString.split(",")]
1126        if len(p) == 2:
1127            cshape.append((p[0], p[1], 0.))
1128        elif len(p) == 3:
1129            cshape.append(tuple(p))
1130        else:
1131            raise ValueError(
1132                'Invalid shape point "%s", should be either 2d or 3d' % pointString)
1133    return cshape

Convert xml shape string into float tuples.

This method converts the 2d or 3d shape string from SUMO's xml file into a list containing 3d float-tuples. Non existent z coordinates default to zero. If shapeString is empty, an empty list will be returned.

def lane2edge(laneID):
1136def lane2edge(laneID):
1137    return laneID[:laneID.rfind("_")]
def lane2index(laneID):
1140def lane2index(laneID):
1141    return int(laneID[laneID.rfind("_") + 1:])
def hasInternal(filename):
1144def hasInternal(filename):
1145    with openz(filename) as f:
1146        for line in f:
1147            if '<edge id="' in line:
1148                if '<edge id=":' in line:
1149                    return True
1150                else:
1151                    return False
1152    return False
def readNet(filename, **others):
1155def readNet(filename, **others):
1156    """ load a .net.xml file
1157    The following named options are supported:
1158
1159        'net' : initialize data structures with an existing net object (default Net())
1160        'withPrograms' : import all traffic light programs (default False)
1161        'withLatestPrograms' : import only the last program for each traffic light.
1162                               This is the program that would be active in sumo by default.
1163                               (default False)
1164        'withConnections' : import all connections (default True)
1165        'withFoes' : import right-of-way information (default True)
1166        'withInternal' : import internal edges and lanes (default False)
1167        'withPedestrianConnections' : import connections between sidewalks, crossings (default False)
1168        'lxml' : set to False to use the xml.sax parser instead of the lxml parser
1169        'maxcache' : set maximum cache size (default 1000) or 0 to disable optimal route caching
1170    """
1171    netreader = NetReader(**others)
1172    try:
1173        source = gzip.open(filename)
1174        source.read(10)
1175        source.seek(0)
1176    except IOError:
1177        source = filename
1178    if HAVE_LXML and others.get("lxml", True):
1179        if isinstance(source, pathlib.Path):
1180            source = str(source)
1181        for event, v in lxml.etree.iterparse(source, events=("start", "end")):
1182            if event == "start":
1183                netreader.startElement(v.tag, v.attrib)
1184            elif event == "end":
1185                netreader.endElement(v.tag)
1186            v.clear()  # reduce memory footprint
1187    else:
1188        parse(source, netreader)
1189    net = netreader.getNet()
1190    maxcache = others.get('maxcache', 1000)
1191    if HAVE_LRU_CACHE and maxcache is not None and maxcache > 0:
1192        net.initRoutingCache(maxcache)
1193    return net

load a .net.xml file The following named options are supported:

'net' : initialize data structures with an existing net object (default Net())
'withPrograms' : import all traffic light programs (default False)
'withLatestPrograms' : import only the last program for each traffic light.
                       This is the program that would be active in sumo by default.
                       (default False)
'withConnections' : import all connections (default True)
'withFoes' : import right-of-way information (default True)
'withInternal' : import internal edges and lanes (default False)
'withPedestrianConnections' : import connections between sidewalks, crossings (default False)
'lxml' : set to False to use the xml.sax parser instead of the lxml parser
'maxcache' : set maximum cache size (default 1000) or 0 to disable optimal route caching