mirror of
https://github.com/Ahp06/SUMO_Emissions.git
synced 2025-02-21 06:18:13 +00:00
Initial commit
This commit is contained in:
commit
643e8bb1fa
116
.gitignore
vendored
Normal file
116
.gitignore
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
68
README.md
Normal file
68
README.md
Normal file
@ -0,0 +1,68 @@
|
||||
# SUMO Emissions
|
||||
|
||||
This "Proof of concept" aims to simulate the impact that connected vehicles and smart urban infrastructure would have on pollutant emissions.
|
||||
Using the SUMO simulator, we developed several parameters and measures using Traci to act on the road infrastructure and vehicles.
|
||||
|
||||
We imagined that for a map of a given city, the city would be divided into areas,
|
||||
which when the pollution rate exceeds a certain threshold in these then we act on the infrastructure and the vehicles present in this zone.
|
||||
|
||||

|
||||
|
||||
# Prerequisites:
|
||||
* Python >3.7 : https://www.python.org/downloads/
|
||||
* External Python librairies : shapely, parse, jsonpickle : ``` > pip install [LIBRARY_NAME] ```
|
||||
* SUMO 1.0.0 : http://sumo.dlr.de/wiki/Downloads
|
||||
|
||||
# How to run
|
||||
|
||||
This application can be launched from an IDE, or from a shell (linux, Windows, MacOS).
|
||||
You will need a config.json configuration file (see [default_config.json](https://github.com/Ahp06/SUMO_Emissions/wiki/Configuration-file) for a template) and a simulation file.
|
||||
You can use your own scenario file (osm.sumocfg file), see : [SUMO Tutorials](http://sumo.dlr.de/wiki/Tutorials).
|
||||
|
||||
**With a Shell:**
|
||||
|
||||
```
|
||||
usage: runner.py [-h] [-new_dump NEW_DUMP] [-areas AREAS]
|
||||
[-simulation_dir SIMULATION_DIR] [-run RUN]
|
||||
[-c config1 [config2 ...]] [-c_dir C_DIR] [-save] [-csv]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
-new_dump NEW_DUMP, --new_dump NEW_DUMP
|
||||
Load and create a new data dump with the configuration
|
||||
file chosen
|
||||
-areas AREAS, --areas AREAS
|
||||
Will create a grid with "areas x areas" areas
|
||||
-simulation_dir SIMULATION_DIR, --simulation_dir SIMULATION_DIR
|
||||
Choose the simulation directory
|
||||
-run RUN, --run RUN Run a simulation process with the dump chosen
|
||||
-c config1 [config2 ...], --c config1 [config2 ...]
|
||||
Choose your(s) configuration file(s) from your working
|
||||
directory
|
||||
-c_dir C_DIR, --c_dir C_DIR
|
||||
Choose a directory which contains your(s)
|
||||
configuration file(s)
|
||||
-save, --save Save the logs into the logs folder
|
||||
-csv, --csv Export all data emissions into a CSV file
|
||||
```
|
||||
|
||||
Create a data dump from simulation directory :
|
||||
|
||||
```py ./runner.py -new_dump dump -areas 10 -simulation_dir [PATH_TO_SIMUL_DIR]```
|
||||
|
||||
This command will create new dump called "dump" from the simulation directory chosen with a 10x10 grid.
|
||||
|
||||
Run simulations in parallel with multiple configuration files :
|
||||
|
||||
```py ./runner.py -run dump -c [PATH_TO_CONFIG1] [PATH_TO_CONFIG2] -save -csv```
|
||||
|
||||
This command will run a simulation dump "dump" with the configuration file(s) "config1" and "config2"
|
||||
with CSV data export and logs backup.
|
||||
|
||||
From a folder which contains multiple configuration files :
|
||||
|
||||
```py ./runner.py -run dump -c_dir [PATH_TO_CONFIG_DIR] -save -csv```
|
||||
|
||||
Log and csv files will be written in a sub folder of the simulation folder.
|
||||
|
||||
|
132
sumo_project/actions.py
Normal file
132
sumo_project/actions.py
Normal file
@ -0,0 +1,132 @@
|
||||
"""
|
||||
This module defines all possible actions on the simulation
|
||||
"""
|
||||
|
||||
import traci
|
||||
|
||||
from model import Area
|
||||
|
||||
|
||||
def compute_edge_weight(edge_id):
|
||||
"""
|
||||
Sum the different pollutant emissions on the edge with the identifier edge_id
|
||||
:param edge_id: The edge ID
|
||||
:return: The sum (in mg) of all pollutant emissions
|
||||
"""
|
||||
co2 = traci.edge.getCO2Emission(edge_id)
|
||||
co = traci.edge.getCOEmission(edge_id)
|
||||
nox = traci.edge.getNOxEmission(edge_id)
|
||||
hc = traci.edge.getHCEmission(edge_id)
|
||||
pmx = traci.edge.getPMxEmission(edge_id)
|
||||
|
||||
return co2 + co + nox + hc + pmx
|
||||
|
||||
|
||||
def adjust_edges_weights(area):
|
||||
"""
|
||||
Changes the edge weight of all edges into the area
|
||||
:param area: The Area object
|
||||
:return:
|
||||
"""
|
||||
area.weight_adjusted = True
|
||||
for lane in area._lanes:
|
||||
edge_id = traci.lane.getEdgeID(lane.lane_id)
|
||||
weight = compute_edge_weight(edge_id) # by default edges weight = length/mean speed
|
||||
traci.edge.setEffort(edge_id, weight)
|
||||
|
||||
for veh_id in traci.vehicle.getIDList():
|
||||
traci.vehicle.rerouteEffort(veh_id)
|
||||
|
||||
|
||||
def limit_speed_into_area(area: Area, speed_rf):
|
||||
"""
|
||||
Limit the speed into the area by speed_rf factor
|
||||
:param area: The Area object
|
||||
:param speed_rf: The speed reduction factor (must be positive)
|
||||
:return:
|
||||
"""
|
||||
area.limited_speed = True
|
||||
for lane in area._lanes:
|
||||
traci.lane.setMaxSpeed(lane.lane_id, speed_rf * lane.initial_max_speed)
|
||||
|
||||
|
||||
def modifyLogic(logic, rf):
|
||||
"""
|
||||
Change the logic of a traffic light by decreasing the overall duration of the traffic light
|
||||
:param logic: The Logic object
|
||||
:param rf: The reduction factor (must be positive)
|
||||
:return: A new Logic object with all phases modified
|
||||
"""
|
||||
new_phases = []
|
||||
for phase in logic._phases:
|
||||
new_phase = traci.trafficlight.Phase(phase.duration * rf, phase.minDuration * rf, phase.maxDuration * rf,
|
||||
phase.phaseDef)
|
||||
new_phases.append(new_phase)
|
||||
|
||||
return traci.trafficlight.Logic("new-program", 0, 0, 0, new_phases)
|
||||
|
||||
|
||||
def adjust_traffic_light_phase_duration(area, reduction_factor):
|
||||
"""
|
||||
Set all logics modification on traffic lights into the area
|
||||
:param area: The Area object
|
||||
:param reduction_factor: The reduction factor (must be positive)
|
||||
:return:
|
||||
"""
|
||||
area.tls_adjusted = True
|
||||
for tl in area._tls:
|
||||
for logic in tl._logics:
|
||||
traci.trafficlights.setCompleteRedYellowGreenDefinition(tl.tl_id, modifyLogic(logic, reduction_factor))
|
||||
|
||||
|
||||
def count_vehicles_in_area(area):
|
||||
"""
|
||||
Count the vehicles number into the area
|
||||
:param area: The Area object
|
||||
:return: The number of vehicles into the area
|
||||
"""
|
||||
vehicles_in_area = 0
|
||||
for lane in area._lanes:
|
||||
vehicles_in_area += traci.lane.getLastStepVehicleNumber(lane.lane_id)
|
||||
return vehicles_in_area
|
||||
|
||||
|
||||
def lock_area(area):
|
||||
"""
|
||||
Prohibits access to the area to a particular vehicle class
|
||||
NOT FIXED : Some vehicles continue to go into the area
|
||||
if they can not turn around and then will stay blocked there
|
||||
as long as "lock_area" will not be reversed
|
||||
:param area: The Area object
|
||||
:return:
|
||||
"""
|
||||
area.locked = True
|
||||
for lane in area._lanes:
|
||||
# The passenger class is an example, you have to adapt this code
|
||||
traci.lane.setDisallowed(lane.lane_id, 'passenger')
|
||||
|
||||
|
||||
def reverse_actions(area):
|
||||
"""
|
||||
Reverse all actions made in an area
|
||||
:param area: The Area object
|
||||
:return:
|
||||
"""
|
||||
# Reset max speed to original
|
||||
if area.limited_speed:
|
||||
area.limited_speed = False
|
||||
for lane in area._lanes:
|
||||
traci.lane.setMaxSpeed(lane.lane_id, lane.initial_max_speed)
|
||||
|
||||
# Reset traffic lights initial duration
|
||||
if area.tls_adjusted:
|
||||
area.tls_adjusted = False
|
||||
for tl in area._tls:
|
||||
for initial_logic in tl._logics:
|
||||
traci.trafficlights.setCompleteRedYellowGreenDefinition(tl.tl_id, initial_logic._logic)
|
||||
|
||||
# Unlock the area
|
||||
if area.locked:
|
||||
area.locked = False
|
||||
for lane in area._lanes:
|
||||
traci.lane.setAllowed(lane.lane_id, []) # empty means all classes are allowed
|
83
sumo_project/config.py
Normal file
83
sumo_project/config.py
Normal file
@ -0,0 +1,83 @@
|
||||
"""
|
||||
This module defines the global configuration for the simulation
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
|
||||
from data import Data
|
||||
from model import Emission
|
||||
|
||||
|
||||
class Config:
|
||||
"""
|
||||
The Config class defines all simulation properties that can be changed
|
||||
"""
|
||||
|
||||
def __init__(self,config_file, data : Data):
|
||||
"""
|
||||
Default constructor
|
||||
"""
|
||||
self.import_config_file(config_file)
|
||||
self.init_traci(data.dir)
|
||||
self.check_config()
|
||||
|
||||
def import_config_file(self, config_file):
|
||||
"""
|
||||
Import your configuration file in JSON format
|
||||
:param config_file: The path to your configuration file
|
||||
:return:
|
||||
"""
|
||||
with open(f'{config_file}', 'r') as f:
|
||||
data = json.load(f)
|
||||
|
||||
for option in data:
|
||||
self.__setattr__(option, data[option])
|
||||
self.config_filename = os.path.basename(f.name)
|
||||
self.check_config()
|
||||
|
||||
def check_config(self):
|
||||
"""
|
||||
Check the relevance of user configuration choices
|
||||
:return:
|
||||
"""
|
||||
# Weight routing mode cannot be combined with other actions
|
||||
if self.weight_routing_mode:
|
||||
self.limit_speed_mode = False
|
||||
self.adjust_traffic_light_mode = False
|
||||
self.lock_area_mode = False
|
||||
|
||||
# If without_actions_mode is chosen
|
||||
if self.without_actions_mode:
|
||||
self.limit_speed_mode = False
|
||||
self.adjust_traffic_light_mode = False
|
||||
self.weight_routing_mode = False
|
||||
self.lock_area_mode = False
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
:return: All properties chosen by the user
|
||||
"""
|
||||
return (
|
||||
f'step number = {self.n_steps}\n'
|
||||
f'window size = {self.window_size}\n'
|
||||
f'weight routing mode = {self.weight_routing_mode}\n'
|
||||
f'lock area mode = {self.lock_area_mode}\n'
|
||||
f'limit speed mode = {self.limit_speed_mode}, RF = {self.speed_rf * 100}%\n'
|
||||
f'adjust traffic light mode = {self.adjust_traffic_light_mode},'
|
||||
f'RF = {self.trafficLights_duration_rf * 100}%\n'
|
||||
)
|
||||
|
||||
def init_traci(self, simulation_dir):
|
||||
"""
|
||||
Init the Traci API
|
||||
:param simulation_dir: The path to the simulation directory
|
||||
:return:
|
||||
"""
|
||||
simdir = os.path.join(os.path.dirname(__file__), f'{simulation_dir}')
|
||||
|
||||
for f in os.listdir(simdir):
|
||||
if f.endswith('.sumocfg'):
|
||||
self._SUMOCFG = os.path.join(simdir, f)
|
||||
sumo_binary = os.path.join(os.environ['SUMO_HOME'], 'bin', self._SUMOCMD)
|
||||
self.sumo_cmd = [sumo_binary, "-c", self._SUMOCFG]
|
284
sumo_project/configurator.py
Normal file
284
sumo_project/configurator.py
Normal file
@ -0,0 +1,284 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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'")
|
||||
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
from sys import argv
|
||||
from types import SimpleNamespace
|
||||
from xml.etree import ElementTree
|
||||
|
||||
import randomTrips
|
||||
import sumolib
|
||||
|
||||
# Absolute path of the directory the script is in
|
||||
SCRIPTDIR = os.path.dirname(__file__)
|
||||
TEMPLATEDIR = os.path.join(SCRIPTDIR, 'templates')
|
||||
SUMOBIN = os.path.join(os.environ['SUMO_HOME'], 'bin')
|
||||
|
||||
# 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
|
||||
"""
|
||||
vehicle_classes = {
|
||||
'passenger': {
|
||||
'--vehicle-class': 'passenger',
|
||||
'--vclass': 'passenger',
|
||||
'--prefix': 'veh',
|
||||
'--min-distance': 300,
|
||||
'--trip-attributes': 'departLane="best"',
|
||||
},
|
||||
'bus': {
|
||||
'--vehicle-class': 'bus',
|
||||
'--vclass': 'bus',
|
||||
'--prefix': 'bus',
|
||||
},
|
||||
'truck': {
|
||||
'--vehicle-class': 'truck',
|
||||
'--vclass': 'truck',
|
||||
'--prefix': 'truck',
|
||||
'--min-distance': 600,
|
||||
'--trip-attributes': 'departLane="best"',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class RandomTripsGenerator:
|
||||
def __init__(self, netpath, routepath, output, vclass, density, *flags, **opts):
|
||||
self.vclass = vclass
|
||||
self.density = density
|
||||
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):
|
||||
logging.info(f'Generating trips for vehicle class {self.vclass} with density of {self.density} veh/km/h')
|
||||
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()
|
||||
|
||||
logging.debug(f'density = {density}')
|
||||
period = 3600 / (length / 1000) / density
|
||||
logging.debug(f'Period computed for network : {period}, vclass={self.vclass}')
|
||||
self.options.update({'-p': period})
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def load_netconvert_template(osm_input, out_name):
|
||||
netconfig = ElementTree.parse(os.path.join(TEMPLATEDIR, 'simul.netcfg'))
|
||||
root = netconfig.getroot()
|
||||
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')
|
||||
return netconfig
|
||||
|
||||
|
||||
def load_polyconvert_template(osm_file, type_file, scenario_name):
|
||||
polyconfig = ElementTree.parse(os.path.join(TEMPLATEDIR, 'simul.polycfg'))
|
||||
root = polyconfig.getroot()
|
||||
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')
|
||||
return polyconfig
|
||||
|
||||
|
||||
def load_sumoconfig_template(simulation_name, routefiles=(), generate_polygons=False, seed=None):
|
||||
routefiles = routefiles or (f'{simulation_name}.rou.xml',)
|
||||
sumoconfig = ElementTree.parse(os.path.join(TEMPLATEDIR, 'simul.sumocfg'))
|
||||
root = sumoconfig.getroot()
|
||||
root.find('input/net-file').set('value', f'{simulation_name}.net.xml')
|
||||
root.find('input/route-files').set('value', ','.join(routefiles))
|
||||
additional = root.find('input/additional-files')
|
||||
if generate_polygons:
|
||||
additional.set('value', f'{simulation_name}.poly.xml')
|
||||
else:
|
||||
root.find('input').remove(additional)
|
||||
root.find('report/log').set('value', f'{simulation_name}.log')
|
||||
# Set the seed for the random number generator. By default, use the current time
|
||||
root.find('random_number/seed').set('value', seed or str(int(time.time())))
|
||||
return sumoconfig
|
||||
|
||||
|
||||
def generate_scenario(osm_file, out_path, scenario_name, generate_polygons=False):
|
||||
net_template = load_netconvert_template(osm_file, scenario_name)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdirname:
|
||||
# Generate NETCONVERT configuration
|
||||
netconfig = os.path.join(tmpdirname, f'{scenario_name}.netcfg')
|
||||
net_template.write(netconfig)
|
||||
# Copy typemaps to tempdir
|
||||
shutil.copytree(os.path.join(TEMPLATEDIR, 'typemap'), os.path.join(tmpdirname, 'typemap'))
|
||||
# Call NETCONVERT
|
||||
logging.info("Generating network…")
|
||||
netconvertcmd = [os.path.join(SUMOBIN, 'netconvert'), '-c', netconfig]
|
||||
logging.debug(f'Calling {" ".join(netconvertcmd)}')
|
||||
subprocess.run(netconvertcmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
# Optionaly generate polygons
|
||||
if generate_polygons:
|
||||
generate_polygons_(osm_file, scenario_name, tmpdirname)
|
||||
# Move files to destination
|
||||
ignore_patterns = shutil.ignore_patterns('*.polycfg', '*.netcfg', 'typemap')
|
||||
shutil.copytree(tmpdirname, out_path, ignore=ignore_patterns)
|
||||
|
||||
|
||||
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
|
||||
logging.info('Generating polygons…')
|
||||
polyconvert_cmd = [os.path.join(SUMOBIN, 'polyconvert'), '-c', polyconfig]
|
||||
logging.debug(f'Calling {" ".join(polyconvert_cmd)}')
|
||||
subprocess.run(polyconvert_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
|
||||
def generate_mobility(out_path, name, vclasses, end_time):
|
||||
netfile = f'{name}.net.xml'
|
||||
netpath = os.path.join(out_path, netfile)
|
||||
output = os.path.join(out_path, f'{name}.trips.xml')
|
||||
routefiles = []
|
||||
for vclass, density in vclasses.items():
|
||||
# simname.bus.rou.xml, simname.passenger.rou.xml, ...
|
||||
routefile = f'{name}.{vclass}.rou.xml'
|
||||
routepath = os.path.join(out_path, routefile)
|
||||
routefiles.append(routefile)
|
||||
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})
|
||||
generator.generate()
|
||||
return routefiles
|
||||
|
||||
|
||||
def generate_sumo_configuration(routefiles, path, scenario_name, generate_polygons):
|
||||
sumo_template = load_sumoconfig_template(scenario_name, routefiles, generate_polygons)
|
||||
sumo_template.write(os.path.join(path, f'{scenario_name}.sumocfg'))
|
||||
|
||||
|
||||
def generate_all(args):
|
||||
simulation_name = args.name
|
||||
simulation_dir = os.path.join(args.path, simulation_name)
|
||||
try:
|
||||
generate_polygons = args.generate_polygons
|
||||
except AttributeError:
|
||||
generate_polygons = False
|
||||
osm_file = args.osmfile
|
||||
logs_dir = os.path.join(simulation_dir, 'log')
|
||||
|
||||
generate_scenario(osm_file, simulation_dir, simulation_name, generate_polygons)
|
||||
routefiles = generate_mobility(simulation_dir, simulation_name, args.vclasses, args.end)
|
||||
generate_sumo_configuration(routefiles, simulation_dir, simulation_name, generate_polygons)
|
||||
# Move all logs to logdir
|
||||
move_logs(simulation_dir, logs_dir)
|
||||
|
||||
|
||||
def move_logs(simulation_dir, logs_dir):
|
||||
for f in os.listdir(simulation_dir):
|
||||
if os.path.splitext(f)[1] == '.log':
|
||||
shutil.move(os.path.join(simulation_dir, f), logs_dir)
|
||||
|
||||
|
||||
def dict_to_list(d):
|
||||
return [item for k in d for item in (k, d[k])]
|
||||
|
||||
|
||||
def parse_command_line(args=None):
|
||||
parser = argparse.ArgumentParser()
|
||||
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')
|
||||
parser.add_argument('--name', required=True, help='Name of the SUMO scenario to generate')
|
||||
parser.add_argument('--generate-polygons', default=False, action='store_true',
|
||||
help='Whether to generate polygons and POIs (defaults to false).')
|
||||
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 '
|
||||
'given in vehicles per hour per kilometer. For now, the following vehicle classes are '
|
||||
'available: passenger, truck, bus.')
|
||||
parser.add_argument('--seed', help='Initializes the random number generator.')
|
||||
parser.add_argument('-e', '--end', type=int, default=200, help='end time (default 200)')
|
||||
return parser.parse_args(args=args)
|
||||
|
||||
|
||||
def handle_args(options):
|
||||
# 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}
|
||||
# Delete simul_dir if it already exists
|
||||
simul_dir = os.path.join(options.path, options.name)
|
||||
if os.path.isdir(simul_dir):
|
||||
input(f'{simul_dir} already exists ! Press Enter to delete...')
|
||||
shutil.rmtree(simul_dir)
|
||||
logging.debug(f'Options : {options}')
|
||||
generate_all(options)
|
||||
|
||||
|
||||
def parse_json(json_file):
|
||||
logging.info(f'Loading config from {json_file}')
|
||||
config = SimpleNamespace(**json.load(json_file))
|
||||
logging.debug(f'Config {config}')
|
||||
return config
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Try to load the config file
|
||||
if len(argv) > 2 and argv[1] == '-c' or argv[1] == '--config' or argv[1] == '-config':
|
||||
try:
|
||||
with open(argv[2]) as jsonfile:
|
||||
config = parse_json(jsonfile)
|
||||
handle_args(config)
|
||||
except FileNotFoundError:
|
||||
msg = f'The config file {argv[2]} does not exist!'
|
||||
logging.fatal(msg)
|
||||
raise FileNotFoundError(msg)
|
||||
else:
|
||||
# Run with command line arguments
|
||||
config = parse_command_line()
|
||||
handle_args(config)
|
119
sumo_project/data.py
Normal file
119
sumo_project/data.py
Normal file
@ -0,0 +1,119 @@
|
||||
"""
|
||||
This module is used for loading simulation data
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import traci
|
||||
from typing import List
|
||||
|
||||
import jsonpickle
|
||||
from parse import search
|
||||
from shapely.geometry import LineString
|
||||
|
||||
from model import Area, Lane, TrafficLight, Phase, Logic
|
||||
|
||||
|
||||
class Data:
|
||||
|
||||
def __init__(self, dump_name, map_bounds, areas_number,simulation_dir):
|
||||
"""
|
||||
Data constructor
|
||||
:param dump_name : The dump name chosen by the user
|
||||
:param map_bounds: The bounds of the simulated map
|
||||
:param areas_number: The number of areas in line and row chosen by the user
|
||||
:param simulation_dir: The directory which contains all files needed for SUMO
|
||||
"""
|
||||
self.dump_name = dump_name
|
||||
self.map_bounds = map_bounds
|
||||
self.areas_number = areas_number
|
||||
self.dir = simulation_dir
|
||||
|
||||
def init_grid(self):
|
||||
"""
|
||||
Initialize the grid of the loaded map from the cfg file with areas_number x areas_number areas
|
||||
"""
|
||||
self.grid = list()
|
||||
areas_number = self.areas_number
|
||||
|
||||
width = self.map_bounds[1][0] / areas_number
|
||||
height = self.map_bounds[1][1] / areas_number
|
||||
for i in range(areas_number):
|
||||
for j in range(areas_number):
|
||||
# bounds coordinates for the area : (xmin, ymin, xmax, ymax)
|
||||
ar_bounds = ((i * width, j * height), (i * width, (j + 1) * height),
|
||||
((i + 1) * width, (j + 1) * height), ((i + 1) * width, j * height))
|
||||
name = 'Area ({},{})'.format(i, j)
|
||||
area = Area(ar_bounds, name)
|
||||
self.grid.append(area)
|
||||
return self.grid
|
||||
|
||||
def get_all_lanes(self) -> List[Lane]:
|
||||
"""
|
||||
Recover and creates a list of Lane objects
|
||||
:return: The lanes list
|
||||
"""
|
||||
lanes = []
|
||||
for lane_id in traci.lane.getIDList():
|
||||
polygon_lane = LineString(traci.lane.getShape(lane_id))
|
||||
initial_max_speed = traci.lane.getMaxSpeed(lane_id)
|
||||
lanes.append(Lane(lane_id, polygon_lane, initial_max_speed))
|
||||
return lanes
|
||||
|
||||
def parse_phase(self, phase_repr):
|
||||
"""
|
||||
Because the SUMO object Phase does not contain accessors,
|
||||
we parse the string representation to retrieve data members.
|
||||
:param phase_repr: The Phase string representation
|
||||
:return: An new Phase instance
|
||||
"""
|
||||
duration = search('duration: {:f}', phase_repr)
|
||||
min_duration = search('minDuration: {:f}', phase_repr)
|
||||
max_duration = search('maxDuration: {:f}', phase_repr)
|
||||
phase_def = search('phaseDef: {}\n', phase_repr)
|
||||
|
||||
if phase_def is None:
|
||||
phase_def = ''
|
||||
else:
|
||||
phase_def = phase_def[0]
|
||||
|
||||
|
||||
|
||||
|
||||
return Phase(duration[0], min_duration[0], max_duration[0], phase_def)
|
||||
|
||||
def add_data_to_areas(self):
|
||||
"""
|
||||
Adds all recovered data to different areas
|
||||
:param areas: The list of areas
|
||||
:return:
|
||||
"""
|
||||
lanes = self.get_all_lanes()
|
||||
for area in self.grid:
|
||||
for lane in lanes: # add lanes
|
||||
if area.rectangle.intersects(lane.polygon):
|
||||
area.add_lane(lane)
|
||||
for tl_id in traci.trafficlight.getIDList(): # add traffic lights
|
||||
if lane.lane_id in traci.trafficlight.getControlledLanes(tl_id):
|
||||
logics = []
|
||||
for l in traci.trafficlight.getCompleteRedYellowGreenDefinition(tl_id): # add logics
|
||||
phases = []
|
||||
for phase in traci.trafficlight.Logic.getPhases(l): # add phases to logics
|
||||
phases.append(self.parse_phase(phase.__repr__()))
|
||||
logics.append(Logic(l, phases))
|
||||
area.add_tl(TrafficLight(tl_id, logics))
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Save simulation data into a json file
|
||||
:param dump_name: The name of your data dump
|
||||
:return:
|
||||
"""
|
||||
dump_dir = f'{self.dir}/dump'
|
||||
if not os.path.exists(dump_dir):
|
||||
os.mkdir(dump_dir)
|
||||
|
||||
s = json.dumps(json.loads(jsonpickle.encode(self)), indent=4) # for pretty JSON
|
||||
with open(f'{dump_dir}/{self.dump_name}.json', 'w') as f:
|
||||
f.write(s)
|
||||
|
95
sumo_project/emissions.py
Normal file
95
sumo_project/emissions.py
Normal file
@ -0,0 +1,95 @@
|
||||
"""
|
||||
This module defines how pollutant emissions are recovered and how we act on the areas
|
||||
"""
|
||||
|
||||
import traci
|
||||
from typing import List
|
||||
|
||||
import actions
|
||||
from model import Vehicle, Emission
|
||||
from runner import RunProcess
|
||||
|
||||
|
||||
def compute_vehicle_emissions(veh_id):
|
||||
"""
|
||||
Recover the emissions of different pollutants from a vehicle and create an Emission instance
|
||||
:param veh_id: The vehicle ID
|
||||
:return: A new Emission instance
|
||||
"""
|
||||
co2 = traci.vehicle.getCO2Emission(veh_id)
|
||||
co = traci.vehicle.getCOEmission(veh_id)
|
||||
nox = traci.vehicle.getNOxEmission(veh_id)
|
||||
hc = traci.vehicle.getHCEmission(veh_id)
|
||||
pmx = traci.vehicle.getPMxEmission(veh_id)
|
||||
|
||||
return Emission(co2, co, nox, hc, pmx)
|
||||
|
||||
|
||||
def get_all_vehicles() -> List[Vehicle]:
|
||||
"""
|
||||
Recover all useful information about vehicles and creates a vehicles list
|
||||
:return: A list of vehicles instances
|
||||
"""
|
||||
vehicles = list()
|
||||
for veh_id in traci.vehicle.getIDList():
|
||||
veh_pos = traci.vehicle.getPosition(veh_id)
|
||||
vehicle = Vehicle(veh_id, veh_pos)
|
||||
vehicle.emissions = compute_vehicle_emissions(veh_id)
|
||||
vehicles.append(vehicle)
|
||||
return vehicles
|
||||
|
||||
def get_emissions(p : RunProcess, vehicles: List[Vehicle], current_step):
|
||||
"""
|
||||
For each area retrieves the acquired emissions in the window,
|
||||
and acts according to the configuration chosen by the user
|
||||
:param p: The current process
|
||||
:param vehicles: The list of vehicles
|
||||
:param current_step: The simulation current step
|
||||
:return:
|
||||
"""
|
||||
for area in p.data.grid:
|
||||
total_emissions = Emission()
|
||||
for vehicle in vehicles:
|
||||
if vehicle.pos in area:
|
||||
total_emissions += vehicle.emissions
|
||||
|
||||
# Adding of the total of emissions pollutant at the current step into memory
|
||||
area.emissions_by_step.append(total_emissions)
|
||||
|
||||
# If the sum of pollutant emissions (in mg) exceeds the threshold
|
||||
if area.sum_emissions_into_window(current_step) >= p.config.emissions_threshold:
|
||||
|
||||
if p.config.limit_speed_mode and not area.limited_speed:
|
||||
p.logger.info(f'Action - Decreased max speed into {area.name} by {p.config.speed_rf * 100}%')
|
||||
actions.limit_speed_into_area(area, p.config.speed_rf)
|
||||
if p.config.adjust_traffic_light_mode and not area.tls_adjusted:
|
||||
p.logger.info(
|
||||
f'Action - Decreased traffic lights duration by {p.config.trafficLights_duration_rf * 100}%')
|
||||
actions.adjust_traffic_light_phase_duration(area, p.config.trafficLights_duration_rf)
|
||||
|
||||
if p.config.lock_area_mode and not area.locked:
|
||||
if actions.count_vehicles_in_area(area):
|
||||
p.logger.info(f'Action - {area.name} blocked')
|
||||
actions.lock_area(area)
|
||||
|
||||
if p.config.weight_routing_mode and not area.weight_adjusted:
|
||||
actions.adjust_edges_weights(area)
|
||||
|
||||
traci.polygon.setFilled(area.name, True)
|
||||
|
||||
else:
|
||||
if area.infrastructure_changed():
|
||||
p.logger.info(f'Action - Reversed actions into area {area.name}')
|
||||
actions.reverse_actions(area)
|
||||
traci.polygon.setFilled(area.name, False)
|
||||
|
||||
|
||||
def get_reduction_percentage(ref, total):
|
||||
"""
|
||||
Return the reduction percentage of total emissions between reference and an other simulation
|
||||
:param ref: The sum of all pollutant emissions (in mg) for the simulation of reference
|
||||
:param total: The sum of all pollutant emissions (in mg) for the current simulation launched
|
||||
:return:
|
||||
"""
|
||||
return (ref - total) / ref * 100
|
||||
|
BIN
sumo_project/files/imgs/simulation_example.PNG
Normal file
BIN
sumo_project/files/imgs/simulation_example.PNG
Normal file
Binary file not shown.
After Width: | Height: | Size: 489 KiB |
@ -0,0 +1 @@
|
||||
python "%SUMO_HOME%\tools\randomTrips.py" -n osm.net.xml --seed 42 --fringe-factor 5 -p 1.164091 -r osm.passenger.rou.xml -o osm.passenger.trips.xml -e 3600 --vehicle-class passenger --vclass passenger --prefix veh --min-distance 300 --trip-attributes "departLane=\"best\"" --validate
|
19764
sumo_project/files/simulations/mulhouse_simulation/osm.net.xml
Normal file
19764
sumo_project/files/simulations/mulhouse_simulation/osm.net.xml
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- generated on 10/11/18 20:15:27 by Eclipse SUMO netconvert Version 1.0.1
|
||||
-->
|
||||
|
||||
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/netconvertConfiguration.xsd">
|
||||
|
||||
<input>
|
||||
<type-files value="C:\Program%20Files%20(x86)\Eclipse\Sumo\data\typemap\osmNetconvert.typ.xml"/>
|
||||
<osm-files value="osm_bbox.osm.xml"/>
|
||||
</input>
|
||||
|
||||
<output>
|
||||
<output-file value="osm.net.xml"/>
|
||||
<output.street-names value="true"/>
|
||||
<output.original-names value="true"/>
|
||||
</output>
|
||||
|
||||
<processing>
|
||||
<geometry.remove value="true"/>
|
||||
<roundabouts.guess value="true"/>
|
||||
</processing>
|
||||
|
||||
<tls_building>
|
||||
<tls.discard-simple value="true"/>
|
||||
<tls.join value="true"/>
|
||||
<tls.guess-signals value="true"/>
|
||||
<tls.default-type value="actuated"/>
|
||||
</tls_building>
|
||||
|
||||
<ramp_guessing>
|
||||
<ramps.guess value="true"/>
|
||||
</ramp_guessing>
|
||||
|
||||
<junctions>
|
||||
<junctions.join value="true"/>
|
||||
<junctions.corner-detail value="5"/>
|
||||
</junctions>
|
||||
|
||||
<report>
|
||||
<verbose value="true"/>
|
||||
</report>
|
||||
|
||||
</configuration>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3892
sumo_project/files/simulations/mulhouse_simulation/osm.poly.xml
Normal file
3892
sumo_project/files/simulations/mulhouse_simulation/osm.poly.xml
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- generated on 10/11/18 20:15:31 by Eclipse SUMO polyconvert Version 1.0.1
|
||||
-->
|
||||
|
||||
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/polyconvertConfiguration.xsd">
|
||||
|
||||
<input>
|
||||
<net-file value="osm.net.xml"/>
|
||||
<osm-files value="osm_bbox.osm.xml"/>
|
||||
<osm.keep-full-type value="true"/>
|
||||
<type-file value="C:\Program%20Files%20(x86)\Eclipse\Sumo\data\typemap\osmPolyconvert.typ.xml"/>
|
||||
</input>
|
||||
|
||||
<output>
|
||||
<output-file value="osm.poly.xml"/>
|
||||
</output>
|
||||
|
||||
<report>
|
||||
<verbose value="true"/>
|
||||
</report>
|
||||
|
||||
</configuration>
|
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- generated on 10/11/18 20:15:33 by Eclipse SUMO Version 1.0.1
|
||||
-->
|
||||
|
||||
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/sumoConfiguration.xsd">
|
||||
|
||||
<input>
|
||||
<net-file value="osm.net.xml"/>
|
||||
<route-files value="osm.passenger.trips.xml"/>
|
||||
<additional-files value="osm.poly.xml"/>
|
||||
</input>
|
||||
|
||||
<processing>
|
||||
<ignore-route-errors value="true"/>
|
||||
</processing>
|
||||
|
||||
<routing>
|
||||
<device.rerouting.adaptation-steps value="180"/>
|
||||
</routing>
|
||||
|
||||
<report>
|
||||
<verbose value="true"/>
|
||||
<duration-log.statistics value="true"/>
|
||||
<no-step-log value="true"/>
|
||||
</report>
|
||||
|
||||
<gui_only>
|
||||
<gui-settings-file value="osm.view.xml"/>
|
||||
</gui_only>
|
||||
|
||||
</configuration>
|
@ -0,0 +1,5 @@
|
||||
|
||||
<viewsettings>
|
||||
<scheme name="real world"/>
|
||||
<delay value="20"/>
|
||||
</viewsettings>
|
94449
sumo_project/files/simulations/mulhouse_simulation/osm_bbox.osm.xml
Normal file
94449
sumo_project/files/simulations/mulhouse_simulation/osm_bbox.osm.xml
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
sumo-gui -c osm.sumocfg
|
278
sumo_project/model.py
Normal file
278
sumo_project/model.py
Normal file
@ -0,0 +1,278 @@
|
||||
"""
|
||||
This module defines the business model of our application
|
||||
"""
|
||||
|
||||
import collections
|
||||
from traci._trafficlight import Logic as SUMO_Logic
|
||||
from typing import Tuple, Set
|
||||
|
||||
from shapely.geometry import Point, LineString
|
||||
from shapely.geometry import Polygon
|
||||
from shapely.geometry.base import BaseGeometry
|
||||
|
||||
|
||||
class Lane:
|
||||
"""
|
||||
The Lane class includes the polygon defining the lane
|
||||
and keep in memory the initial maximum speed of the lane
|
||||
"""
|
||||
|
||||
def __init__(self, lane_id: str, polygon: LineString, initial_max_speed: float):
|
||||
"""
|
||||
Lane constructor
|
||||
|
||||
:param lane_id: The ID of the lane
|
||||
:param polygon: The polygon defining the shape of the lane
|
||||
:param initial_max_speed: The initial maximum speed
|
||||
"""
|
||||
self.polygon = polygon
|
||||
self.lane_id = lane_id
|
||||
self.initial_max_speed = initial_max_speed
|
||||
|
||||
def __hash__(self):
|
||||
"""Overrides the default implementation"""
|
||||
return hash(self.lane_id)
|
||||
|
||||
|
||||
class Phase:
|
||||
"""
|
||||
The Phase class defines a phase of a traffic light
|
||||
"""
|
||||
|
||||
def __init__(self, duration: float, minDuration: float, maxDuration: float, phaseDef: str):
|
||||
"""
|
||||
Phase constructor
|
||||
|
||||
:param duration: The duration of the phase (in seconds)
|
||||
:param minDuration: The minimum duration of the phase
|
||||
:param maxDuration: The maximum duration of the phase
|
||||
:param phaseDef: The definition of the phase, following the definition rules of SUMO
|
||||
(See : http://sumo.dlr.de/wiki/Simulation/Traffic_Lights#.3Cphase.3E_Attributes)
|
||||
"""
|
||||
|
||||
self.duration = duration
|
||||
self.minDuration = minDuration
|
||||
self.maxDuration = maxDuration
|
||||
self.phaseDef = phaseDef
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
:return: The Phase string representation
|
||||
"""
|
||||
repr = f'Phase(duration:{self.duration},minDuration:{self.minDuration},maxDuration:{self.maxDuration},phaseDef:{self.phaseDef})'
|
||||
return str(repr)
|
||||
|
||||
|
||||
class Logic:
|
||||
"""
|
||||
The Logic class defines the strategy of a traffic light.
|
||||
This class includes the Logic instance of SUMO with all phases corresponding to it.
|
||||
A Logic object contains multiple phases.
|
||||
"""
|
||||
|
||||
def __init__(self, logic: SUMO_Logic, phases: Set[Phase]):
|
||||
"""
|
||||
Logic constructor
|
||||
:param logic: The SUMO Logic object
|
||||
:param phases: The list of phases belonging to this logic
|
||||
"""
|
||||
self._logic = logic
|
||||
self._phases: Set[Phase] = phases
|
||||
|
||||
|
||||
class TrafficLight:
|
||||
"""
|
||||
This TrafficLight class defines a traffic light
|
||||
"""
|
||||
|
||||
def __init__(self, tl_id: str, logics: Set[Logic]):
|
||||
"""
|
||||
TrafficLight constructor
|
||||
:param tl_id: The traffic light ID
|
||||
:param logics: The list of logics belonging to the traffic light
|
||||
"""
|
||||
self.tl_id = tl_id
|
||||
self._logics: Set[Logic] = logics
|
||||
|
||||
def __hash__(self):
|
||||
"""Overrides the default implementation"""
|
||||
return hash(self.tl_id)
|
||||
|
||||
|
||||
class Emission:
|
||||
"""
|
||||
This class defines the different pollutant emissions
|
||||
"""
|
||||
|
||||
def __init__(self, co2=0, co=0, nox=0, hc=0, pmx=0):
|
||||
"""
|
||||
Emission constructor
|
||||
:param co2: Quantity of CO2(in mg)
|
||||
:param co: Quantity of C0(in mg)
|
||||
:param nox: Quantity of Nox(in mg)
|
||||
:param hc: Quantity of HC(in mg)
|
||||
:param pmx: Quantity of PMx(in mg)
|
||||
"""
|
||||
self.co2 = co2
|
||||
self.co = co
|
||||
self.nox = nox
|
||||
self.hc = hc
|
||||
self.pmx = pmx
|
||||
|
||||
def __add__(self, other):
|
||||
"""
|
||||
Add two emission objects
|
||||
:param other: The other Emission object to add
|
||||
:return: A new object whose emission values are the sum of both Emission object
|
||||
"""
|
||||
return Emission(self.co2 + other.co2, self.co + other.co, self.nox + other.nox, self.hc + other.hc,
|
||||
self.pmx + other.pmx)
|
||||
|
||||
def value(self):
|
||||
"""
|
||||
:return: The sum of all emissions
|
||||
"""
|
||||
return self.co2 + self.co + self.nox + self.hc + self.pmx
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
:return: The Emission string representation
|
||||
"""
|
||||
repr = f'Emission(co2={self.co2},co={self.co},nox={self.nox},hc={self.hc},pmx={self.pmx})'
|
||||
return str(repr)
|
||||
|
||||
|
||||
class Area:
|
||||
"""
|
||||
The Area class defines a grid area of the simulation map
|
||||
"""
|
||||
|
||||
def __init__(self, coords, name):
|
||||
"""
|
||||
Area constructor
|
||||
:param coords: The coordinates of the zone,
|
||||
defined by the bounds coordinates of this area : (xmin, ymin, xmax, ymax)
|
||||
:param name: The Area name
|
||||
"""
|
||||
self.limited_speed = False
|
||||
self.locked = False
|
||||
self.tls_adjusted = False
|
||||
self.weight_adjusted = False
|
||||
self.rectangle = Polygon(coords)
|
||||
self.name = name
|
||||
self.emissions_by_step = []
|
||||
self._lanes: Set[Lane] = set()
|
||||
self._tls: Set[TrafficLight] = set()
|
||||
|
||||
def set_window_size(self, window_size):
|
||||
self.window = collections.deque(maxlen=window_size)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Overrides the equal definition
|
||||
:param other: The other Area object
|
||||
:return: True if the two rectangles are equals
|
||||
"""
|
||||
return self.rectangle.__eq__(other)
|
||||
|
||||
def __contains__(self, item):
|
||||
"""
|
||||
:param item: A position on the map
|
||||
:return: True if the area contains the item
|
||||
"""
|
||||
return self.rectangle.contains(item)
|
||||
|
||||
@property
|
||||
def bounds(self):
|
||||
"""
|
||||
Return the bounds rectangle of this area
|
||||
:return:
|
||||
"""
|
||||
return self.rectangle.bounds
|
||||
|
||||
def intersects(self, other: BaseGeometry) -> bool:
|
||||
"""
|
||||
:param other: A BaseGeometry object
|
||||
:return: True if this area intersects with other
|
||||
"""
|
||||
return self.rectangle.intersects(other)
|
||||
|
||||
def add_lane(self, lane: Lane):
|
||||
"""
|
||||
Add a new lane object into lanes list
|
||||
:param lane: A Lane object
|
||||
:return:
|
||||
"""
|
||||
self._lanes.add(lane)
|
||||
|
||||
def add_tl(self, tl: TrafficLight):
|
||||
"""
|
||||
Add a new trafficLight object into lanes list
|
||||
:param tl: A TrafficLight object
|
||||
:return:
|
||||
"""
|
||||
self._tls.add(tl)
|
||||
|
||||
def remove_lane(self, lane: Lane):
|
||||
"""
|
||||
Remove a lane from lanes list
|
||||
:param lane: The Lane object to remove
|
||||
:return:
|
||||
"""
|
||||
self._lanes.remove(lane)
|
||||
|
||||
def sum_all_emissions(self):
|
||||
"""
|
||||
Sum all Emissions object from initial step to final step
|
||||
:return: The sum Emission object
|
||||
"""
|
||||
sum = Emission()
|
||||
for emission in self.emissions_by_step:
|
||||
sum += emission
|
||||
return sum
|
||||
|
||||
def sum_emissions_into_window(self, current_step):
|
||||
"""
|
||||
Sum all Emissions object into the acquisition window
|
||||
:param current_step: The current step of the simulation
|
||||
:return:
|
||||
"""
|
||||
self.window.appendleft(self.emissions_by_step[current_step].value())
|
||||
|
||||
sum = 0
|
||||
for i in range(self.window.__len__()):
|
||||
sum += self.window[i]
|
||||
return sum
|
||||
|
||||
@classmethod
|
||||
def from_bounds(cls, xmin, ymin, xmax, ymax):
|
||||
return cls((
|
||||
(xmin, ymin),
|
||||
(xmin, ymax),
|
||||
(xmax, ymax),
|
||||
(xmax, ymin)))
|
||||
|
||||
def infrastructure_changed(self):
|
||||
return (self.limited_speed or self.locked or self.tls_adjusted or self.weight_adjusted)
|
||||
|
||||
|
||||
class Vehicle:
|
||||
"""
|
||||
The Vehicle class defines a vehicle object
|
||||
"""
|
||||
|
||||
def __init__(self, veh_id: int, pos: Tuple[float, float]):
|
||||
"""
|
||||
Vehicle constructor
|
||||
:param veh_id: The vehicle ID
|
||||
:param pos: The position of the vehicle one the map
|
||||
"""
|
||||
self.emissions: Emission = Emission()
|
||||
self.veh_id = veh_id
|
||||
self.pos = Point(pos)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""
|
||||
:return: The Vehicle string representation
|
||||
"""
|
||||
return str(self.__dict__)
|
276
sumo_project/runner.py
Normal file
276
sumo_project/runner.py
Normal file
@ -0,0 +1,276 @@
|
||||
"""
|
||||
This module defines the entry point of the application
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import datetime
|
||||
import itertools
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import traci
|
||||
|
||||
import jsonpickle
|
||||
|
||||
from config import Config
|
||||
from data import Data
|
||||
import emissions
|
||||
from model import Emission
|
||||
|
||||
|
||||
"""
|
||||
Init the Traci API
|
||||
"""
|
||||
if 'SUMO_HOME' in os.environ:
|
||||
tools = os.path.join(os.environ['SUMO_HOME'], 'tools')
|
||||
sys.path.append(tools)
|
||||
else:
|
||||
sys.exit("please declare environment variable 'SUMO_HOME'")
|
||||
|
||||
class RunProcess(multiprocessing.Process):
|
||||
"""
|
||||
Run process inheriting from multiprocessing.Process
|
||||
"""
|
||||
|
||||
def __init__(self, data: Data, config: Config, save_logs: bool, csv_export: bool):
|
||||
"""
|
||||
RunProcess constructor
|
||||
:param data: The data instance
|
||||
:param config: The config instance
|
||||
:param save_logs: If save_logs == True, it will save the logs into the logs directory
|
||||
:param csv_export: If csv_export == True, it will export all emissions data into a csv file
|
||||
"""
|
||||
multiprocessing.Process.__init__(self)
|
||||
self.data = data
|
||||
self.config = config
|
||||
self.save_logs = save_logs
|
||||
self.csv_export = csv_export
|
||||
|
||||
def init_logger(self):
|
||||
"""
|
||||
Init logger properties
|
||||
"""
|
||||
now = datetime.datetime.now()
|
||||
current_date = now.strftime("%Y_%m_%d_%H_%M_%S")
|
||||
|
||||
logdir = f'{self.data.dir}/logs/'
|
||||
logging.info(logdir)
|
||||
if not os.path.exists(logdir):
|
||||
os.mkdir(logdir)
|
||||
|
||||
conf_name = self.config.config_filename.replace('.json', '')
|
||||
log_filename = f'{logdir}/{current_date}.log'
|
||||
|
||||
self.logger = logging.getLogger(f'sumo_logger')
|
||||
self.logger.setLevel(logging.INFO)
|
||||
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
|
||||
if self.save_logs:
|
||||
file_handler = logging.FileHandler(log_filename)
|
||||
file_handler.setFormatter(formatter)
|
||||
self.logger.addHandler(file_handler)
|
||||
|
||||
handler = logging.StreamHandler()
|
||||
handler.setFormatter(formatter)
|
||||
self.logger.addHandler(handler)
|
||||
|
||||
def export_data_to_csv(self):
|
||||
"""
|
||||
Export all Emission objects as a CSV file into the csv directory
|
||||
"""
|
||||
csv_dir = f'{self.data.dir}/csv'
|
||||
if not os.path.exists(csv_dir):
|
||||
os.mkdir(csv_dir)
|
||||
|
||||
now = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S")
|
||||
conf_name = self.config.config_filename.replace('.json', '')
|
||||
|
||||
csvfile = os.path.join(csv_dir, f'{self.data.dump_name}_{conf_name}_{now}.csv')
|
||||
with open(csvfile, 'w') as f:
|
||||
writer = csv.writer(f)
|
||||
# Write CSV headers
|
||||
writer.writerow(itertools.chain(('Step',), (a.name for a in self.data.grid)))
|
||||
# Write all areas emission value for each step
|
||||
for step in range(self.config.n_steps):
|
||||
em_for_step = (f'{a.emissions_by_step[step].value():.3f}' for a in self.data.grid)
|
||||
writer.writerow(itertools.chain((step,), em_for_step))
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Launch a simulation, will be called when a RunProcess instance is started
|
||||
"""
|
||||
try:
|
||||
self.init_logger()
|
||||
self.logger.info(f'Running simulation dump "{self.data.dump_name}" with the config "{self.config.config_filename}" ...')
|
||||
|
||||
if self.config.without_actions_mode:
|
||||
self.logger.info('Reference simulation')
|
||||
|
||||
traci.start(self.config.sumo_cmd)
|
||||
|
||||
for area in self.data.grid: # Set acquisition window size
|
||||
area.set_window_size(self.config.window_size)
|
||||
traci.polygon.add(area.name, area.rectangle.exterior.coords, (255, 0, 0)) # Add polygon for UI
|
||||
|
||||
self.logger.info(f'Loaded simulation file : {self.config._SUMOCFG}')
|
||||
self.logger.info('Loading data for the simulation')
|
||||
|
||||
start = time.perf_counter()
|
||||
self.logger.info('Simulation started...')
|
||||
step = 0
|
||||
while step < self.config.n_steps:
|
||||
traci.simulationStep()
|
||||
|
||||
vehicles = emissions.get_all_vehicles()
|
||||
emissions.get_emissions(self, vehicles, step)
|
||||
step += 1
|
||||
|
||||
print(f'step = {step}/{self.config.n_steps}', end='\r')
|
||||
|
||||
finally:
|
||||
traci.close(False)
|
||||
|
||||
total_emissions = Emission()
|
||||
for area in self.data.grid:
|
||||
total_emissions += area.sum_all_emissions()
|
||||
|
||||
self.logger.info(f'Total emissions = {total_emissions.value()} mg')
|
||||
for pollutant in ['co2','co','nox','hc','pmx']:
|
||||
value = total_emissions.__getattribute__(pollutant)
|
||||
self.logger.info(f'{pollutant.upper()} = {value} mg')
|
||||
|
||||
simulation_time = round(time.perf_counter() - start, 2)
|
||||
self.logger.info(f'End of the simulation ({simulation_time}s)')
|
||||
|
||||
# 1 step is equal to one second simulated
|
||||
self.logger.info(f'Real-time factor : {self.config.n_steps / simulation_time}')
|
||||
|
||||
if self.csv_export:
|
||||
self.export_data_to_csv()
|
||||
self.logger.info(f'Exported data into the csv folder')
|
||||
|
||||
def create_dump(dump_name, simulation_dir, areas_number):
|
||||
"""
|
||||
Create a new dump with config file and dump_name chosen
|
||||
:param dump_name: The name of the data dump
|
||||
:param simulation_dir: The simulation directory
|
||||
:param areas_number: The number of areas in grid
|
||||
:return:
|
||||
"""
|
||||
|
||||
#sumo_binary = os.path.join(os.environ['SUMO_HOME'], 'bin', 'sumo')
|
||||
#sumo_cmd = [sumo_binary, "-c", f'files/simulations/{simulation_dir}/osm.sumocfg']
|
||||
|
||||
for f in os.listdir(simulation_dir):
|
||||
if f.endswith('.sumocfg'):
|
||||
_SUMOCFG = os.path.join(simulation_dir, f)
|
||||
|
||||
sumo_binary = os.path.join(os.environ['SUMO_HOME'], 'bin', 'sumo')
|
||||
sumo_cmd = [sumo_binary, "-c", _SUMOCFG]
|
||||
|
||||
|
||||
traci.start(sumo_cmd)
|
||||
if not os.path.isfile(f'{simulation_dir}/dump/{dump_name}.json'):
|
||||
start = time.perf_counter()
|
||||
data = Data(dump_name, traci.simulation.getNetBoundary(), areas_number, simulation_dir)
|
||||
data.init_grid()
|
||||
data.add_data_to_areas()
|
||||
data.save()
|
||||
|
||||
loading_time = round(time.perf_counter() - start, 2)
|
||||
print(f'Data loaded ({loading_time}s)')
|
||||
print(f'Dump {dump_name} created')
|
||||
else:
|
||||
print(f'Dump with name {dump_name} already exists')
|
||||
|
||||
traci.close(False)
|
||||
|
||||
def add_options(parser):
|
||||
"""
|
||||
Add command line options
|
||||
:param parser: The command line parser
|
||||
:return:
|
||||
"""
|
||||
|
||||
parser.add_argument("-new_dump", "--new_dump", type=str,
|
||||
help='Load and create a new data dump with the configuration file chosen')
|
||||
parser.add_argument("-areas", "--areas", type=int,
|
||||
help='Will create a grid with "areas x areas" areas')
|
||||
parser.add_argument("-simulation_dir", "--simulation_dir", type=str,
|
||||
help='Choose the simulation directory')
|
||||
|
||||
parser.add_argument("-run", "--run", type=str,
|
||||
help='Run a simulation process with the dump chosen')
|
||||
parser.add_argument("-c", "--c", metavar =('config1','config2'), nargs='+', type=str,
|
||||
help='Choose your(s) configuration file(s) from your working directory')
|
||||
parser.add_argument("-c_dir", "--c_dir", type=str,
|
||||
help='Choose a directory which contains your(s) configuration file(s)')
|
||||
parser.add_argument("-save", "--save", action="store_true",
|
||||
help='Save the logs into the logs folder')
|
||||
parser.add_argument("-csv", "--csv", action="store_true",
|
||||
help="Export all data emissions into a CSV file")
|
||||
|
||||
def check_user_entry(args):
|
||||
"""
|
||||
Check the user entry consistency
|
||||
"""
|
||||
if (args.new_dump is not None):
|
||||
if(args.areas is None or args.simulation_dir is None):
|
||||
print('The -new_dump argument requires the -areas and -simulation_dir options')
|
||||
return False
|
||||
|
||||
if (args.run is not None):
|
||||
if(args.c is None and args.c_dir is None):
|
||||
print('The -run argument requires the -c or -c_dir')
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def main(args):
|
||||
"""
|
||||
The entry point of the application
|
||||
:param args: Command line options
|
||||
:return:
|
||||
"""
|
||||
parser = argparse.ArgumentParser()
|
||||
add_options(parser)
|
||||
args = parser.parse_args(args)
|
||||
|
||||
if(check_user_entry(args)):
|
||||
|
||||
if args.new_dump is not None:
|
||||
if (args.simulation_dir is not None) and (args.areas is not None):
|
||||
create_dump(args.new_dump, args.simulation_dir, args.areas)
|
||||
|
||||
if args.run is not None:
|
||||
dump_path = f'{args.run}'
|
||||
if os.path.isfile(dump_path):
|
||||
with open(dump_path, 'r') as f:
|
||||
data = jsonpickle.decode(f.read())
|
||||
|
||||
process = []
|
||||
files = []
|
||||
|
||||
if args.c is not None:
|
||||
for config in args.c:
|
||||
files.append(f'{config}')
|
||||
|
||||
if args.c_dir is not None:
|
||||
path = f'{args.c_dir}'
|
||||
bundle_files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
|
||||
for config in bundle_files:
|
||||
files.append(os.path.join(path, config))
|
||||
|
||||
for conf in files: # Initialize all process
|
||||
config = Config(conf,data)
|
||||
p = RunProcess(data, config, args.save, args.csv)
|
||||
process.append(p)
|
||||
p.start()
|
||||
|
||||
for p in process : p.join()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
59
sumo_project/templates/simul.netcfg
Normal file
59
sumo_project/templates/simul.netcfg
Normal file
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/netconvertConfiguration.xsd">
|
||||
|
||||
<input>
|
||||
<type-files value="typemap/osmNetconvert.typ.xml,typemap/osmNetconvertUrbanDe.typ.xml,typemap/osmNetconvertPedestrians.typ.xml,typemap/osmNetconvertBicycle.typ.xml,typemap/osmBidiRailNetconvert.typ.xml" />
|
||||
<osm-files value="simul.raw.osm" />
|
||||
</input>
|
||||
|
||||
<output>
|
||||
<!--<output-prefix value="simul." />-->
|
||||
<output-file value="simul.net.xml"/>
|
||||
</output>
|
||||
|
||||
<tls_building>
|
||||
<tls.join value="true"/>
|
||||
<tls.guess-signals value="true"/>
|
||||
<tls.discard-simple value="true" />
|
||||
<tls.default-type value="actuated"/>
|
||||
<tls.min-dur value="5"/>
|
||||
<tls.max-dur value="30"/>
|
||||
</tls_building>
|
||||
|
||||
<ramp_guessing>
|
||||
<ramps.guess value="true"/>
|
||||
</ramp_guessing>
|
||||
|
||||
<junctions>
|
||||
<junctions.join value="true"/>
|
||||
</junctions>
|
||||
|
||||
<processing>
|
||||
<geometry.remove value="true"/>
|
||||
<roundabouts.guess value="true"/>
|
||||
|
||||
<!--
|
||||
<junctions.limit-turn-speed value="1"/>
|
||||
<crossings.guess value="true"/>
|
||||
<walkingareas value="false"/>
|
||||
<osm.elevation value="true"/>
|
||||
<no-turnarounds value="false"/>
|
||||
<no-turnarounds.tls value="true"/>
|
||||
<no-turnarounds.except-deadend value="true"/>
|
||||
<no-internal-links value="false"/>
|
||||
<ignore-errors value="false"/>
|
||||
<ignore-errors.connections value="false"/>
|
||||
<show-errors.connections-first-try value="true"/>-->
|
||||
</processing>
|
||||
|
||||
<building_defaults>
|
||||
<default.crossing-width value="2.0"/>
|
||||
</building_defaults>
|
||||
|
||||
<report>
|
||||
<verbose value="true"/>
|
||||
<log value="netconvert.log"/>
|
||||
</report>
|
||||
|
||||
</configuration>
|
24
sumo_project/templates/simul.polycfg
Normal file
24
sumo_project/templates/simul.polycfg
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/polyconvertConfiguration.xsd">
|
||||
|
||||
<input>
|
||||
<net-file value="simul.net.xml"/>
|
||||
<osm-files value="simul.raw.osm"/>
|
||||
<type-file value="typemap/osmPolyconvert.typ.xml"/>
|
||||
</input>
|
||||
|
||||
<output>
|
||||
<output-file value="simul.poly.xml"/>
|
||||
</output>
|
||||
|
||||
<processing>
|
||||
<ignore-errors value="true"/>
|
||||
</processing>
|
||||
|
||||
<report>
|
||||
<verbose value="true"/>
|
||||
<log value="polyconvert.log"/>
|
||||
</report>
|
||||
|
||||
</configuration>
|
30
sumo_project/templates/simul.sumocfg
Normal file
30
sumo_project/templates/simul.sumocfg
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/sumoConfiguration.xsd">
|
||||
|
||||
<input>
|
||||
<net-file value="simul.net.xml"/>
|
||||
<route-files value="simul.rou.xml"/>
|
||||
<additional-files value="simul.poly.xml"/>
|
||||
</input>
|
||||
|
||||
<processing>
|
||||
<lateral-resolution value="0.3"/>
|
||||
<ignore-junction-blocker value="60"/>
|
||||
<collision.action value="teleport"/>
|
||||
<time-to-teleport value="180"/>
|
||||
<max-depart-delay value="900"/>
|
||||
<time-to-impatience value="30"/>
|
||||
</processing>
|
||||
|
||||
<report>
|
||||
<verbose value="true" synonymes="v"/>
|
||||
<log value="sim.log" synonymes="l log-file"/>
|
||||
<duration-log.statistics value="true"/>
|
||||
</report>
|
||||
|
||||
<random_number>
|
||||
<seed value="1"/>
|
||||
</random_number>
|
||||
|
||||
</configuration>
|
@ -0,0 +1,7 @@
|
||||
<types xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/types_file.xsd">
|
||||
<type id="railway.rail" oneway="false"/>
|
||||
<type id="railway.tram" oneway="false"/>
|
||||
<type id="railway.light_rail" oneway="false"/>
|
||||
<type id="railway.subway" oneway="false"/>
|
||||
<type id="railway.preserved" oneway="false"/>
|
||||
</types>
|
52
sumo_project/templates/typemap/osmNetconvert.typ.xml
Normal file
52
sumo_project/templates/typemap/osmNetconvert.typ.xml
Normal file
@ -0,0 +1,52 @@
|
||||
<types xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/types_file.xsd">
|
||||
<type id="highway.motorway" numLanes="3" speed="44.44" priority="13" oneway="true" disallow="pedestrian bicycle moped rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.motorway_bridge" numLanes="3" speed="44.44" priority="13" oneway="true" disallow="pedestrian bicycle moped rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.motorway_tunnel" numLanes="3" speed="44.44" priority="13" oneway="true" disallow="pedestrian bicycle moped rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.motorway_link" numLanes="1" speed="22.22" priority="12" oneway="true" disallow="pedestrian bicycle moped rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.trunk" numLanes="2" speed="27.78" priority="11" oneway="false" disallow="pedestrian bicycle rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.trunk_bridge" numLanes="2" speed="27.78" priority="11" oneway="false" disallow="pedestrian bicycle rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.trunk_tunnel" numLanes="2" speed="27.78" priority="11" oneway="false" disallow="pedestrian bicycle rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.trunk_link" numLanes="1" speed="22.22" priority="10" oneway="false" disallow="pedestrian bicycle rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.primary" numLanes="2" speed="27.78" priority="9" oneway="false" disallow="pedestrian bicycle rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.primary_bridge" numLanes="2" speed="27.78" priority="9" oneway="false" disallow="pedestrian bicycle rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.primary_tunnel" numLanes="2" speed="27.78" priority="9" oneway="false" disallow="pedestrian bicycle rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.primary_link" numLanes="1" speed="22.22" priority="8" oneway="false" disallow="pedestrian bicycle rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.secondary" numLanes="2" speed="27.78" priority="7" oneway="false" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.secondary_bridge" numLanes="2" speed="27.78" priority="7" oneway="false" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.secondary_tunnel" numLanes="2" speed="27.78" priority="7" oneway="false" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.secondary_link" numLanes="1" speed="22.22" priority="6" oneway="false" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.tertiary" numLanes="1" speed="22.22" priority="6" oneway="false" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.tertiary_bridge" numLanes="1" speed="22.22" priority="6" oneway="false" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.tertiary_tunnel" numLanes="1" speed="22.22" priority="6" oneway="false" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.tertiary_link" numLanes="1" speed="22.22" priority="5" oneway="false" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.unclassified" numLanes="1" speed="13.89" priority="5" oneway="false" disallow="rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.residential" numLanes="1" speed="13.89" priority="4" oneway="false" disallow="rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.residential_bridge" numLanes="1" speed="13.89" priority="4" oneway="false" disallow="rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.residential_tunnel" numLanes="1" speed="13.89" priority="4" oneway="false" disallow="rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.living_street" numLanes="1" speed="2.78" priority="3" oneway="false" disallow="rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.track" numLanes="1" speed="5.56" priority="1" oneway="false" disallow="rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.unsurfaced" numLanes="1" speed="8.33" priority="1" oneway="false" disallow="rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.services" numLanes="1" speed="8.33" priority="1" oneway="false" disallow="rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.service" numLanes="1" speed="5.56" priority="1" oneway="false" disallow="rail rail_urban rail_electric tram ship"/>
|
||||
|
||||
<!-- everything which serves mainly pedestrians is oneway because all current pedestrian models do not care about direction -->
|
||||
<type id="highway.footway" numLanes="1" speed="2.78" priority="1" oneway="true" width="2" allow="pedestrian"/>
|
||||
<type id="highway.pedestrian" numLanes="1" speed="2.78" priority="1" oneway="true" width="2" allow="pedestrian bicycle"/>
|
||||
<type id="highway.path" numLanes="1" speed="2.78" priority="1" oneway="true" width="2" allow="pedestrian bicycle"/>
|
||||
<type id="highway.bridleway" numLanes="1" speed="2.78" priority="1" oneway="true" width="2" allow="pedestrian bicycle"/>
|
||||
<type id="highway.cycleway" numLanes="1" speed="5.56" priority="1" oneway="false" width="2" allow="bicycle"/>
|
||||
<type id="highway.step" numLanes="1" speed="1.39" priority="1" oneway="true" width="2" allow="pedestrian"/>
|
||||
<type id="highway.steps" numLanes="1" speed="1.39" priority="1" oneway="true" width="2" allow="pedestrian"/>
|
||||
<type id="highway.stairs" numLanes="1" speed="1.39" priority="1" oneway="true" width="2" allow="pedestrian"/>
|
||||
|
||||
<type id="highway.bus_guideway" numLanes="1" speed="8.33" priority="1" oneway="true" allow="bus"/>
|
||||
<type id="highway.raceway" numLanes="2" speed="83.33" priority="14" oneway="false" allow="vip"/>
|
||||
<type id="highway.ford" numLanes="1" speed="2.78" priority="1" oneway="false" allow="army"/>
|
||||
|
||||
<type id="railway.rail" numLanes="1" speed="83.33" priority="15" oneway="true" allow="rail rail_electric"/>
|
||||
<type id="railway.tram" numLanes="1" speed="13.89" priority="15" oneway="true" allow="tram"/>
|
||||
<type id="railway.light_rail" numLanes="1" speed="27.78" priority="15" oneway="true" allow="rail_urban"/>
|
||||
<type id="railway.subway" numLanes="1" speed="27.78" priority="15" oneway="true" allow="rail_urban"/>
|
||||
<type id="railway.preserved" numLanes="1" speed="27.78" priority="15" oneway="true" allow="rail"/>
|
||||
|
||||
</types>
|
@ -0,0 +1,6 @@
|
||||
<types xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/types_file.xsd">
|
||||
<type id="cycleway.lane" bikeLaneWidth="1.0" allow="bicycle"/>
|
||||
<type id="cycleway.opposite_lane" bikeLaneWidth="1.0" allow="bicycle"/>
|
||||
<type id="cycleway.track" bikeLaneWidth="1.5" allow="bicycle"/>
|
||||
<type id="cycleway.opposite_track" bikeLaneWidth="1.5" allow="bicycle"/>
|
||||
</types>
|
@ -0,0 +1,18 @@
|
||||
<types xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/types_file.xsd">
|
||||
<type id="highway.primary" sidewalkWidth="2" disallow="pedestrian bicycle rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.primary_link" sidewalkWidth="2" disallow="pedestrian bicycle rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.primary_bridge" sidewalkWidth="2" disallow="pedestrian bicycle rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.primary_tunnel" disallow="pedestrian bicycle rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.secondary" sidewalkWidth="1.5" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.secondary_link" sidewalkWidth="1.5" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.secondary_bridge" sidewalkWidth="1.5" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.secondary_tunnel" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.tertiary" sidewalkWidth="1.5" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.tertiary_link" sidewalkWidth="1.5" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.tertiary_bridge" sidewalkWidth="1.5" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.tertiary_tunnel" disallow="pedestrian rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.unclassified" sidewalkWidth="1.5" disallow="rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.residential" sidewalkWidth="1.5" disallow="rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.residential_bridge" sidewalkWidth="1.5" disallow="rail rail_urban rail_electric tram ship"/>
|
||||
<type id="highway.residential_tunnel" sidewalkWidth="1.5" disallow="rail rail_urban rail_electric tram ship"/>
|
||||
</types>
|
@ -0,0 +1,5 @@
|
||||
<types xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/types_file.xsd">
|
||||
<type id="waterway.river" width="7" numLanes="1" speed="10" priority="13" oneway="false" allow="ship"/>
|
||||
<type id="waterway.canal" width="5" numLanes="1" speed="10" priority="13" oneway="false" allow="ship"/>
|
||||
<type id="route.ferry" width="5" numLanes="1" speed="10" priority="13" oneway="false" allow="ship"/>
|
||||
</types>
|
12
sumo_project/templates/typemap/osmNetconvertUrbanDe.typ.xml
Normal file
12
sumo_project/templates/typemap/osmNetconvertUrbanDe.typ.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<types xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/types_file.xsd">
|
||||
<type id="highway.motorway" speed="27.78"/>
|
||||
<type id="highway.motorway_link" speed="16.67"/>
|
||||
<type id="highway.trunk" speed="13.89"/>
|
||||
<type id="highway.trunk_link" speed="13.89"/>
|
||||
<type id="highway.primary" speed="13.89"/>
|
||||
<type id="highway.primary_link" speed="13.89"/>
|
||||
<type id="highway.secondary" speed="13.89"/>
|
||||
<type id="highway.secondary_link" speed="13.89"/>
|
||||
<type id="highway.tertiary" speed="13.89"/>
|
||||
<type id="highway.tertiary_link" speed="13.89"/>
|
||||
</types>
|
@ -0,0 +1,3 @@
|
||||
<polygonTypes>
|
||||
<polygonType id="boundary" name="boundary" color="1,0,0" layer="100" fill="false"/>
|
||||
</polygonTypes>
|
40
sumo_project/templates/typemap/osmPolyconvert.typ.xml
Normal file
40
sumo_project/templates/typemap/osmPolyconvert.typ.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<polygonTypes>
|
||||
<polygonType id="waterway" name="water" color=".71,.82,.82" layer="4" discard="true"/>
|
||||
<polygonType id="natural" name="natural" color=".55,.77,.42" layer="4" discard="true"/>
|
||||
<polygonType id="natural.water" name="water" color=".71,.82,.82" layer="4" discard="true"/>
|
||||
<polygonType id="natural.wetland" name="water" color=".71,.82,.82" layer="4" discard="true"/>
|
||||
<polygonType id="natural.wood" name="forest" color=".55,.77,.42" layer="4" discard="true"/>
|
||||
<polygonType id="natural.land" name="land" color=".98,.87,.46" layer="4" discard="true"/>
|
||||
|
||||
<polygonType id="landuse" name="landuse" color=".76,.76,.51" layer="3"/>
|
||||
<polygonType id="landuse.forest" name="forest" color=".55,.77,.42" layer="3"/>
|
||||
<polygonType id="landuse.park" name="park" color=".81,.96,.79" layer="3"/>
|
||||
<polygonType id="landuse.residential" name="residential" color=".92,.92,.89" layer="3"/>
|
||||
<polygonType id="landuse.commercial" name="commercial" color=".82,.82,.80" layer="3"/>
|
||||
<polygonType id="landuse.industrial" name="industrial" color=".82,.82,.80" layer="3"/>
|
||||
<polygonType id="landuse.military" name="military" color=".60,.60,.36" layer="3"/>
|
||||
<polygonType id="landuse.farm" name="farm" color=".95,.95,.80" layer="3"/>
|
||||
<polygonType id="landuse.greenfield" name="farm" color=".95,.95,.80" layer="3"/>
|
||||
<polygonType id="landuse.village_green" name="farm" color=".95,.95,.80" layer="3"/>
|
||||
|
||||
<polygonType id="tourism" name="tourism" color=".81,.96,.79" layer="2"/>
|
||||
<polygonType id="military" name="military" color=".60,.60,.36" layer="2"/>
|
||||
<polygonType id="sport" name="sport" color=".31,.90,.49" layer="2"/>
|
||||
<polygonType id="leisure" name="leisure" color=".81,.96,.79" layer="2"/>
|
||||
<polygonType id="leisure.park" name="tourism" color=".81,.96,.79" layer="2"/>
|
||||
<polygonType id="aeroway" name="aeroway" color=".50,.50,.50" layer="2"/>
|
||||
<polygonType id="aerialway" name="aerialway" color=".20,.20,.20" layer="2"/>
|
||||
|
||||
<polygonType id="shop" name="shop" color=".93,.78,1.0" layer="1"/>
|
||||
<polygonType id="historic" name="historic" color=".50,1.0,.50" layer="1"/>
|
||||
<polygonType id="man_made" name="building" color="1.0,.90,.90" layer="1"/>
|
||||
<polygonType id="building" name="building" color="1.0,.50,.50" layer="1"/>
|
||||
<polygonType id="amenity" name="amenity" color=".93,.78,.78" layer="1"/>
|
||||
<polygonType id="amenity.parking" name="parking" color=".72,.72,.70" layer="1"/>
|
||||
<polygonType id="power" name="power" color=".10,.10,.30" layer="1" discard="true"/>
|
||||
<polygonType id="highway" name="highway" color=".10,.10,.10" layer="1" discard="true"/>
|
||||
|
||||
<polygonType id="boundary" name="boundary" color="1.0,.10,.10" layer="0" fill="false"/>
|
||||
<polygonType id="admin_level" name="admin_level" color="1.0,.33,.33" layer="0" fill="false" discard="true"/>
|
||||
<polygonType id="place" name="admin_level" color="1.0,.33,.33" layer="0" fill="false" discard="true"/>
|
||||
</polygonTypes>
|
143
sumo_project/tests/configurator_tests.py
Normal file
143
sumo_project/tests/configurator_tests.py
Normal file
@ -0,0 +1,143 @@
|
||||
import io
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import configurator
|
||||
|
||||
# Absolute path of the directory the script is in
|
||||
SCRIPTDIR = os.path.dirname(__file__)
|
||||
|
||||
|
||||
class TemplateTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.sim_name = 'test_simulation'
|
||||
self.sim_path = '/test_simulation'
|
||||
self.log_path = '/test_simulation/log'
|
||||
|
||||
def test_load_netconvert_template(self):
|
||||
tree = configurator.load_netconvert_template('test.osm', 'test_simulation')
|
||||
self.assertEqual(tree.find('input/osm-files').get('value'), 'test.osm')
|
||||
self.assertEqual(tree.find('output/output-file').get('value'), f'{self.sim_name}.net.xml')
|
||||
self.assertEqual(tree.find('report/log').get('value'), f'{self.sim_name}.netconvert.log')
|
||||
|
||||
def test_load_sumoconfig_template_default(self):
|
||||
tree = configurator.load_sumoconfig_template(self.sim_name)
|
||||
self.assertEqual(tree.find('input/net-file').get('value'), f'{self.sim_name}.net.xml')
|
||||
self.assertEqual(tree.find('input/route-files').get('value'), f'{self.sim_name}.rou.xml')
|
||||
self.assertEqual(tree.find('report/log').get('value'), f'{self.sim_name}.log')
|
||||
|
||||
def test_load_sumoconfig_template_with_polygons(self):
|
||||
tree = configurator.load_sumoconfig_template(self.sim_name, generate_polygons=True)
|
||||
self.assertEqual(tree.find('input/net-file').get('value'), f'{self.sim_name}.net.xml')
|
||||
self.assertEqual(tree.find('input/route-files').get('value'), f'{self.sim_name}.rou.xml')
|
||||
self.assertEqual(tree.find('report/log').get('value'), f'{self.sim_name}.log')
|
||||
self.assertEqual(tree.find('input/additional-files').get('value'), f'{self.sim_name}.poly.xml')
|
||||
|
||||
def test_load_sumoconfig_template_with_routefiles(self):
|
||||
routefiles = (f'{self.sim_name}.bus.rou.xml', f'{self.sim_name}.passenger.rou.xml')
|
||||
tree = configurator.load_sumoconfig_template(self.sim_name, routefiles)
|
||||
self.assertEqual(tree.find('input/net-file').get('value'), f'{self.sim_name}.net.xml')
|
||||
self.assertEqual(tree.find('input/route-files').get('value'), ','.join(routefiles))
|
||||
self.assertEqual(tree.find('report/log').get('value'), f'{self.sim_name}.log')
|
||||
|
||||
def test_load_sumoconfig_template_with_seed(self):
|
||||
routefiles = (f'{self.sim_name}.bus.rou.xml', f'{self.sim_name}.passenger.rou.xml')
|
||||
tree = configurator.load_sumoconfig_template(self.sim_name, routefiles, seed=42)
|
||||
self.assertEqual(tree.find('random_number/seed').get('value'), 42)
|
||||
|
||||
def test_load_polyconvert_template(self):
|
||||
tree = configurator.load_polyconvert_template(
|
||||
osm_file=f'{self.sim_name}.osm',
|
||||
type_file='typemap/test.typ.xml',
|
||||
scenario_name=f'{self.sim_name}'
|
||||
)
|
||||
self.assertEqual(tree.find('input/osm-files').get('value'), f'{self.sim_name}.osm')
|
||||
self.assertEqual(tree.find('input/net-file').get('value'), f'{self.sim_name}.net.xml')
|
||||
self.assertEqual(tree.find('input/type-file').get('value'), 'typemap/test.typ.xml')
|
||||
self.assertEqual(tree.find('output/output-file').get('value'), f'{self.sim_name}.poly.xml')
|
||||
self.assertEqual(tree.find('report/log').get('value'), f'{self.sim_name}.polyconvert.log')
|
||||
|
||||
|
||||
class GenerationTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.base_path = tempfile.mkdtemp()
|
||||
self.sim_name = 'test_simulation'
|
||||
self.sim_path = os.path.join(self.base_path, self.sim_name)
|
||||
self.log_path = os.path.join(self.sim_name, 'log')
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.base_path)
|
||||
|
||||
def test_generate_scenario(self):
|
||||
osm_file = os.path.join(SCRIPTDIR, 'sample.osm')
|
||||
configurator.generate_scenario(osm_file, self.sim_path, self.sim_name, generate_polygons=False)
|
||||
self.assert_is_file(os.path.join(self.sim_path, f'{self.sim_name}.net.xml'))
|
||||
|
||||
def test_generate_scenario_with_polygons(self):
|
||||
osm_file = os.path.join(SCRIPTDIR, 'sample.osm')
|
||||
configurator.generate_scenario(osm_file, self.sim_path, self.sim_name, generate_polygons=True)
|
||||
self.assert_is_dir(self.sim_path)
|
||||
generated_files = [
|
||||
f'{self.sim_name}.poly.xml',
|
||||
f'{self.sim_name}.net.xml'
|
||||
]
|
||||
for f in generated_files:
|
||||
self.assert_is_file(os.path.join(self.sim_path, f))
|
||||
|
||||
def test_generate_mobility(self):
|
||||
# The scenario must be generated before the mobility
|
||||
osm_file = os.path.join(SCRIPTDIR, 'sample.osm')
|
||||
trips_file = os.path.join(self.sim_path, f'{self.sim_name}.trips.xml')
|
||||
configurator.generate_scenario(osm_file, self.sim_path, self.sim_name)
|
||||
classes = {'passenger': 10, 'truck': 1}
|
||||
routefiles = configurator.generate_mobility(self.sim_path, self.sim_name, vclasses=classes, end_time=200)
|
||||
|
||||
self.assert_is_file(trips_file)
|
||||
for f in routefiles:
|
||||
self.assert_is_file(os.path.join(self.sim_path, f))
|
||||
|
||||
def assert_exists(self, path):
|
||||
self.assertTrue(os.path.exists(path), msg=f'{path} does not exist')
|
||||
|
||||
def assert_is_file(self, path):
|
||||
self.assert_exists(path)
|
||||
self.assertTrue(os.path.isfile(path), msg=f'{path} is not a file')
|
||||
|
||||
def assert_is_dir(self, path):
|
||||
self.assert_exists(path)
|
||||
self.assertTrue(os.path.isdir(path), msg=f'{path} is not a directory')
|
||||
|
||||
|
||||
class InputTests(unittest.TestCase):
|
||||
def test_commandline(self):
|
||||
options = ['--name', 'test-config', '--path', '/some/path', '--vclass', 'passenger=10', 'truck=1', '--', 'test.osm']
|
||||
actual_conf = configurator.parse_command_line(options)
|
||||
self.assertEqual(actual_conf.name, 'test-config')
|
||||
self.assertEqual(actual_conf.osmfile, 'test.osm')
|
||||
self.assertEqual(actual_conf.path, '/some/path')
|
||||
self.assertEqual(actual_conf.vclasses, {'passenger': '10', 'truck': '1'})
|
||||
|
||||
def test_from_config_file(self):
|
||||
options = """
|
||||
{
|
||||
"name": "test-config",
|
||||
"path": "/some/path",
|
||||
"vclasses": {
|
||||
"passenger": 10,
|
||||
"truck": 1
|
||||
},
|
||||
"osmfile": "test.osm"
|
||||
}
|
||||
"""
|
||||
actual_conf = configurator.parse_json(io.StringIO(options))
|
||||
self.assertEqual(actual_conf.name, 'test-config')
|
||||
self.assertEqual(actual_conf.osmfile, 'test.osm')
|
||||
self.assertEqual(actual_conf.path, '/some/path')
|
||||
self.assertEqual(actual_conf.vclasses, {'passenger': 10, 'truck': 1})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
49966
sumo_project/tests/sample.osm
Normal file
49966
sumo_project/tests/sample.osm
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user