diff --git a/__pycache__/matsim.cpython-39.pyc b/__pycache__/matsim.cpython-39.pyc index 69dfed8..e527997 100644 Binary files a/__pycache__/matsim.cpython-39.pyc and b/__pycache__/matsim.cpython-39.pyc differ diff --git a/matsim.py b/matsim.py index acf3fc4..df3de96 100644 --- a/matsim.py +++ b/matsim.py @@ -1,6 +1,5 @@ import math import subprocess -import xmltodict import gzip import shutil @@ -9,19 +8,22 @@ from shapely.geometry import Point import hub.helpers.constants as cte from lxml import etree +CONFIG_DTD = "http://www.matsim.org/files/dtd/config_v2.dtd" +FACILITIES_DTD = "http://www.matsim.org/files/dtd/facilities_v1.dtd" +POPULATION_DTD = "http://www.matsim.org/files/dtd/population_v5.dtd" + + # 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._city = city + self._output_file_path = output_file_path - self.facilities = { - '@name': self.city.name + ' Facilities', + self._facilities = { + 'name': self._city.name + ' Facilities', 'facility': [] } - self.population = etree.Element("population") - def _export(self): self._export_facilities() self._export_network() @@ -34,16 +36,18 @@ class Matsim: 'geometry': [] } - for building in self.city.buildings: + facilities_xml = etree.Element('facilities', name=self._facilities['name']) + + 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]), + 'id': building.name, + 'x': str(building.centroid[0]), + 'y': str(building.centroid[1]), 'activity': [] } @@ -59,28 +63,48 @@ class Matsim: building_schedules.append(schedule) activity_info = { - '@type': building.function, - 'capacity': { - '@value': math.ceil(capacity) - }, + 'type': building.function, + 'capacity': math.ceil(capacity), 'opentime': _convert_schedules(building_schedules) } + facility_xml = etree.SubElement(facilities_xml, 'facility', { + 'id': facility['id'], + 'x': facility['x'], + 'y': facility['y'], + }) + + activity_xml = etree.SubElement(facility_xml, 'activity', { + 'type': activity_info['type'] + }) + + etree.SubElement(activity_xml, 'capacity', { + 'value': activity_info['capacity'] + }) + + etree.SubElement(activity_xml, 'opentime', { + 'day': activity_info['opentime'][0]['day'], + 'start_time': activity_info['opentime'][0]['start_time'], + 'end_time': activity_info['opentime'][0]['end_time'] + }) + facility['activity'].append(activity_info) - self.facilities['facility'].append(facility) + self._facilities['facility'].append(facility) gdf = gpd.GeoDataFrame( buildings_shape_data, - crs=self.city.srs_name + 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) + xml_content = etree.tostring(facilities_xml, 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}_facilities.xml" + output_file = f"{self._output_file_path}/{self._city.name}_facilities.xml" with open(output_file, 'w') as file: + file.write("") + file.write(f"") file.write(xml_content) with open(output_file, 'rb') as f_in: @@ -95,34 +119,35 @@ class Matsim: "-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" + f"{self._output_file_path}/{self._city.name}_network.xml.gz" ] subprocess.run(command) def _export_population(self): + population = etree.Element("population") id = 0 # Generate work facilities work = [] - for facility in self.facilities['facility']: - if facility['activity'][0]['@type'] != cte.RESIDENTIAL: + 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'], + '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']['@value']) + 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', { + person = etree.SubElement(population, 'person', { 'id': str(id), 'sex': 'm', 'age': '32', @@ -133,10 +158,10 @@ class Matsim: # Residential activity etree.SubElement(plan, 'act', { - 'type': facility['activity'][0]['@type'], - 'facility': facility['@id'], - 'x': facility['@x'], - 'y': facility['@y'], + 'type': facility['activity'][0]['type'], + 'facility': facility['id'], + 'x': facility['x'], + 'y': facility['y'], 'end_time': '7:30:00' }) @@ -158,10 +183,10 @@ class Matsim: # Residential activity (return) etree.SubElement(plan, 'act', { - 'type': facility['activity'][0]['@type'], - 'facility': facility['@id'], - 'x': facility['@x'], - 'y': facility['@y'], + 'type': facility['activity'][0]['type'], + 'facility': facility['id'], + 'x': facility['x'], + 'y': facility['y'], }) work[current_work]['capacity'] -= 1 @@ -171,10 +196,10 @@ class Matsim: 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') + xml_content = etree.tostring(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" + output_file = f"{self._output_file_path}/{self._city.name}_population.xml" with open(output_file, 'w') as file: file.write(xml_content) @@ -185,7 +210,7 @@ class Matsim: def _export_config(self): parameterset = [] - for facility in self.facilities['facility']: + 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']}, @@ -204,9 +229,9 @@ class Matsim: 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': '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'}, @@ -237,7 +262,7 @@ class Matsim: 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: + with open(f"{self._output_file_path}/{self._city.name}_config.xml", 'w') as file: file.write(xml_content) def _convert_schedules(building_schedules): @@ -259,8 +284,8 @@ def _convert_schedules(building_schedules): 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 + 'day': day[0:3], + 'start_time': opening_hour, + 'end_time': closing_hour }) return converted_schedules diff --git a/requirements.txt b/requirements.txt index c64163c..90cdfe7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,4 @@ -cerc-hub \ No newline at end of file +cerc-hub +geopandas +shapely +lxml