sumolib.miscutils
1# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo 2# Copyright (C) 2012-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 miscutils.py 14# @author Jakob Erdmann 15# @author Michael Behrisch 16# @author Mirko Barthauer 17# @date 2012-05-08 18 19from __future__ import absolute_import 20from __future__ import print_function 21from __future__ import division 22import sys 23import time 24import os 25import math 26import colorsys 27import socket 28import random 29import gzip 30import codecs 31import io 32from pathlib import Path 33from types import ModuleType, FunctionType 34from gc import get_referents 35try: 36 from urllib.request import urlopen 37except ImportError: 38 from urllib import urlopen 39# needed for backward compatibility 40from .statistics import Statistics, geh, uMax, uMin, round # noqa 41 42PRACTICAL_INFINITY = 1e400 43_BLACKLIST = type, ModuleType, FunctionType 44 45 46def get_size(obj): 47 """sum size of object & members. 48 lifted from https://stackoverflow.com/a/30316760 49 """ 50 if isinstance(obj, (_BLACKLIST)): 51 raise TypeError('getsize() does not take argument of type: ' + str(type(obj))) 52 seen_ids = set() 53 size = 0 54 objects = [obj] 55 while objects: 56 need_referents = [] 57 for obj in objects: 58 if not isinstance(obj, _BLACKLIST) and id(obj) not in seen_ids: 59 seen_ids.add(id(obj)) 60 size += sys.getsizeof(obj) 61 need_referents.append(obj) 62 objects = get_referents(*need_referents) 63 return size 64 65 66def benchmark(func): 67 """ 68 decorator for timing a function 69 """ 70 def benchmark_wrapper(*args, **kwargs): 71 started = time.time() 72 now = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.localtime()) 73 print('function %s called at %s' % (func.__name__, now)) 74 sys.stdout.flush() 75 result = func(*args, **kwargs) 76 print('function %s finished after %f seconds' % 77 (func.__name__, time.time() - started)) 78 sys.stdout.flush() 79 return result 80 return benchmark_wrapper 81 82 83class Benchmarker: 84 """ 85 class for benchmarking a function using a "with"-statement. 86 Preferable over the "benchmark" function for the following use cases 87 - benchmarking a code block that isn't wrapped in a function 88 - benchmarking a function only in some calls 89 """ 90 91 def __init__(self, active, description): 92 self.active = active 93 self.description = description 94 95 def __enter__(self): 96 self.started = time.time() 97 98 def __exit__(self, *args): 99 if self.active: 100 duration = time.time() - self.started 101 print("%s finished after %s" % (self.description, humanReadableTime(duration))) 102 103 104class working_dir: 105 """ 106 temporarily change working directory using 'with' statement 107 """ 108 109 def __init__(self, dir): 110 self.dir = dir 111 self.origdir = os.getcwd() 112 113 def __enter__(self): 114 os.chdir(self.dir) 115 116 def __exit__(self, type, value, traceback): 117 os.chdir(self.origdir) 118 119 120class Colorgen: 121 DISTINCT = [ 122 (0.17, 1.0, 0.5), 123 (0.0, 0.9, 1.0), 124 (0.35, 0.67, 0.71), 125 (0.14, 0.9, 1.0), 126 (0.56, 1.0, 0.78), 127 (0.07, 0.8, 0.96), 128 (0.79, 0.83, 0.71), 129 (0.5, 0.71, 0.94), 130 (0.84, 0.79, 0.94), 131 (0.2, 0.76, 0.96), 132 (0.0, 0.24, 0.98), 133 (0.5, 1.0, 0.5), 134 (0.77, 0.25, 1.0), 135 (0.09, 0.76, 0.67), 136 (0.15, 0.22, 1.0), 137 (0.0, 1.0, 0.5), 138 (0.38, 0.33, 1.0), 139 (0.67, 1.0, 0.5), 140 ] 141 142 def __init__(self, hsv, cycleLength=10.67): 143 self.hsv = hsv 144 self.cycle = [int(random.random() * 256) for x in self.hsv] 145 self.cycleOffset = int(round(256 / cycleLength)) 146 self.distinctIndex = 0 147 148 def get_value(self, opt, index): 149 if opt == 'random': 150 return random.random() 151 if opt == 'cycle': 152 # the 255 below is intentional to get all color values when cycling long enough 153 self.cycle[index] = (self.cycle[index] + self.cycleOffset) % 255 154 return self.cycle[index] / 255.0 155 if opt == 'distinct': 156 if index == 0: 157 self.distinctIndex = (self.distinctIndex + 1) % len(self.DISTINCT) 158 return self.DISTINCT[self.distinctIndex][index] 159 return float(opt) 160 161 def floatTuple(self): 162 """return color as a tuple of floats each in [0,1]""" 163 return colorsys.hsv_to_rgb(*[self.get_value(o, i) for i, o in enumerate(self.hsv)]) 164 165 def byteTuple(self): 166 """return color as a tuple of bytes each in [0,255]""" 167 return tuple([int(round(255 * x)) for x in self.floatTuple()]) 168 169 def __call__(self): 170 """return constant or randomized rgb-color string""" 171 return ','.join(map(str, self.byteTuple())) 172 173 174class priorityDictionary(dict): 175 176 def __init__(self): 177 '''Initialize priorityDictionary by creating binary heap 178 of pairs (value,key). Note that changing or removing a dict entry will 179 not remove the old pair from the heap until it is found by smallest() or 180 until the heap is rebuilt.''' 181 self.__heap = [] 182 dict.__init__(self) 183 184 def smallest(self): 185 '''Find smallest item after removing deleted items from heap.''' 186 if len(self) == 0: 187 raise IndexError("smallest of empty priorityDictionary") 188 heap = self.__heap 189 while heap[0][1] not in self or self[heap[0][1]] != heap[0][0]: 190 lastItem = heap.pop() 191 insertionPoint = 0 192 while 1: 193 smallChild = 2 * insertionPoint + 1 194 if smallChild + 1 < len(heap) and \ 195 heap[smallChild][0] > heap[smallChild + 1][0]: 196 smallChild += 1 197 if smallChild >= len(heap) or lastItem <= heap[smallChild]: 198 heap[insertionPoint] = lastItem 199 break 200 heap[insertionPoint] = heap[smallChild] 201 insertionPoint = smallChild 202 return heap[0][1] 203 204 def __iter__(self): 205 '''Create destructive sorted iterator of priorityDictionary.''' 206 def iterfn(): 207 while len(self) > 0: 208 x = self.smallest() 209 yield x 210 del self[x] 211 return iterfn() 212 213 def __setitem__(self, key, val): 214 '''Change value stored in dictionary and add corresponding 215 pair to heap. Rebuilds the heap if the number of deleted items grows 216 too large, to avoid memory leakage.''' 217 dict.__setitem__(self, key, val) 218 heap = self.__heap 219 if len(heap) > 2 * len(self): 220 self.__heap = [(v, k) for k, v in self.items()] 221 self.__heap.sort() # builtin sort likely faster than O(n) heapify 222 else: 223 newPair = (val, key) 224 insertionPoint = len(heap) 225 heap.append(None) 226 while insertionPoint > 0 and val < heap[(insertionPoint - 1) // 2][0]: 227 heap[insertionPoint] = heap[(insertionPoint - 1) // 2] 228 insertionPoint = (insertionPoint - 1) // 2 229 heap[insertionPoint] = newPair 230 231 def setdefault(self, key, val): 232 '''Reimplement setdefault to call our customized __setitem__.''' 233 if key not in self: 234 self[key] = val 235 return self[key] 236 237 def update(self, other): 238 for key in other.keys(): 239 self[key] = other[key] 240 241 242def getFreeSocketPort(numTries=10): 243 for _ in range(numTries): 244 try: 245 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 246 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 247 s.bind(('', 0)) 248 p = s.getsockname()[1] 249 s.close() 250 return p 251 except socket.error: 252 pass 253 return None 254 255 256def getSocketStream(port, mode='rb'): 257 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 258 s.bind(("localhost", port)) 259 s.listen(1) 260 conn, _ = s.accept() 261 return conn.makefile(mode) 262 263 264# euclidean distance between two coordinates in the plane 265def euclidean(a, b): 266 return math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) 267 268 269def humanReadableTime(seconds): 270 result = "" 271 sign = '-' if seconds < 0 else '' 272 seconds = abs(seconds) 273 ds = 3600 * 24 274 if seconds > ds: 275 result = "%s:" % int(seconds / ds) 276 seconds = seconds % ds 277 result += "%02i:" % int(seconds / 3600) 278 seconds = seconds % 3600 279 result += "%02i:" % int(seconds / 60) 280 seconds = seconds % 60 281 if seconds == int(seconds): 282 seconds = int(seconds) 283 result += "%02i" % seconds 284 return sign + result 285 286 287SPECIAL_TIME_STRINGS = ["triggered", "containerTriggered", "split", "begin"] 288 289 290def parseTime(t, factor=1): 291 try: 292 return float(t) * factor 293 except ValueError: 294 pass 295 try: 296 # prepended zero is ignored if the date value already contains days 297 days, hours, minutes, seconds = ([0] + list(map(float, t.split(':'))))[-4:] 298 sign = -1 if t.strip()[0] == '-' else 1 299 return (3600 * 24 * days + 3600 * hours + 60 * minutes + seconds) * sign * factor 300 except ValueError: 301 if t in SPECIAL_TIME_STRINGS: 302 # signal special case but don't crash 303 return None 304 else: 305 raise 306 307 308def parseBool(val): 309 # see data/xsd/baseTypes:boolType 310 return val in ["true", "True", "x", "1", "yes", "on"] 311 312 313def getFlowNumber(flow): 314 """interpret number of vehicles from a flow parsed by sumolib.xml.parse""" 315 if flow.number is not None: 316 return int(flow.number) 317 if flow.end is not None: 318 duration = parseTime(flow.end) - parseTime(flow.begin) 319 period = 0 320 isFractional = False 321 if flow.period is not None: 322 if 'exp' in flow.period: 323 # use expected value 324 period = 1 / float(flow.period[4:-1]) 325 isFractional = True 326 else: 327 period = float(flow.period) 328 elif flow.probability is not None: 329 # use expected value 330 period = 1 / float(flow.probability) 331 isFractional = True 332 for attr in ['perHour', 'vehsPerHour']: 333 if flow.hasAttribute(attr): 334 period = 3600 / float(flow.getAttribute(attr)) 335 if period > 0: 336 count = duration / period 337 if isFractional: 338 return count 339 else: 340 # flows with a regular period always start at least one vehicle at the begin time 341 return math.ceil(count) 342 else: 343 return 1 344 345 346def intIfPossible(val): 347 if int(val) == val: 348 return int(val) 349 else: 350 return val 351 352 353def openz(fileOrURL, mode="r", **kwargs): 354 """ 355 Opens transparently files, URLs and gzipped files for reading and writing. 356 Special file names "stdout" and "stderr" are handled as well. 357 Also enforces UTF8 on text output / input and should handle BOMs in input. 358 Should be compatible with python 2 and 3. 359 """ 360 encoding = kwargs.get("encoding", "utf8" if "w" in mode else "utf-8-sig") 361 try: 362 if fileOrURL.startswith("http://") or fileOrURL.startswith("https://"): 363 return io.BytesIO(urlopen(fileOrURL).read()) 364 if fileOrURL == "stdout": 365 return sys.stdout 366 if fileOrURL == "stderr": 367 return sys.stderr 368 if fileOrURL.endswith(".gz") and "w" in mode: 369 if "b" in mode: 370 return gzip.open(fileOrURL, mode="w") 371 return gzip.open(fileOrURL, mode="wt", encoding=encoding) 372 if kwargs.get("trySocket") and fileOrURL.isdigit(): 373 return getSocketStream(int(fileOrURL), mode) 374 if kwargs.get("tryGZip", True) and "r" in mode: 375 with gzip.open(fileOrURL) as fd: 376 fd.read(1) 377 if "b" in mode: 378 return gzip.open(fileOrURL) 379 if sys.version_info[0] < 3: 380 return codecs.getreader('utf-8')(gzip.open(fileOrURL)) 381 return gzip.open(fileOrURL, mode="rt", encoding=encoding) 382 except OSError as e: 383 if kwargs.get("printErrors"): 384 print(e, file=sys.stderr) 385 except IOError as e: 386 if kwargs.get("printErrors"): 387 print(e, file=sys.stderr) 388 if "b" in mode: 389 return io.open(fileOrURL, mode=mode) 390 return io.open(fileOrURL, mode=mode, encoding=encoding) 391 392 393def short_names(filenames, noEmpty): 394 if len(filenames) == 1: 395 return filenames 396 reversedNames = [''.join(reversed(f)) for f in filenames] 397 prefix = os.path.commonprefix(filenames) 398 suffix = os.path.commonprefix(reversedNames) 399 prefixLen = len(prefix) 400 suffixLen = len(suffix) 401 shortened = [f[prefixLen:-suffixLen] for f in filenames] 402 if noEmpty and any([not f for f in shortened]): 403 # make longer to avoid empty file names 404 base = os.path.basename(prefix) 405 shortened = [base + f for f in shortened] 406 return shortened 407 408 409def getBaseName(filename): 410 """strip extensions such as .net.xml.gz""" 411 if filename[-11:] == ".net.xml.gz" and len(filename) > 11: 412 return filename[:-11] 413 elif filename[-8:] == ".net.xml" and len(filename) > 8: 414 return filename[:-8] 415 elif filename[-7:] == ".xml.gz" and len(filename) > 7: 416 return filename[:-7] 417 elif filename[-4:] == ".xml" and len(filename) > 4: 418 return filename[:-4] 419 else: 420 return filename 421 422def flattenPath(filename, sep='_'): 423 """create a filename that encodes the original directory structure""" 424 parts = Path(filename).parts 425 clean = [p for p in parts if p not in ('/', '\\') and ':' not in p] 426 return sep.join(clean)
47def get_size(obj): 48 """sum size of object & members. 49 lifted from https://stackoverflow.com/a/30316760 50 """ 51 if isinstance(obj, (_BLACKLIST)): 52 raise TypeError('getsize() does not take argument of type: ' + str(type(obj))) 53 seen_ids = set() 54 size = 0 55 objects = [obj] 56 while objects: 57 need_referents = [] 58 for obj in objects: 59 if not isinstance(obj, _BLACKLIST) and id(obj) not in seen_ids: 60 seen_ids.add(id(obj)) 61 size += sys.getsizeof(obj) 62 need_referents.append(obj) 63 objects = get_referents(*need_referents) 64 return size
sum size of object & members. lifted from https://stackoverflow.com/a/30316760
67def benchmark(func): 68 """ 69 decorator for timing a function 70 """ 71 def benchmark_wrapper(*args, **kwargs): 72 started = time.time() 73 now = time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.localtime()) 74 print('function %s called at %s' % (func.__name__, now)) 75 sys.stdout.flush() 76 result = func(*args, **kwargs) 77 print('function %s finished after %f seconds' % 78 (func.__name__, time.time() - started)) 79 sys.stdout.flush() 80 return result 81 return benchmark_wrapper
decorator for timing a function
84class Benchmarker: 85 """ 86 class for benchmarking a function using a "with"-statement. 87 Preferable over the "benchmark" function for the following use cases 88 - benchmarking a code block that isn't wrapped in a function 89 - benchmarking a function only in some calls 90 """ 91 92 def __init__(self, active, description): 93 self.active = active 94 self.description = description 95 96 def __enter__(self): 97 self.started = time.time() 98 99 def __exit__(self, *args): 100 if self.active: 101 duration = time.time() - self.started 102 print("%s finished after %s" % (self.description, humanReadableTime(duration)))
class for benchmarking a function using a "with"-statement. Preferable over the "benchmark" function for the following use cases
- benchmarking a code block that isn't wrapped in a function
- benchmarking a function only in some calls
105class working_dir: 106 """ 107 temporarily change working directory using 'with' statement 108 """ 109 110 def __init__(self, dir): 111 self.dir = dir 112 self.origdir = os.getcwd() 113 114 def __enter__(self): 115 os.chdir(self.dir) 116 117 def __exit__(self, type, value, traceback): 118 os.chdir(self.origdir)
temporarily change working directory using 'with' statement
121class Colorgen: 122 DISTINCT = [ 123 (0.17, 1.0, 0.5), 124 (0.0, 0.9, 1.0), 125 (0.35, 0.67, 0.71), 126 (0.14, 0.9, 1.0), 127 (0.56, 1.0, 0.78), 128 (0.07, 0.8, 0.96), 129 (0.79, 0.83, 0.71), 130 (0.5, 0.71, 0.94), 131 (0.84, 0.79, 0.94), 132 (0.2, 0.76, 0.96), 133 (0.0, 0.24, 0.98), 134 (0.5, 1.0, 0.5), 135 (0.77, 0.25, 1.0), 136 (0.09, 0.76, 0.67), 137 (0.15, 0.22, 1.0), 138 (0.0, 1.0, 0.5), 139 (0.38, 0.33, 1.0), 140 (0.67, 1.0, 0.5), 141 ] 142 143 def __init__(self, hsv, cycleLength=10.67): 144 self.hsv = hsv 145 self.cycle = [int(random.random() * 256) for x in self.hsv] 146 self.cycleOffset = int(round(256 / cycleLength)) 147 self.distinctIndex = 0 148 149 def get_value(self, opt, index): 150 if opt == 'random': 151 return random.random() 152 if opt == 'cycle': 153 # the 255 below is intentional to get all color values when cycling long enough 154 self.cycle[index] = (self.cycle[index] + self.cycleOffset) % 255 155 return self.cycle[index] / 255.0 156 if opt == 'distinct': 157 if index == 0: 158 self.distinctIndex = (self.distinctIndex + 1) % len(self.DISTINCT) 159 return self.DISTINCT[self.distinctIndex][index] 160 return float(opt) 161 162 def floatTuple(self): 163 """return color as a tuple of floats each in [0,1]""" 164 return colorsys.hsv_to_rgb(*[self.get_value(o, i) for i, o in enumerate(self.hsv)]) 165 166 def byteTuple(self): 167 """return color as a tuple of bytes each in [0,255]""" 168 return tuple([int(round(255 * x)) for x in self.floatTuple()]) 169 170 def __call__(self): 171 """return constant or randomized rgb-color string""" 172 return ','.join(map(str, self.byteTuple()))
149 def get_value(self, opt, index): 150 if opt == 'random': 151 return random.random() 152 if opt == 'cycle': 153 # the 255 below is intentional to get all color values when cycling long enough 154 self.cycle[index] = (self.cycle[index] + self.cycleOffset) % 255 155 return self.cycle[index] / 255.0 156 if opt == 'distinct': 157 if index == 0: 158 self.distinctIndex = (self.distinctIndex + 1) % len(self.DISTINCT) 159 return self.DISTINCT[self.distinctIndex][index] 160 return float(opt)
175class priorityDictionary(dict): 176 177 def __init__(self): 178 '''Initialize priorityDictionary by creating binary heap 179 of pairs (value,key). Note that changing or removing a dict entry will 180 not remove the old pair from the heap until it is found by smallest() or 181 until the heap is rebuilt.''' 182 self.__heap = [] 183 dict.__init__(self) 184 185 def smallest(self): 186 '''Find smallest item after removing deleted items from heap.''' 187 if len(self) == 0: 188 raise IndexError("smallest of empty priorityDictionary") 189 heap = self.__heap 190 while heap[0][1] not in self or self[heap[0][1]] != heap[0][0]: 191 lastItem = heap.pop() 192 insertionPoint = 0 193 while 1: 194 smallChild = 2 * insertionPoint + 1 195 if smallChild + 1 < len(heap) and \ 196 heap[smallChild][0] > heap[smallChild + 1][0]: 197 smallChild += 1 198 if smallChild >= len(heap) or lastItem <= heap[smallChild]: 199 heap[insertionPoint] = lastItem 200 break 201 heap[insertionPoint] = heap[smallChild] 202 insertionPoint = smallChild 203 return heap[0][1] 204 205 def __iter__(self): 206 '''Create destructive sorted iterator of priorityDictionary.''' 207 def iterfn(): 208 while len(self) > 0: 209 x = self.smallest() 210 yield x 211 del self[x] 212 return iterfn() 213 214 def __setitem__(self, key, val): 215 '''Change value stored in dictionary and add corresponding 216 pair to heap. Rebuilds the heap if the number of deleted items grows 217 too large, to avoid memory leakage.''' 218 dict.__setitem__(self, key, val) 219 heap = self.__heap 220 if len(heap) > 2 * len(self): 221 self.__heap = [(v, k) for k, v in self.items()] 222 self.__heap.sort() # builtin sort likely faster than O(n) heapify 223 else: 224 newPair = (val, key) 225 insertionPoint = len(heap) 226 heap.append(None) 227 while insertionPoint > 0 and val < heap[(insertionPoint - 1) // 2][0]: 228 heap[insertionPoint] = heap[(insertionPoint - 1) // 2] 229 insertionPoint = (insertionPoint - 1) // 2 230 heap[insertionPoint] = newPair 231 232 def setdefault(self, key, val): 233 '''Reimplement setdefault to call our customized __setitem__.''' 234 if key not in self: 235 self[key] = val 236 return self[key] 237 238 def update(self, other): 239 for key in other.keys(): 240 self[key] = other[key]
177 def __init__(self): 178 '''Initialize priorityDictionary by creating binary heap 179 of pairs (value,key). Note that changing or removing a dict entry will 180 not remove the old pair from the heap until it is found by smallest() or 181 until the heap is rebuilt.''' 182 self.__heap = [] 183 dict.__init__(self)
Initialize priorityDictionary by creating binary heap of pairs (value,key). Note that changing or removing a dict entry will not remove the old pair from the heap until it is found by smallest() or until the heap is rebuilt.
185 def smallest(self): 186 '''Find smallest item after removing deleted items from heap.''' 187 if len(self) == 0: 188 raise IndexError("smallest of empty priorityDictionary") 189 heap = self.__heap 190 while heap[0][1] not in self or self[heap[0][1]] != heap[0][0]: 191 lastItem = heap.pop() 192 insertionPoint = 0 193 while 1: 194 smallChild = 2 * insertionPoint + 1 195 if smallChild + 1 < len(heap) and \ 196 heap[smallChild][0] > heap[smallChild + 1][0]: 197 smallChild += 1 198 if smallChild >= len(heap) or lastItem <= heap[smallChild]: 199 heap[insertionPoint] = lastItem 200 break 201 heap[insertionPoint] = heap[smallChild] 202 insertionPoint = smallChild 203 return heap[0][1]
Find smallest item after removing deleted items from heap.
232 def setdefault(self, key, val): 233 '''Reimplement setdefault to call our customized __setitem__.''' 234 if key not in self: 235 self[key] = val 236 return self[key]
Reimplement setdefault to call our customized __setitem__.
D.update([E, ]**F) -> None. Update D from dict/iterable E and F. If E is present and has a .keys() method, then does: for k in E: D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]
243def getFreeSocketPort(numTries=10): 244 for _ in range(numTries): 245 try: 246 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 247 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 248 s.bind(('', 0)) 249 p = s.getsockname()[1] 250 s.close() 251 return p 252 except socket.error: 253 pass 254 return None
270def humanReadableTime(seconds): 271 result = "" 272 sign = '-' if seconds < 0 else '' 273 seconds = abs(seconds) 274 ds = 3600 * 24 275 if seconds > ds: 276 result = "%s:" % int(seconds / ds) 277 seconds = seconds % ds 278 result += "%02i:" % int(seconds / 3600) 279 seconds = seconds % 3600 280 result += "%02i:" % int(seconds / 60) 281 seconds = seconds % 60 282 if seconds == int(seconds): 283 seconds = int(seconds) 284 result += "%02i" % seconds 285 return sign + result
291def parseTime(t, factor=1): 292 try: 293 return float(t) * factor 294 except ValueError: 295 pass 296 try: 297 # prepended zero is ignored if the date value already contains days 298 days, hours, minutes, seconds = ([0] + list(map(float, t.split(':'))))[-4:] 299 sign = -1 if t.strip()[0] == '-' else 1 300 return (3600 * 24 * days + 3600 * hours + 60 * minutes + seconds) * sign * factor 301 except ValueError: 302 if t in SPECIAL_TIME_STRINGS: 303 # signal special case but don't crash 304 return None 305 else: 306 raise
314def getFlowNumber(flow): 315 """interpret number of vehicles from a flow parsed by sumolib.xml.parse""" 316 if flow.number is not None: 317 return int(flow.number) 318 if flow.end is not None: 319 duration = parseTime(flow.end) - parseTime(flow.begin) 320 period = 0 321 isFractional = False 322 if flow.period is not None: 323 if 'exp' in flow.period: 324 # use expected value 325 period = 1 / float(flow.period[4:-1]) 326 isFractional = True 327 else: 328 period = float(flow.period) 329 elif flow.probability is not None: 330 # use expected value 331 period = 1 / float(flow.probability) 332 isFractional = True 333 for attr in ['perHour', 'vehsPerHour']: 334 if flow.hasAttribute(attr): 335 period = 3600 / float(flow.getAttribute(attr)) 336 if period > 0: 337 count = duration / period 338 if isFractional: 339 return count 340 else: 341 # flows with a regular period always start at least one vehicle at the begin time 342 return math.ceil(count) 343 else: 344 return 1
interpret number of vehicles from a flow parsed by sumolib.xml.parse
354def openz(fileOrURL, mode="r", **kwargs): 355 """ 356 Opens transparently files, URLs and gzipped files for reading and writing. 357 Special file names "stdout" and "stderr" are handled as well. 358 Also enforces UTF8 on text output / input and should handle BOMs in input. 359 Should be compatible with python 2 and 3. 360 """ 361 encoding = kwargs.get("encoding", "utf8" if "w" in mode else "utf-8-sig") 362 try: 363 if fileOrURL.startswith("http://") or fileOrURL.startswith("https://"): 364 return io.BytesIO(urlopen(fileOrURL).read()) 365 if fileOrURL == "stdout": 366 return sys.stdout 367 if fileOrURL == "stderr": 368 return sys.stderr 369 if fileOrURL.endswith(".gz") and "w" in mode: 370 if "b" in mode: 371 return gzip.open(fileOrURL, mode="w") 372 return gzip.open(fileOrURL, mode="wt", encoding=encoding) 373 if kwargs.get("trySocket") and fileOrURL.isdigit(): 374 return getSocketStream(int(fileOrURL), mode) 375 if kwargs.get("tryGZip", True) and "r" in mode: 376 with gzip.open(fileOrURL) as fd: 377 fd.read(1) 378 if "b" in mode: 379 return gzip.open(fileOrURL) 380 if sys.version_info[0] < 3: 381 return codecs.getreader('utf-8')(gzip.open(fileOrURL)) 382 return gzip.open(fileOrURL, mode="rt", encoding=encoding) 383 except OSError as e: 384 if kwargs.get("printErrors"): 385 print(e, file=sys.stderr) 386 except IOError as e: 387 if kwargs.get("printErrors"): 388 print(e, file=sys.stderr) 389 if "b" in mode: 390 return io.open(fileOrURL, mode=mode) 391 return io.open(fileOrURL, mode=mode, encoding=encoding)
Opens transparently files, URLs and gzipped files for reading and writing. Special file names "stdout" and "stderr" are handled as well. Also enforces UTF8 on text output / input and should handle BOMs in input. Should be compatible with python 2 and 3.
394def short_names(filenames, noEmpty): 395 if len(filenames) == 1: 396 return filenames 397 reversedNames = [''.join(reversed(f)) for f in filenames] 398 prefix = os.path.commonprefix(filenames) 399 suffix = os.path.commonprefix(reversedNames) 400 prefixLen = len(prefix) 401 suffixLen = len(suffix) 402 shortened = [f[prefixLen:-suffixLen] for f in filenames] 403 if noEmpty and any([not f for f in shortened]): 404 # make longer to avoid empty file names 405 base = os.path.basename(prefix) 406 shortened = [base + f for f in shortened] 407 return shortened
410def getBaseName(filename): 411 """strip extensions such as .net.xml.gz""" 412 if filename[-11:] == ".net.xml.gz" and len(filename) > 11: 413 return filename[:-11] 414 elif filename[-8:] == ".net.xml" and len(filename) > 8: 415 return filename[:-8] 416 elif filename[-7:] == ".xml.gz" and len(filename) > 7: 417 return filename[:-7] 418 elif filename[-4:] == ".xml" and len(filename) > 4: 419 return filename[:-4] 420 else: 421 return filename
strip extensions such as .net.xml.gz
423def flattenPath(filename, sep='_'): 424 """create a filename that encodes the original directory structure""" 425 parts = Path(filename).parts 426 clean = [p for p in parts if p not in ('/', '\\') and ':' not in p] 427 return sep.join(clean)
create a filename that encodes the original directory structure