partial matsim implementation
This commit is contained in:
parent
c6f6498a23
commit
b0b5916f0b
2
hub/data/traffic/osm/.gitignore
vendored
Normal file
2
hub/data/traffic/osm/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.gitignore
|
||||||
|
!.gitignore
|
0
hub/exports/traffic/helpers/__init__.py
Normal file
0
hub/exports/traffic/helpers/__init__.py
Normal file
25
hub/exports/traffic/helpers/osm.py
Normal file
25
hub/exports/traffic/helpers/osm.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class Osm:
|
||||||
|
_osm_file = {
|
||||||
|
'CA.02.5935': 'https://download.geofabrik.de/north-america/canada/british-columbia-latest.osm.pbf',
|
||||||
|
'CA.10.06': 'https://download.geofabrik.de/north-america/canada/quebec-latest.osm.pbf',
|
||||||
|
'CA.10.13': 'https://download.geofabrik.de/north-america/canada/quebec-latest.osm.pbf',
|
||||||
|
'CA.10.14': 'https://download.geofabrik.de/north-america/canada/quebec-latest.osm.pbf',
|
||||||
|
'CA.10.16': 'https://download.geofabrik.de/north-america/canada/quebec-latest.osm.pbf',
|
||||||
|
'DE.01.082': 'https://download.geofabrik.de/europe/germany/baden-wuerttemberg-latest.osm.pbf',
|
||||||
|
'US.NY.047': 'https://download.geofabrik.de/north-america/us/new-york-latest.osm.pbf',
|
||||||
|
'CA.10.12': 'https://download.geofabrik.de/north-america/canada/quebec-latest.osm.pbf',
|
||||||
|
'IL.01.': 'https://download.geofabrik.de/asia/israel-and-palestine-latest.osm.pbf',
|
||||||
|
'ES.07.PM': 'https://download.geofabrik.de/europe/spain/islas-baleares-latest.osm.pbf'
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pfb_file(self, region_code):
|
||||||
|
if region_code in self._osm_file.keys():
|
||||||
|
return self._osm_file[region_code]
|
||||||
|
logging.error('Specific osm data unknown for %s', region_code)
|
||||||
|
raise NotImplementedError('Specific osm data unknown for %s', region_code)
|
@ -1,10 +1,15 @@
|
|||||||
import gzip
|
import gzip
|
||||||
import math
|
import math
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import geopandas as gpd
|
import geopandas as gpd
|
||||||
|
import requests
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from shapely import Polygon
|
from shapely import Polygon
|
||||||
|
from helpers.osm import Osm
|
||||||
|
import hub.helpers.constants as cte
|
||||||
|
|
||||||
|
|
||||||
class Matsim:
|
class Matsim:
|
||||||
@ -28,6 +33,22 @@ class Matsim:
|
|||||||
self._export_population()
|
self._export_population()
|
||||||
self._export_config()
|
self._export_config()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _jar_path(self):
|
||||||
|
"""
|
||||||
|
Get the matsim-network-from-osm installation path
|
||||||
|
:return: str
|
||||||
|
"""
|
||||||
|
return shutil.which('matsim-network-from-osm.jar')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _java_path(self):
|
||||||
|
"""
|
||||||
|
Get the matsim-network-from-osm installation path
|
||||||
|
:return: str
|
||||||
|
"""
|
||||||
|
return shutil.which('java')
|
||||||
|
|
||||||
def _export_facilities(self):
|
def _export_facilities(self):
|
||||||
"""
|
"""
|
||||||
Exports the city's facilities data to XML and shapefile formats.
|
Exports the city's facilities data to XML and shapefile formats.
|
||||||
@ -86,8 +107,6 @@ class Matsim:
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print('error: ', ex)
|
print('error: ', ex)
|
||||||
|
|
||||||
# todo: this may be changed to
|
|
||||||
|
|
||||||
viewport = Polygon([
|
viewport = Polygon([
|
||||||
(self._city.lower_corner[self._x], self._city.upper_corner[self._y]),
|
(self._city.lower_corner[self._x], self._city.upper_corner[self._y]),
|
||||||
(self._city.upper_corner[self._x], self._city.upper_corner[self._y]),
|
(self._city.upper_corner[self._x], self._city.upper_corner[self._y]),
|
||||||
@ -133,8 +152,8 @@ class Matsim:
|
|||||||
if day[0:3] != 'hol':
|
if day[0:3] != 'hol':
|
||||||
converted_schedules.append({
|
converted_schedules.append({
|
||||||
'day': day[0:3],
|
'day': day[0:3],
|
||||||
'start_time': f"{opening_hour:02}:00:00",
|
'start_time': f'{opening_hour:02}:00:00',
|
||||||
'end_time': f"{closing_hour:02}:00:00"
|
'end_time': f'{closing_hour:02}:00:00'
|
||||||
})
|
})
|
||||||
|
|
||||||
return converted_schedules
|
return converted_schedules
|
||||||
@ -148,15 +167,310 @@ class Matsim:
|
|||||||
:param xml_content: The XML content to be written to the file.
|
:param xml_content: The XML content to be written to the file.
|
||||||
:param xml_dtd: An optional DOCTYPE declaration to be included at the beginning of the file.
|
:param xml_dtd: An optional DOCTYPE declaration to be included at the beginning of the file.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if xml_dtd is None:
|
if xml_dtd is None:
|
||||||
xml_dtd = ''
|
xml_dtd = ''
|
||||||
|
|
||||||
with open(output_file, 'w') as file:
|
with open(output_file, 'w') as file:
|
||||||
file.write("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
|
file.write('<?xml version="1.0" encoding="utf-8"?>\n')
|
||||||
file.write(xml_dtd)
|
file.write(xml_dtd)
|
||||||
file.write(xml_content)
|
file.write(xml_content)
|
||||||
|
|
||||||
with open(output_file, 'rb') as f_in:
|
with open(output_file, 'rb') as f_in:
|
||||||
with gzip.open(output_file + '.gz', 'wb') as f_out:
|
with gzip.open(output_file + '.gz', 'wb') as f_out:
|
||||||
shutil.copyfileobj(f_in, f_out)
|
shutil.copyfileobj(f_in, f_out)
|
||||||
|
|
||||||
|
def _export_network(self):
|
||||||
|
"""
|
||||||
|
Generates a transportation network file from the city's OpenStreetMap data and buildings shapefile.
|
||||||
|
"""
|
||||||
|
url = Osm().pfb_file(self._city.location.region_code)
|
||||||
|
path = (Path(__file__).parent.parent.parent / f'data/weather/epw/{url.rsplit("/", 1)[1]}').resolve()
|
||||||
|
if not path.exists():
|
||||||
|
with open(path, 'wb') as pbf_file:
|
||||||
|
pbf_file.write(requests.get(url, allow_redirects=True).content)
|
||||||
|
command = [
|
||||||
|
self._java_path,
|
||||||
|
'-jar', self._jar_path,
|
||||||
|
path,
|
||||||
|
f'{self._output_file_path}/buildings_shapefile.shp',
|
||||||
|
f'{self._output_file_path}/{self._city.name}_network.xml.gz',
|
||||||
|
self._crs
|
||||||
|
]
|
||||||
|
subprocess.run(command)
|
||||||
|
|
||||||
|
def _export_population(self):
|
||||||
|
"""
|
||||||
|
Generates and exports the city's population data to an XML file.
|
||||||
|
"""
|
||||||
|
population = etree.Element('population')
|
||||||
|
person_id = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Generate work facilities
|
||||||
|
work = []
|
||||||
|
for facility in self._facilities['facility']:
|
||||||
|
if facility['activity'][0]['type'] != cte.RESIDENTIAL:
|
||||||
|
work.append({
|
||||||
|
'type': facility['activity'][0]['type'],
|
||||||
|
'capacity': int(facility['activity'][0]['capacity']),
|
||||||
|
'facility': facility['id'],
|
||||||
|
'x': facility['x'],
|
||||||
|
'y': facility['y'],
|
||||||
|
'start_time': '08:00:00',
|
||||||
|
'end_time': '18:00:00'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Generate the population from residential places first
|
||||||
|
current_work = 0
|
||||||
|
for facility in self._facilities['facility']:
|
||||||
|
if facility['activity'][0]['type'] == cte.RESIDENTIAL:
|
||||||
|
max_capacity = int(facility['activity'][0]['capacity'])
|
||||||
|
for i in range(max_capacity):
|
||||||
|
person = etree.SubElement(population, 'person', {
|
||||||
|
'id': str(person_id),
|
||||||
|
'sex': 'm',
|
||||||
|
'age': '32',
|
||||||
|
'car_avail': 'always',
|
||||||
|
'employed': 'yes',
|
||||||
|
})
|
||||||
|
plan = etree.SubElement(person, 'plan', {'selected': 'yes'})
|
||||||
|
|
||||||
|
# Residential activity
|
||||||
|
etree.SubElement(plan, 'act', {
|
||||||
|
'type': f'{facility["activity"][0]["type"]}',
|
||||||
|
'facility': f'{facility["id"]}',
|
||||||
|
'x': f'{facility["x"]}',
|
||||||
|
'y': f'{facility["y"]}',
|
||||||
|
'end_time': '7:30:00'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Leg to work
|
||||||
|
etree.SubElement(plan, 'leg', {'mode': 'car'})
|
||||||
|
|
||||||
|
# Work activity
|
||||||
|
etree.SubElement(plan, 'act', {
|
||||||
|
'type': f'{work[current_work]["type"]}',
|
||||||
|
'facility': f'{work[current_work]["facility"]}',
|
||||||
|
'x': f'{work[current_work]["x"]}',
|
||||||
|
'y': f'{work[current_work]["y"]}',
|
||||||
|
'start_time': f'{work[current_work]["start_time"]}',
|
||||||
|
'end_time': f'{work[current_work]["end_time"]}',
|
||||||
|
})
|
||||||
|
# Leg to home
|
||||||
|
etree.SubElement(plan, 'leg', {'mode': 'car'})
|
||||||
|
# Residential activity (return)
|
||||||
|
etree.SubElement(plan, 'act', {
|
||||||
|
'type': f'{facility["activity"][0]["type"]}',
|
||||||
|
'facility': f'{facility["id"]}',
|
||||||
|
'x': f'{facility["x"]}',
|
||||||
|
'y': f'{facility["y"]}',
|
||||||
|
})
|
||||||
|
work[current_work]['capacity'] -= 1
|
||||||
|
if work[current_work]['capacity'] == 0:
|
||||||
|
current_work += 1
|
||||||
|
|
||||||
|
person_id += 1
|
||||||
|
except Exception as ex:
|
||||||
|
print('error: ', ex)
|
||||||
|
|
||||||
|
# Write xml content to file
|
||||||
|
xml_content = etree.tostring(population, pretty_print=True, encoding='UTF-8').decode('utf-8')
|
||||||
|
output_file = f'{self._output_file_path}/{self._city.name}_population.xml'
|
||||||
|
self._save_xml(
|
||||||
|
output_file,
|
||||||
|
xml_content,
|
||||||
|
f'<!DOCTYPE population SYSTEM "http://www.matsim.org/files/dtd/population_v5.dtd">\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _add_param(parent, name, value):
|
||||||
|
"""
|
||||||
|
Helper function to add a parameter to an XML element.
|
||||||
|
|
||||||
|
:param parent: The parent XML element to which the parameter should be added.
|
||||||
|
:param name: The name of the parameter.
|
||||||
|
:param value: The value of the parameter.
|
||||||
|
"""
|
||||||
|
etree.SubElement(parent, 'param', {
|
||||||
|
'name': name,
|
||||||
|
'value': value
|
||||||
|
})
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _add_parameterset(parent, attribute_type, parameters):
|
||||||
|
"""
|
||||||
|
Helper function to add a set of parameters to an XML element.
|
||||||
|
|
||||||
|
:param parent: The parent XML element to which the parameterset should be added.
|
||||||
|
:param attribute_type: The attribute type for the parameterset.
|
||||||
|
:param parameters: A list of tuples, each containing the name and value of a parameter.
|
||||||
|
"""
|
||||||
|
parameterset = etree.SubElement(parent, 'parameterset', {
|
||||||
|
'type': attribute_type
|
||||||
|
})
|
||||||
|
for name, value in parameters:
|
||||||
|
Matsim._add_param(parameterset, name, value)
|
||||||
|
|
||||||
|
def _export_config(self):
|
||||||
|
"""
|
||||||
|
Creates and exports the simulation configuration settings to an XML file.
|
||||||
|
"""
|
||||||
|
root = etree.Element('config')
|
||||||
|
|
||||||
|
# ======== NETWORK ========= #
|
||||||
|
network_path_module = etree.SubElement(root, 'module', {
|
||||||
|
'name': 'network'
|
||||||
|
})
|
||||||
|
Matsim._add_param(network_path_module, 'inputNetworkFile', f'{self._city.name}_network.xml.gz/')
|
||||||
|
|
||||||
|
# ======== POPULATION ========= #
|
||||||
|
population_path_module = etree.SubElement(root, 'module', {
|
||||||
|
'name': 'plans'
|
||||||
|
})
|
||||||
|
Matsim._add_param(population_path_module, 'inputPlansFile', f'{self._city.name}_population.xml.gz')
|
||||||
|
|
||||||
|
# ======== FACILITIES ========= #
|
||||||
|
facilities_path_module = etree.SubElement(root, 'module', {
|
||||||
|
'name': 'facilities'
|
||||||
|
})
|
||||||
|
Matsim._add_param(facilities_path_module, 'inputFacilitiesFile', f'{self._city.name}_facilities.xml.gz')
|
||||||
|
Matsim._add_param(facilities_path_module, 'facilitiesSource', 'fromFile')
|
||||||
|
|
||||||
|
# ======== CONTROLER ========= #
|
||||||
|
controler_module = etree.SubElement(root, 'module', {
|
||||||
|
'name': 'controler'
|
||||||
|
})
|
||||||
|
controler_params = [
|
||||||
|
('writeEventsInterval', '1000'),
|
||||||
|
('writePlansInterval', '1000'),
|
||||||
|
('eventsFileFormat', 'xml'),
|
||||||
|
('outputDirectory', f"{self._output_file_path}/Montreal"),
|
||||||
|
('firstIteration', '0'),
|
||||||
|
('lastIteration', '10'),
|
||||||
|
('mobsim', 'qsim'),
|
||||||
|
]
|
||||||
|
for param in controler_params:
|
||||||
|
Matsim._add_param(controler_module, param[0], param[1])
|
||||||
|
|
||||||
|
# ======== QSIM ========= #
|
||||||
|
qsim_module = etree.SubElement(root, 'module', {
|
||||||
|
'name': 'qsim'
|
||||||
|
})
|
||||||
|
qsim_params = [
|
||||||
|
('startTime', '00:00:00'),
|
||||||
|
('endTime', '30:00:00'),
|
||||||
|
('flowCapacityFactor', '1.00'),
|
||||||
|
('storageCapacityFactor', '1.00'),
|
||||||
|
('numberOfThreads', '1'),
|
||||||
|
('snapshotperiod', '00:00:01'),
|
||||||
|
('removeStuckVehicles', 'false'),
|
||||||
|
('stuckTime', '3600.0'),
|
||||||
|
('timeStepSize', '00:00:01'),
|
||||||
|
('trafficDynamics', 'queue'),
|
||||||
|
]
|
||||||
|
for param in qsim_params:
|
||||||
|
Matsim._add_param(qsim_module, param[0], param[1])
|
||||||
|
|
||||||
|
# ======== SCORING ========= #
|
||||||
|
score_module = etree.SubElement(root, 'module', {
|
||||||
|
'name': 'planCalcScore'
|
||||||
|
})
|
||||||
|
Matsim._add_param(score_module, 'BrainExpBeta', '1.0')
|
||||||
|
Matsim._add_param(score_module, 'learningRate', '1.0')
|
||||||
|
|
||||||
|
scoring_paramset = etree.SubElement(score_module, 'parameterset', {
|
||||||
|
'type': 'scoringParameters'
|
||||||
|
})
|
||||||
|
scoring_paramset_params = [
|
||||||
|
('earlyDeparture', '0.0'),
|
||||||
|
('lateArrival', '0.0'),
|
||||||
|
('marginalUtilityOfMoney', '0.062'),
|
||||||
|
('performing', '0.96'),
|
||||||
|
('utilityOfLineSwitch', '0.0'),
|
||||||
|
('waitingPt', '-0.18'),
|
||||||
|
]
|
||||||
|
for param in scoring_paramset_params:
|
||||||
|
Matsim._add_param(scoring_paramset, param[0], param[1])
|
||||||
|
|
||||||
|
mode_paramsets = [
|
||||||
|
('car',
|
||||||
|
[
|
||||||
|
('marginalUtilityOfTraveling_util_hr', '0.0'),
|
||||||
|
('constant', '-0.562'),
|
||||||
|
('monetaryDistanceRate', '-0.0004')
|
||||||
|
]),
|
||||||
|
('walk',
|
||||||
|
[
|
||||||
|
('marginalUtilityOfTraveling_util_hr', '-1.14'),
|
||||||
|
('constant', '0.0'),
|
||||||
|
('marginalUtilityOfDistance_util_m', '0.0')
|
||||||
|
])
|
||||||
|
]
|
||||||
|
for mode, parameters in mode_paramsets:
|
||||||
|
Matsim._add_parameterset(scoring_paramset, 'modeParams', [('mode', mode)] + parameters)
|
||||||
|
|
||||||
|
activity_paramsets = [
|
||||||
|
('residential',[
|
||||||
|
('priority', '1'),
|
||||||
|
('typicalDuration', '13:00:00'),
|
||||||
|
('minimalDuration', '01:00:00'),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
|
||||||
|
for facility in self._facilities['facility']:
|
||||||
|
if not any(activity[0] == facility['activity'][0]['type'] for activity in activity_paramsets):
|
||||||
|
activity_paramsets.append(
|
||||||
|
(facility['activity'][0]['type'], [
|
||||||
|
('priority', '1'),
|
||||||
|
('typicalDuration', '09:00:00'),
|
||||||
|
('minimalDuration', '08:00:00'),
|
||||||
|
('openingTime', '08:00:00'),
|
||||||
|
('earliestEndTime', '17:00:00'),
|
||||||
|
('latestStartTime', '09:00:00'),
|
||||||
|
('closingTime', '18:00:00'),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
|
||||||
|
for activity_type, parameters in activity_paramsets:
|
||||||
|
Matsim._add_parameterset(scoring_paramset, "activityParams", [("activityType", activity_type)] + parameters)
|
||||||
|
|
||||||
|
# ======== STRATEGY ========= #
|
||||||
|
strategy_module = etree.SubElement(root, 'module', {
|
||||||
|
'name': 'strategy'
|
||||||
|
})
|
||||||
|
Matsim._add_param(strategy_module, 'maxAgentPlanMemorySize', '6')
|
||||||
|
|
||||||
|
strategy_paramsets = [
|
||||||
|
('ChangeExpBeta',[
|
||||||
|
('weight', '0.7'),
|
||||||
|
]),
|
||||||
|
('ReRoute',[
|
||||||
|
('disableAfterIteration', '2900'),
|
||||||
|
('weight', '0.01'),
|
||||||
|
]),
|
||||||
|
('SubtourModeChoice', [
|
||||||
|
('disableAfterIteration', '2900'),
|
||||||
|
('weight', '0.01'),
|
||||||
|
]),
|
||||||
|
('TimeAllocationMutator', [
|
||||||
|
('disableAfterIteration', '2900'),
|
||||||
|
('weight', '0.01'),
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
for name, parameters in strategy_paramsets:
|
||||||
|
Matsim._add_parameterset(strategy_module, 'strategysettings', [("strategyName", name)] + parameters)
|
||||||
|
|
||||||
|
# ======== SUBTOUR MODE CHOICE ========= #
|
||||||
|
subtour_module = etree.SubElement(root, 'module', {
|
||||||
|
'name': 'subtourModeChoice'
|
||||||
|
})
|
||||||
|
# Defines the chain-based modes, seperated by commas
|
||||||
|
Matsim._add_param(subtour_module, 'chainBasedModes', 'car')
|
||||||
|
# Defines whether car availability must be considered or not. An agent has no car only if it has no license, or never access to a car
|
||||||
|
Matsim._add_param(subtour_module, 'considerCarAvailability', 'true')
|
||||||
|
# Defines all the modes available, including chain-based modes, seperated by commas
|
||||||
|
Matsim._add_param(subtour_module, 'modes', 'car,walk')
|
||||||
|
|
||||||
|
# Write xml content to file
|
||||||
|
xml_content = etree.tostring(root, pretty_print=True, encoding='UTF-8').decode('utf-8')
|
||||||
|
output_file = f"{self._output_file_path}/{self._city.name}_config.xml"
|
||||||
|
self._save_xml(output_file, xml_content, f'<!DOCTYPE config SYSTEM "http://www.matsim.org/files/dtd/config_v2.dtd">\n')
|
||||||
|
@ -23,4 +23,5 @@ geopandas
|
|||||||
triangle
|
triangle
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
Pillow
|
Pillow
|
||||||
pathlib
|
pathlib
|
||||||
|
lxml
|
Loading…
Reference in New Issue
Block a user