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