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

sum size of object & members. lifted from https://stackoverflow.com/a/30316760

def benchmark(func):
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

decorator for timing a function

class Benchmarker:
 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)))

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
Benchmarker(active, description)
91    def __init__(self, active, description):
92        self.active = active
93        self.description = description
active
description
class working_dir:
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)

temporarily change working directory using 'with' statement

working_dir(dir)
109    def __init__(self, dir):
110        self.dir = dir
111        self.origdir = os.getcwd()
dir
origdir
class Colorgen:
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()))
Colorgen(hsv, cycleLength=10.67)
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
DISTINCT = [(0.17, 1.0, 0.5), (0.0, 0.9, 1.0), (0.35, 0.67, 0.71), (0.14, 0.9, 1.0), (0.56, 1.0, 0.78), (0.07, 0.8, 0.96), (0.79, 0.83, 0.71), (0.5, 0.71, 0.94), (0.84, 0.79, 0.94), (0.2, 0.76, 0.96), (0.0, 0.24, 0.98), (0.5, 1.0, 0.5), (0.77, 0.25, 1.0), (0.09, 0.76, 0.67), (0.15, 0.22, 1.0), (0.0, 1.0, 0.5), (0.38, 0.33, 1.0), (0.67, 1.0, 0.5)]
hsv
cycle
cycleOffset
distinctIndex
def get_value(self, opt, index):
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)
def floatTuple(self):
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)])

return color as a tuple of floats each in [0,1]

def byteTuple(self):
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()])

return color as a tuple of bytes each in [0,255]

class priorityDictionary(builtins.dict):
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]
priorityDictionary()
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)

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.

def smallest(self):
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]

Find smallest item after removing deleted items from heap.

def setdefault(self, key, val):
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]

Reimplement setdefault to call our customized __setitem__.

def update(self, other):
237    def update(self, other):
238        for key in other.keys():
239            self[key] = other[key]

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]

def getFreeSocketPort(numTries=10):
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
def getSocketStream(port, mode='rb'):
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)
def euclidean(a, b):
265def euclidean(a, b):
266    return math.sqrt((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2)
def humanReadableTime(seconds):
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
SPECIAL_TIME_STRINGS = ['triggered', 'containerTriggered', 'split', 'begin']
def parseTime(t, factor=1):
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
def parseBool(val):
308def parseBool(val):
309    # see data/xsd/baseTypes:boolType
310    return val in ["true", "True", "x", "1", "yes", "on"]
def getFlowNumber(flow):
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

interpret number of vehicles from a flow parsed by sumolib.xml.parse

def intIfPossible(val):
346def intIfPossible(val):
347    if int(val) == val:
348        return int(val)
349    else:
350        return val
def openz(fileOrURL, mode='r', **kwargs):
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)

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.

def short_names(filenames, noEmpty):
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
def getBaseName(filename):
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

strip extensions such as .net.xml.gz