140 lines
4.0 KiB
Python
140 lines
4.0 KiB
Python
import math
|
|
import subprocess
|
|
import xmltodict
|
|
|
|
import geopandas as gpd
|
|
from shapely.geometry import Point
|
|
|
|
from matsim_activity_to_matsim_schedule import MatsimActivityToMatsimSchedule
|
|
from hub_function_to_matsim_activity import HubFunctionToMatsimActivity
|
|
|
|
|
|
# TODO: Please check this https://github.com/matsim-org/matsim-code-examples/issues/63 for the CRS in matsim should match the city
|
|
|
|
class MatSimEngine:
|
|
def __init__(self, city, output_file_path):
|
|
self._city = city
|
|
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):
|
|
java_path = "java"
|
|
jar_path = "matsim.jar"
|
|
command = [java_path, "-jar", jar_path]
|
|
|
|
# Must generate this config file first.
|
|
# command.append(config_file_path)
|
|
|
|
subprocess.run(command)
|