2018-12-07 14:53:00 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2019-02-01 16:58:55 +00:00
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
|
|
|
|
if 'SUMO_HOME' in os.environ:
|
|
|
|
TOOLSDIR = os.path.join(os.environ['SUMO_HOME'], 'tools')
|
|
|
|
sys.path.append(TOOLSDIR)
|
|
|
|
else:
|
|
|
|
sys.exit("Please declare environment variable 'SUMO_HOME'")
|
|
|
|
|
|
|
|
|
2018-12-14 17:12:44 +00:00
|
|
|
import argparse
|
2019-02-01 15:49:19 +00:00
|
|
|
import datetime
|
2019-01-30 14:40:53 +00:00
|
|
|
import json
|
2019-02-01 15:49:19 +00:00
|
|
|
import logging
|
2018-12-07 14:53:00 +00:00
|
|
|
import shutil
|
|
|
|
import subprocess
|
2018-12-10 12:16:35 +00:00
|
|
|
import tempfile
|
2019-02-01 10:50:41 +00:00
|
|
|
import time
|
2019-01-30 14:40:53 +00:00
|
|
|
from sys import argv
|
|
|
|
from types import SimpleNamespace
|
2018-12-10 12:16:35 +00:00
|
|
|
from xml.etree import ElementTree
|
2018-12-07 14:53:00 +00:00
|
|
|
|
|
|
|
import randomTrips
|
2018-12-14 18:18:53 +00:00
|
|
|
import sumolib
|
2018-12-07 14:53:00 +00:00
|
|
|
|
2018-12-07 15:20:31 +00:00
|
|
|
# Absolute path of the directory the script is in
|
|
|
|
SCRIPTDIR = os.path.dirname(__file__)
|
2018-12-13 14:10:08 +00:00
|
|
|
TEMPLATEDIR = os.path.join(SCRIPTDIR, 'templates')
|
2019-02-01 16:58:55 +00:00
|
|
|
SUMOBIN = os.path.join(os.environ['SUMO_HOME'], 'bin')
|
2018-12-07 14:53:00 +00:00
|
|
|
|
2019-02-01 15:49:19 +00:00
|
|
|
# Init logger
|
|
|
|
logfile = os.path.join(SCRIPTDIR, f'files/logs/configurator_{datetime.datetime.utcnow().isoformat()}.log')
|
|
|
|
logging.basicConfig(
|
|
|
|
filename=logfile,
|
|
|
|
level=logging.DEBUG,
|
|
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
|
|
)
|
|
|
|
|
|
|
|
"""
|
|
|
|
Definition of vehicle classes.
|
|
|
|
See http://sumo.dlr.de/wiki/Definition_of_Vehicles,_Vehicle_Types,_and_Routes#Abstract_Vehicle_Class
|
|
|
|
"""
|
2018-12-13 19:58:07 +00:00
|
|
|
vehicle_classes = {
|
|
|
|
'passenger': {
|
|
|
|
'--vehicle-class': 'passenger',
|
|
|
|
'--vclass': 'passenger',
|
2018-12-14 17:11:32 +00:00
|
|
|
'--prefix': 'veh',
|
|
|
|
'--min-distance': 300,
|
|
|
|
'--trip-attributes': 'departLane="best"',
|
2018-12-13 19:58:07 +00:00
|
|
|
},
|
|
|
|
'bus': {
|
|
|
|
'--vehicle-class': 'bus',
|
|
|
|
'--vclass': 'bus',
|
2018-12-14 17:11:32 +00:00
|
|
|
'--prefix': 'bus',
|
|
|
|
},
|
|
|
|
'truck': {
|
|
|
|
'--vehicle-class': 'truck',
|
|
|
|
'--vclass': 'truck',
|
|
|
|
'--prefix': 'truck',
|
|
|
|
'--min-distance': 600,
|
|
|
|
'--trip-attributes': 'departLane="best"',
|
2018-12-13 19:58:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-07 14:53:00 +00:00
|
|
|
|
2018-12-14 17:13:31 +00:00
|
|
|
class RandomTripsGenerator:
|
|
|
|
def __init__(self, netpath, routepath, output, vclass, density, *flags, **opts):
|
|
|
|
self.vclass = vclass
|
2019-01-30 14:22:13 +00:00
|
|
|
self.density = density
|
2018-12-14 17:13:31 +00:00
|
|
|
self.options = {
|
|
|
|
# Default options
|
|
|
|
'--net-file': netpath,
|
|
|
|
'--output-trip-file': output,
|
|
|
|
'--route-file': routepath,
|
|
|
|
**opts
|
|
|
|
}
|
|
|
|
self.flags = [*flags]
|
|
|
|
edges = sumolib.net.readNet(netpath).getEdges()
|
|
|
|
self._init_trips(edges, vclass, density)
|
|
|
|
self.options.update(vehicle_classes[self.vclass])
|
|
|
|
|
|
|
|
def generate(self):
|
2019-02-01 15:49:19 +00:00
|
|
|
logging.info(f'Generating trips for vehicle class {self.vclass} with density of {self.density} veh/km/h')
|
2018-12-14 17:13:31 +00:00
|
|
|
randomTrips.main(randomTrips.get_options(dict_to_list(self.options) + self.flags))
|
|
|
|
|
|
|
|
def _init_trips(self, edges, vclass, density):
|
|
|
|
"""
|
|
|
|
:param edges: foo.rou.xml
|
|
|
|
:param density: vehicle/km/h
|
|
|
|
"""
|
|
|
|
# calculate the total length of the available lanes
|
|
|
|
length = 0.
|
|
|
|
for edge in edges:
|
|
|
|
if edge.allows(vclass):
|
|
|
|
length += edge.getLaneNumber() * edge.getLength()
|
|
|
|
|
2019-02-01 15:49:19 +00:00
|
|
|
logging.debug(f'density = {density}')
|
2018-12-14 17:13:31 +00:00
|
|
|
period = 3600 / (length / 1000) / density
|
2019-02-01 15:49:19 +00:00
|
|
|
logging.debug(f'Period computed for network : {period}, vclass={self.vclass}')
|
|
|
|
self.options.update({'-p': period})
|
2018-12-14 17:13:31 +00:00
|
|
|
|
|
|
|
|
2018-12-14 18:18:53 +00:00
|
|
|
class StoreDictKeyPair(argparse.Action):
|
|
|
|
def __call__(self, parser, namespace, values, option_string=None):
|
|
|
|
pairs = {}
|
|
|
|
for kv in values:
|
|
|
|
k, v = kv.split("=")
|
|
|
|
pairs[k] = v
|
|
|
|
setattr(namespace, self.dest, pairs)
|
|
|
|
|
|
|
|
|
2018-12-10 12:16:35 +00:00
|
|
|
def load_netconvert_template(osm_input, out_name):
|
2018-12-13 21:02:54 +00:00
|
|
|
netconfig = ElementTree.parse(os.path.join(TEMPLATEDIR, 'simul.netcfg'))
|
|
|
|
root = netconfig.getroot()
|
2018-12-10 12:16:35 +00:00
|
|
|
root.find('input/osm-files').set('value', osm_input)
|
|
|
|
root.find('output/output-file').set('value', f'{out_name}.net.xml')
|
|
|
|
root.find('report/log').set('value', f'{out_name}.netconvert.log')
|
2018-12-13 21:02:54 +00:00
|
|
|
return netconfig
|
2018-12-10 12:16:35 +00:00
|
|
|
|
|
|
|
|
2018-12-10 16:59:26 +00:00
|
|
|
def load_polyconvert_template(osm_file, type_file, scenario_name):
|
2018-12-13 21:02:54 +00:00
|
|
|
polyconfig = ElementTree.parse(os.path.join(TEMPLATEDIR, 'simul.polycfg'))
|
|
|
|
root = polyconfig.getroot()
|
2018-12-10 16:59:26 +00:00
|
|
|
root.find('input/osm-files').set('value', osm_file)
|
|
|
|
root.find('input/net-file').set('value', f'{scenario_name}.net.xml')
|
|
|
|
root.find('input/type-file').set('value', type_file)
|
|
|
|
root.find('output/output-file').set('value', f'{scenario_name}.poly.xml')
|
|
|
|
root.find('report/log').set('value', f'{scenario_name}.polyconvert.log')
|
2018-12-13 21:02:54 +00:00
|
|
|
return polyconfig
|
2018-12-10 16:59:26 +00:00
|
|
|
|
|
|
|
|
2019-02-01 10:50:41 +00:00
|
|
|
def load_sumoconfig_template(simulation_name, routefiles=(), generate_polygons=False, seed=None):
|
2018-12-13 19:58:07 +00:00
|
|
|
routefiles = routefiles or (f'{simulation_name}.rou.xml',)
|
2018-12-13 21:02:54 +00:00
|
|
|
sumoconfig = ElementTree.parse(os.path.join(TEMPLATEDIR, 'simul.sumocfg'))
|
|
|
|
root = sumoconfig.getroot()
|
2018-12-10 16:59:26 +00:00
|
|
|
root.find('input/net-file').set('value', f'{simulation_name}.net.xml')
|
2018-12-13 21:02:54 +00:00
|
|
|
root.find('input/route-files').set('value', ','.join(routefiles))
|
2019-01-30 13:20:15 +00:00
|
|
|
additional = root.find('input/additional-files')
|
2018-12-14 18:43:13 +00:00
|
|
|
if generate_polygons:
|
2019-01-30 13:20:15 +00:00
|
|
|
additional.set('value', f'{simulation_name}.poly.xml')
|
|
|
|
else:
|
|
|
|
root.find('input').remove(additional)
|
2018-12-10 16:59:26 +00:00
|
|
|
root.find('report/log').set('value', f'{simulation_name}.log')
|
2019-02-01 10:50:41 +00:00
|
|
|
# Set the seed for the random number generator. By default, use the current time
|
2019-02-01 17:18:07 +00:00
|
|
|
root.find('random_number/seed').set('value', seed or str(int(time.time())))
|
2018-12-13 21:02:54 +00:00
|
|
|
return sumoconfig
|
2018-12-10 16:59:26 +00:00
|
|
|
|
|
|
|
|
2018-12-14 18:43:13 +00:00
|
|
|
def generate_scenario(osm_file, out_path, scenario_name, generate_polygons=False):
|
2018-12-10 16:59:26 +00:00
|
|
|
net_template = load_netconvert_template(osm_file, scenario_name)
|
2018-12-13 14:05:37 +00:00
|
|
|
|
2018-12-10 12:16:35 +00:00
|
|
|
with tempfile.TemporaryDirectory() as tmpdirname:
|
2018-12-14 18:43:13 +00:00
|
|
|
# Generate NETCONVERT configuration
|
2018-12-10 16:59:26 +00:00
|
|
|
netconfig = os.path.join(tmpdirname, f'{scenario_name}.netcfg')
|
|
|
|
net_template.write(netconfig)
|
|
|
|
# Copy typemaps to tempdir
|
2018-12-13 14:10:08 +00:00
|
|
|
shutil.copytree(os.path.join(TEMPLATEDIR, 'typemap'), os.path.join(tmpdirname, 'typemap'))
|
2018-12-14 18:43:13 +00:00
|
|
|
# Call NETCONVERT
|
2019-02-01 15:49:19 +00:00
|
|
|
logging.info("Generating network…")
|
2019-02-01 16:58:55 +00:00
|
|
|
netconvertcmd = [os.path.join(SUMOBIN, 'netconvert'), '-c', netconfig]
|
2019-02-01 15:49:19 +00:00
|
|
|
logging.debug(f'Calling {" ".join(netconvertcmd)}')
|
2018-12-10 12:16:35 +00:00
|
|
|
subprocess.run(netconvertcmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
2018-12-14 18:43:13 +00:00
|
|
|
# Optionaly generate polygons
|
|
|
|
if generate_polygons:
|
|
|
|
generate_polygons_(osm_file, scenario_name, tmpdirname)
|
2018-12-10 16:59:26 +00:00
|
|
|
# Move files to destination
|
2018-12-13 19:58:07 +00:00
|
|
|
ignore_patterns = shutil.ignore_patterns('*.polycfg', '*.netcfg', 'typemap')
|
|
|
|
shutil.copytree(tmpdirname, out_path, ignore=ignore_patterns)
|
2018-12-07 14:53:00 +00:00
|
|
|
|
|
|
|
|
2018-12-14 18:43:13 +00:00
|
|
|
def generate_polygons_(osm_file, scenario_name, dest):
|
|
|
|
polyconfig = os.path.join(dest, f'{scenario_name}.polycfg')
|
|
|
|
poly_template = load_polyconvert_template(osm_file, 'typemap/osmPolyconvert.typ.xml', scenario_name)
|
|
|
|
poly_template.write(polyconfig)
|
|
|
|
# Call POLYCONVERT
|
2019-02-01 15:49:19 +00:00
|
|
|
logging.info('Generating polygons…')
|
2019-02-01 16:58:55 +00:00
|
|
|
polyconvert_cmd = [os.path.join(SUMOBIN, 'polyconvert'), '-c', polyconfig]
|
2019-02-01 15:49:19 +00:00
|
|
|
logging.debug(f'Calling {" ".join(polyconvert_cmd)}')
|
2018-12-14 18:43:13 +00:00
|
|
|
subprocess.run(polyconvert_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
|
|
|
|
|
|
|
2019-02-01 17:18:07 +00:00
|
|
|
def generate_mobility(out_path, name, vclasses, end_time):
|
2018-12-13 21:02:54 +00:00
|
|
|
netfile = f'{name}.net.xml'
|
|
|
|
netpath = os.path.join(out_path, netfile)
|
|
|
|
output = os.path.join(out_path, f'{name}.trips.xml')
|
2018-12-13 19:58:07 +00:00
|
|
|
routefiles = []
|
2019-01-30 14:22:13 +00:00
|
|
|
for vclass, density in vclasses.items():
|
2018-12-13 19:58:07 +00:00
|
|
|
# simname.bus.rou.xml, simname.passenger.rou.xml, ...
|
2018-12-14 17:13:31 +00:00
|
|
|
routefile = f'{name}.{vclass}.rou.xml'
|
2018-12-13 21:02:54 +00:00
|
|
|
routepath = os.path.join(out_path, routefile)
|
2018-12-13 19:58:07 +00:00
|
|
|
routefiles.append(routefile)
|
2019-02-01 15:49:19 +00:00
|
|
|
logging.debug(routefile)
|
|
|
|
generator = RandomTripsGenerator(netpath, routepath, output, vclass, float(density))
|
|
|
|
generator.flags.append('-l')
|
|
|
|
generator.flags.append('--validate')
|
|
|
|
generator.options.update(**{'--end': end_time})
|
2018-12-14 17:13:31 +00:00
|
|
|
generator.generate()
|
2018-12-13 19:58:07 +00:00
|
|
|
return routefiles
|
|
|
|
|
|
|
|
|
2019-02-01 15:49:19 +00:00
|
|
|
def generate_sumo_configuration(routefiles, path, scenario_name, generate_polygons):
|
|
|
|
sumo_template = load_sumoconfig_template(scenario_name, routefiles, generate_polygons)
|
2018-12-13 14:05:37 +00:00
|
|
|
sumo_template.write(os.path.join(path, f'{scenario_name}.sumocfg'))
|
|
|
|
|
|
|
|
|
2018-12-14 18:43:13 +00:00
|
|
|
def generate_all(args):
|
|
|
|
simulation_name = args.name
|
|
|
|
simulation_dir = os.path.join(args.path, simulation_name)
|
2019-01-30 14:40:53 +00:00
|
|
|
try:
|
|
|
|
generate_polygons = args.generate_polygons
|
|
|
|
except AttributeError:
|
|
|
|
generate_polygons = False
|
2018-12-14 18:43:13 +00:00
|
|
|
osm_file = args.osmfile
|
2018-12-13 14:05:37 +00:00
|
|
|
logs_dir = os.path.join(simulation_dir, 'log')
|
2019-02-01 15:49:19 +00:00
|
|
|
|
2019-01-30 14:40:53 +00:00
|
|
|
generate_scenario(osm_file, simulation_dir, simulation_name, generate_polygons)
|
2019-02-01 17:18:07 +00:00
|
|
|
routefiles = generate_mobility(simulation_dir, simulation_name, args.vclasses, args.end)
|
2019-02-01 15:49:19 +00:00
|
|
|
generate_sumo_configuration(routefiles, simulation_dir, simulation_name, generate_polygons)
|
2018-12-13 14:05:37 +00:00
|
|
|
# Move all logs to logdir
|
2018-12-13 14:20:11 +00:00
|
|
|
move_logs(simulation_dir, logs_dir)
|
|
|
|
|
|
|
|
|
|
|
|
def move_logs(simulation_dir, logs_dir):
|
2018-12-13 14:05:37 +00:00
|
|
|
for f in os.listdir(simulation_dir):
|
|
|
|
if os.path.splitext(f)[1] == '.log':
|
|
|
|
shutil.move(os.path.join(simulation_dir, f), logs_dir)
|
2018-12-07 14:53:00 +00:00
|
|
|
|
|
|
|
|
2018-12-13 19:58:07 +00:00
|
|
|
def dict_to_list(d):
|
|
|
|
return [item for k in d for item in (k, d[k])]
|
|
|
|
|
|
|
|
|
2019-02-01 16:58:55 +00:00
|
|
|
def parse_command_line(args=None):
|
2018-12-14 17:12:44 +00:00
|
|
|
parser = argparse.ArgumentParser()
|
2018-12-14 18:18:53 +00:00
|
|
|
parser.add_argument('osmfile', help='Path to the .osm file to convert to a SUMO simulation')
|
|
|
|
parser.add_argument('--path', help='Where to generate the files')
|
2018-12-14 18:43:13 +00:00
|
|
|
parser.add_argument('--name', required=True, help='Name of the SUMO scenario to generate')
|
2019-02-01 15:49:19 +00:00
|
|
|
parser.add_argument('--generate-polygons', default=False, action='store_true',
|
|
|
|
help='Whether to generate polygons and POIs (defaults to false).')
|
2018-12-14 18:18:53 +00:00
|
|
|
parser.add_argument('--vclass', dest='vclasses', action=StoreDictKeyPair,
|
|
|
|
nargs="+", metavar="VCLASS=DENSITY",
|
|
|
|
help='Generate this vclass with given density, in pair form vclass=density. The density is '
|
2019-01-30 13:20:15 +00:00
|
|
|
'given in vehicles per hour per kilometer. For now, the following vehicle classes are '
|
|
|
|
'available: passenger, truck, bus.')
|
2019-02-01 10:50:41 +00:00
|
|
|
parser.add_argument('--seed', help='Initializes the random number generator.')
|
2019-02-01 17:18:07 +00:00
|
|
|
parser.add_argument('-e', '--end', type=int, default=200, help='end time (default 200)')
|
2019-02-01 16:58:55 +00:00
|
|
|
return parser.parse_args(args=args)
|
2019-01-30 14:40:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
def handle_args(options):
|
2019-01-30 14:22:13 +00:00
|
|
|
# If no vehicle classes are specified, use 'passenger' as a default with a density of 10 cars/km/h.
|
|
|
|
options.vclasses = options.vclasses or {'passenger': 10}
|
2019-01-30 13:20:15 +00:00
|
|
|
# Delete simul_dir if it already exists
|
2018-12-14 18:43:13 +00:00
|
|
|
simul_dir = os.path.join(options.path, options.name)
|
2018-12-14 18:18:53 +00:00
|
|
|
if os.path.isdir(simul_dir):
|
2019-01-30 13:20:15 +00:00
|
|
|
input(f'{simul_dir} already exists ! Press Enter to delete...')
|
2018-12-14 18:18:53 +00:00
|
|
|
shutil.rmtree(simul_dir)
|
2019-02-01 15:49:19 +00:00
|
|
|
logging.debug(f'Options : {options}')
|
2018-12-14 18:43:13 +00:00
|
|
|
generate_all(options)
|
2018-12-14 17:12:44 +00:00
|
|
|
|
|
|
|
|
2019-01-30 14:40:53 +00:00
|
|
|
def parse_json(json_file):
|
2019-02-01 15:49:19 +00:00
|
|
|
logging.info(f'Loading config from {json_file}')
|
2019-01-30 14:40:53 +00:00
|
|
|
config = SimpleNamespace(**json.load(json_file))
|
2019-02-01 15:49:19 +00:00
|
|
|
logging.debug(f'Config {config}')
|
2019-02-01 16:58:55 +00:00
|
|
|
return config
|
2019-01-30 14:40:53 +00:00
|
|
|
|
|
|
|
|
2018-12-07 14:53:00 +00:00
|
|
|
if __name__ == '__main__':
|
2019-02-01 16:58:55 +00:00
|
|
|
# Try to load the config file
|
|
|
|
if len(argv) > 2 and argv[1] == '-c' or argv[1] == '--config' or argv[1] == '-config':
|
2019-01-30 14:40:53 +00:00
|
|
|
try:
|
|
|
|
with open(argv[2]) as jsonfile:
|
2019-02-01 16:58:55 +00:00
|
|
|
config = parse_json(jsonfile)
|
|
|
|
handle_args(config)
|
2019-01-30 14:40:53 +00:00
|
|
|
except FileNotFoundError:
|
2019-02-01 15:49:19 +00:00
|
|
|
msg = f'The config file {argv[2]} does not exist!'
|
|
|
|
logging.fatal(msg)
|
|
|
|
raise FileNotFoundError(msg)
|
2019-01-30 14:40:53 +00:00
|
|
|
else:
|
|
|
|
# Run with command line arguments
|
2019-02-01 16:58:55 +00:00
|
|
|
config = parse_command_line()
|
|
|
|
handle_args(config)
|