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: Add the ability for a building to have multiple activities # 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) # TODO: check http://www.matsim.org/files/dtd/facilities_v1.dtd seems to be missing values here 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)