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 hasNode(self, id):
 423        return id in self._id2node
 424
 425    def getNode(self, id):
 426        return self._id2node[id]
 427
 428    def getNodes(self):
 429        return self._nodes
 430
 431    def getTLS(self, tlid):
 432        return self._id2tls[tlid]
 433
 434    def getTLSSecure(self, tlid):
 435        if tlid in self._id2tls:
 436            tls = self._id2tls[tlid]
 437        else:
 438            tls = TLS(tlid)
 439            self._id2tls[tlid] = tls
 440            self._tlss.append(tls)
 441        return tls
 442
 443    def getTrafficLights(self):
 444        return self._tlss
 445
 446    def addTLS(self, tlid, inLane, outLane, linkNo):
 447        tls = self.getTLSSecure(tlid)
 448        tls.addConnection(inLane, outLane, linkNo)
 449        return tls
 450
 451    def addTLSProgram(self, tlid, programID, offset, type, removeOthers):
 452        tls = self.getTLSSecure(tlid)
 453        program = TLSProgram(programID, offset, type)
 454        if removeOthers:
 455            tls.removePrograms()
 456        tls.addProgram(program)
 457        return program
 458
 459    def setFoes(self, junctionID, index, foes, prohibits):
 460        self._id2node[junctionID].setFoes(index, foes, prohibits)
 461
 462    def forbids(self, possProhibitor, possProhibited):
 463        return possProhibitor.getFrom().getToNode().forbids(possProhibitor, possProhibited)
 464
 465    def getUpstreamEdges(self, edge, distance, stopOnTLS, stopOnTurnaround):
 466        """return a list of lists of the form
 467           [[firstEdge, pos, [edge_0, edge_1, ..., edge_k], aborted], ...]
 468           where
 469             firstEdge: is the upstream edge furthest away from the intersection,
 470             [edge_0, ..., edge_k]: is the list of edges from the intersection upstream to firstEdge
 471             pos: is the position on firstEdge with distance to the end of the input edge
 472             aborted: a flag indicating whether the downstream
 473                 search stopped at a TLS or a node without incoming edges before reaching the distance threshold
 474        """
 475        ret = []
 476        seen = set()
 477        toProc = []
 478        toProc.append([edge, 0, []])
 479        while not len(toProc) == 0:
 480            ie = toProc.pop()
 481            if ie[0] in seen:
 482                continue
 483            seen.add(ie[0])
 484            if ie[1] + ie[0].getLength() >= distance:
 485                ret.append(
 486                    [ie[0], ie[0].getLength() + ie[1] - distance, ie[2], False])
 487                continue
 488            if len(ie[0]._incoming) == 0:
 489                ret.append([ie[0], ie[0].getLength() + ie[1], ie[2], True])
 490                continue
 491            mn = []
 492            stop = False
 493            for ci in ie[0]._incoming:
 494                if ci not in seen:
 495                    prev = copy(ie[2])
 496                    if stopOnTLS and ci._tls and ci != edge and not stop:
 497                        ret.append([ie[0], ie[1], prev, True])
 498                        stop = True
 499                    elif (stopOnTurnaround and ie[0]._incoming[ci][0].getDirection() == Connection.LINKDIR_TURN and
 500                          not stop):
 501                        ret.append([ie[0], ie[1], prev, True])
 502                        stop = True
 503                    else:
 504                        prev.append(ie[0])
 505                        mn.append([ci, ie[0].getLength() + ie[1], prev])
 506            if not stop:
 507                toProc.extend(mn)
 508        return ret
 509
 510    def getEdgesByOrigID(self, origID):
 511        if self._origIdx is None:
 512            self._origIdx = defaultdict(set)
 513            for the_edge in self._edges:
 514                for the_lane in the_edge.getLanes():
 515                    for oID in the_lane.getParam("origId", "").split():
 516                        self._origIdx[oID].add(the_edge)
 517        return self._origIdx[origID]
 518
 519    def getGeometries(self, useLanes, includeJunctions=False):
 520        for e in self._edges:
 521            if useLanes:
 522                for the_lane in e.getLanes():
 523                    yield the_lane.getID(), the_lane.getShape(), the_lane.getWidth()
 524            else:
 525                yield e.getID(), e.getShape(includeJunctions), sum([the_lane.getWidth() for the_lane in e.getLanes()])
 526
 527    def getBBoxXY(self):
 528        """
 529        Get the bounding box (bottom left and top right coordinates) for a net;
 530        Coordinates are in X and Y (not Lat and Lon)
 531
 532        :return [(bottom_left_X, bottom_left_Y), (top_right_X, top_right_Y)]
 533        """
 534        return [(self._ranges[0][0], self._ranges[1][0]),
 535                (self._ranges[0][1], self._ranges[1][1])]
 536
 537    # the diagonal of the bounding box of all nodes
 538    def getBBoxDiameter(self):
 539        return math.sqrt(
 540            (self._ranges[0][0] - self._ranges[0][1]) ** 2 +
 541            (self._ranges[1][0] - self._ranges[1][1]) ** 2)
 542
 543    def hasGeoProj(self):
 544        projString = self._location["projParameter"]
 545        return projString != "!"
 546
 547    def getGeoProj(self):
 548        if not self.hasGeoProj() or not HAVE_PYPROJ:
 549            raise RuntimeError("Network does not provide geo-projection or pyproj not installed.")
 550        if self._proj is None:
 551            try:
 552                self._proj = pyproj.Proj(projparams=self._location["projParameter"])
 553            except RuntimeError:
 554                if hasattr(pyproj.datadir, 'set_data_dir'):
 555                    pyproj.datadir.set_data_dir('/usr/share/proj')
 556                    self._proj = pyproj.Proj(projparams=self._location["projParameter"])
 557                raise
 558        return self._proj
 559
 560    def getLocationOffset(self):
 561        """ offset to be added after converting from geo-coordinates to UTM"""
 562        return list(map(float, self._location["netOffset"].split(",")))
 563
 564    def getBoundary(self):
 565        """ return xmin,ymin,xmax,ymax network coordinates"""
 566        return list(map(float, self._location["convBoundary"].split(",")))
 567
 568    def convertLonLat2XY(self, lon, lat, rawUTM=False):
 569        x, y = self.getGeoProj()(lon, lat)
 570        if rawUTM:
 571            return x, y
 572        else:
 573            x_off, y_off = self.getLocationOffset()
 574            return x + x_off, y + y_off
 575
 576    def convertXY2LonLat(self, x, y, rawUTM=False):
 577        if not rawUTM:
 578            x_off, y_off = self.getLocationOffset()
 579            x -= x_off
 580            y -= y_off
 581        return self.getGeoProj()(x, y, inverse=True)
 582
 583    def move(self, dx, dy, dz=0):
 584        for n in self._nodes:
 585            n._coord = (n._coord[0] + dx, n._coord[1] + dy, n._coord[2] + dz)
 586        for e in self._edges:
 587            for _lane in e.getLanes():
 588                _lane.setShape([(p[0] + dx, p[1] + dy, p[2] + dz) for p in _lane.getShape3D()])
 589            e.rebuildShape()
 590
 591    def getInternalPath(self, conn, fastest=False):
 592        minInternalCost = PRACTICAL_INFINITY
 593        minPath = None
 594        for c in conn:
 595            if c.getViaLaneID() != "":
 596                viaCost = 0
 597                viaID = c.getViaLaneID()
 598                viaPath = []
 599                while viaID != "":
 600                    viaLane = self.getLane(viaID)
 601                    viaCost += viaLane.getLength() if not fastest else viaLane.getLength() / viaLane.getSpeed()
 602                    viaID = viaLane.getOutgoing()[0].getViaLaneID()
 603                    viaPath.append(viaLane.getEdge())
 604                if viaCost < minInternalCost:
 605                    minInternalCost = viaCost
 606                    minPath = viaPath
 607        return minPath, minInternalCost
 608
 609    def getOptimalPath(self, fromEdge, toEdge, fastest=False, maxCost=PRACTICAL_INFINITY, vClass=None,
 610                       reversalPenalty=0, includeFromToCost=True, withInternal=False, ignoreDirection=False,
 611                       fromPos=None, toPos=None, preferences={}):
 612        """
 613        Finds the optimal (shortest or fastest) path for vClass from fromEdge to toEdge
 614        by using using Dijkstra's algorithm.
 615        It returns a pair of a tuple of edges and the cost.
 616        If no path is found the first element is None.
 617        The cost for the returned path is equal to the sum of all edge costs in the path,
 618        including the internal connectors, if they are present in the network.
 619        The path itself does not include internal edges except for the case
 620        when the start or end edge are internal edges.
 621        The search may be limited using the given threshold.
 622        The preferences declare a mapping from the 'routingType' of each edge to divisor
 623        that is applied to the computed cost of that edge (i.e. a preference of 2 reduces cost by a factor of 0.5).
 624        """
 625
 626        if preferences:
 627            if fastest:
 628                def speedFunc(edge):
 629                    return preferences.get(edge.getRoutingType(), 1.0) * edge.getSpeed()
 630            else:
 631                def speedFunc(edge):
 632                    return preferences.get(edge.getRoutingType(), 1.0)
 633        elif fastest:
 634            def speedFunc(edge):
 635                return edge.getSpeed()
 636        else:
 637            def speedFunc(edge):
 638                return 1.0
 639
 640        def remainder(edge, pos):
 641            if pos < 0:
 642                return min(-pos, edge.getLength())
 643            return max(0., edge.getLength() - pos)
 644
 645        def getToNormalIncoming(edge):
 646            if edge.getFunction() == '':
 647                return [(e, None) for e in edge.getToNode().getIncoming() if e.getFunction() == '']
 648            else:
 649                return []
 650
 651        if self._edgeSuccessorCache != (vClass, ignoreDirection):
 652            self._edgeSuccessorCache = (vClass, ignoreDirection)
 653            for e1 in self.getEdges():
 654                e1._succCacche = tuple(chain(e1.getAllowedOutgoing(vClass).items(),
 655                                       e1.getIncoming().items() if ignoreDirection else [],
 656                                       getToNormalIncoming(e1) if ignoreDirection and not self.hasWalkingArea else []))
 657
 658        if self._edgeCostCache != (fastest, preferences):
 659            self._edgeCostCache = (fastest, preferences)
 660            for e1 in self.getEdges():
 661                e1._costCache = e1.getLength() / speedFunc(e1)
 662
 663        if self.hasInternal:
 664            appendix = []
 665            appendixCost = 0.
 666            while toEdge.getFunction() == "internal":
 667                appendix = [toEdge] + appendix
 668                appendixCost += toEdge._costCache
 669                toEdge = list(toEdge.getIncoming().keys())[0]
 670
 671        def finalizeCost(cost, path):
 672            if includeFromToCost:
 673                # add costs for (part of) the first edge, still needs to be fixed for wrong direction travel
 674                remainFrom = fromEdge.getLength() if fromPos is None else remainder(fromEdge, fromPos)
 675                cost += remainFrom / speedFunc(fromEdge)
 676                # remove costs for (part of) the last edge, still needs to be fixed for wrong direction travel
 677                removeTo = 0. if toPos is None else remainder(toEdge, toPos)
 678            else:
 679                removeTo = toEdge.getLength() if len(path) > 1 else 0.
 680            cost -= removeTo / speedFunc(toEdge)
 681            return cost
 682
 683        def constructPath(dist):
 684            # destination was already reached in a previous query
 685            cost, pred = dist[toEdge]
 686            path = [toEdge]
 687            while pred is not None:
 688                if self.hasInternal and withInternal:
 689                    viaPath, minInternalCost = self.getInternalPath(pred.getAllowedOutgoing(vClass).get(path[-1], []),
 690                                                                    fastest=fastest)
 691                    if viaPath is not None:
 692                        path += reversed(viaPath)
 693                path.append(pred)
 694                _, pred = dist[pred]
 695
 696            path.reverse()
 697            cost = finalizeCost(cost, path)
 698            assert cost >= 0
 699            if self.hasInternal:
 700                if appendix:
 701                    return tuple(path + appendix), cost + appendixCost
 702                elif ignoreDirection and self.hasWalkingArea and not withInternal:
 703                    return [e for e in path if e.getFunction() == ''], cost
 704            return tuple(path), cost
 705
 706        needLoop = (fromEdge == toEdge
 707                    and fromPos is not None
 708                    and toPos is not None
 709                    and fromPos > toPos
 710                    and not ignoreDirection)
 711
 712        seen = set()
 713        dist = {}
 714        q = []
 715        if self._routingCache is not None:
 716            if needLoop:
 717                # use cached results from all follower edges:
 718                bestCost = maxCost
 719                bestPath = None
 720                for e2, conn in fromEdge.getAllowedOutgoing(vClass).items():
 721                    path, cost = self.getOptimalPath(e2, toEdge, fastest=fastest, maxCost=maxCost, vClass=vClass,
 722                                                     reversalPenalty=reversalPenalty,
 723                                                     includeFromToCost=includeFromToCost,
 724                                                     withInternal=withInternal, fromPos=0, toPos=toPos,
 725                                                     preferences=preferences)
 726                    if path is not None and cost < bestCost:
 727                        bestPath = path
 728                        bestCost = cost
 729                if bestPath is not None:
 730                    path = [fromEdge]
 731                    if self.hasInternal and withInternal:
 732                        viaPath, minInternalCost = self.getInternalPath(
 733                            fromEdge.getAllowedOutgoing(vClass).get(path[0], []), fastest=fastest)
 734                        if viaPath is not None:
 735                            path += viaPath
 736                            bestCost += minInternalCost
 737                    path += list(bestPath)
 738                    if includeFromToCost:
 739                        bestCost += remainder(fromEdge, fromPos) / speedFunc(fromEdge)
 740                    return tuple(path), bestCost
 741                else:
 742                    return None, PRACTICAL_INFINITY
 743
 744            dist = self._routingCache(fromEdge, fastest, vClass, ignoreDirection, reversalPenalty,
 745                                      tuple(preferences.items()))
 746            if toEdge in dist:
 747                return constructPath(dist)
 748            else:
 749                # initialize heap from previous query
 750                q = []
 751                frontier = set(dist.keys())
 752                for cost, prev in dist.values():
 753                    frontier.discard(prev)
 754                for e in frontier:
 755                    cost, prev = dist[e]
 756                    heapq.heappush(q, (cost, e, prev))
 757        elif needLoop:
 758            # start search on successors of fromEdge
 759            for e2, conn in fromEdge.getAllowedOutgoing(vClass).items():
 760                q.append((e2._costCache, e2, fromEdge))
 761
 762        if len(dist) == 0:
 763            dist[fromEdge] = (0., None)
 764            if not needLoop:
 765                q.append((0., fromEdge, None))
 766
 767        while q:
 768            cost, e1, prev = heapq.heappop(q)
 769            if e1 in seen:
 770                continue
 771            seen.add(e1)
 772            if e1 == toEdge:
 773                return constructPath(dist)
 774            if cost > maxCost:
 775                return None, cost
 776
 777            for e2, conn in e1._succCacche:
 778                if e2 not in seen:
 779                    newCost = cost + e2._costCache
 780                    #  print(cost, newCost, e2.getID(), speedFunc(e2))
 781                    if e2 == e1.getBidi():
 782                        newCost += reversalPenalty
 783                    if self.hasInternal and conn is not None:
 784                        viaPath, minInternalCost = self.getInternalPath(conn, fastest=fastest)
 785                        if viaPath is not None:
 786                            newCost += minInternalCost
 787                    if e2 not in dist or newCost < dist[e2][0]:
 788                        dist[e2] = (newCost, e1)
 789                        heapq.heappush(q, (newCost, e2, e1))
 790        return None, PRACTICAL_INFINITY
 791
 792    def getShortestPath(self, fromEdge, toEdge, maxCost=PRACTICAL_INFINITY, vClass=None, reversalPenalty=0,
 793                        includeFromToCost=True, withInternal=False, ignoreDirection=False,
 794                        fromPos=None, toPos=None, preferences={}):
 795        """
 796        Finds the shortest path from fromEdge to toEdge respecting vClass, using Dijkstra's algorithm.
 797        It returns a pair of a tuple of edges and the cost. If no path is found the first element is None.
 798        The cost for the returned path is equal to the sum of all edge lengths in the path,
 799        including the internal connectors, if they are present in the network.
 800        The path itself does not include internal edges except for the case
 801        when the start or end edge are internal edges.
 802        The search may be limited using the given threshold.
 803        The preferences declare a mapping from the 'routingType' of each edge to divisor
 804        that is applied to the lenght of that edge
 805        (i.e. a preference of 2 reduces reduces effective length by a factor of 0.5).
 806        """
 807
 808        return self.getOptimalPath(fromEdge, toEdge, False, maxCost, vClass, reversalPenalty,
 809                                   includeFromToCost, withInternal, ignoreDirection, fromPos, toPos,
 810                                   preferences)
 811
 812    def getFastestPath(self, fromEdge, toEdge, maxCost=PRACTICAL_INFINITY, vClass=None, reversalPenalty=0,
 813                       includeFromToCost=True, withInternal=False, ignoreDirection=False,
 814                       fromPos=None, toPos=None, preferences={}):
 815        """
 816        Finds the fastest path from fromEdge to toEdge respecting vClass, using Dijkstra's algorithm.
 817        It returns a pair of a tuple of edges and the cost. If no path is found the first element is None.
 818        The cost for the returned path is equal to the sum of all edge costs in the path,
 819        including the internal connectors, if they are present in the network.
 820        The path itself does not include internal edges except for the case
 821        when the start or end edge are internal edges.
 822        The search may be limited using the given threshold.
 823        The preferences declare a mapping from the 'routingType' of each edge to divisor
 824        that is applied to the computed traveltime of that edge
 825        (i.e. a preference of 2 reduces effective traveltime by a factor of 0.5).
 826        """
 827
 828        return self.getOptimalPath(fromEdge, toEdge, True, maxCost, vClass, reversalPenalty,
 829                                   includeFromToCost, withInternal, ignoreDirection, fromPos, toPos,
 830                                   preferences)
 831
 832    def getReachable(self, source, vclass=None, useIncoming=False, cache=None):
 833        if vclass is not None and not source.allows(vclass):
 834            raise RuntimeError("'{}' does not allow {}".format(source.getID(), vclass))
 835        fringe = [source]
 836        found = set()
 837        found.add(source)
 838        while len(fringe) > 0:
 839            new_fringe = []
 840            for e in fringe:
 841                if vclass == "pedestrian":
 842                    cands = chain(chain(*e.getIncoming().values()), chain(*e.getOutgoing().values()))
 843                else:
 844                    cands = chain(*(e.getIncoming().values() if useIncoming else e.getOutgoing().values()))
 845                # print("\n".join(map(str, list(cands))))
 846                for conn in cands:
 847                    if vclass is None or (
 848                            conn.getFromLane().allows(vclass)
 849                            and conn.getToLane().allows(vclass)):
 850                        for reachable in [conn.getTo(), conn.getFrom()]:
 851                            if reachable not in found:
 852                                # print("added %s via %s" % (reachable, conn))
 853                                if cache and reachable in cache:
 854                                    found.update(cache[reachable])
 855                                else:
 856                                    found.add(reachable)
 857                                    new_fringe.append(reachable)
 858            fringe = new_fringe
 859        if cache is not None:
 860            cache[source] = tuple(found)
 861        return found
 862
 863
 864class NetReader(handler.ContentHandler):
 865
 866    """Reads a network, storing the edge geometries, lane numbers and max. speeds"""
 867
 868    def __init__(self, **others):
 869        self._net = others.get('net', Net())
 870        self._currentEdge = None
 871        self._currentNode = None
 872        self._currentConnection = None
 873        self._currentLane = None
 874        self._crossingID2edgeIDs = {}
 875        self._withPhases = others.get('withPrograms', False)
 876        self._latestProgram = others.get('withLatestPrograms', False)
 877        if self._latestProgram:
 878            self._withPhases = True
 879        self._withConnections = others.get('withConnections', True)
 880        self._withFoes = others.get('withFoes', True)
 881        self._withPedestrianConnections = others.get('withPedestrianConnections', False)
 882        self._withMacroConnectors = others.get('withMacroConnectors', False)
 883        self._withInternal = others.get('withInternal', self._withPedestrianConnections)
 884        if self._withPedestrianConnections and not self._withInternal:
 885            sys.stderr.write("Warning: Option withPedestrianConnections requires withInternal\n")
 886            self._withInternal = True
 887        self._bidiEdgeIDs = {}
 888
 889    def startElement(self, name, attrs):
 890        if name == 'net':
 891            parts = attrs["version"].split('.', 1)
 892            self._net._version = (int(parts[0]), float(parts[1]))
 893        elif name == 'location':
 894            self._net.setLocation(attrs["netOffset"], attrs["convBoundary"], attrs[
 895                                  "origBoundary"], attrs["projParameter"])
 896        elif name == 'type':
 897            self._net._edgeTypes[attrs['id']] = EdgeType(attrs['id'], attrs.get('allow'), attrs.get('disallow'))
 898        elif name == 'edge':
 899            function = attrs.get('function', '')
 900            if (function == ''
 901                    or (self._withInternal and function in ['internal', 'crossing', 'walkingarea'])
 902                    or (self._withMacroConnectors and function == 'connector')):
 903                prio = -1
 904                if 'priority' in attrs:
 905                    prio = int(attrs['priority'])
 906
 907                # get the  ids
 908                edgeID = attrs['id']
 909                fromNodeID = attrs.get('from', None)
 910                toNodeID = attrs.get('to', None)
 911
 912                # for internal junctions use the junction's id for from and to node
 913                if function == 'internal' or function == 'crossing' or function == 'walkingarea':
 914                    fromNodeID = toNodeID = edgeID[1:edgeID.rfind('_')]
 915
 916                # remember edges crossed by pedestrians to link them later to the crossing objects
 917                if function == 'crossing':
 918                    self._crossingID2edgeIDs[edgeID] = attrs.get('crossingEdges').split(' ')
 919
 920                self._currentEdge = self._net.addEdge(edgeID, fromNodeID, toNodeID, prio, function,
 921                                                      attrs.get('name', ''), attrs.get('type', ''),
 922                                                      attrs.get('routingType', ''))
 923
 924                self._currentEdge.setRawShape(convertShape(attrs.get('shape', '')))
 925
 926                bidi = attrs.get('bidi', '')
 927                if bidi:
 928                    self._bidiEdgeIDs[edgeID] = bidi
 929            else:
 930                if function in ['crossing', 'walkingarea']:
 931                    self._net._crossings_and_walkingAreas.add(attrs['id'])
 932                elif function == 'connector':
 933                    self._net._macroConnectors.add(attrs['id'])
 934                self._currentEdge = None
 935        elif name == 'lane' and self._currentEdge is not None:
 936            self._currentLane = self._net.addLane(
 937                self._currentEdge,
 938                float(attrs['speed']),
 939                float(attrs['length']),
 940                float(attrs.get('width', 3.2)),
 941                attrs.get('allow'),
 942                attrs.get('disallow'),
 943                attrs.get('acceleration') == "1")
 944            self._currentLane.setShape(convertShape(attrs.get('shape', '')))
 945        elif name == 'neigh' and self._currentLane is not None:
 946            self._currentLane.setNeigh(attrs['lane'])
 947        elif name == 'junction':
 948            if attrs['id'][0] != ':':
 949                intLanes = None
 950                if self._withInternal:
 951                    intLanes = attrs["intLanes"].split(" ")
 952                self._currentNode = self._net.addNode(attrs['id'], attrs['type'],
 953                                                      tuple(
 954                                                          map(float, [attrs['x'], attrs['y'],
 955                                                                      attrs['z'] if 'z' in attrs else '0'])),
 956                                                      attrs['incLanes'].split(" "), intLanes)
 957                self._currentNode.setShape(
 958                    convertShape(attrs.get('shape', '')))
 959                if 'fringe' in attrs:
 960                    self._currentNode._fringe = attrs['fringe']
 961
 962        elif name == 'succ' and self._withConnections:  # deprecated
 963            if attrs['edge'][0] != ':':
 964                self._currentEdge = self._net.getEdge(attrs['edge'])
 965                self._currentLane = attrs['lane']
 966                self._currentLane = int(
 967                    self._currentLane[self._currentLane.rfind('_') + 1:])
 968            else:
 969                self._currentEdge = None
 970        elif name == 'succlane' and self._withConnections:  # deprecated
 971            lid = attrs['lane']
 972            if lid[0] != ':' and lid != "SUMO_NO_DESTINATION" and self._currentEdge:
 973                connected = self._net.getEdge(lid[:lid.rfind('_')])
 974                tolane = int(lid[lid.rfind('_') + 1:])
 975                if 'tl' in attrs and attrs['tl'] != "":
 976                    tl = attrs['tl']
 977                    tllink = int(attrs['linkIdx'])
 978                    tlid = attrs['tl']
 979                    toEdge = self._net.getEdge(lid[:lid.rfind('_')])
 980                    tolane2 = toEdge._lanes[tolane]
 981                    tls = self._net.addTLS(
 982                        tlid, self._currentEdge._lanes[self._currentLane], tolane2, tllink)
 983                    self._currentEdge.setTLS(tls)
 984                else:
 985                    tl = ""
 986                    tllink = -1
 987                toEdge = self._net.getEdge(lid[:lid.rfind('_')])
 988                tolane = toEdge._lanes[tolane]
 989                viaLaneID = attrs['via']
 990                self._net.addConnection(self._currentEdge, connected, self._currentEdge._lanes[
 991                                        self._currentLane], tolane,
 992                                        attrs['dir'], tl, tllink, -1,
 993                                        attrs.get('allow'), attrs.get('disallow'), attrs['state'], viaLaneID)
 994        elif name == 'connection' and self._withConnections and (attrs['from'][0] != ":" or self._withInternal):
 995            fromEdgeID = attrs['from']
 996            toEdgeID = attrs['to']
 997            if ((self._withPedestrianConnections or not (fromEdgeID in self._net._crossings_and_walkingAreas or
 998                                                         toEdgeID in self._net._crossings_and_walkingAreas))
 999                and (self._withMacroConnectors or not (fromEdgeID in self._net._macroConnectors or toEdgeID in
1000                                                       self._net._macroConnectors))):
1001                fromEdge = self._net.getEdge(fromEdgeID)
1002                toEdge = self._net.getEdge(toEdgeID)
1003                fromLane = fromEdge.getLane(int(attrs['fromLane']))
1004                toLane = toEdge.getLane(int(attrs['toLane']))
1005                if 'tl' in attrs and attrs['tl'] != "":
1006                    tl = attrs['tl']
1007                    tllink = int(attrs['linkIndex'])
1008                    tllink2 = int(attrs.get('linkIndex2', -1))
1009                    tls = self._net.addTLS(tl, fromLane, toLane, tllink)
1010                    fromEdge.setTLS(tls)
1011                else:
1012                    tl = ""
1013                    tllink = -1
1014                    tllink2 = -1
1015                try:
1016                    viaLaneID = attrs['via']
1017                except KeyError:
1018                    viaLaneID = ''
1019
1020                self._currentConnection = self._net.addConnection(
1021                    fromEdge, toEdge, fromLane, toLane, attrs['dir'], tl,
1022                    tllink, tllink2, attrs.get('allow'), attrs.get('disallow'), attrs['state'], viaLaneID)
1023
1024        # 'row-logic' is deprecated!!!
1025        elif self._withFoes and name == 'ROWLogic':
1026            self._currentNode = attrs['id']
1027        elif name == 'logicitem' and self._withFoes:  # deprecated
1028            self._net.setFoes(
1029                self._currentNode, int(attrs['request']), attrs["foes"], attrs["response"])
1030        elif name == 'request' and self._withFoes:
1031            self._currentNode.setFoes(
1032                int(attrs['index']), attrs["foes"], attrs["response"])
1033        # tl-logic is deprecated!!! NOTE: nevertheless, this is still used by
1034        # netconvert... (Leo)
1035        elif self._withPhases and name == 'tlLogic':
1036            self._currentProgram = self._net.addTLSProgram(
1037                attrs['id'], attrs['programID'],
1038                intIfPossible(float(attrs['offset'])), attrs['type'], self._latestProgram)
1039        elif self._withPhases and name == 'phase':
1040            self._currentProgram.addPhase(
1041                attrs['state'],
1042                intIfPossible(float(attrs['duration'])),
1043                intIfPossible(float(attrs['minDur'])) if 'minDur' in attrs else -1,
1044                intIfPossible(float(attrs['maxDur'])) if 'maxDur' in attrs else -1,
1045                list(map(int, attrs['next'].split())) if 'next' in attrs else [],
1046                attrs['name'] if 'name' in attrs else ""
1047            )
1048        elif name == 'roundabout':
1049            self._net.addRoundabout(
1050                attrs['nodes'].split(), attrs['edges'].split())
1051        elif name == 'param':
1052            if self._currentLane is not None:
1053                self._currentLane.setParam(attrs['key'], attrs['value'])
1054            elif self._currentEdge is not None:
1055                self._currentEdge.setParam(attrs['key'], attrs['value'])
1056            elif self._currentNode is not None:
1057                self._currentNode.setParam(attrs['key'], attrs['value'])
1058            elif self._currentConnection is not None:
1059                self._currentConnection.setParam(attrs['key'], attrs['value'])
1060            elif self._withPhases and self._currentProgram is not None:
1061                self._currentProgram.setParam(attrs['key'], attrs['value'])
1062
1063    def endElement(self, name):
1064        if name == 'lane':
1065            self._currentLane = None
1066        elif name == 'edge':
1067            self._currentEdge = None
1068        elif name == 'junction':
1069            self._currentNode = None
1070        elif name == 'connection':
1071            self._currentConnection = None
1072        # 'row-logic' is deprecated!!!
1073        elif name == 'ROWLogic' or name == 'row-logic':
1074            self._haveROWLogic = False
1075        # tl-logic is deprecated!!!
1076        elif self._withPhases and (name == 'tlLogic' or name == 'tl-logic'):
1077            self._currentProgram = None
1078        elif name == 'net':
1079            for edgeID, bidiID in self._bidiEdgeIDs.items():
1080                self._net.getEdge(edgeID)._bidi = self._net.getEdge(bidiID)
1081
1082    def endDocument(self):
1083        # set crossed edges of pedestrian crossings
1084        for crossingID, crossedEdgeIDs in self._crossingID2edgeIDs.items():
1085            pedCrossing = self._net.getEdge(crossingID)
1086            for crossedEdgeID in crossedEdgeIDs:
1087                pedCrossing._addCrossingEdge(self._net.getEdge(crossedEdgeID))
1088
1089    def getNet(self):
1090        return self._net
1091
1092
1093def convertShape(shapeString):
1094    """ Convert xml shape string into float tuples.
1095
1096    This method converts the 2d or 3d shape string from SUMO's xml file
1097    into a list containing 3d float-tuples. Non existent z coordinates default
1098    to zero. If shapeString is empty, an empty list will be returned.
1099    """
1100
1101    cshape = []
1102    for pointString in shapeString.split():
1103        p = [float(e) for e in pointString.split(",")]
1104        if len(p) == 2:
1105            cshape.append((p[0], p[1], 0.))
1106        elif len(p) == 3:
1107            cshape.append(tuple(p))
1108        else:
1109            raise ValueError(
1110                'Invalid shape point "%s", should be either 2d or 3d' % pointString)
1111    return cshape
1112
1113
1114def lane2edge(laneID):
1115    return laneID[:laneID.rfind("_")]
1116
1117
1118def lane2index(laneID):
1119    return int(laneID[laneID.rfind("_") + 1:])
1120
1121
1122def hasInternal(filename):
1123    with openz(filename) as f:
1124        for line in f:
1125            if '<edge id="' in line:
1126                if '<edge id=":' in line:
1127                    return True
1128                else:
1129                    return False
1130    return False
1131
1132
1133def readNet(filename, **others):
1134    """ load a .net.xml file
1135    The following named options are supported:
1136
1137        'net' : initialize data structures with an existing net object (default Net())
1138        'withPrograms' : import all traffic light programs (default False)
1139        'withLatestPrograms' : import only the last program for each traffic light.
1140                               This is the program that would be active in sumo by default.
1141                               (default False)
1142        'withConnections' : import all connections (default True)
1143        'withFoes' : import right-of-way information (default True)
1144        'withInternal' : import internal edges and lanes (default False)
1145        'withPedestrianConnections' : import connections between sidewalks, crossings (default False)
1146        'lxml' : set to False to use the xml.sax parser instead of the lxml parser
1147        'maxcache' : set maximum cache size (default 1000) or 0 to disable optimal route caching
1148    """
1149    netreader = NetReader(**others)
1150    try:
1151        source = gzip.open(filename)
1152        source.read(10)
1153        source.seek(0)
1154    except IOError:
1155        source = filename
1156    if HAVE_LXML and others.get("lxml", True):
1157        if isinstance(source, pathlib.Path):
1158            source = str(source)
1159        for event, v in lxml.etree.iterparse(source, events=("start", "end")):
1160            if event == "start":
1161                netreader.startElement(v.tag, v.attrib)
1162            elif event == "end":
1163                netreader.endElement(v.tag)
1164            v.clear()  # reduce memory footprint
1165    else:
1166        parse(source, netreader)
1167    net = netreader.getNet()
1168    maxcache = others.get('maxcache', 1000)
1169    if HAVE_LRU_CACHE and maxcache is not None and maxcache > 0:
1170        net.initRoutingCache(maxcache)
1171    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 hasNode(self, id):
424        return id in self._id2node
425
426    def getNode(self, id):
427        return self._id2node[id]
428
429    def getNodes(self):
430        return self._nodes
431
432    def getTLS(self, tlid):
433        return self._id2tls[tlid]
434
435    def getTLSSecure(self, tlid):
436        if tlid in self._id2tls:
437            tls = self._id2tls[tlid]
438        else:
439            tls = TLS(tlid)
440            self._id2tls[tlid] = tls
441            self._tlss.append(tls)
442        return tls
443
444    def getTrafficLights(self):
445        return self._tlss
446
447    def addTLS(self, tlid, inLane, outLane, linkNo):
448        tls = self.getTLSSecure(tlid)
449        tls.addConnection(inLane, outLane, linkNo)
450        return tls
451
452    def addTLSProgram(self, tlid, programID, offset, type, removeOthers):
453        tls = self.getTLSSecure(tlid)
454        program = TLSProgram(programID, offset, type)
455        if removeOthers:
456            tls.removePrograms()
457        tls.addProgram(program)
458        return program
459
460    def setFoes(self, junctionID, index, foes, prohibits):
461        self._id2node[junctionID].setFoes(index, foes, prohibits)
462
463    def forbids(self, possProhibitor, possProhibited):
464        return possProhibitor.getFrom().getToNode().forbids(possProhibitor, possProhibited)
465
466    def getUpstreamEdges(self, edge, distance, stopOnTLS, stopOnTurnaround):
467        """return a list of lists of the form
468           [[firstEdge, pos, [edge_0, edge_1, ..., edge_k], aborted], ...]
469           where
470             firstEdge: is the upstream edge furthest away from the intersection,
471             [edge_0, ..., edge_k]: is the list of edges from the intersection upstream to firstEdge
472             pos: is the position on firstEdge with distance to the end of the input edge
473             aborted: a flag indicating whether the downstream
474                 search stopped at a TLS or a node without incoming edges before reaching the distance threshold
475        """
476        ret = []
477        seen = set()
478        toProc = []
479        toProc.append([edge, 0, []])
480        while not len(toProc) == 0:
481            ie = toProc.pop()
482            if ie[0] in seen:
483                continue
484            seen.add(ie[0])
485            if ie[1] + ie[0].getLength() >= distance:
486                ret.append(
487                    [ie[0], ie[0].getLength() + ie[1] - distance, ie[2], False])
488                continue
489            if len(ie[0]._incoming) == 0:
490                ret.append([ie[0], ie[0].getLength() + ie[1], ie[2], True])
491                continue
492            mn = []
493            stop = False
494            for ci in ie[0]._incoming:
495                if ci not in seen:
496                    prev = copy(ie[2])
497                    if stopOnTLS and ci._tls and ci != edge and not stop:
498                        ret.append([ie[0], ie[1], prev, True])
499                        stop = True
500                    elif (stopOnTurnaround and ie[0]._incoming[ci][0].getDirection() == Connection.LINKDIR_TURN and
501                          not stop):
502                        ret.append([ie[0], ie[1], prev, True])
503                        stop = True
504                    else:
505                        prev.append(ie[0])
506                        mn.append([ci, ie[0].getLength() + ie[1], prev])
507            if not stop:
508                toProc.extend(mn)
509        return ret
510
511    def getEdgesByOrigID(self, origID):
512        if self._origIdx is None:
513            self._origIdx = defaultdict(set)
514            for the_edge in self._edges:
515                for the_lane in the_edge.getLanes():
516                    for oID in the_lane.getParam("origId", "").split():
517                        self._origIdx[oID].add(the_edge)
518        return self._origIdx[origID]
519
520    def getGeometries(self, useLanes, includeJunctions=False):
521        for e in self._edges:
522            if useLanes:
523                for the_lane in e.getLanes():
524                    yield the_lane.getID(), the_lane.getShape(), the_lane.getWidth()
525            else:
526                yield e.getID(), e.getShape(includeJunctions), sum([the_lane.getWidth() for the_lane in e.getLanes()])
527
528    def getBBoxXY(self):
529        """
530        Get the bounding box (bottom left and top right coordinates) for a net;
531        Coordinates are in X and Y (not Lat and Lon)
532
533        :return [(bottom_left_X, bottom_left_Y), (top_right_X, top_right_Y)]
534        """
535        return [(self._ranges[0][0], self._ranges[1][0]),
536                (self._ranges[0][1], self._ranges[1][1])]
537
538    # the diagonal of the bounding box of all nodes
539    def getBBoxDiameter(self):
540        return math.sqrt(
541            (self._ranges[0][0] - self._ranges[0][1]) ** 2 +
542            (self._ranges[1][0] - self._ranges[1][1]) ** 2)
543
544    def hasGeoProj(self):
545        projString = self._location["projParameter"]
546        return projString != "!"
547
548    def getGeoProj(self):
549        if not self.hasGeoProj() or not HAVE_PYPROJ:
550            raise RuntimeError("Network does not provide geo-projection or pyproj not installed.")
551        if self._proj is None:
552            try:
553                self._proj = pyproj.Proj(projparams=self._location["projParameter"])
554            except RuntimeError:
555                if hasattr(pyproj.datadir, 'set_data_dir'):
556                    pyproj.datadir.set_data_dir('/usr/share/proj')
557                    self._proj = pyproj.Proj(projparams=self._location["projParameter"])
558                raise
559        return self._proj
560
561    def getLocationOffset(self):
562        """ offset to be added after converting from geo-coordinates to UTM"""
563        return list(map(float, self._location["netOffset"].split(",")))
564
565    def getBoundary(self):
566        """ return xmin,ymin,xmax,ymax network coordinates"""
567        return list(map(float, self._location["convBoundary"].split(",")))
568
569    def convertLonLat2XY(self, lon, lat, rawUTM=False):
570        x, y = self.getGeoProj()(lon, lat)
571        if rawUTM:
572            return x, y
573        else:
574            x_off, y_off = self.getLocationOffset()
575            return x + x_off, y + y_off
576
577    def convertXY2LonLat(self, x, y, rawUTM=False):
578        if not rawUTM:
579            x_off, y_off = self.getLocationOffset()
580            x -= x_off
581            y -= y_off
582        return self.getGeoProj()(x, y, inverse=True)
583
584    def move(self, dx, dy, dz=0):
585        for n in self._nodes:
586            n._coord = (n._coord[0] + dx, n._coord[1] + dy, n._coord[2] + dz)
587        for e in self._edges:
588            for _lane in e.getLanes():
589                _lane.setShape([(p[0] + dx, p[1] + dy, p[2] + dz) for p in _lane.getShape3D()])
590            e.rebuildShape()
591
592    def getInternalPath(self, conn, fastest=False):
593        minInternalCost = PRACTICAL_INFINITY
594        minPath = None
595        for c in conn:
596            if c.getViaLaneID() != "":
597                viaCost = 0
598                viaID = c.getViaLaneID()
599                viaPath = []
600                while viaID != "":
601                    viaLane = self.getLane(viaID)
602                    viaCost += viaLane.getLength() if not fastest else viaLane.getLength() / viaLane.getSpeed()
603                    viaID = viaLane.getOutgoing()[0].getViaLaneID()
604                    viaPath.append(viaLane.getEdge())
605                if viaCost < minInternalCost:
606                    minInternalCost = viaCost
607                    minPath = viaPath
608        return minPath, minInternalCost
609
610    def getOptimalPath(self, fromEdge, toEdge, fastest=False, maxCost=PRACTICAL_INFINITY, vClass=None,
611                       reversalPenalty=0, includeFromToCost=True, withInternal=False, ignoreDirection=False,
612                       fromPos=None, toPos=None, preferences={}):
613        """
614        Finds the optimal (shortest or fastest) path for vClass from fromEdge to toEdge
615        by using using Dijkstra's algorithm.
616        It returns a pair of a tuple of edges and the cost.
617        If no path is found the first element is None.
618        The cost for the returned path is equal to the sum of all edge costs in the path,
619        including the internal connectors, if they are present in the network.
620        The path itself does not include internal edges except for the case
621        when the start or end edge are internal edges.
622        The search may be limited using the given threshold.
623        The preferences declare a mapping from the 'routingType' of each edge to divisor
624        that is applied to the computed cost of that edge (i.e. a preference of 2 reduces cost by a factor of 0.5).
625        """
626
627        if preferences:
628            if fastest:
629                def speedFunc(edge):
630                    return preferences.get(edge.getRoutingType(), 1.0) * edge.getSpeed()
631            else:
632                def speedFunc(edge):
633                    return preferences.get(edge.getRoutingType(), 1.0)
634        elif fastest:
635            def speedFunc(edge):
636                return edge.getSpeed()
637        else:
638            def speedFunc(edge):
639                return 1.0
640
641        def remainder(edge, pos):
642            if pos < 0:
643                return min(-pos, edge.getLength())
644            return max(0., edge.getLength() - pos)
645
646        def getToNormalIncoming(edge):
647            if edge.getFunction() == '':
648                return [(e, None) for e in edge.getToNode().getIncoming() if e.getFunction() == '']
649            else:
650                return []
651
652        if self._edgeSuccessorCache != (vClass, ignoreDirection):
653            self._edgeSuccessorCache = (vClass, ignoreDirection)
654            for e1 in self.getEdges():
655                e1._succCacche = tuple(chain(e1.getAllowedOutgoing(vClass).items(),
656                                       e1.getIncoming().items() if ignoreDirection else [],
657                                       getToNormalIncoming(e1) if ignoreDirection and not self.hasWalkingArea else []))
658
659        if self._edgeCostCache != (fastest, preferences):
660            self._edgeCostCache = (fastest, preferences)
661            for e1 in self.getEdges():
662                e1._costCache = e1.getLength() / speedFunc(e1)
663
664        if self.hasInternal:
665            appendix = []
666            appendixCost = 0.
667            while toEdge.getFunction() == "internal":
668                appendix = [toEdge] + appendix
669                appendixCost += toEdge._costCache
670                toEdge = list(toEdge.getIncoming().keys())[0]
671
672        def finalizeCost(cost, path):
673            if includeFromToCost:
674                # add costs for (part of) the first edge, still needs to be fixed for wrong direction travel
675                remainFrom = fromEdge.getLength() if fromPos is None else remainder(fromEdge, fromPos)
676                cost += remainFrom / speedFunc(fromEdge)
677                # remove costs for (part of) the last edge, still needs to be fixed for wrong direction travel
678                removeTo = 0. if toPos is None else remainder(toEdge, toPos)
679            else:
680                removeTo = toEdge.getLength() if len(path) > 1 else 0.
681            cost -= removeTo / speedFunc(toEdge)
682            return cost
683
684        def constructPath(dist):
685            # destination was already reached in a previous query
686            cost, pred = dist[toEdge]
687            path = [toEdge]
688            while pred is not None:
689                if self.hasInternal and withInternal:
690                    viaPath, minInternalCost = self.getInternalPath(pred.getAllowedOutgoing(vClass).get(path[-1], []),
691                                                                    fastest=fastest)
692                    if viaPath is not None:
693                        path += reversed(viaPath)
694                path.append(pred)
695                _, pred = dist[pred]
696
697            path.reverse()
698            cost = finalizeCost(cost, path)
699            assert cost >= 0
700            if self.hasInternal:
701                if appendix:
702                    return tuple(path + appendix), cost + appendixCost
703                elif ignoreDirection and self.hasWalkingArea and not withInternal:
704                    return [e for e in path if e.getFunction() == ''], cost
705            return tuple(path), cost
706
707        needLoop = (fromEdge == toEdge
708                    and fromPos is not None
709                    and toPos is not None
710                    and fromPos > toPos
711                    and not ignoreDirection)
712
713        seen = set()
714        dist = {}
715        q = []
716        if self._routingCache is not None:
717            if needLoop:
718                # use cached results from all follower edges:
719                bestCost = maxCost
720                bestPath = None
721                for e2, conn in fromEdge.getAllowedOutgoing(vClass).items():
722                    path, cost = self.getOptimalPath(e2, toEdge, fastest=fastest, maxCost=maxCost, vClass=vClass,
723                                                     reversalPenalty=reversalPenalty,
724                                                     includeFromToCost=includeFromToCost,
725                                                     withInternal=withInternal, fromPos=0, toPos=toPos,
726                                                     preferences=preferences)
727                    if path is not None and cost < bestCost:
728                        bestPath = path
729                        bestCost = cost
730                if bestPath is not None:
731                    path = [fromEdge]
732                    if self.hasInternal and withInternal:
733                        viaPath, minInternalCost = self.getInternalPath(
734                            fromEdge.getAllowedOutgoing(vClass).get(path[0], []), fastest=fastest)
735                        if viaPath is not None:
736                            path += viaPath
737                            bestCost += minInternalCost
738                    path += list(bestPath)
739                    if includeFromToCost:
740                        bestCost += remainder(fromEdge, fromPos) / speedFunc(fromEdge)
741                    return tuple(path), bestCost
742                else:
743                    return None, PRACTICAL_INFINITY
744
745            dist = self._routingCache(fromEdge, fastest, vClass, ignoreDirection, reversalPenalty,
746                                      tuple(preferences.items()))
747            if toEdge in dist:
748                return constructPath(dist)
749            else:
750                # initialize heap from previous query
751                q = []
752                frontier = set(dist.keys())
753                for cost, prev in dist.values():
754                    frontier.discard(prev)
755                for e in frontier:
756                    cost, prev = dist[e]
757                    heapq.heappush(q, (cost, e, prev))
758        elif needLoop:
759            # start search on successors of fromEdge
760            for e2, conn in fromEdge.getAllowedOutgoing(vClass).items():
761                q.append((e2._costCache, e2, fromEdge))
762
763        if len(dist) == 0:
764            dist[fromEdge] = (0., None)
765            if not needLoop:
766                q.append((0., fromEdge, None))
767
768        while q:
769            cost, e1, prev = heapq.heappop(q)
770            if e1 in seen:
771                continue
772            seen.add(e1)
773            if e1 == toEdge:
774                return constructPath(dist)
775            if cost > maxCost:
776                return None, cost
777
778            for e2, conn in e1._succCacche:
779                if e2 not in seen:
780                    newCost = cost + e2._costCache
781                    #  print(cost, newCost, e2.getID(), speedFunc(e2))
782                    if e2 == e1.getBidi():
783                        newCost += reversalPenalty
784                    if self.hasInternal and conn is not None:
785                        viaPath, minInternalCost = self.getInternalPath(conn, fastest=fastest)
786                        if viaPath is not None:
787                            newCost += minInternalCost
788                    if e2 not in dist or newCost < dist[e2][0]:
789                        dist[e2] = (newCost, e1)
790                        heapq.heappush(q, (newCost, e2, e1))
791        return None, PRACTICAL_INFINITY
792
793    def getShortestPath(self, fromEdge, toEdge, maxCost=PRACTICAL_INFINITY, vClass=None, reversalPenalty=0,
794                        includeFromToCost=True, withInternal=False, ignoreDirection=False,
795                        fromPos=None, toPos=None, preferences={}):
796        """
797        Finds the shortest path from fromEdge to toEdge respecting vClass, using Dijkstra's algorithm.
798        It returns a pair of a tuple of edges and the cost. If no path is found the first element is None.
799        The cost for the returned path is equal to the sum of all edge lengths in the path,
800        including the internal connectors, if they are present in the network.
801        The path itself does not include internal edges except for the case
802        when the start or end edge are internal edges.
803        The search may be limited using the given threshold.
804        The preferences declare a mapping from the 'routingType' of each edge to divisor
805        that is applied to the lenght of that edge
806        (i.e. a preference of 2 reduces reduces effective length by a factor of 0.5).
807        """
808
809        return self.getOptimalPath(fromEdge, toEdge, False, maxCost, vClass, reversalPenalty,
810                                   includeFromToCost, withInternal, ignoreDirection, fromPos, toPos,
811                                   preferences)
812
813    def getFastestPath(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 fastest 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 costs 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 computed traveltime of that edge
826        (i.e. a preference of 2 reduces effective traveltime by a factor of 0.5).
827        """
828
829        return self.getOptimalPath(fromEdge, toEdge, True, maxCost, vClass, reversalPenalty,
830                                   includeFromToCost, withInternal, ignoreDirection, fromPos, toPos,
831                                   preferences)
832
833    def getReachable(self, source, vclass=None, useIncoming=False, cache=None):
834        if vclass is not None and not source.allows(vclass):
835            raise RuntimeError("'{}' does not allow {}".format(source.getID(), vclass))
836        fringe = [source]
837        found = set()
838        found.add(source)
839        while len(fringe) > 0:
840            new_fringe = []
841            for e in fringe:
842                if vclass == "pedestrian":
843                    cands = chain(chain(*e.getIncoming().values()), chain(*e.getOutgoing().values()))
844                else:
845                    cands = chain(*(e.getIncoming().values() if useIncoming else e.getOutgoing().values()))
846                # print("\n".join(map(str, list(cands))))
847                for conn in cands:
848                    if vclass is None or (
849                            conn.getFromLane().allows(vclass)
850                            and conn.getToLane().allows(vclass)):
851                        for reachable in [conn.getTo(), conn.getFrom()]:
852                            if reachable not in found:
853                                # print("added %s via %s" % (reachable, conn))
854                                if cache and reachable in cache:
855                                    found.update(cache[reachable])
856                                else:
857                                    found.add(reachable)
858                                    new_fringe.append(reachable)
859            fringe = new_fringe
860        if cache is not None:
861            cache[source] = tuple(found)
862        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 hasNode(self, id):
423    def hasNode(self, id):
424        return id in self._id2node
def getNode(self, id):
426    def getNode(self, id):
427        return self._id2node[id]
def getNodes(self):
429    def getNodes(self):
430        return self._nodes
def getTLS(self, tlid):
432    def getTLS(self, tlid):
433        return self._id2tls[tlid]
def getTLSSecure(self, tlid):
435    def getTLSSecure(self, tlid):
436        if tlid in self._id2tls:
437            tls = self._id2tls[tlid]
438        else:
439            tls = TLS(tlid)
440            self._id2tls[tlid] = tls
441            self._tlss.append(tls)
442        return tls
def getTrafficLights(self):
444    def getTrafficLights(self):
445        return self._tlss
def addTLS(self, tlid, inLane, outLane, linkNo):
447    def addTLS(self, tlid, inLane, outLane, linkNo):
448        tls = self.getTLSSecure(tlid)
449        tls.addConnection(inLane, outLane, linkNo)
450        return tls
def addTLSProgram(self, tlid, programID, offset, type, removeOthers):
452    def addTLSProgram(self, tlid, programID, offset, type, removeOthers):
453        tls = self.getTLSSecure(tlid)
454        program = TLSProgram(programID, offset, type)
455        if removeOthers:
456            tls.removePrograms()
457        tls.addProgram(program)
458        return program
def setFoes(self, junctionID, index, foes, prohibits):
460    def setFoes(self, junctionID, index, foes, prohibits):
461        self._id2node[junctionID].setFoes(index, foes, prohibits)
def forbids(self, possProhibitor, possProhibited):
463    def forbids(self, possProhibitor, possProhibited):
464        return possProhibitor.getFrom().getToNode().forbids(possProhibitor, possProhibited)
def getUpstreamEdges(self, edge, distance, stopOnTLS, stopOnTurnaround):
466    def getUpstreamEdges(self, edge, distance, stopOnTLS, stopOnTurnaround):
467        """return a list of lists of the form
468           [[firstEdge, pos, [edge_0, edge_1, ..., edge_k], aborted], ...]
469           where
470             firstEdge: is the upstream edge furthest away from the intersection,
471             [edge_0, ..., edge_k]: is the list of edges from the intersection upstream to firstEdge
472             pos: is the position on firstEdge with distance to the end of the input edge
473             aborted: a flag indicating whether the downstream
474                 search stopped at a TLS or a node without incoming edges before reaching the distance threshold
475        """
476        ret = []
477        seen = set()
478        toProc = []
479        toProc.append([edge, 0, []])
480        while not len(toProc) == 0:
481            ie = toProc.pop()
482            if ie[0] in seen:
483                continue
484            seen.add(ie[0])
485            if ie[1] + ie[0].getLength() >= distance:
486                ret.append(
487                    [ie[0], ie[0].getLength() + ie[1] - distance, ie[2], False])
488                continue
489            if len(ie[0]._incoming) == 0:
490                ret.append([ie[0], ie[0].getLength() + ie[1], ie[2], True])
491                continue
492            mn = []
493            stop = False
494            for ci in ie[0]._incoming:
495                if ci not in seen:
496                    prev = copy(ie[2])
497                    if stopOnTLS and ci._tls and ci != edge and not stop:
498                        ret.append([ie[0], ie[1], prev, True])
499                        stop = True
500                    elif (stopOnTurnaround and ie[0]._incoming[ci][0].getDirection() == Connection.LINKDIR_TURN and
501                          not stop):
502                        ret.append([ie[0], ie[1], prev, True])
503                        stop = True
504                    else:
505                        prev.append(ie[0])
506                        mn.append([ci, ie[0].getLength() + ie[1], prev])
507            if not stop:
508                toProc.extend(mn)
509        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):
511    def getEdgesByOrigID(self, origID):
512        if self._origIdx is None:
513            self._origIdx = defaultdict(set)
514            for the_edge in self._edges:
515                for the_lane in the_edge.getLanes():
516                    for oID in the_lane.getParam("origId", "").split():
517                        self._origIdx[oID].add(the_edge)
518        return self._origIdx[origID]
def getGeometries(self, useLanes, includeJunctions=False):
520    def getGeometries(self, useLanes, includeJunctions=False):
521        for e in self._edges:
522            if useLanes:
523                for the_lane in e.getLanes():
524                    yield the_lane.getID(), the_lane.getShape(), the_lane.getWidth()
525            else:
526                yield e.getID(), e.getShape(includeJunctions), sum([the_lane.getWidth() for the_lane in e.getLanes()])
def getBBoxXY(self):
528    def getBBoxXY(self):
529        """
530        Get the bounding box (bottom left and top right coordinates) for a net;
531        Coordinates are in X and Y (not Lat and Lon)
532
533        :return [(bottom_left_X, bottom_left_Y), (top_right_X, top_right_Y)]
534        """
535        return [(self._ranges[0][0], self._ranges[1][0]),
536                (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):
539    def getBBoxDiameter(self):
540        return math.sqrt(
541            (self._ranges[0][0] - self._ranges[0][1]) ** 2 +
542            (self._ranges[1][0] - self._ranges[1][1]) ** 2)
def hasGeoProj(self):
544    def hasGeoProj(self):
545        projString = self._location["projParameter"]
546        return projString != "!"
def getGeoProj(self):
548    def getGeoProj(self):
549        if not self.hasGeoProj() or not HAVE_PYPROJ:
550            raise RuntimeError("Network does not provide geo-projection or pyproj not installed.")
551        if self._proj is None:
552            try:
553                self._proj = pyproj.Proj(projparams=self._location["projParameter"])
554            except RuntimeError:
555                if hasattr(pyproj.datadir, 'set_data_dir'):
556                    pyproj.datadir.set_data_dir('/usr/share/proj')
557                    self._proj = pyproj.Proj(projparams=self._location["projParameter"])
558                raise
559        return self._proj
def getLocationOffset(self):
561    def getLocationOffset(self):
562        """ offset to be added after converting from geo-coordinates to UTM"""
563        return list(map(float, self._location["netOffset"].split(",")))

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

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

return xmin,ymin,xmax,ymax network coordinates

def convertLonLat2XY(self, lon, lat, rawUTM=False):
569    def convertLonLat2XY(self, lon, lat, rawUTM=False):
570        x, y = self.getGeoProj()(lon, lat)
571        if rawUTM:
572            return x, y
573        else:
574            x_off, y_off = self.getLocationOffset()
575            return x + x_off, y + y_off
def convertXY2LonLat(self, x, y, rawUTM=False):
577    def convertXY2LonLat(self, x, y, rawUTM=False):
578        if not rawUTM:
579            x_off, y_off = self.getLocationOffset()
580            x -= x_off
581            y -= y_off
582        return self.getGeoProj()(x, y, inverse=True)
def move(self, dx, dy, dz=0):
584    def move(self, dx, dy, dz=0):
585        for n in self._nodes:
586            n._coord = (n._coord[0] + dx, n._coord[1] + dy, n._coord[2] + dz)
587        for e in self._edges:
588            for _lane in e.getLanes():
589                _lane.setShape([(p[0] + dx, p[1] + dy, p[2] + dz) for p in _lane.getShape3D()])
590            e.rebuildShape()
def getInternalPath(self, conn, fastest=False):
592    def getInternalPath(self, conn, fastest=False):
593        minInternalCost = PRACTICAL_INFINITY
594        minPath = None
595        for c in conn:
596            if c.getViaLaneID() != "":
597                viaCost = 0
598                viaID = c.getViaLaneID()
599                viaPath = []
600                while viaID != "":
601                    viaLane = self.getLane(viaID)
602                    viaCost += viaLane.getLength() if not fastest else viaLane.getLength() / viaLane.getSpeed()
603                    viaID = viaLane.getOutgoing()[0].getViaLaneID()
604                    viaPath.append(viaLane.getEdge())
605                if viaCost < minInternalCost:
606                    minInternalCost = viaCost
607                    minPath = viaPath
608        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={}):
610    def getOptimalPath(self, fromEdge, toEdge, fastest=False, maxCost=PRACTICAL_INFINITY, vClass=None,
611                       reversalPenalty=0, includeFromToCost=True, withInternal=False, ignoreDirection=False,
612                       fromPos=None, toPos=None, preferences={}):
613        """
614        Finds the optimal (shortest or fastest) path for vClass from fromEdge to toEdge
615        by using using Dijkstra's algorithm.
616        It returns a pair of a tuple of edges and the cost.
617        If no path is found the first element is None.
618        The cost for the returned path is equal to the sum of all edge costs in the path,
619        including the internal connectors, if they are present in the network.
620        The path itself does not include internal edges except for the case
621        when the start or end edge are internal edges.
622        The search may be limited using the given threshold.
623        The preferences declare a mapping from the 'routingType' of each edge to divisor
624        that is applied to the computed cost of that edge (i.e. a preference of 2 reduces cost by a factor of 0.5).
625        """
626
627        if preferences:
628            if fastest:
629                def speedFunc(edge):
630                    return preferences.get(edge.getRoutingType(), 1.0) * edge.getSpeed()
631            else:
632                def speedFunc(edge):
633                    return preferences.get(edge.getRoutingType(), 1.0)
634        elif fastest:
635            def speedFunc(edge):
636                return edge.getSpeed()
637        else:
638            def speedFunc(edge):
639                return 1.0
640
641        def remainder(edge, pos):
642            if pos < 0:
643                return min(-pos, edge.getLength())
644            return max(0., edge.getLength() - pos)
645
646        def getToNormalIncoming(edge):
647            if edge.getFunction() == '':
648                return [(e, None) for e in edge.getToNode().getIncoming() if e.getFunction() == '']
649            else:
650                return []
651
652        if self._edgeSuccessorCache != (vClass, ignoreDirection):
653            self._edgeSuccessorCache = (vClass, ignoreDirection)
654            for e1 in self.getEdges():
655                e1._succCacche = tuple(chain(e1.getAllowedOutgoing(vClass).items(),
656                                       e1.getIncoming().items() if ignoreDirection else [],
657                                       getToNormalIncoming(e1) if ignoreDirection and not self.hasWalkingArea else []))
658
659        if self._edgeCostCache != (fastest, preferences):
660            self._edgeCostCache = (fastest, preferences)
661            for e1 in self.getEdges():
662                e1._costCache = e1.getLength() / speedFunc(e1)
663
664        if self.hasInternal:
665            appendix = []
666            appendixCost = 0.
667            while toEdge.getFunction() == "internal":
668                appendix = [toEdge] + appendix
669                appendixCost += toEdge._costCache
670                toEdge = list(toEdge.getIncoming().keys())[0]
671
672        def finalizeCost(cost, path):
673            if includeFromToCost:
674                # add costs for (part of) the first edge, still needs to be fixed for wrong direction travel
675                remainFrom = fromEdge.getLength() if fromPos is None else remainder(fromEdge, fromPos)
676                cost += remainFrom / speedFunc(fromEdge)
677                # remove costs for (part of) the last edge, still needs to be fixed for wrong direction travel
678                removeTo = 0. if toPos is None else remainder(toEdge, toPos)
679            else:
680                removeTo = toEdge.getLength() if len(path) > 1 else 0.
681            cost -= removeTo / speedFunc(toEdge)
682            return cost
683
684        def constructPath(dist):
685            # destination was already reached in a previous query
686            cost, pred = dist[toEdge]
687            path = [toEdge]
688            while pred is not None:
689                if self.hasInternal and withInternal:
690                    viaPath, minInternalCost = self.getInternalPath(pred.getAllowedOutgoing(vClass).get(path[-1], []),
691                                                                    fastest=fastest)
692                    if viaPath is not None:
693                        path += reversed(viaPath)
694                path.append(pred)
695                _, pred = dist[pred]
696
697            path.reverse()
698            cost = finalizeCost(cost, path)
699            assert cost >= 0
700            if self.hasInternal:
701                if appendix:
702                    return tuple(path + appendix), cost + appendixCost
703                elif ignoreDirection and self.hasWalkingArea and not withInternal:
704                    return [e for e in path if e.getFunction() == ''], cost
705            return tuple(path), cost
706
707        needLoop = (fromEdge == toEdge
708                    and fromPos is not None
709                    and toPos is not None
710                    and fromPos > toPos
711                    and not ignoreDirection)
712
713        seen = set()
714        dist = {}
715        q = []
716        if self._routingCache is not None:
717            if needLoop:
718                # use cached results from all follower edges:
719                bestCost = maxCost
720                bestPath = None
721                for e2, conn in fromEdge.getAllowedOutgoing(vClass).items():
722                    path, cost = self.getOptimalPath(e2, toEdge, fastest=fastest, maxCost=maxCost, vClass=vClass,
723                                                     reversalPenalty=reversalPenalty,
724                                                     includeFromToCost=includeFromToCost,
725                                                     withInternal=withInternal, fromPos=0, toPos=toPos,
726                                                     preferences=preferences)
727                    if path is not None and cost < bestCost:
728                        bestPath = path
729                        bestCost = cost
730                if bestPath is not None:
731                    path = [fromEdge]
732                    if self.hasInternal and withInternal:
733                        viaPath, minInternalCost = self.getInternalPath(
734                            fromEdge.getAllowedOutgoing(vClass).get(path[0], []), fastest=fastest)
735                        if viaPath is not None:
736                            path += viaPath
737                            bestCost += minInternalCost
738                    path += list(bestPath)
739                    if includeFromToCost:
740                        bestCost += remainder(fromEdge, fromPos) / speedFunc(fromEdge)
741                    return tuple(path), bestCost
742                else:
743                    return None, PRACTICAL_INFINITY
744
745            dist = self._routingCache(fromEdge, fastest, vClass, ignoreDirection, reversalPenalty,
746                                      tuple(preferences.items()))
747            if toEdge in dist:
748                return constructPath(dist)
749            else:
750                # initialize heap from previous query
751                q = []
752                frontier = set(dist.keys())
753                for cost, prev in dist.values():
754                    frontier.discard(prev)
755                for e in frontier:
756                    cost, prev = dist[e]
757                    heapq.heappush(q, (cost, e, prev))
758        elif needLoop:
759            # start search on successors of fromEdge
760            for e2, conn in fromEdge.getAllowedOutgoing(vClass).items():
761                q.append((e2._costCache, e2, fromEdge))
762
763        if len(dist) == 0:
764            dist[fromEdge] = (0., None)
765            if not needLoop:
766                q.append((0., fromEdge, None))
767
768        while q:
769            cost, e1, prev = heapq.heappop(q)
770            if e1 in seen:
771                continue
772            seen.add(e1)
773            if e1 == toEdge:
774                return constructPath(dist)
775            if cost > maxCost:
776                return None, cost
777
778            for e2, conn in e1._succCacche:
779                if e2 not in seen:
780                    newCost = cost + e2._costCache
781                    #  print(cost, newCost, e2.getID(), speedFunc(e2))
782                    if e2 == e1.getBidi():
783                        newCost += reversalPenalty
784                    if self.hasInternal and conn is not None:
785                        viaPath, minInternalCost = self.getInternalPath(conn, fastest=fastest)
786                        if viaPath is not None:
787                            newCost += minInternalCost
788                    if e2 not in dist or newCost < dist[e2][0]:
789                        dist[e2] = (newCost, e1)
790                        heapq.heappush(q, (newCost, e2, e1))
791        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={}):
793    def getShortestPath(self, fromEdge, toEdge, maxCost=PRACTICAL_INFINITY, vClass=None, reversalPenalty=0,
794                        includeFromToCost=True, withInternal=False, ignoreDirection=False,
795                        fromPos=None, toPos=None, preferences={}):
796        """
797        Finds the shortest path from fromEdge to toEdge respecting vClass, using Dijkstra's algorithm.
798        It returns a pair of a tuple of edges and the cost. If no path is found the first element is None.
799        The cost for the returned path is equal to the sum of all edge lengths in the path,
800        including the internal connectors, if they are present in the network.
801        The path itself does not include internal edges except for the case
802        when the start or end edge are internal edges.
803        The search may be limited using the given threshold.
804        The preferences declare a mapping from the 'routingType' of each edge to divisor
805        that is applied to the lenght of that edge
806        (i.e. a preference of 2 reduces reduces effective length by a factor of 0.5).
807        """
808
809        return self.getOptimalPath(fromEdge, toEdge, False, maxCost, vClass, reversalPenalty,
810                                   includeFromToCost, withInternal, ignoreDirection, fromPos, toPos,
811                                   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={}):
813    def getFastestPath(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 fastest 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 costs 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 computed traveltime of that edge
826        (i.e. a preference of 2 reduces effective traveltime by a factor of 0.5).
827        """
828
829        return self.getOptimalPath(fromEdge, toEdge, True, maxCost, vClass, reversalPenalty,
830                                   includeFromToCost, withInternal, ignoreDirection, fromPos, toPos,
831                                   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):
833    def getReachable(self, source, vclass=None, useIncoming=False, cache=None):
834        if vclass is not None and not source.allows(vclass):
835            raise RuntimeError("'{}' does not allow {}".format(source.getID(), vclass))
836        fringe = [source]
837        found = set()
838        found.add(source)
839        while len(fringe) > 0:
840            new_fringe = []
841            for e in fringe:
842                if vclass == "pedestrian":
843                    cands = chain(chain(*e.getIncoming().values()), chain(*e.getOutgoing().values()))
844                else:
845                    cands = chain(*(e.getIncoming().values() if useIncoming else e.getOutgoing().values()))
846                # print("\n".join(map(str, list(cands))))
847                for conn in cands:
848                    if vclass is None or (
849                            conn.getFromLane().allows(vclass)
850                            and conn.getToLane().allows(vclass)):
851                        for reachable in [conn.getTo(), conn.getFrom()]:
852                            if reachable not in found:
853                                # print("added %s via %s" % (reachable, conn))
854                                if cache and reachable in cache:
855                                    found.update(cache[reachable])
856                                else:
857                                    found.add(reachable)
858                                    new_fringe.append(reachable)
859            fringe = new_fringe
860        if cache is not None:
861            cache[source] = tuple(found)
862        return found
class NetReader(xml.sax.handler.ContentHandler):
 865class NetReader(handler.ContentHandler):
 866
 867    """Reads a network, storing the edge geometries, lane numbers and max. speeds"""
 868
 869    def __init__(self, **others):
 870        self._net = others.get('net', Net())
 871        self._currentEdge = None
 872        self._currentNode = None
 873        self._currentConnection = None
 874        self._currentLane = None
 875        self._crossingID2edgeIDs = {}
 876        self._withPhases = others.get('withPrograms', False)
 877        self._latestProgram = others.get('withLatestPrograms', False)
 878        if self._latestProgram:
 879            self._withPhases = True
 880        self._withConnections = others.get('withConnections', True)
 881        self._withFoes = others.get('withFoes', True)
 882        self._withPedestrianConnections = others.get('withPedestrianConnections', False)
 883        self._withMacroConnectors = others.get('withMacroConnectors', False)
 884        self._withInternal = others.get('withInternal', self._withPedestrianConnections)
 885        if self._withPedestrianConnections and not self._withInternal:
 886            sys.stderr.write("Warning: Option withPedestrianConnections requires withInternal\n")
 887            self._withInternal = True
 888        self._bidiEdgeIDs = {}
 889
 890    def startElement(self, name, attrs):
 891        if name == 'net':
 892            parts = attrs["version"].split('.', 1)
 893            self._net._version = (int(parts[0]), float(parts[1]))
 894        elif name == 'location':
 895            self._net.setLocation(attrs["netOffset"], attrs["convBoundary"], attrs[
 896                                  "origBoundary"], attrs["projParameter"])
 897        elif name == 'type':
 898            self._net._edgeTypes[attrs['id']] = EdgeType(attrs['id'], attrs.get('allow'), attrs.get('disallow'))
 899        elif name == 'edge':
 900            function = attrs.get('function', '')
 901            if (function == ''
 902                    or (self._withInternal and function in ['internal', 'crossing', 'walkingarea'])
 903                    or (self._withMacroConnectors and function == 'connector')):
 904                prio = -1
 905                if 'priority' in attrs:
 906                    prio = int(attrs['priority'])
 907
 908                # get the  ids
 909                edgeID = attrs['id']
 910                fromNodeID = attrs.get('from', None)
 911                toNodeID = attrs.get('to', None)
 912
 913                # for internal junctions use the junction's id for from and to node
 914                if function == 'internal' or function == 'crossing' or function == 'walkingarea':
 915                    fromNodeID = toNodeID = edgeID[1:edgeID.rfind('_')]
 916
 917                # remember edges crossed by pedestrians to link them later to the crossing objects
 918                if function == 'crossing':
 919                    self._crossingID2edgeIDs[edgeID] = attrs.get('crossingEdges').split(' ')
 920
 921                self._currentEdge = self._net.addEdge(edgeID, fromNodeID, toNodeID, prio, function,
 922                                                      attrs.get('name', ''), attrs.get('type', ''),
 923                                                      attrs.get('routingType', ''))
 924
 925                self._currentEdge.setRawShape(convertShape(attrs.get('shape', '')))
 926
 927                bidi = attrs.get('bidi', '')
 928                if bidi:
 929                    self._bidiEdgeIDs[edgeID] = bidi
 930            else:
 931                if function in ['crossing', 'walkingarea']:
 932                    self._net._crossings_and_walkingAreas.add(attrs['id'])
 933                elif function == 'connector':
 934                    self._net._macroConnectors.add(attrs['id'])
 935                self._currentEdge = None
 936        elif name == 'lane' and self._currentEdge is not None:
 937            self._currentLane = self._net.addLane(
 938                self._currentEdge,
 939                float(attrs['speed']),
 940                float(attrs['length']),
 941                float(attrs.get('width', 3.2)),
 942                attrs.get('allow'),
 943                attrs.get('disallow'),
 944                attrs.get('acceleration') == "1")
 945            self._currentLane.setShape(convertShape(attrs.get('shape', '')))
 946        elif name == 'neigh' and self._currentLane is not None:
 947            self._currentLane.setNeigh(attrs['lane'])
 948        elif name == 'junction':
 949            if attrs['id'][0] != ':':
 950                intLanes = None
 951                if self._withInternal:
 952                    intLanes = attrs["intLanes"].split(" ")
 953                self._currentNode = self._net.addNode(attrs['id'], attrs['type'],
 954                                                      tuple(
 955                                                          map(float, [attrs['x'], attrs['y'],
 956                                                                      attrs['z'] if 'z' in attrs else '0'])),
 957                                                      attrs['incLanes'].split(" "), intLanes)
 958                self._currentNode.setShape(
 959                    convertShape(attrs.get('shape', '')))
 960                if 'fringe' in attrs:
 961                    self._currentNode._fringe = attrs['fringe']
 962
 963        elif name == 'succ' and self._withConnections:  # deprecated
 964            if attrs['edge'][0] != ':':
 965                self._currentEdge = self._net.getEdge(attrs['edge'])
 966                self._currentLane = attrs['lane']
 967                self._currentLane = int(
 968                    self._currentLane[self._currentLane.rfind('_') + 1:])
 969            else:
 970                self._currentEdge = None
 971        elif name == 'succlane' and self._withConnections:  # deprecated
 972            lid = attrs['lane']
 973            if lid[0] != ':' and lid != "SUMO_NO_DESTINATION" and self._currentEdge:
 974                connected = self._net.getEdge(lid[:lid.rfind('_')])
 975                tolane = int(lid[lid.rfind('_') + 1:])
 976                if 'tl' in attrs and attrs['tl'] != "":
 977                    tl = attrs['tl']
 978                    tllink = int(attrs['linkIdx'])
 979                    tlid = attrs['tl']
 980                    toEdge = self._net.getEdge(lid[:lid.rfind('_')])
 981                    tolane2 = toEdge._lanes[tolane]
 982                    tls = self._net.addTLS(
 983                        tlid, self._currentEdge._lanes[self._currentLane], tolane2, tllink)
 984                    self._currentEdge.setTLS(tls)
 985                else:
 986                    tl = ""
 987                    tllink = -1
 988                toEdge = self._net.getEdge(lid[:lid.rfind('_')])
 989                tolane = toEdge._lanes[tolane]
 990                viaLaneID = attrs['via']
 991                self._net.addConnection(self._currentEdge, connected, self._currentEdge._lanes[
 992                                        self._currentLane], tolane,
 993                                        attrs['dir'], tl, tllink, -1,
 994                                        attrs.get('allow'), attrs.get('disallow'), attrs['state'], viaLaneID)
 995        elif name == 'connection' and self._withConnections and (attrs['from'][0] != ":" or self._withInternal):
 996            fromEdgeID = attrs['from']
 997            toEdgeID = attrs['to']
 998            if ((self._withPedestrianConnections or not (fromEdgeID in self._net._crossings_and_walkingAreas or
 999                                                         toEdgeID in self._net._crossings_and_walkingAreas))
1000                and (self._withMacroConnectors or not (fromEdgeID in self._net._macroConnectors or toEdgeID in
1001                                                       self._net._macroConnectors))):
1002                fromEdge = self._net.getEdge(fromEdgeID)
1003                toEdge = self._net.getEdge(toEdgeID)
1004                fromLane = fromEdge.getLane(int(attrs['fromLane']))
1005                toLane = toEdge.getLane(int(attrs['toLane']))
1006                if 'tl' in attrs and attrs['tl'] != "":
1007                    tl = attrs['tl']
1008                    tllink = int(attrs['linkIndex'])
1009                    tllink2 = int(attrs.get('linkIndex2', -1))
1010                    tls = self._net.addTLS(tl, fromLane, toLane, tllink)
1011                    fromEdge.setTLS(tls)
1012                else:
1013                    tl = ""
1014                    tllink = -1
1015                    tllink2 = -1
1016                try:
1017                    viaLaneID = attrs['via']
1018                except KeyError:
1019                    viaLaneID = ''
1020
1021                self._currentConnection = self._net.addConnection(
1022                    fromEdge, toEdge, fromLane, toLane, attrs['dir'], tl,
1023                    tllink, tllink2, attrs.get('allow'), attrs.get('disallow'), attrs['state'], viaLaneID)
1024
1025        # 'row-logic' is deprecated!!!
1026        elif self._withFoes and name == 'ROWLogic':
1027            self._currentNode = attrs['id']
1028        elif name == 'logicitem' and self._withFoes:  # deprecated
1029            self._net.setFoes(
1030                self._currentNode, int(attrs['request']), attrs["foes"], attrs["response"])
1031        elif name == 'request' and self._withFoes:
1032            self._currentNode.setFoes(
1033                int(attrs['index']), attrs["foes"], attrs["response"])
1034        # tl-logic is deprecated!!! NOTE: nevertheless, this is still used by
1035        # netconvert... (Leo)
1036        elif self._withPhases and name == 'tlLogic':
1037            self._currentProgram = self._net.addTLSProgram(
1038                attrs['id'], attrs['programID'],
1039                intIfPossible(float(attrs['offset'])), attrs['type'], self._latestProgram)
1040        elif self._withPhases and name == 'phase':
1041            self._currentProgram.addPhase(
1042                attrs['state'],
1043                intIfPossible(float(attrs['duration'])),
1044                intIfPossible(float(attrs['minDur'])) if 'minDur' in attrs else -1,
1045                intIfPossible(float(attrs['maxDur'])) if 'maxDur' in attrs else -1,
1046                list(map(int, attrs['next'].split())) if 'next' in attrs else [],
1047                attrs['name'] if 'name' in attrs else ""
1048            )
1049        elif name == 'roundabout':
1050            self._net.addRoundabout(
1051                attrs['nodes'].split(), attrs['edges'].split())
1052        elif name == 'param':
1053            if self._currentLane is not None:
1054                self._currentLane.setParam(attrs['key'], attrs['value'])
1055            elif self._currentEdge is not None:
1056                self._currentEdge.setParam(attrs['key'], attrs['value'])
1057            elif self._currentNode is not None:
1058                self._currentNode.setParam(attrs['key'], attrs['value'])
1059            elif self._currentConnection is not None:
1060                self._currentConnection.setParam(attrs['key'], attrs['value'])
1061            elif self._withPhases and self._currentProgram is not None:
1062                self._currentProgram.setParam(attrs['key'], attrs['value'])
1063
1064    def endElement(self, name):
1065        if name == 'lane':
1066            self._currentLane = None
1067        elif name == 'edge':
1068            self._currentEdge = None
1069        elif name == 'junction':
1070            self._currentNode = None
1071        elif name == 'connection':
1072            self._currentConnection = None
1073        # 'row-logic' is deprecated!!!
1074        elif name == 'ROWLogic' or name == 'row-logic':
1075            self._haveROWLogic = False
1076        # tl-logic is deprecated!!!
1077        elif self._withPhases and (name == 'tlLogic' or name == 'tl-logic'):
1078            self._currentProgram = None
1079        elif name == 'net':
1080            for edgeID, bidiID in self._bidiEdgeIDs.items():
1081                self._net.getEdge(edgeID)._bidi = self._net.getEdge(bidiID)
1082
1083    def endDocument(self):
1084        # set crossed edges of pedestrian crossings
1085        for crossingID, crossedEdgeIDs in self._crossingID2edgeIDs.items():
1086            pedCrossing = self._net.getEdge(crossingID)
1087            for crossedEdgeID in crossedEdgeIDs:
1088                pedCrossing._addCrossingEdge(self._net.getEdge(crossedEdgeID))
1089
1090    def getNet(self):
1091        return self._net

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

NetReader(**others)
869    def __init__(self, **others):
870        self._net = others.get('net', Net())
871        self._currentEdge = None
872        self._currentNode = None
873        self._currentConnection = None
874        self._currentLane = None
875        self._crossingID2edgeIDs = {}
876        self._withPhases = others.get('withPrograms', False)
877        self._latestProgram = others.get('withLatestPrograms', False)
878        if self._latestProgram:
879            self._withPhases = True
880        self._withConnections = others.get('withConnections', True)
881        self._withFoes = others.get('withFoes', True)
882        self._withPedestrianConnections = others.get('withPedestrianConnections', False)
883        self._withMacroConnectors = others.get('withMacroConnectors', False)
884        self._withInternal = others.get('withInternal', self._withPedestrianConnections)
885        if self._withPedestrianConnections and not self._withInternal:
886            sys.stderr.write("Warning: Option withPedestrianConnections requires withInternal\n")
887            self._withInternal = True
888        self._bidiEdgeIDs = {}
def startElement(self, name, attrs):
 890    def startElement(self, name, attrs):
 891        if name == 'net':
 892            parts = attrs["version"].split('.', 1)
 893            self._net._version = (int(parts[0]), float(parts[1]))
 894        elif name == 'location':
 895            self._net.setLocation(attrs["netOffset"], attrs["convBoundary"], attrs[
 896                                  "origBoundary"], attrs["projParameter"])
 897        elif name == 'type':
 898            self._net._edgeTypes[attrs['id']] = EdgeType(attrs['id'], attrs.get('allow'), attrs.get('disallow'))
 899        elif name == 'edge':
 900            function = attrs.get('function', '')
 901            if (function == ''
 902                    or (self._withInternal and function in ['internal', 'crossing', 'walkingarea'])
 903                    or (self._withMacroConnectors and function == 'connector')):
 904                prio = -1
 905                if 'priority' in attrs:
 906                    prio = int(attrs['priority'])
 907
 908                # get the  ids
 909                edgeID = attrs['id']
 910                fromNodeID = attrs.get('from', None)
 911                toNodeID = attrs.get('to', None)
 912
 913                # for internal junctions use the junction's id for from and to node
 914                if function == 'internal' or function == 'crossing' or function == 'walkingarea':
 915                    fromNodeID = toNodeID = edgeID[1:edgeID.rfind('_')]
 916
 917                # remember edges crossed by pedestrians to link them later to the crossing objects
 918                if function == 'crossing':
 919                    self._crossingID2edgeIDs[edgeID] = attrs.get('crossingEdges').split(' ')
 920
 921                self._currentEdge = self._net.addEdge(edgeID, fromNodeID, toNodeID, prio, function,
 922                                                      attrs.get('name', ''), attrs.get('type', ''),
 923                                                      attrs.get('routingType', ''))
 924
 925                self._currentEdge.setRawShape(convertShape(attrs.get('shape', '')))
 926
 927                bidi = attrs.get('bidi', '')
 928                if bidi:
 929                    self._bidiEdgeIDs[edgeID] = bidi
 930            else:
 931                if function in ['crossing', 'walkingarea']:
 932                    self._net._crossings_and_walkingAreas.add(attrs['id'])
 933                elif function == 'connector':
 934                    self._net._macroConnectors.add(attrs['id'])
 935                self._currentEdge = None
 936        elif name == 'lane' and self._currentEdge is not None:
 937            self._currentLane = self._net.addLane(
 938                self._currentEdge,
 939                float(attrs['speed']),
 940                float(attrs['length']),
 941                float(attrs.get('width', 3.2)),
 942                attrs.get('allow'),
 943                attrs.get('disallow'),
 944                attrs.get('acceleration') == "1")
 945            self._currentLane.setShape(convertShape(attrs.get('shape', '')))
 946        elif name == 'neigh' and self._currentLane is not None:
 947            self._currentLane.setNeigh(attrs['lane'])
 948        elif name == 'junction':
 949            if attrs['id'][0] != ':':
 950                intLanes = None
 951                if self._withInternal:
 952                    intLanes = attrs["intLanes"].split(" ")
 953                self._currentNode = self._net.addNode(attrs['id'], attrs['type'],
 954                                                      tuple(
 955                                                          map(float, [attrs['x'], attrs['y'],
 956                                                                      attrs['z'] if 'z' in attrs else '0'])),
 957                                                      attrs['incLanes'].split(" "), intLanes)
 958                self._currentNode.setShape(
 959                    convertShape(attrs.get('shape', '')))
 960                if 'fringe' in attrs:
 961                    self._currentNode._fringe = attrs['fringe']
 962
 963        elif name == 'succ' and self._withConnections:  # deprecated
 964            if attrs['edge'][0] != ':':
 965                self._currentEdge = self._net.getEdge(attrs['edge'])
 966                self._currentLane = attrs['lane']
 967                self._currentLane = int(
 968                    self._currentLane[self._currentLane.rfind('_') + 1:])
 969            else:
 970                self._currentEdge = None
 971        elif name == 'succlane' and self._withConnections:  # deprecated
 972            lid = attrs['lane']
 973            if lid[0] != ':' and lid != "SUMO_NO_DESTINATION" and self._currentEdge:
 974                connected = self._net.getEdge(lid[:lid.rfind('_')])
 975                tolane = int(lid[lid.rfind('_') + 1:])
 976                if 'tl' in attrs and attrs['tl'] != "":
 977                    tl = attrs['tl']
 978                    tllink = int(attrs['linkIdx'])
 979                    tlid = attrs['tl']
 980                    toEdge = self._net.getEdge(lid[:lid.rfind('_')])
 981                    tolane2 = toEdge._lanes[tolane]
 982                    tls = self._net.addTLS(
 983                        tlid, self._currentEdge._lanes[self._currentLane], tolane2, tllink)
 984                    self._currentEdge.setTLS(tls)
 985                else:
 986                    tl = ""
 987                    tllink = -1
 988                toEdge = self._net.getEdge(lid[:lid.rfind('_')])
 989                tolane = toEdge._lanes[tolane]
 990                viaLaneID = attrs['via']
 991                self._net.addConnection(self._currentEdge, connected, self._currentEdge._lanes[
 992                                        self._currentLane], tolane,
 993                                        attrs['dir'], tl, tllink, -1,
 994                                        attrs.get('allow'), attrs.get('disallow'), attrs['state'], viaLaneID)
 995        elif name == 'connection' and self._withConnections and (attrs['from'][0] != ":" or self._withInternal):
 996            fromEdgeID = attrs['from']
 997            toEdgeID = attrs['to']
 998            if ((self._withPedestrianConnections or not (fromEdgeID in self._net._crossings_and_walkingAreas or
 999                                                         toEdgeID in self._net._crossings_and_walkingAreas))
1000                and (self._withMacroConnectors or not (fromEdgeID in self._net._macroConnectors or toEdgeID in
1001                                                       self._net._macroConnectors))):
1002                fromEdge = self._net.getEdge(fromEdgeID)
1003                toEdge = self._net.getEdge(toEdgeID)
1004                fromLane = fromEdge.getLane(int(attrs['fromLane']))
1005                toLane = toEdge.getLane(int(attrs['toLane']))
1006                if 'tl' in attrs and attrs['tl'] != "":
1007                    tl = attrs['tl']
1008                    tllink = int(attrs['linkIndex'])
1009                    tllink2 = int(attrs.get('linkIndex2', -1))
1010                    tls = self._net.addTLS(tl, fromLane, toLane, tllink)
1011                    fromEdge.setTLS(tls)
1012                else:
1013                    tl = ""
1014                    tllink = -1
1015                    tllink2 = -1
1016                try:
1017                    viaLaneID = attrs['via']
1018                except KeyError:
1019                    viaLaneID = ''
1020
1021                self._currentConnection = self._net.addConnection(
1022                    fromEdge, toEdge, fromLane, toLane, attrs['dir'], tl,
1023                    tllink, tllink2, attrs.get('allow'), attrs.get('disallow'), attrs['state'], viaLaneID)
1024
1025        # 'row-logic' is deprecated!!!
1026        elif self._withFoes and name == 'ROWLogic':
1027            self._currentNode = attrs['id']
1028        elif name == 'logicitem' and self._withFoes:  # deprecated
1029            self._net.setFoes(
1030                self._currentNode, int(attrs['request']), attrs["foes"], attrs["response"])
1031        elif name == 'request' and self._withFoes:
1032            self._currentNode.setFoes(
1033                int(attrs['index']), attrs["foes"], attrs["response"])
1034        # tl-logic is deprecated!!! NOTE: nevertheless, this is still used by
1035        # netconvert... (Leo)
1036        elif self._withPhases and name == 'tlLogic':
1037            self._currentProgram = self._net.addTLSProgram(
1038                attrs['id'], attrs['programID'],
1039                intIfPossible(float(attrs['offset'])), attrs['type'], self._latestProgram)
1040        elif self._withPhases and name == 'phase':
1041            self._currentProgram.addPhase(
1042                attrs['state'],
1043                intIfPossible(float(attrs['duration'])),
1044                intIfPossible(float(attrs['minDur'])) if 'minDur' in attrs else -1,
1045                intIfPossible(float(attrs['maxDur'])) if 'maxDur' in attrs else -1,
1046                list(map(int, attrs['next'].split())) if 'next' in attrs else [],
1047                attrs['name'] if 'name' in attrs else ""
1048            )
1049        elif name == 'roundabout':
1050            self._net.addRoundabout(
1051                attrs['nodes'].split(), attrs['edges'].split())
1052        elif name == 'param':
1053            if self._currentLane is not None:
1054                self._currentLane.setParam(attrs['key'], attrs['value'])
1055            elif self._currentEdge is not None:
1056                self._currentEdge.setParam(attrs['key'], attrs['value'])
1057            elif self._currentNode is not None:
1058                self._currentNode.setParam(attrs['key'], attrs['value'])
1059            elif self._currentConnection is not None:
1060                self._currentConnection.setParam(attrs['key'], attrs['value'])
1061            elif self._withPhases and self._currentProgram is not None:
1062                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):
1064    def endElement(self, name):
1065        if name == 'lane':
1066            self._currentLane = None
1067        elif name == 'edge':
1068            self._currentEdge = None
1069        elif name == 'junction':
1070            self._currentNode = None
1071        elif name == 'connection':
1072            self._currentConnection = None
1073        # 'row-logic' is deprecated!!!
1074        elif name == 'ROWLogic' or name == 'row-logic':
1075            self._haveROWLogic = False
1076        # tl-logic is deprecated!!!
1077        elif self._withPhases and (name == 'tlLogic' or name == 'tl-logic'):
1078            self._currentProgram = None
1079        elif name == 'net':
1080            for edgeID, bidiID in self._bidiEdgeIDs.items():
1081                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):
1083    def endDocument(self):
1084        # set crossed edges of pedestrian crossings
1085        for crossingID, crossedEdgeIDs in self._crossingID2edgeIDs.items():
1086            pedCrossing = self._net.getEdge(crossingID)
1087            for crossedEdgeID in crossedEdgeIDs:
1088                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):
1090    def getNet(self):
1091        return self._net
def convertShape(shapeString):
1094def convertShape(shapeString):
1095    """ Convert xml shape string into float tuples.
1096
1097    This method converts the 2d or 3d shape string from SUMO's xml file
1098    into a list containing 3d float-tuples. Non existent z coordinates default
1099    to zero. If shapeString is empty, an empty list will be returned.
1100    """
1101
1102    cshape = []
1103    for pointString in shapeString.split():
1104        p = [float(e) for e in pointString.split(",")]
1105        if len(p) == 2:
1106            cshape.append((p[0], p[1], 0.))
1107        elif len(p) == 3:
1108            cshape.append(tuple(p))
1109        else:
1110            raise ValueError(
1111                'Invalid shape point "%s", should be either 2d or 3d' % pointString)
1112    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):
1115def lane2edge(laneID):
1116    return laneID[:laneID.rfind("_")]
def lane2index(laneID):
1119def lane2index(laneID):
1120    return int(laneID[laneID.rfind("_") + 1:])
def hasInternal(filename):
1123def hasInternal(filename):
1124    with openz(filename) as f:
1125        for line in f:
1126            if '<edge id="' in line:
1127                if '<edge id=":' in line:
1128                    return True
1129                else:
1130                    return False
1131    return False
def readNet(filename, **others):
1134def readNet(filename, **others):
1135    """ load a .net.xml file
1136    The following named options are supported:
1137
1138        'net' : initialize data structures with an existing net object (default Net())
1139        'withPrograms' : import all traffic light programs (default False)
1140        'withLatestPrograms' : import only the last program for each traffic light.
1141                               This is the program that would be active in sumo by default.
1142                               (default False)
1143        'withConnections' : import all connections (default True)
1144        'withFoes' : import right-of-way information (default True)
1145        'withInternal' : import internal edges and lanes (default False)
1146        'withPedestrianConnections' : import connections between sidewalks, crossings (default False)
1147        'lxml' : set to False to use the xml.sax parser instead of the lxml parser
1148        'maxcache' : set maximum cache size (default 1000) or 0 to disable optimal route caching
1149    """
1150    netreader = NetReader(**others)
1151    try:
1152        source = gzip.open(filename)
1153        source.read(10)
1154        source.seek(0)
1155    except IOError:
1156        source = filename
1157    if HAVE_LXML and others.get("lxml", True):
1158        if isinstance(source, pathlib.Path):
1159            source = str(source)
1160        for event, v in lxml.etree.iterparse(source, events=("start", "end")):
1161            if event == "start":
1162                netreader.startElement(v.tag, v.attrib)
1163            elif event == "end":
1164                netreader.endElement(v.tag)
1165            v.clear()  # reduce memory footprint
1166    else:
1167        parse(source, netreader)
1168    net = netreader.getNet()
1169    maxcache = others.get('maxcache', 1000)
1170    if HAVE_LRU_CACHE and maxcache is not None and maxcache > 0:
1171        net.initRoutingCache(maxcache)
1172    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