Matsim exporter. Generates population and config
This commit is contained in:
parent
4cf89d7938
commit
c566168b19
BIN
__pycache__/matsim.cpython-39.pyc
Normal file
BIN
__pycache__/matsim.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
5
main.py
5
main.py
|
@ -6,7 +6,7 @@ from hub.imports.usage_factory import UsageFactory
|
||||||
from hub.helpers.dictionaries import Dictionaries
|
from hub.helpers.dictionaries import Dictionaries
|
||||||
|
|
||||||
from matsim_engine import MatSimEngine
|
from matsim_engine import MatSimEngine
|
||||||
from matsim_visualizer import MatSimVisualizer
|
from matsim import Matsim
|
||||||
|
|
||||||
try:
|
try:
|
||||||
file_path = (Path(__file__).parent / 'input_files' / 'summerschool_all_buildings.geojson')
|
file_path = (Path(__file__).parent / 'input_files' / 'summerschool_all_buildings.geojson')
|
||||||
|
@ -24,7 +24,8 @@ try:
|
||||||
ConstructionFactory(construction_format, city).enrich()
|
ConstructionFactory(construction_format, city).enrich()
|
||||||
UsageFactory(usage_format, city).enrich()
|
UsageFactory(usage_format, city).enrich()
|
||||||
|
|
||||||
MatSimEngine(city, 'output_files')
|
# Matsim(city, 'output_files')._export()
|
||||||
|
MatSimEngine('output_files/Montreal_config.xml').run()
|
||||||
|
|
||||||
# visualizer = MatSimVisualizer('output_files/network.xml.gz', 'output_files/output_events.xml.gz')
|
# visualizer = MatSimVisualizer('output_files/network.xml.gz', 'output_files/output_events.xml.gz')
|
||||||
# visualizer.visualize()
|
# visualizer.visualize()
|
||||||
|
|
266
matsim.py
Normal file
266
matsim.py
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
import math
|
||||||
|
import subprocess
|
||||||
|
import xmltodict
|
||||||
|
import gzip
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
import geopandas as gpd
|
||||||
|
from shapely.geometry import Point
|
||||||
|
import hub.helpers.constants as cte
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
# TODO: remove xmltodict completely and replace with lxml as it doesnt allow for repeated mixed ordered tags
|
||||||
|
class Matsim:
|
||||||
|
def __init__(self, city, output_file_path):
|
||||||
|
self.city = city
|
||||||
|
self.output_file_path = output_file_path
|
||||||
|
|
||||||
|
self.facilities = {
|
||||||
|
'@name': self.city.name + ' Facilities',
|
||||||
|
'facility': []
|
||||||
|
}
|
||||||
|
|
||||||
|
self.population = etree.Element("population")
|
||||||
|
|
||||||
|
def _export(self):
|
||||||
|
self._export_facilities()
|
||||||
|
self._export_network()
|
||||||
|
self._export_population()
|
||||||
|
self._export_config()
|
||||||
|
|
||||||
|
def _export_facilities(self):
|
||||||
|
buildings_shape_data = {
|
||||||
|
'id': [],
|
||||||
|
'geometry': []
|
||||||
|
}
|
||||||
|
|
||||||
|
for building in self.city.buildings:
|
||||||
|
for surface in building.grounds:
|
||||||
|
for coord in surface.solid_polygon.coordinates:
|
||||||
|
buildings_shape_data['id'].append(f"{building.name}")
|
||||||
|
buildings_shape_data['geometry'].append(Point(coord[0], coord[1]))
|
||||||
|
|
||||||
|
facility = {
|
||||||
|
'@id': building.name,
|
||||||
|
'@x': str(building.centroid[0]),
|
||||||
|
'@y': str(building.centroid[1]),
|
||||||
|
'activity': []
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(building.thermal_zones_from_internal_zones) > 1:
|
||||||
|
raise NotImplementedError("multi-zone buildings aren't yet supported")
|
||||||
|
|
||||||
|
building_schedules = []
|
||||||
|
|
||||||
|
capacity = 0
|
||||||
|
for thermal_zone in building.thermal_zones_from_internal_zones:
|
||||||
|
capacity = thermal_zone.occupancy.occupancy_density * building.floor_area * building.storeys_above_ground
|
||||||
|
for schedule in thermal_zone.occupancy.occupancy_schedules:
|
||||||
|
building_schedules.append(schedule)
|
||||||
|
|
||||||
|
activity_info = {
|
||||||
|
'@type': building.function,
|
||||||
|
'capacity': {
|
||||||
|
'@value': math.ceil(capacity)
|
||||||
|
},
|
||||||
|
'opentime': _convert_schedules(building_schedules)
|
||||||
|
}
|
||||||
|
|
||||||
|
facility['activity'].append(activity_info)
|
||||||
|
self.facilities['facility'].append(facility)
|
||||||
|
|
||||||
|
gdf = gpd.GeoDataFrame(
|
||||||
|
buildings_shape_data,
|
||||||
|
crs=self.city.srs_name
|
||||||
|
)
|
||||||
|
gdf.to_file("input_files/buildings_shapefile.shp")
|
||||||
|
|
||||||
|
# Convert the Python dictionary to an XML string
|
||||||
|
xml_content = xmltodict.unparse({'facilities': self.facilities}, pretty=True, short_empty_elements=True)
|
||||||
|
|
||||||
|
# Write the XML to the file
|
||||||
|
output_file = f"{self.output_file_path}/{self.city.name}_facilities.xml"
|
||||||
|
with open(output_file, 'w') as file:
|
||||||
|
file.write(xml_content)
|
||||||
|
|
||||||
|
with open(output_file, 'rb') as f_in:
|
||||||
|
with gzip.open(output_file + '.gz', 'wb') as f_out:
|
||||||
|
shutil.copyfileobj(f_in, f_out)
|
||||||
|
|
||||||
|
def _export_network(self):
|
||||||
|
java_path = "java"
|
||||||
|
jar_path = "matsim-network-from-osm.jar"
|
||||||
|
command = [
|
||||||
|
java_path,
|
||||||
|
"-jar", jar_path,
|
||||||
|
"input_files/merged-network.osm.pbf",
|
||||||
|
"input_files/buildings_shapefile.shp",
|
||||||
|
f"{self.output_file_path}/{self.city.name}_network.xml.gz"
|
||||||
|
]
|
||||||
|
subprocess.run(command)
|
||||||
|
|
||||||
|
def _export_population(self):
|
||||||
|
id = 0
|
||||||
|
|
||||||
|
# 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']['@value']),
|
||||||
|
'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']['@value'])
|
||||||
|
for _ in range(max_capacity):
|
||||||
|
person = etree.SubElement(self.population, 'person', {
|
||||||
|
'id': str(id),
|
||||||
|
'sex': 'm',
|
||||||
|
'age': '32',
|
||||||
|
'car_avail': 'always',
|
||||||
|
'employed': 'yes',
|
||||||
|
})
|
||||||
|
plan = etree.SubElement(person, 'plan', {'selected': 'yes'})
|
||||||
|
|
||||||
|
# Residential activity
|
||||||
|
etree.SubElement(plan, 'act', {
|
||||||
|
'type': facility['activity'][0]['@type'],
|
||||||
|
'facility': facility['@id'],
|
||||||
|
'x': facility['@x'],
|
||||||
|
'y': facility['@y'],
|
||||||
|
'end_time': '7:30:00'
|
||||||
|
})
|
||||||
|
|
||||||
|
# Leg to work
|
||||||
|
etree.SubElement(plan, 'leg', {'mode': 'car'})
|
||||||
|
|
||||||
|
# Work activity
|
||||||
|
etree.SubElement(plan, 'act', {
|
||||||
|
'type': work[current_work]['type'],
|
||||||
|
'facility': work[current_work]['facility'],
|
||||||
|
'x': work[current_work]['x'],
|
||||||
|
'y': work[current_work]['y'],
|
||||||
|
'start_time': work[current_work]['start_time'],
|
||||||
|
'end_time': work[current_work]['end_time'],
|
||||||
|
})
|
||||||
|
|
||||||
|
# Leg to home
|
||||||
|
etree.SubElement(plan, 'leg', {'mode': 'car'})
|
||||||
|
|
||||||
|
# Residential activity (return)
|
||||||
|
etree.SubElement(plan, 'act', {
|
||||||
|
'type': facility['activity'][0]['@type'],
|
||||||
|
'facility': facility['@id'],
|
||||||
|
'x': facility['@x'],
|
||||||
|
'y': facility['@y'],
|
||||||
|
})
|
||||||
|
|
||||||
|
work[current_work]['capacity'] -= 1
|
||||||
|
if work[current_work]['capacity'] == 0:
|
||||||
|
current_work += 1
|
||||||
|
|
||||||
|
id += 1
|
||||||
|
|
||||||
|
# Convert the Python dictionary to an XML string
|
||||||
|
xml_content = etree.tostring(self.population, pretty_print=True, encoding='UTF-8', xml_declaration=True).decode('utf-8')
|
||||||
|
|
||||||
|
# Write the XML to the file
|
||||||
|
output_file = f"{self.output_file_path}/{self.city.name}_population.xml"
|
||||||
|
with open(output_file, 'w') as file:
|
||||||
|
file.write(xml_content)
|
||||||
|
|
||||||
|
with open(output_file, 'rb') as f_in:
|
||||||
|
with gzip.open(output_file + '.gz', 'wb') as f_out:
|
||||||
|
shutil.copyfileobj(f_in, f_out)
|
||||||
|
|
||||||
|
def _export_config(self):
|
||||||
|
parameterset = []
|
||||||
|
|
||||||
|
for facility in self.facilities['facility']:
|
||||||
|
if facility['activity'][0]['@type'] == cte.RESIDENTIAL:
|
||||||
|
parameterset.append({'@type':'activityParams', 'param': [
|
||||||
|
{'@name':'activityType','@value':facility['activity'][0]['@type']},
|
||||||
|
{'@name':'typicalDuration', '@value' : '12:00:00'},
|
||||||
|
{'@name':'priority','@value': '1'}
|
||||||
|
]})
|
||||||
|
else:
|
||||||
|
parameterset.append({'@type':'activityParams', 'param': [
|
||||||
|
{'@name': 'activityType', '@value': facility['activity'][0]['@type']},
|
||||||
|
{'@name':'openingTime','@value': '08:00:00'},
|
||||||
|
{'@name':'closingTime','@value': '18:00:00'},
|
||||||
|
{'@name':'typicalDuration','@value': '08:00:00'},
|
||||||
|
{'@name':'priority','@value': '1'}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
config = {
|
||||||
|
'module': [
|
||||||
|
{'@name': 'network', 'param': {'@name':'inputNetworkFile', '@value': f"{self.city.name}_network.xml.gz"}},
|
||||||
|
{'@name': 'plans', 'param': {'@name':'inputPlansFile', '@value': f"{self.city.name}_population.xml.gz"}},
|
||||||
|
{'@name': 'facilities', 'param': {'@name':'inputFacilitiesFile', '@value': f"{self.city.name}_facilities.xml.gz"}},
|
||||||
|
{'@name': 'controler', 'param': [
|
||||||
|
{'@name': 'outputDirectory', '@value': '/output'},
|
||||||
|
{'@name': 'firstIteration', '@value': '0'},
|
||||||
|
{'@name': 'lastIteration', '@value': '10'},
|
||||||
|
]},
|
||||||
|
{'@name': 'qsim', 'param': [
|
||||||
|
{'@name': 'startTime', '@value': '00:00:00'},
|
||||||
|
{'@name': 'endTime', '@value': '00:00:00'},
|
||||||
|
{'@name': 'snapshotperiod', '@value': '00:00:00'},
|
||||||
|
]},
|
||||||
|
{'@name':'planCalcStore','param':[
|
||||||
|
{'@name':'learningRate', '@value':'1.0'},
|
||||||
|
{'@name':'BrainExpBeta', '@value':'2.0'},
|
||||||
|
{'@name':'lateArrival', '@value':'-18'},
|
||||||
|
{'@name':'earlyDeparture', '@value':'-0'},
|
||||||
|
{'@name':'performing', '@value':'+6'},
|
||||||
|
{'@name':'waiting', '@value':'-0'},
|
||||||
|
],'parameterset': parameterset},
|
||||||
|
{'@name':'strategy','param':[
|
||||||
|
{'@name': 'maxAgentPlanMemorySize', '@value': '5'},
|
||||||
|
{'@name': 'ModuleProbability_1', '@value': '0.9'},
|
||||||
|
{'@name': 'Module_1', '@value': 'BestScore'},
|
||||||
|
{'@name': 'ModuleProbability_2', '@value': '0.1'},
|
||||||
|
{'@name': 'Module_2', '@value': 'ReRoute'},
|
||||||
|
]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
xml_content = xmltodict.unparse({'config': config}, pretty=True, short_empty_elements=True)
|
||||||
|
|
||||||
|
with open(f"{self.output_file_path}/{self.city.name}_config.xml", 'w') as file:
|
||||||
|
file.write(xml_content)
|
||||||
|
|
||||||
|
def _convert_schedules(building_schedules):
|
||||||
|
converted_schedules = []
|
||||||
|
for schedule in building_schedules:
|
||||||
|
opening_hour = 0
|
||||||
|
closing_hour = 0
|
||||||
|
|
||||||
|
for i, value in enumerate(schedule.values):
|
||||||
|
if value > 0:
|
||||||
|
opening_hour = i
|
||||||
|
break
|
||||||
|
|
||||||
|
for i, value in reversed(list(enumerate(schedule.values))):
|
||||||
|
if value > 0:
|
||||||
|
closing_hour = i
|
||||||
|
break
|
||||||
|
|
||||||
|
for day in schedule.day_types:
|
||||||
|
if day[0:3] != 'hol':
|
||||||
|
converted_schedules.append({
|
||||||
|
'@day': day[0:3],
|
||||||
|
'@start_time': opening_hour,
|
||||||
|
'@end_time': closing_hour
|
||||||
|
})
|
||||||
|
return converted_schedules
|
119
matsim_engine.py
119
matsim_engine.py
|
@ -9,121 +9,8 @@ from matsim_activity_to_matsim_schedule import MatsimActivityToMatsimSchedule
|
||||||
from hub_function_to_matsim_activity import HubFunctionToMatsimActivity
|
from hub_function_to_matsim_activity import HubFunctionToMatsimActivity
|
||||||
|
|
||||||
class MatSimEngine:
|
class MatSimEngine:
|
||||||
def __init__(self, city, output_file_path):
|
def __init__(self, config_file_path):
|
||||||
self._city = city
|
self._config_file_path = config_file_path
|
||||||
self._output_file_path = output_file_path
|
|
||||||
|
|
||||||
facilities_dict = {
|
|
||||||
'facilities': {
|
|
||||||
'@name': 'Montreal Facilities',
|
|
||||||
'facility': []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hub_function_to_matsim = HubFunctionToMatsimActivity()
|
|
||||||
matsim_schedule = MatsimActivityToMatsimSchedule()
|
|
||||||
|
|
||||||
buildings_shape_data = {
|
|
||||||
'id': [],
|
|
||||||
'geometry': []
|
|
||||||
}
|
|
||||||
|
|
||||||
# 1- Facilities
|
|
||||||
# TODO: this should come from the factories, please check idf generation
|
|
||||||
for building in city.buildings:
|
|
||||||
activity = hub_function_to_matsim.dictionary[building.function].split(',')
|
|
||||||
|
|
||||||
for surface in building.grounds:
|
|
||||||
for coord in surface.solid_polygon.coordinates:
|
|
||||||
buildings_shape_data['id'].append(f"{building.name}")
|
|
||||||
buildings_shape_data['geometry'].append(Point(coord[0], coord[1]))
|
|
||||||
|
|
||||||
facility = {
|
|
||||||
'@id': building.name,
|
|
||||||
'@x': str(building.centroid[0]),
|
|
||||||
'@y': str(building.centroid[1]),
|
|
||||||
'activity': []
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(building.thermal_zones_from_internal_zones) > 1:
|
|
||||||
raise NotImplementedError("multi-zone buildings aren't yet supported")
|
|
||||||
|
|
||||||
building_schedules = []
|
|
||||||
|
|
||||||
capacity = 0
|
|
||||||
for thermal_zone in building.thermal_zones_from_internal_zones:
|
|
||||||
capacity = thermal_zone.occupancy.occupancy_density * building.floor_area * building.storeys_above_ground
|
|
||||||
for schedule in thermal_zone.occupancy.occupancy_schedules:
|
|
||||||
building_schedules.append(schedule)
|
|
||||||
|
|
||||||
for new_activity in activity:
|
|
||||||
activity_info = {
|
|
||||||
'@type': new_activity,
|
|
||||||
'capacity': {
|
|
||||||
'@value': math.ceil(capacity)
|
|
||||||
},
|
|
||||||
'opentime': []
|
|
||||||
}
|
|
||||||
|
|
||||||
for schedule in building_schedules:
|
|
||||||
opening_hour = 0
|
|
||||||
closing_hour = 0
|
|
||||||
|
|
||||||
# Find opening hour (first hour > 0)
|
|
||||||
for i, value in enumerate(schedule.values):
|
|
||||||
if value > 0:
|
|
||||||
opening_hour = i
|
|
||||||
break
|
|
||||||
|
|
||||||
for i, value in reversed(list(enumerate(schedule.values))):
|
|
||||||
if value > 0:
|
|
||||||
closing_hour = i
|
|
||||||
break
|
|
||||||
|
|
||||||
for day in schedule.day_types:
|
|
||||||
if day[0:3] != 'hol':
|
|
||||||
activity_info['opentime'].append({
|
|
||||||
'@day': day[0:3],
|
|
||||||
'@start_time': opening_hour,
|
|
||||||
'@end_time': closing_hour
|
|
||||||
})
|
|
||||||
|
|
||||||
facility['activity'].append(activity_info)
|
|
||||||
|
|
||||||
facilities_dict['facilities']['facility'].append(facility)
|
|
||||||
|
|
||||||
gdf = gpd.GeoDataFrame(
|
|
||||||
buildings_shape_data,
|
|
||||||
crs=city.srs_name
|
|
||||||
)
|
|
||||||
|
|
||||||
gdf.to_file("input_files/buildings_shapefile.shp")
|
|
||||||
|
|
||||||
# Convert the Python dictionary to an XML string
|
|
||||||
xml_content = xmltodict.unparse(facilities_dict, pretty=True, short_empty_elements=True)
|
|
||||||
|
|
||||||
# Write the XML to the file
|
|
||||||
with open(f"{output_file_path}/{city.name}_facilities.xml", 'w') as file:
|
|
||||||
file.write(xml_content)
|
|
||||||
|
|
||||||
# 2- Network
|
|
||||||
# First get only the part of the network necessary for the simulation
|
|
||||||
java_path = "java"
|
|
||||||
jar_path = "matsim-network-from-osm.jar"
|
|
||||||
command = [
|
|
||||||
java_path,
|
|
||||||
"-jar", jar_path,
|
|
||||||
"input_files/merged-network.osm.pbf",
|
|
||||||
"input_files/buildings_shapefile.shp",
|
|
||||||
f"{output_file_path}/network.xml.gz"
|
|
||||||
]
|
|
||||||
subprocess.run(command)
|
|
||||||
|
|
||||||
# 3- Population
|
|
||||||
|
|
||||||
# 3.1 - Public Transport
|
|
||||||
|
|
||||||
# 4- Config Generation
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
java_path = "java"
|
java_path = "java"
|
||||||
|
@ -131,6 +18,6 @@ class MatSimEngine:
|
||||||
command = [java_path, "-jar", jar_path]
|
command = [java_path, "-jar", jar_path]
|
||||||
|
|
||||||
# Must generate this config file first.
|
# Must generate this config file first.
|
||||||
# command.append(config_file_path)
|
command.append(self._config_file_path)
|
||||||
|
|
||||||
subprocess.run(command)
|
subprocess.run(command)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user