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

The whole sumo network.

hasInternal
hasWalkingArea
def initRoutingCache(self, maxsize=1000):
244    def initRoutingCache(self, maxsize=1000):
245        self._routingCache = lru_cache(maxsize=maxsize)(lambda fromEdge, fromPos, fastest, vClass, ignoreDirection, reversalPenalty, preferences: {})  # noqa
def getVersion(self):
247    def getVersion(self):
248        return self._version
def getEdgeType(self, typeID):
250    def getEdgeType(self, typeID):
251        return self._edgeTypes[typeID]
def setLocation(self, netOffset, convBoundary, origBoundary, projParameter):
253    def setLocation(self, netOffset, convBoundary, origBoundary, projParameter):
254        self._location["netOffset"] = netOffset
255        self._location["convBoundary"] = convBoundary
256        self._location["origBoundary"] = origBoundary
257        self._location["projParameter"] = projParameter
def loadSelection(self, selectionFile):
259    def loadSelection(self, selectionFile):
260        self.resetSelection()
261        with io.open(selectionFile, "r", encoding="utf-8") as f:
262            for line in f:
263                line = line.strip()
264                if line.startswith("edge:"):
265                    edgeID = line[5:]
266                    if edgeID in self._id2edge:
267                        self.getEdge(edgeID).select()
268                elif line.startswith("junction:"):
269                    nodeID = line[9:]
270                    if nodeID in self._id2node:
271                        self.getNode(nodeID).select()
def resetSelection(self):
273    def resetSelection(self):
274        for n in self._nodes:
275            n.select(False)
276        for e in self._edges:
277            e.select(False)
def addNode(self, id, type=None, coord=None, incLanes=None, intLanes=None):
279    def addNode(self, id, type=None, coord=None, incLanes=None, intLanes=None):
280        if id is None:
281            return None
282        if id not in self._id2node:
283            n = node.Node(id, type, coord, incLanes, intLanes)
284            self._nodes.append(n)
285            self._id2node[id] = n
286        self.setAdditionalNodeInfo(
287            self._id2node[id], type, coord, incLanes, intLanes)
288        return self._id2node[id]
def setAdditionalNodeInfo(self, node, type, coord, incLanes, intLanes=None):
290    def setAdditionalNodeInfo(self, node, type, coord, incLanes, intLanes=None):
291        if coord is not None and node._coord is None:
292            node._coord = coord
293            self._ranges[0][0] = min(self._ranges[0][0], coord[0])
294            self._ranges[0][1] = max(self._ranges[0][1], coord[0])
295            self._ranges[1][0] = min(self._ranges[1][0], coord[1])
296            self._ranges[1][1] = max(self._ranges[1][1], coord[1])
297        if incLanes is not None and node._incLanes is None:
298            node._incLanes = incLanes
299        if intLanes is not None and node._intLanes is None:
300            node._intLanes = intLanes
301        if type is not None and node._type is None:
302            node._type = type
def addEdge( self, id, fromID, toID, prio, function, name, edgeType='', routingType=''):
304    def addEdge(self, id, fromID, toID, prio, function, name, edgeType='', routingType=''):
305        if id not in self._id2edge:
306            fromN = self.addNode(fromID)
307            toN = self.addNode(toID)
308            e = edge.Edge(id, fromN, toN, prio, function, name, edgeType, routingType)
309            self._edges.append(e)
310            self._id2edge[id] = e
311            if function:
312                self.hasInternal = True
313                if function == "walkingarea":
314                    self.hasWalkingArea = True
315        return self._id2edge[id]
def addLane( self, edge, speed, length, width, allow=None, disallow=None, acceleration=False):
317    def addLane(self, edge, speed, length, width, allow=None, disallow=None, acceleration=False):
318        return lane.Lane(edge, speed, length, width, allow, disallow, acceleration)
def addRoundabout(self, nodes, edges=None):
320    def addRoundabout(self, nodes, edges=None):
321        r = roundabout.Roundabout(nodes, edges)
322        self._roundabouts.append(r)
323        return r
def addConnection( self, fromEdge, toEdge, fromlane, tolane, direction, tls, tllink, tllink2, allow, disallow, state, viaLaneID=None):
325    def addConnection(self, fromEdge, toEdge, fromlane, tolane, direction,
326                      tls, tllink, tllink2, allow, disallow, state, viaLaneID=None):
327        conn = connection.Connection(
328            fromEdge, toEdge, fromlane, tolane, direction,
329            tls, tllink, tllink2, allow, disallow, state, viaLaneID)
330        fromEdge.addOutgoing(conn)
331        fromlane.addOutgoing(conn)
332        toEdge._addIncoming(conn)
333        if viaLaneID:
334            try:
335                # internal lanes are only available when building with option withInternal=True
336                viaLane = self.getLane(viaLaneID)
337                viaEdge = viaLane.getEdge()
338                viaEdge._addIncoming(connection.Connection(
339                    fromEdge, viaEdge, fromlane, viaLane, direction, tls,
340                    tllink, tllink2, allow, disallow, state, ''))
341            except Exception:
342                pass
343        return conn
def getEdges(self, withInternal=True):
345    def getEdges(self, withInternal=True):
346        if not withInternal:
347            return [e for e in self._edges if e.getFunction() == '']
348        else:
349            return self._edges
def getRoundabouts(self):
351    def getRoundabouts(self):
352        return self._roundabouts
def hasEdge(self, id):
354    def hasEdge(self, id):
355        return id in self._id2edge
def getEdge(self, id):
357    def getEdge(self, id):
358        return self._id2edge[id]
def getLane(self, laneID):
360    def getLane(self, laneID):
361        edge_id, lane_index = laneID.rsplit("_", 1)
362        return self.getEdge(edge_id).getLane(int(lane_index))
def getNeighboringEdges(self, x, y, r=0.1, includeJunctions=True, allowFallback=True):
373    def getNeighboringEdges(self, x, y, r=0.1, includeJunctions=True, allowFallback=True):
374        edges = []
375        rtree = self._rtreeEdges[includeJunctions]
376        try:
377            if rtree is None:
378                rtree = self._initRTree(self._edges, includeJunctions)
379                self._rtreeEdges[includeJunctions] = rtree
380            for i in rtree.intersection((x - r, y - r, x + r, y + r)):
381                e = self._edges[i]
382                d = sumolib.geomhelper.distancePointToPolygon(
383                    (x, y), e.getShape(includeJunctions))
384                if d < r:
385                    edges.append((e, d))
386        except ImportError:
387            if not allowFallback:
388                raise
389            warnings.warn("Module 'rtree' not available. Using brute-force fallback.")
390            for the_edge in self._edges:
391                d = sumolib.geomhelper.distancePointToPolygon((x, y), the_edge.getShape(includeJunctions))
392                if d < r:
393                    edges.append((the_edge, d))
394        return edges
def getNeighboringLanes(self, x, y, r=0.1, includeJunctions=True, allowFallback=True):
396    def getNeighboringLanes(self, x, y, r=0.1, includeJunctions=True, allowFallback=True):
397        lanes = []
398        rtree = self._rtreeLanes[includeJunctions]
399        try:
400            if rtree is None:
401                for the_edge in self._edges:
402                    self._allLanes += the_edge.getLanes()
403                rtree = self._initRTree(self._allLanes, includeJunctions)
404                self._rtreeLanes[includeJunctions] = rtree
405            for i in rtree.intersection((x - r, y - r, x + r, y + r)):
406                the_lane = self._allLanes[i]
407                d = sumolib.geomhelper.distancePointToPolygon((x, y), the_lane.getShape(includeJunctions))
408                if d < r:
409                    lanes.append((the_lane, d))
410        except ImportError:
411            if not allowFallback:
412                raise
413            warnings.warn("Module 'rtree' not available. Using brute-force fallback.")
414            for the_edge in self._edges:
415                for the_lane in the_edge.getLanes():
416                    d = sumolib.geomhelper.distancePointToPolygon((x, y), the_lane.getShape(includeJunctions))
417                    if d < r:
418                        lanes.append((the_lane, d))
419        return lanes
def hasNode(self, id):
421    def hasNode(self, id):
422        return id in self._id2node
def getNode(self, id):
424    def getNode(self, id):
425        return self._id2node[id]
def getNodes(self):
427    def getNodes(self):
428        return self._nodes
def getTLS(self, tlid):
430    def getTLS(self, tlid):
431        return self._id2tls[tlid]
def getTLSSecure(self, tlid):
433    def getTLSSecure(self, tlid):
434        if tlid in self._id2tls:
435            tls = self._id2tls[tlid]
436        else:
437            tls = TLS(tlid)
438            self._id2tls[tlid] = tls
439            self._tlss.append(tls)
440        return tls
def getTrafficLights(self):
442    def getTrafficLights(self):
443        return self._tlss
def addTLS(self, tlid, inLane, outLane, linkNo):
445    def addTLS(self, tlid, inLane, outLane, linkNo):
446        tls = self.getTLSSecure(tlid)
447        tls.addConnection(inLane, outLane, linkNo)
448        return tls
def addTLSProgram(self, tlid, programID, offset, type, removeOthers):
450    def addTLSProgram(self, tlid, programID, offset, type, removeOthers):
451        tls = self.getTLSSecure(tlid)
452        program = TLSProgram(programID, offset, type)
453        if removeOthers:
454            tls.removePrograms()
455        tls.addProgram(program)
456        return program
def setFoes(self, junctionID, index, foes, prohibits):
458    def setFoes(self, junctionID, index, foes, prohibits):
459        self._id2node[junctionID].setFoes(index, foes, prohibits)
def forbids(self, possProhibitor, possProhibited):
461    def forbids(self, possProhibitor, possProhibited):
462        return possProhibitor.getFrom().getToNode().forbids(possProhibitor, possProhibited)
def getUpstreamEdges(self, edge, distance, stopOnTLS, stopOnTurnaround):
464    def getUpstreamEdges(self, edge, distance, stopOnTLS, stopOnTurnaround):
465        """return a list of lists of the form
466           [[firstEdge, pos, [edge_0, edge_1, ..., edge_k], aborted], ...]
467           where
468             firstEdge: is the upstream edge furthest away from the intersection,
469             [edge_0, ..., edge_k]: is the list of edges from the intersection upstream to firstEdge
470             pos: is the position on firstEdge with distance to the end of the input edge
471             aborted: a flag indicating whether the downstream
472                 search stopped at a TLS or a node without incoming edges before reaching the distance threshold
473        """
474        ret = []
475        seen = set()
476        toProc = []
477        toProc.append([edge, 0, []])
478        while not len(toProc) == 0:
479            ie = toProc.pop()
480            if ie[0] in seen:
481                continue
482            seen.add(ie[0])
483            if ie[1] + ie[0].getLength() >= distance:
484                ret.append(
485                    [ie[0], ie[0].getLength() + ie[1] - distance, ie[2], False])
486                continue
487            if len(ie[0]._incoming) == 0:
488                ret.append([ie[0], ie[0].getLength() + ie[1], ie[2], True])
489                continue
490            mn = []
491            stop = False
492            for ci in ie[0]._incoming:
493                if ci not in seen:
494                    prev = copy(ie[2])
495                    if stopOnTLS and ci._tls and ci != edge and not stop:
496                        ret.append([ie[0], ie[1], prev, True])
497                        stop = True
498                    elif (stopOnTurnaround and ie[0]._incoming[ci][0].getDirection() == Connection.LINKDIR_TURN and
499                          not stop):
500                        ret.append([ie[0], ie[1], prev, True])
501                        stop = True
502                    else:
503                        prev.append(ie[0])
504                        mn.append([ci, ie[0].getLength() + ie[1], prev])
505            if not stop:
506                toProc.extend(mn)
507        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):
509    def getEdgesByOrigID(self, origID):
510        if self._origIdx is None:
511            self._origIdx = defaultdict(set)
512            for the_edge in self._edges:
513                for the_lane in the_edge.getLanes():
514                    for oID in the_lane.getParam("origId", "").split():
515                        self._origIdx[oID].add(the_edge)
516        return self._origIdx[origID]
def getGeometries(self, useLanes, includeJunctions=False):
518    def getGeometries(self, useLanes, includeJunctions=False):
519        for e in self._edges:
520            if useLanes:
521                for the_lane in e.getLanes():
522                    yield the_lane.getID(), the_lane.getShape(), the_lane.getWidth()
523            else:
524                yield e.getID(), e.getShape(includeJunctions), sum([the_lane.getWidth() for the_lane in e.getLanes()])
def getBBoxXY(self):
526    def getBBoxXY(self):
527        """
528        Get the bounding box (bottom left and top right coordinates) for a net;
529        Coordinates are in X and Y (not Lat and Lon)
530
531        :return [(bottom_left_X, bottom_left_Y), (top_right_X, top_right_Y)]
532        """
533        return [(self._ranges[0][0], self._ranges[1][0]),
534                (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):
537    def getBBoxDiameter(self):
538        return math.sqrt(
539            (self._ranges[0][0] - self._ranges[0][1]) ** 2 +
540            (self._ranges[1][0] - self._ranges[1][1]) ** 2)
def hasGeoProj(self):
542    def hasGeoProj(self):
543        projString = self._location["projParameter"]
544        return projString != "!"
def getGeoProj(self):
546    def getGeoProj(self):
547        if not self.hasGeoProj() or not HAVE_PYPROJ:
548            raise RuntimeError("Network does not provide geo-projection or pyproj not installed.")
549        if self._proj is None:
550            try:
551                self._proj = pyproj.Proj(projparams=self._location["projParameter"])
552            except RuntimeError:
553                if hasattr(pyproj.datadir, 'set_data_dir'):
554                    pyproj.datadir.set_data_dir('/usr/share/proj')
555                    self._proj = pyproj.Proj(projparams=self._location["projParameter"])
556                raise
557        return self._proj
def getLocationOffset(self):
559    def getLocationOffset(self):
560        """ offset to be added after converting from geo-coordinates to UTM"""
561        return list(map(float, self._location["netOffset"].split(",")))

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

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

return xmin,ymin,xmax,ymax network coordinates

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

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={}):
781    def getShortestPath(self, fromEdge, toEdge, maxCost=1e400, vClass=None, reversalPenalty=0,
782                        includeFromToCost=True, withInternal=False, ignoreDirection=False,
783                        fromPos=None, toPos=None, preferences={}):
784        """
785        Finds the shortest path from fromEdge to toEdge respecting vClass, using Dijkstra's algorithm.
786        It returns a pair of a tuple of edges and the cost. If no path is found the first element is None.
787        The cost for the returned path is equal to the sum of all edge lengths in the path,
788        including the internal connectors, if they are present in the network.
789        The path itself does not include internal edges except for the case
790        when the start or end edge are internal edges.
791        The search may be limited using the given threshold.
792        The preferences declare a mapping from the 'routingType' of each edge to divisor
793        that is applied to the lenght of that edge
794        (i.e. a preference of 2 reduces reduces effective length by a factor of 0.5).
795        """
796
797        return self.getOptimalPath(fromEdge, toEdge, False, maxCost, vClass, reversalPenalty,
798                                   includeFromToCost, withInternal, ignoreDirection, fromPos, toPos,
799                                   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={}):
801    def getFastestPath(self, fromEdge, toEdge, maxCost=1e400, vClass=None, reversalPenalty=0,
802                       includeFromToCost=True, withInternal=False, ignoreDirection=False,
803                       fromPos=None, toPos=None, preferences={}):
804        """
805        Finds the fastest path from fromEdge to toEdge respecting vClass, using Dijkstra's algorithm.
806        It returns a pair of a tuple of edges and the cost. If no path is found the first element is None.
807        The cost for the returned path is equal to the sum of all edge costs in the path,
808        including the internal connectors, if they are present in the network.
809        The path itself does not include internal edges except for the case
810        when the start or end edge are internal edges.
811        The search may be limited using the given threshold.
812        The preferences declare a mapping from the 'routingType' of each edge to divisor
813        that is applied to the computed traveltime of that edge
814        (i.e. a preference of 2 reduces effective traveltime by a factor of 0.5).
815        """
816
817        return self.getOptimalPath(fromEdge, toEdge, True, maxCost, vClass, reversalPenalty,
818                                   includeFromToCost, withInternal, ignoreDirection, fromPos, toPos,
819                                   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):
821    def getReachable(self, source, vclass=None, useIncoming=False, cache=None):
822        if vclass is not None and not source.allows(vclass):
823            raise RuntimeError("'{}' does not allow {}".format(source.getID(), vclass))
824        fringe = [source]
825        found = set()
826        found.add(source)
827        while len(fringe) > 0:
828            new_fringe = []
829            for e in fringe:
830                if vclass == "pedestrian":
831                    cands = chain(chain(*e.getIncoming().values()), chain(*e.getOutgoing().values()))
832                else:
833                    cands = chain(*(e.getIncoming().values() if useIncoming else e.getOutgoing().values()))
834                # print("\n".join(map(str, list(cands))))
835                for conn in cands:
836                    if vclass is None or (
837                            conn.getFromLane().allows(vclass)
838                            and conn.getToLane().allows(vclass)):
839                        for reachable in [conn.getTo(), conn.getFrom()]:
840                            if reachable not in found:
841                                # print("added %s via %s" % (reachable, conn))
842                                if cache and reachable in cache:
843                                    found.update(cache[reachable])
844                                else:
845                                    found.add(reachable)
846                                    new_fringe.append(reachable)
847            fringe = new_fringe
848        if cache is not None:
849            cache[source] = tuple(found)
850        return found
class NetReader(xml.sax.handler.ContentHandler):
 853class NetReader(handler.ContentHandler):
 854
 855    """Reads a network, storing the edge geometries, lane numbers and max. speeds"""
 856
 857    def __init__(self, **others):
 858        self._net = others.get('net', Net())
 859        self._currentEdge = None
 860        self._currentNode = None
 861        self._currentConnection = None
 862        self._currentLane = None
 863        self._crossingID2edgeIDs = {}
 864        self._withPhases = others.get('withPrograms', False)
 865        self._latestProgram = others.get('withLatestPrograms', False)
 866        if self._latestProgram:
 867            self._withPhases = True
 868        self._withConnections = others.get('withConnections', True)
 869        self._withFoes = others.get('withFoes', True)
 870        self._withPedestrianConnections = others.get('withPedestrianConnections', False)
 871        self._withMacroConnectors = others.get('withMacroConnectors', False)
 872        self._withInternal = others.get('withInternal', self._withPedestrianConnections)
 873        if self._withPedestrianConnections and not self._withInternal:
 874            sys.stderr.write("Warning: Option withPedestrianConnections requires withInternal\n")
 875            self._withInternal = True
 876        self._bidiEdgeIDs = {}
 877
 878    def startElement(self, name, attrs):
 879        if name == 'net':
 880            parts = attrs["version"].split('.', 1)
 881            self._net._version = (int(parts[0]), float(parts[1]))
 882        elif name == 'location':
 883            self._net.setLocation(attrs["netOffset"], attrs["convBoundary"], attrs[
 884                                  "origBoundary"], attrs["projParameter"])
 885        elif name == 'type':
 886            self._net._edgeTypes[attrs['id']] = EdgeType(attrs['id'], attrs.get('allow'), attrs.get('disallow'))
 887        elif name == 'edge':
 888            function = attrs.get('function', '')
 889            if (function == ''
 890                    or (self._withInternal and function in ['internal', 'crossing', 'walkingarea'])
 891                    or (self._withMacroConnectors and function == 'connector')):
 892                prio = -1
 893                if 'priority' in attrs:
 894                    prio = int(attrs['priority'])
 895
 896                # get the  ids
 897                edgeID = attrs['id']
 898                fromNodeID = attrs.get('from', None)
 899                toNodeID = attrs.get('to', None)
 900
 901                # for internal junctions use the junction's id for from and to node
 902                if function == 'internal' or function == 'crossing' or function == 'walkingarea':
 903                    fromNodeID = toNodeID = edgeID[1:edgeID.rfind('_')]
 904
 905                # remember edges crossed by pedestrians to link them later to the crossing objects
 906                if function == 'crossing':
 907                    self._crossingID2edgeIDs[edgeID] = attrs.get('crossingEdges').split(' ')
 908
 909                self._currentEdge = self._net.addEdge(edgeID, fromNodeID, toNodeID, prio, function,
 910                                                      attrs.get('name', ''), attrs.get('type', ''),
 911                                                      attrs.get('routingType', ''))
 912
 913                self._currentEdge.setRawShape(convertShape(attrs.get('shape', '')))
 914
 915                bidi = attrs.get('bidi', '')
 916                if bidi:
 917                    self._bidiEdgeIDs[edgeID] = bidi
 918            else:
 919                if function in ['crossing', 'walkingarea']:
 920                    self._net._crossings_and_walkingAreas.add(attrs['id'])
 921                elif function == 'connector':
 922                    self._net._macroConnectors.add(attrs['id'])
 923                self._currentEdge = None
 924        elif name == 'lane' and self._currentEdge is not None:
 925            self._currentLane = self._net.addLane(
 926                self._currentEdge,
 927                float(attrs['speed']),
 928                float(attrs['length']),
 929                float(attrs.get('width', 3.2)),
 930                attrs.get('allow'),
 931                attrs.get('disallow'),
 932                attrs.get('acceleration') == "1")
 933            self._currentLane.setShape(convertShape(attrs.get('shape', '')))
 934        elif name == 'neigh' and self._currentLane is not None:
 935            self._currentLane.setNeigh(attrs['lane'])
 936        elif name == 'junction':
 937            if attrs['id'][0] != ':':
 938                intLanes = None
 939                if self._withInternal:
 940                    intLanes = attrs["intLanes"].split(" ")
 941                self._currentNode = self._net.addNode(attrs['id'], attrs['type'],
 942                                                      tuple(
 943                                                          map(float, [attrs['x'], attrs['y'],
 944                                                                      attrs['z'] if 'z' in attrs else '0'])),
 945                                                      attrs['incLanes'].split(" "), intLanes)
 946                self._currentNode.setShape(
 947                    convertShape(attrs.get('shape', '')))
 948                if 'fringe' in attrs:
 949                    self._currentNode._fringe = attrs['fringe']
 950
 951        elif name == 'succ' and self._withConnections:  # deprecated
 952            if attrs['edge'][0] != ':':
 953                self._currentEdge = self._net.getEdge(attrs['edge'])
 954                self._currentLane = attrs['lane']
 955                self._currentLane = int(
 956                    self._currentLane[self._currentLane.rfind('_') + 1:])
 957            else:
 958                self._currentEdge = None
 959        elif name == 'succlane' and self._withConnections:  # deprecated
 960            lid = attrs['lane']
 961            if lid[0] != ':' and lid != "SUMO_NO_DESTINATION" and self._currentEdge:
 962                connected = self._net.getEdge(lid[:lid.rfind('_')])
 963                tolane = int(lid[lid.rfind('_') + 1:])
 964                if 'tl' in attrs and attrs['tl'] != "":
 965                    tl = attrs['tl']
 966                    tllink = int(attrs['linkIdx'])
 967                    tlid = attrs['tl']
 968                    toEdge = self._net.getEdge(lid[:lid.rfind('_')])
 969                    tolane2 = toEdge._lanes[tolane]
 970                    tls = self._net.addTLS(
 971                        tlid, self._currentEdge._lanes[self._currentLane], tolane2, tllink)
 972                    self._currentEdge.setTLS(tls)
 973                else:
 974                    tl = ""
 975                    tllink = -1
 976                toEdge = self._net.getEdge(lid[:lid.rfind('_')])
 977                tolane = toEdge._lanes[tolane]
 978                viaLaneID = attrs['via']
 979                self._net.addConnection(self._currentEdge, connected, self._currentEdge._lanes[
 980                                        self._currentLane], tolane,
 981                                        attrs['dir'], tl, tllink, -1,
 982                                        attrs.get('allow'), attrs.get('disallow'), attrs['state'], viaLaneID)
 983        elif name == 'connection' and self._withConnections and (attrs['from'][0] != ":" or self._withInternal):
 984            fromEdgeID = attrs['from']
 985            toEdgeID = attrs['to']
 986            if ((self._withPedestrianConnections or not (fromEdgeID in self._net._crossings_and_walkingAreas or
 987                                                         toEdgeID in self._net._crossings_and_walkingAreas))
 988                and (self._withMacroConnectors or not (fromEdgeID in self._net._macroConnectors or toEdgeID in
 989                                                       self._net._macroConnectors))):
 990                fromEdge = self._net.getEdge(fromEdgeID)
 991                toEdge = self._net.getEdge(toEdgeID)
 992                fromLane = fromEdge.getLane(int(attrs['fromLane']))
 993                toLane = toEdge.getLane(int(attrs['toLane']))
 994                if 'tl' in attrs and attrs['tl'] != "":
 995                    tl = attrs['tl']
 996                    tllink = int(attrs['linkIndex'])
 997                    tllink2 = int(attrs.get('linkIndex2', -1))
 998                    tls = self._net.addTLS(tl, fromLane, toLane, tllink)
 999                    fromEdge.setTLS(tls)
1000                else:
1001                    tl = ""
1002                    tllink = -1
1003                    tllink2 = -1
1004                try:
1005                    viaLaneID = attrs['via']
1006                except KeyError:
1007                    viaLaneID = ''
1008
1009                self._currentConnection = self._net.addConnection(
1010                    fromEdge, toEdge, fromLane, toLane, attrs['dir'], tl,
1011                    tllink, tllink2, attrs.get('allow'), attrs.get('disallow'), attrs['state'], viaLaneID)
1012
1013        # 'row-logic' is deprecated!!!
1014        elif self._withFoes and name == 'ROWLogic':
1015            self._currentNode = attrs['id']
1016        elif name == 'logicitem' and self._withFoes:  # deprecated
1017            self._net.setFoes(
1018                self._currentNode, int(attrs['request']), attrs["foes"], attrs["response"])
1019        elif name == 'request' and self._withFoes:
1020            self._currentNode.setFoes(
1021                int(attrs['index']), attrs["foes"], attrs["response"])
1022        # tl-logic is deprecated!!! NOTE: nevertheless, this is still used by
1023        # netconvert... (Leo)
1024        elif self._withPhases and name == 'tlLogic':
1025            self._currentProgram = self._net.addTLSProgram(
1026                attrs['id'], attrs['programID'],
1027                intIfPossible(float(attrs['offset'])), attrs['type'], self._latestProgram)
1028        elif self._withPhases and name == 'phase':
1029            self._currentProgram.addPhase(
1030                attrs['state'],
1031                intIfPossible(float(attrs['duration'])),
1032                intIfPossible(float(attrs['minDur'])) if 'minDur' in attrs else -1,
1033                intIfPossible(float(attrs['maxDur'])) if 'maxDur' in attrs else -1,
1034                list(map(int, attrs['next'].split())) if 'next' in attrs else [],
1035                attrs['name'] if 'name' in attrs else ""
1036            )
1037        elif name == 'roundabout':
1038            self._net.addRoundabout(
1039                attrs['nodes'].split(), attrs['edges'].split())
1040        elif name == 'param':
1041            if self._currentLane is not None:
1042                self._currentLane.setParam(attrs['key'], attrs['value'])
1043            elif self._currentEdge is not None:
1044                self._currentEdge.setParam(attrs['key'], attrs['value'])
1045            elif self._currentNode is not None:
1046                self._currentNode.setParam(attrs['key'], attrs['value'])
1047            elif self._currentConnection is not None:
1048                self._currentConnection.setParam(attrs['key'], attrs['value'])
1049            elif self._withPhases and self._currentProgram is not None:
1050                self._currentProgram.setParam(attrs['key'], attrs['value'])
1051
1052    def endElement(self, name):
1053        if name == 'lane':
1054            self._currentLane = None
1055        elif name == 'edge':
1056            self._currentEdge = None
1057        elif name == 'junction':
1058            self._currentNode = None
1059        elif name == 'connection':
1060            self._currentConnection = None
1061        # 'row-logic' is deprecated!!!
1062        elif name == 'ROWLogic' or name == 'row-logic':
1063            self._haveROWLogic = False
1064        # tl-logic is deprecated!!!
1065        elif self._withPhases and (name == 'tlLogic' or name == 'tl-logic'):
1066            self._currentProgram = None
1067        elif name == 'net':
1068            for edgeID, bidiID in self._bidiEdgeIDs.items():
1069                self._net.getEdge(edgeID)._bidi = self._net.getEdge(bidiID)
1070
1071    def endDocument(self):
1072        # set crossed edges of pedestrian crossings
1073        for crossingID, crossedEdgeIDs in self._crossingID2edgeIDs.items():
1074            pedCrossing = self._net.getEdge(crossingID)
1075            for crossedEdgeID in crossedEdgeIDs:
1076                pedCrossing._addCrossingEdge(self._net.getEdge(crossedEdgeID))
1077
1078    def getNet(self):
1079        return self._net

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

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