From 956143e42688353cc6fb2a104de9758c2dafdfda Mon Sep 17 00:00:00 2001 From: "andersonrochatavares@gmail.com" Date: Fri, 11 Oct 2013 14:57:59 +0000 Subject: [PATCH] committing odpopulator package --- src/odpopulator/__init__.py | 83 ++++++++ src/odpopulator/odkeeper.py | 170 ++++++++++++++++ src/odpopulator/odloader.py | 372 ++++++++++++++++++++++++++++++++++++ src/odpopulator/odmatrix.py | 175 +++++++++++++++++ src/odpopulator/odparser.py | 113 +++++++++++ src/odpopulator/util.py | 35 ++++ 6 files changed, 948 insertions(+) create mode 100644 src/odpopulator/__init__.py create mode 100644 src/odpopulator/odkeeper.py create mode 100644 src/odpopulator/odloader.py create mode 100644 src/odpopulator/odmatrix.py create mode 100644 src/odpopulator/odparser.py create mode 100644 src/odpopulator/util.py diff --git a/src/odpopulator/__init__.py b/src/odpopulator/__init__.py new file mode 100644 index 0000000..3fe2732 --- /dev/null +++ b/src/odpopulator/__init__.py @@ -0,0 +1,83 @@ +''' +OD populator package main file + +Contains functions to generate the od_matrix and parse .taz.xml and .odm files + +''' + +import xml.etree.ElementTree as ET +import odmatrix +import odparser +import odloader + +def generate_odmatrix(taz_file, odm_file): + ''' + Generates an ODMatrix from the .taz.xml and .odm files + :param taz_file: the path to the .taz.xml file + :type taz_file: str + :param odm_file: the path to the .odm file + :type odm_file: str + return: the OD matrix loaded with the districts and trips from the files + :rtype: odmatrix.ODMatrix + ''' + od_matrix = odmatrix.ODMatrix() + + parse_taz_file(od_matrix, taz_file) + parse_odm_file(od_matrix, odm_file) + + return od_matrix + +def parse_taz_file(od_matrix, taz_file): + ''' + Fills the od_matrix with the districts found in the taz_file + :param od_matrix: the OD matrix to be filled + :type od_matrix: odmatrix.ODMatrix + :param taz_file: the path to the .taz.xml file + :type taz_file: str + return: the OD matrix loaded with the districts information + :rtype: odmatrix.ODMatrix + + ''' + taz_tree = ET.parse(taz_file) + + + for element in taz_tree.getroot(): + sources = [] + sinks = [] + for src_snk in element: + + if src_snk.tag == 'tazSource': + + sources.append({ + 'id': src_snk.get('id'), + 'weight': float(src_snk.get('weight')) + }) + + else: + sinks.append({ + 'id': src_snk.get('id'), + 'weight': float(src_snk.get('weight')) + }) + + + + taz = odmatrix.TAZ(element.get('id'), sources, sinks) + od_matrix.add_taz(taz) + + return odmatrix + +def parse_odm_file(od_matrix, odm_file): + ''' + Fills the od_matrix with trip information of the odm_file. + The matrix must be already filled with the districts information + :param od_matrix: the OD matrix to be filled + :type od_matrix: odmatrix.ODMatrix + :param odm_file: the path to the .odm file + :type odm_file: str + return: the OD matrix loaded with the trip information + :rtype: odmatrix.ODMatrix + + ''' + odm_parser = odparser.create_parser(odm_file, od_matrix) + odm_parser.parse() + return od_matrix \ No newline at end of file diff --git a/src/odpopulator/odkeeper.py b/src/odpopulator/odkeeper.py new file mode 100644 index 0000000..4cc113f --- /dev/null +++ b/src/odpopulator/odkeeper.py @@ -0,0 +1,170 @@ +''' +This script can be used standalone or imported in another script. + +As a standalone, it connects to a SUMO simulation and when a vehicle leaves +the simulation, it is replaced by a new one. + +The origins and destinations of inserted vehicles respect a given OD matrix. + +When imported, the user can use the ODKeeper methods act and replace +vehicles in the road network + +This script requires the search module available at maslab-googlecode + +Created on Jan 12, 2013 + +@author: anderson + +''' +import traci, sumolib +import os, sys +from optparse import OptionParser + +sys.path.append('..') +import odpopulator + +#looks up on ../lib to import Guilherme's implementation of Dijkstra algorithm +path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'lib')) +if not path in sys.path: sys.path.append(path) +import search + +class ODKeeper(object): + ''' + Keeps the load in the road network by replacing vehicles who leave + with new ones, respecting the proportions defined in an OD matrix + ''' + + + def __init__(self, road_net, od_matrix, num_veh = 900, max_per_action = 0, aux_prefix = 'aux', + exclude_prefix = None): + ''' + Initializes the od-keeper + + :param road_net: the road network + :type road_net: sumolib.net.Net + :param od_matrix: the OD matrix loaded with districts and trip information + :type od_matrix: odmatrix.ODMatrix + :param num_veh: the number of vehicles to be kept in the network + :type num_veh: int + :param max_per_action: the max. number of insertions per action of the controller + :type max_per_action: int + :param aux_prefix: the prefix of the vehicles created by the ODLoader + :type aux_prefix: str + :param exclude_prefix: the prefix of the vehicle ID's to be discounted while checking the total + :type exclude_prefix: str + + ''' + self._road_net = road_net + self._od_matrix = od_matrix + self._num_veh = num_veh + self._max_per_action = max_per_action + self._aux_prefix = aux_prefix + self._exclude_prefix = exclude_prefix + self._insertions = 0 + + def act(self): + ''' + Must be called at each timestep. Stores the drivers that departed for + writing them later in an output file + + ''' + + thisTs = traci.simulation.getCurrentTime() / 1000 + + #does nothing if we have more vehicles than the desired number + if len(traci.vehicle.getIDList()) > self._num_veh: + return + + for vehId in traci.simulation.getArrivedIDList(): + + if self._exclude_prefix is not None and self._exclude_prefix in vehId: + continue + + (orig_taz, dest_taz) = self._od_matrix.select_od_taz() + orig_edg = self._road_net.getEdge(orig_taz.select_source()['id']) + dest_edg = self._road_net.getEdge(dest_taz.select_sink()['id']) + + theRoute = search.dijkstra( + self._road_net, + orig_edg, + dest_edg, None, True + ) + #tries again if dest is not reachable from orig + if theRoute is None: + continue + + edges = [edge.getID().encode('utf-8') for edge in theRoute] + + vehId = str(thisTs) + '-' + vehId + traci.route.add(vehId, edges) + traci.vehicle.add(vehId, vehId, traci.vehicle.DEPART_NOW, 5.10, 0) + + #print '%s\t%s\t%d' % (orig.getID(), dest.getID(), traci.simulation.getCurrentTime() / 1000) + + #print ['%.2f' % traci.edge.getLastStepOccupancy(e) for e in edges], traci.simulation.getCurrentTime() / 1000 + + +if __name__ == "__main__": + optParser = OptionParser() + + optParser.add_option("-n", "--net-file", dest="netfile", + help="road network file (mandatory)") + optParser.add_option("-t", "--taz-file", dest="tazfile", + help="traffic assignment zones definition file (mandatory)") + optParser.add_option("-m", "--odm-file", dest="odmfile", + help="OD matrix trips definition file (mandatory)") + optParser.add_option("-l", "--limit-per-ts", type='int', dest="max_per_ts", default=0, + help="Limit the number of vehicles to be inserted at each timestep") +# optParser.add_option("-r", "--route-file", dest="routefile", +# help="route file to be generated") + optParser.add_option("-d", "--driver-number", type="int", dest="numveh", + default=1000, help="desired number of drivers to keep") + optParser.add_option("-b", "--begin", type="int", default=0, help="begin time") + optParser.add_option("-e", "--end", type="int", default=7200, help="end time") + optParser.add_option("-p", "--port", type="int", default=8813, help="TraCI port") + optParser.add_option("-x", "--exclude", type="string", default=None, dest='exclude', + help="Exclude replacing drivers whose ID have the given value") + #optParser.add_option("-s", "--seed", type="int", help="random seed") + + (options, args) = optParser.parse_args() +# if not options.netfile or not options.routefile: +# optParser.print_help() +# sys.exit() + + net = sumolib.net.readNet(options.netfile) + + traci.init(options.port) + + drivers = {} #stores drivers information when they depart + + if options.begin > 0: + print 'Skipping %d timesteps.' % options.begin + traci.simulationStep(options.begin * 1000) + +# for drvid in traci.simulation.getDepartedIDList(): +# drivers[drvid] = { +# 'depart': traci.simulation.getCurrentTime() / 1000, +# 'route': traci.vehicle.getRoute(drvid) +# } + + print 'From ts %d to %d, will replace vehicles' % (options.begin, options.end) + + #creates the odloader and initializes it with the drivers already recorded + od_matrix = odpopulator.generate_odmatrix(options.tazfile, options.odmfile) + loadkeeper = ODKeeper(net, od_matrix, options.numveh, options.max_per_ts, 'aux', options.exclude) + + for i in range(options.begin, options.end): + traci.simulationStep() + loadkeeper.act() + + print 'Now, simulating until all drivers leave the network' + while (True): #simulates the remaining steps till the end + traci.simulationStep() + if len(traci.vehicle.getIDList()) == 0: + break + + traci.close() + + print 'DONE.' + + \ No newline at end of file diff --git a/src/odpopulator/odloader.py b/src/odpopulator/odloader.py new file mode 100644 index 0000000..372301a --- /dev/null +++ b/src/odpopulator/odloader.py @@ -0,0 +1,372 @@ +''' +This script can be used standalone or imported in another script. + +As a standalone, it connects to a SUMO simulation and inserts vehicles +at every timestep until the road network load reaches the desired level. +User can specify how long the load should be controlled. + +Each vehicle is inserted according to a given OD matrix. + +When imported, the user can use the ODKeeper methods to control the load +and insert vehicles in the road network + +This script requires the search module available at maslab-googlecode + +Created on Jan 3, 2013 + +@author: anderson + +''' +import os, sys +import random +import traci +import sumolib +from optparse import OptionParser +sys.path.append('..') +import odpopulator + +#looks up on ../lib to import Guilherme's implementation of Dijkstra algorithm +path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'lib')) +if not path in sys.path: sys.path.append(path) +import search + +class ODLoader(object): + ''' + Vehicle loader that keeps the proportions defined in an OD matrix + + ''' + + + def __init__(self, road_net, od_matrix, num_veh = 900, max_per_action = 0, aux_prefix = 'aux', + exclude_prefix = None, output = None): + ''' + Initializes the od-loader + + :param road_net: the road network + :type road_net: sumolib.net.Net + :param od_matrix: the OD matrix loaded with districts and trip information + :type od_matrix: odmatrix.ODMatrix + :param num_veh: the number of vehicles to be kept in the network + :type num_veh: int + :param max_per_action: the max. number of insertions per action of the controller + :type max_per_action: int + :param aux_prefix: the prefix of the vehicles created by the ODLoader + :type aux_prefix: str + :param exclude_prefix: the prefix of the vehicle ID's to be discounted while checking the total + :type exclude_prefix: str + :param output: the file to write the generated demand + :type output: str + + ''' + self._road_net = road_net + self._od_matrix = od_matrix + self._num_veh = num_veh + self._max_per_action = max_per_action + self._aux_prefix = aux_prefix + self._exclude_prefix = exclude_prefix + self._output = output + + self._insertions = 0 + self._started_trips = [] + self._count_complete_trips = -1 + self._complete_trips = 0 + + self._steady_duration = 0 + self._steady_timesteps = -1 + + self._complete_trips_per_ts = [] + + self._launched_vehicles = {} + + def set_steady_duration(self, duration): + ''' + Sets how many timesteps the loader will keep the + number of vehicles in the desired number + + ''' + self._steady_duration = duration + + def is_steady_duration_finished(self): + ''' + Returns whether the duration of the network in steady + state has finished + + ''' + return self._steady_timesteps >= self._steady_duration + + def act(self): + ''' + Must be called every timestep (or in regular intervals of time) + to ensure that the load on the network will be steady + + ''' + + if self.is_steady_duration_finished(): + print 'Warning: steady duration of network has finished' + return + + #saves data of vehicles that just departed + thisTs = traci.simulation.getCurrentTime() / 1000 + for drvid in traci.simulation.getDepartedIDList(): + if self._exclude_prefix is not None and self._exclude_prefix in drvid: + continue + + self._launched_vehicles[drvid] = { + 'depart': thisTs, + 'route': traci.vehicle.getRoute(drvid) + } + + #counts the vehicles without the exclude prefix that are in the network + num_veh = 0 + for veh_id in traci.vehicle.getIDList(): + if self._exclude_prefix is None or self._exclude_prefix not in veh_id: + num_veh += 1 + + inserted_this_ts = 0 + + if self._count_complete_trips == -1 and num_veh >= self._num_veh: + self._count_complete_trips = traci.simulation.getCurrentTime() / 1000 + self._steady_timesteps = 0 + + if self._count_complete_trips > 0: + self._started_trips += traci.simulation.getDepartedIDList() + self._steady_timesteps += 1 + + for arvd in traci.simulation.getArrivedIDList(): + if arvd in self._started_trips: + self._started_trips.remove(arvd) + self._complete_trips += 1 + + self._complete_trips_per_ts.append( + ((traci.simulation.getCurrentTime() / 1000), self._complete_trips) + ) + + #inserts vehicles until the maximum number is reached + #or the maximum insertions per timestep is reached + while num_veh < self._num_veh: + + if self._max_per_action != 0 and inserted_this_ts >= self._max_per_action: + break + + (orig_taz, dest_taz) = self._od_matrix.select_od_taz() + orig_edg = self._road_net.getEdge(orig_taz.select_source()['id']) + dest_edg = self._road_net.getEdge(dest_taz.select_sink()['id']) + + theRoute = search.dijkstra( + self._road_net, + orig_edg, + dest_edg, None, True + ) + #tries again if dest is not reachable from orig + if theRoute is None: + continue + + edges = [edge.getID().encode('utf-8') for edge in theRoute] + + veh_id = self._aux_prefix + str(self._insertions) + traci.route.add(veh_id, edges) + traci.vehicle.add(veh_id, veh_id, + traci.vehicle.DEPART_NOW, 5.10, 13) + + self._insertions += 1 + inserted_this_ts += 1 + num_veh += 1 + + def writeOutput(self): + ''' + Writes the stored drivers into an output file + + ''' + #print 'Writing output...' + + if self._output is None: + print 'Error: no valid file to write.' + return + + #sort drivers by departure time + drvKeys = sorted(self._launched_vehicles, key=lambda x: (self._launched_vehicles[x]['depart'], x)) + #try: + outfile = open(self._output, 'w') + + outfile.write('\n') + + for key in drvKeys: + outfile.write(' \n' % (key, self._launched_vehicles[key]['depart'])) + outfile.write(' \n' % ' '.join(self._launched_vehicles[key]['route'])) + outfile.write(' \n') + + outfile.write('') + +class UniformLoader(ODLoader): + ''' + Simple vehicle loader that assumes an uniform distribution + of origins and destinations among the edges (no need to pass + an od matrix with such distribution) + + ''' + + def act(self): + thisTs = traci.simulation.getCurrentTime() / 1000 + num_veh = 0 + for veh_id in traci.vehicle.getIDList(): + if self._exclude_prefix is None or self._exclude_prefix not in veh_id: + num_veh += 1 + + inserted_this_ts = 0 + + if self._count_complete_trips == -1 and num_veh >= self._num_veh: + self._count_complete_trips = traci.simulation.getCurrentTime() / 1000 + self._steady_timesteps = 0 + + if self._count_complete_trips > 0: + self._started_trips += traci.simulation.getDepartedIDList() + self._steady_timesteps += 1 + + for drvid in traci.simulation.getDepartedIDList(): + if self._exclude_prefix is not None and self._exclude_prefix in drvid: + continue + + self._launched_vehicles[drvid] = { + 'depart': thisTs, + 'route': traci.vehicle.getRoute(drvid) + } + + #inserts vehicles until the maximum number is reached + #or the maximum insertions per timestep is reached + while num_veh < self._num_veh: + + if self._max_per_action != 0 and inserted_this_ts >= self._max_per_action: + break + + congestedRoute = True + numTries = 0 + while (congestedRoute and numTries < 100): #try to distribute load + + #(orig_taz, dest_taz) = self._od_matrix.select_od_taz() + orig_edg = random.choice(self._road_net._edges) + dest_edg = random.choice(self._road_net._edges) + + theRoute = search.dijkstra( + self._road_net, + orig_edg, + dest_edg, None, True + ) + #tries again if dest is not reachable from orig + if theRoute is None: + continue + + edges = [edge.getID().encode('utf-8') for edge in theRoute] + + congestedRoute = False + for e in edges: + if traci.edge.getLastStepOccupancy(e) > 0.7: + congestedRoute = True + break #the inner loop + + numTries += 1 + + veh_id = self._aux_prefix + str(self._insertions) + traci.route.add(veh_id, edges) + traci.vehicle.add(veh_id, veh_id, + traci.vehicle.DEPART_NOW, 0, 13) + + self._insertions += 1 + inserted_this_ts += 1 + num_veh += 1 + +if __name__ == "__main__": + optParser = OptionParser() + + optParser.add_option("-n", "--net-file", dest="netfile", + help="road network file (mandatory)") + optParser.add_option("-t", "--taz-file", dest="tazfile", + help="traffic assignment zones definition file (mandatory)") + optParser.add_option("-m", "--odm-file", dest="odmfile", + help="OD matrix trips definition file (mandatory)") + optParser.add_option("-l", "--limit-per-ts", type='int', dest="max_per_ts", default=0, + help="Limit the number of vehicles to be inserted at each timestep") + optParser.add_option("-o", "--output", type='str', default=None, + help="route file to be generated with the data of drivers inserted by odloader") + optParser.add_option("-d", "--driver-number", type="int", dest="numveh", + default=1000, help="desired number of drivers to keep") + optParser.add_option("-b", "--begin", type="int", default=0, help="begin time") + optParser.add_option("-e", "--end", type="int", default=7200, help="end time") + optParser.add_option("--duration", type="int", default=0, help="duration of control") + optParser.add_option("-p", "--port", type="int", default=8813, help="TraCI port") + optParser.add_option("-x", "--exclude", type="string", default=None, dest='exclude', + help="Exclude replacing drivers whose ID have the given value") + optParser.add_option('-u', '--uniform', action='store_true', default=False, + help = 'use uniform OD distribution instead of OD files') + #optParser.add_option("-s", "--seed", type="int", help="random seed") + optParser.add_option("-s", "--steady-output", type="string", dest="steadyout", + help="the file where the timestep vs #full trips with steady network will be written") + + (options, args) = optParser.parse_args() +# if not options.netfile or not options.routefile: +# optParser.print_help() +# sys.exit() + + net = sumolib.net.readNet(options.netfile) + + traci.init(options.port) + + drivers = {} #stores drivers information when they depart + + if options.begin > 0: + print 'Skipping %d timesteps.' % options.begin + traci.simulationStep(options.begin * 1000) + +# for drvid in traci.simulation.getDepartedIDList(): +# drivers[drvid] = { +# 'depart': traci.simulation.getCurrentTime() / 1000, +# 'route': traci.vehicle.getRoute(drvid) +# } + + print 'From ts %d to %d, will replace vehicles' % (options.begin, options.end) + + #creates the odloader and initializes it with the drivers already recorded + if options.uniform: + loadkeeper = UniformLoader( + net, None, options.numveh, options.max_per_ts, 'aux', options.exclude, options.output + ) + else: + od_matrix = odpopulator.generate_odmatrix(options.tazfile, options.odmfile) + loadkeeper = ODLoader( + net, od_matrix, options.numveh, + options.max_per_ts, 'aux', options.exclude, options.output + ) + if options.duration: + loadkeeper.set_steady_duration(options.duration) + + for i in range(options.begin, options.end): + traci.simulationStep() + + if loadkeeper.is_steady_duration_finished(): + break + + loadkeeper.act() + + print 'OD Loader counted %d complete trips during peak demand which started at %d' %\ + (loadkeeper._complete_trips, loadkeeper._count_complete_trips) + + print 'Writing timesteps vs. fulltrips...' + outstream = open(options.steadyout, 'w') if options.steadyout else sys.stdout + + for i in loadkeeper._complete_trips_per_ts: + outstream.write("%d %d\n" % (i[0], i[1])) + + if options.output is not None: + print 'Writing output route file...' + loadkeeper.writeOutput() + + print 'Now, simulating until all drivers leave the network' + while (True): #simulates the remaining steps till the end + traci.simulationStep() + if len(traci.vehicle.getIDList()) == 0: + break + + traci.close() + + print 'DONE.' + + \ No newline at end of file diff --git a/src/odpopulator/odmatrix.py b/src/odpopulator/odmatrix.py new file mode 100644 index 0000000..cac1618 --- /dev/null +++ b/src/odpopulator/odmatrix.py @@ -0,0 +1,175 @@ +''' +Contains classes to work with OD matrices defined according to +SUMO's specification + +Created on Jan 2, 2013 + +@author: anderson +''' + +import util + +class TAZ(object): + ''' + Encapsulates a Traffic Assignment Zone (TAZ) + + ''' + def __init__(self, taz_id, sources, sinks, destinations = {}): + ''' + Initializes the TAZ + :param taz_id: the ID of this TAZ + :type taz_id: str + :param sources: contains the source edges and their weights: [{'id':x, 'weight':y},...] + :type sources: list(dict) + :param sinks: contains the sink edges and their weights [{'id':x, 'weight':y},...] + :type sinks: list(dict) + :param destinations: contains the destination TAZ's and the number of trips {'taz_id':#trips,...} + :type destinations: dict + + ''' + + self.taz_id = taz_id + self.sources = sources + self.sinks = sinks + self.destinations = destinations + + def set_destinations(self, destinations): + ''' + Stores the given destination dict in this TAZ + :param destinations: dict in the form {'taz_id1': #trips, 'taz_id2': #trips, ...} + :type destinations: dict + + ''' + self.destinations = destinations + + def outgoing_trips(self): + ''' + return: the number of trips originated in this TAZ + :rtype: int + ''' + return sum(self.destinations.values()) + + def select_source(self): + ''' + Returns the ID of a source edge within this TAZ. The chance + for each source to be selected is proportional + to its weight + return: the ID of an edge among the sources of this TAZ + :rtype: str + + ''' + return self._select_edge(self.sources) + + def select_sink(self): + ''' + Returns the ID of a sink edge within this TAZ. The chance + for each sink to be selected is proportional + to its weight + return: the ID of an edge among the sinks of this TAZ + :rtype: str + + ''' + return self._select_edge(self.sinks) + + + def _select_edge(self, the_list): + ''' + Selects an edge in the the_list according to its weight + :param the_list: a list of sources or destinations of this TAZ + :type the_list: list(dict) + + ''' + return util.weighted_selection( + sum([s['weight'] for s in the_list]), + the_list, + lambda s: s['weight'] + ) + + +class ODMatrix(object): + ''' + Encapsulates an OD Matrix. Stores the Traffic Assignment Zones (TAZ) and + the proportion of trips among them + + ''' + + + def __init__(self): + ''' + Initializes the OD Matrix + + ''' + + self.taz_list = [] + + def add_taz(self, taz): + ''' + Adds a traffic assignment zone to the matrix + :param taz: the traffic assignment zone to be added + :type taz: odmatrix.TAZ + + ''' + self.taz_list.append(taz) + + def all_taz(self): + ''' + return: a list with all traffic assignment zones + :rtype: list(odmatrix.TAZ) + + ''' + return self.taz_list + + def select_od_taz(self): + ''' + Performs the weighted selection of origin and destination TAZs + The origin is selected according in proportion to the number of outgoing + its trips compared to the total of all TAZs. + The destination is weighted-selected according to the + number of trips that the origin TAZ generates to each destination. + + return: the origin and destination TAZ's in a list: [odmatrix.TAZ, odmatrix.TAZ] + :rtype: list(odmatrix.TAZ) + + ''' + total_out_trips = sum([taz.outgoing_trips() for taz in self.taz_list]) + origin_taz = util.weighted_selection( + total_out_trips, self.taz_list, lambda taz: taz.outgoing_trips() + ) + + destinations_list = [{'name': x, 'numtrips': y} for x,y in origin_taz.destinations.items()] + + dest_taz_dict = util.weighted_selection( + origin_taz.outgoing_trips(), + destinations_list, + lambda dest: dest['numtrips'] + ) + return [origin_taz, self.find(dest_taz_dict['name'])] + + def incoming_trips(self, taz_id): + ''' + return: the number of trips that ends in the given TAZ + :rtype: int + + ''' + itrips = 0 + for otaz in self.all_taz(): + if taz_id in otaz.destinations: + itrips += otaz.destinations[taz_id] + + return itrips + + def find(self, taz_id): + ''' + Search TAZ by its id and returns it if found. + Returns None if no TAZ is found. + :param taz_id: the ID of the TAZ to be found + :type taz_id: str + return: the TAZ identified by its ID or None + :rtype: odmatrix.TAZ + + ''' + for taz in self.taz_list: + if taz.taz_id == taz_id: + return taz + + return None \ No newline at end of file diff --git a/src/odpopulator/odparser.py b/src/odpopulator/odparser.py new file mode 100644 index 0000000..39968a5 --- /dev/null +++ b/src/odpopulator/odparser.py @@ -0,0 +1,113 @@ +''' +Provides methods and classes to parse OD matrices specified +according to SUMO (v. 0.16.0) format + +Created on Jan 2, 2013 + +@author: anderson + +''' +from tokenize import generate_tokens +from odpopulator import odmatrix +import token +import StringIO + +def create_parser(od_file_name, od_matrix): + ''' + Creates a OD trip definition parser depending on the type of the input file + :param od_file_name: the path to the od trips definition file + :type od_file_name: str + :param od_matrix: the OD matrix to be filled with the trip information + :type od_matrix: odmatrix.ODMatrix + + ''' + + od_file = open(od_file_name, 'r') + firstline = od_file.readline() + od_file.close() + + if '$V' in firstline: + return VFormatODParser(od_file_name, od_matrix) + + else: + raise NotImplementedError('Parser for other OD file format is not available') + + +class ODParser(object): + ''' + Abstract class for a Parser of OD trip information files + + ''' + + def __init__(self, od_file, od_matrix): + ''' + Initializes the parser + :param od_file: path to the od trip definition file + :type od_file: str + :param od_matrix: the OD matrix to be filled with the trip information + :type od_matrix: odmatrix.ODMatrix + + ''' + + self.od_file = od_file + self.taz_list = od_matrix + + def parse(self): + ''' + Parses the od trip information file and fills the OD matrix + with the trip information + + ''' + raise NotImplementedError('parse should be called by subclasses of ODParser only') + +class VFormatODParser(ODParser): + ''' + Parser for V-Format OD matrix files. Follow this link to check the + description of a V-Format file: http://sourceforge.net/apps/mediawiki/sumo/index.php?title=Demand/Importing_O/D_Matrices#Describing_the_Matrix_Cells + + ''' + + def parse(self): + + odfile = open(self.od_file,'r') + + district_names = [] + + line_number = 0 + + for line in odfile.readlines(): + #discounts lines starting with * + if '*' == line[0]: + continue + + line_number += 1 + + #district names are in line 6 + if line_number == 6: + tokens = generate_tokens(StringIO.StringIO(line).readline) # tokenize the string + for toktyp, tokval, _, _, _ in tokens: + if toktyp == token.NUMBER or toktyp == token.STRING: + district_names.append(tokval) + + #trip assignment start at line 7 + if line_number > 6: + #finds the origin TAZ in the array of names + index = line_number - 7 + origin_taz = self.taz_list.find(district_names[index]) + destinations = {} + + #tokenize the current line to get the number of trips for each destiny + tokens = generate_tokens(StringIO.StringIO(line).readline) + + #traverses the tokens and fill the number of trips for each destination + dest_index = 0 + for toktyp, tokval, _, _, _ in tokens: + if toktyp == token.NUMBER: + destinations[district_names[dest_index]] = int(tokval) + dest_index += 1 + origin_taz.set_destinations(destinations) + + + + + \ No newline at end of file diff --git a/src/odpopulator/util.py b/src/odpopulator/util.py new file mode 100644 index 0000000..2651373 --- /dev/null +++ b/src/odpopulator/util.py @@ -0,0 +1,35 @@ +''' +Provides a method for selecting an item with chance proportional to +its weight + +Created on Jan 3, 2013 + +@author: anderson + +''' +import random + +def weighted_selection(total_ammount, items_list, weight_selector): + ''' + Selects an item with chance proportional to its weight among the total + + Algorithm source: + http://stackoverflow.com/questions/3655430/selection-based-on-percentage-weighting/3655453#3655453 + + :param total_ammount: the sum of weights contained in the items_list + :type total_ammount: int|double + :param items_list: the list with the items to be weight-selected + :type items_list: list + :param weight_selector: a function that receives an item and returns its weight + :type weight_selector: function + + ''' + + variate = random.random() * total_ammount + cumulative = 0.0 + for item in items_list: + weight = weight_selector(item) + cumulative += weight + if variate < cumulative: + return item + return item # Shouldn't get here, but just in case of rounding...