From 43118d203edbb9ac9e593cf3b639161af156cfd3 Mon Sep 17 00:00:00 2001 From: Ruben1729 Date: Thu, 1 Feb 2024 14:35:55 -0500 Subject: [PATCH] Visualizer for exiting matsim solutions. Improvement to matsim engine --- README.md | 2 + ...function_to_matsim_activity.cpython-39.pyc | Bin 2388 -> 2419 bytes __pycache__/matsim_engine.cpython-39.pyc | Bin 2242 -> 2304 bytes __pycache__/matsim_visualizer.cpython-39.pyc | Bin 0 -> 4842 bytes hub_function_to_matsim_activity.py | 56 ++++----- main.py | 4 + matsim_engine.py | 22 ++-- matsim_visualizer.py | 113 ++++++++++++++++++ 8 files changed, 162 insertions(+), 35 deletions(-) create mode 100644 __pycache__/matsim_visualizer.cpython-39.pyc create mode 100644 matsim_visualizer.py diff --git a/README.md b/README.md index e546242..4d405aa 100644 --- a/README.md +++ b/README.md @@ -18,3 +18,5 @@ python main.py path/to/config.xml ``` Two scenarios are provided in this repository: equil, sioux. The output for that given simulation will be in the output folder. + +https://www.matsim.org/gallery/munich diff --git a/__pycache__/hub_function_to_matsim_activity.cpython-39.pyc b/__pycache__/hub_function_to_matsim_activity.cpython-39.pyc index 1849855fc62bc25595983e603e9647fd43dbe532..2efa0e6d4761a39d037ccff42a2335b68dd30f59 100644 GIT binary patch delta 327 zcmca2^jU~Ek(ZZ?0SJ_r?@nDmk$0=IBbS1LM`@BmT4`Q#NoIbYLP@?tZemGsX0Ada zP@pWcq;lfzdyK4;cQKk8vXn4nsiuHPH880TCN)x+fFhb;wicMw28-yVFiloqa*P7W zT7X3?!K4*fjWw8U114=#m@;i^7~<_x7=jrz+5B#?WaQ_j-eM`wFUr2foSITv#Rp>Q z6sIQV=cObTRaWtV6zFVT#w5kU=)U(Lez#aM@^e#fv6SZ*W#3{>O)0&_x%nuQ6bqxr<_B!a zjH0eUcNe*V2oE^nIXQx(Sj!8@zr|5dlwXiqR8o11B_%Vtq{s)Z2wC}84j&_SkPH(x XX|Opqx%nxjIjMGxKwdFOnGh2I*BDWc diff --git a/__pycache__/matsim_engine.cpython-39.pyc b/__pycache__/matsim_engine.cpython-39.pyc index fd9aa38375077885c58d3a16afc1f834c65531bf..6df72ef04e9600c6ee66a3315c387547418e7f2e 100644 GIT binary patch delta 772 zcmYjPTWb?R6rMA?+1+e5o9yP+)Jv<_LO^@5UZ_unDnd~ZDu{xX)f2)7n>L&E(lBd< zyosW)kEW2m`DpR6Z$9|uAMghV`V+h~p4qnQ9_E{GzVppFhh;uG&t2QgW=%%l`p$0M zv3BeV*cCcTZIM>zeLM%3YTqMWsLhnnn_86(bWCkSWQY_g29i=`QCg%)*W;Hkv1wdn zktH-tlQ$!*w+ziKz74=g%SbJ>ge~t88dcUya_wjBfa+&Mn=mJ{JviXf0}X~&UyX9Y zxXkWOMtPCLY*IguHs*Z5yf8%uoxZY(ZeNk@+Fyl&P-R$1jJZU+5ayc>4N}C?D;_z* z8Ms)MnXu3-lD~o@q`P5Ba2mY2#c{O9UynuwQ5ckkE1kC(GL@pI0BgCk>iFB OYx#ez_Upt0fgc5ZzgCY$tXc+i{YnK!boDQi-n?LBy#N2c#kn2#K!)$YQFJNNtE?5RKL< zQqFuxD{-Nb+#6Q}H#l)X{S6%8%6|X^vk6r_+UK|L&Cau|eI9-ZytrI0F?xP@AJ@@& z?=`@y(^hL&XboSg6}a5m&nzi=1!?unCd+JDAZ`;Y%3^L|LA`;+9qSUyTxlZdxg*`T z42u)K2f#=xX)Sf7hjgCMYO?Xv8>E9rG_{<1gca?M;Xl_sAZ>Ke?E;o%Njm858d#y_ z?~1G<4TL7cV47Fi(NTkosox7IFvRK$p83+xLyWYOR(la0s9~M-AgxJGAJ~|1oE!63 z^I0gvyezRr7QL^fOh*XsIX0jb>YnjMXlV`06uS+u!W?VcLD#O}9UMHQoW# zRlo2_b<7SdtAV)&_tj^!wd6txJT3@bupxruh9Ks0&c$CYI8gl&zr=6TSB^DU@+ojs z+Kkg-ck@gyPLhCxHcw8{D>}!cu+RiMYMf_fIW4-@4-okj8ET{)|-#;8N%TwR&6aN5??}&T= diff --git a/__pycache__/matsim_visualizer.cpython-39.pyc b/__pycache__/matsim_visualizer.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b13592568810d85c8d12df0cf1bdb2d5cebf7007 GIT binary patch literal 4842 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 Gtp5QzdXt3! literal 0 HcmV?d00001 diff --git a/hub_function_to_matsim_activity.py b/hub_function_to_matsim_activity.py index 1aca7ef..be667f5 100644 --- a/hub_function_to_matsim_activity.py +++ b/hub_function_to_matsim_activity.py @@ -2,7 +2,7 @@ import hub.helpers.constants as cte class HubFunctionToMatsimActivity: """ - Hub function to nrcan construction function class + Hub function to matsim activity construction function class """ def __init__(self): self._dictionary = { @@ -28,43 +28,43 @@ class HubFunctionToMatsimActivity: cte.SECONDARY_SCHOOL: 'edu', cte.UNIVERSITY: 'edu', cte.LABORATORY_AND_RESEARCH_CENTER: 'edu', - cte.STAND_ALONE_RETAIL: 'secondary', + cte.STAND_ALONE_RETAIL: 'work,secondary', cte.HOSPITAL: 'work', cte.OUT_PATIENT_HEALTH_CARE: 'work', cte.HEALTH_CARE: 'work', - cte.RETIREMENT_HOME_OR_ORPHANAGE: 'home', - cte.COMMERCIAL: 'secondary', - cte.STRIP_MALL: 'secondary', - cte.SUPERMARKET: 'secondary', - cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'secondary', - cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD: 'secondary', - cte.RESTAURANT: 'secondary', - cte.QUICK_SERVICE_RESTAURANT: 'secondary', - cte.FULL_SERVICE_RESTAURANT: 'secondary', - cte.HOTEL: 'work', - cte.HOTEL_MEDIUM_CLASS: 'work', - cte.SMALL_HOTEL: 'work', - cte.LARGE_HOTEL: 'work', + cte.RETIREMENT_HOME_OR_ORPHANAGE: 'home,secondary', + cte.COMMERCIAL: 'work,secondary', + cte.STRIP_MALL: 'work,secondary', + cte.SUPERMARKET: 'work,secondary', + cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'work,secondary', + cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD: 'work,secondary', + cte.RESTAURANT: 'work,secondary', + cte.QUICK_SERVICE_RESTAURANT: 'work,secondary', + cte.FULL_SERVICE_RESTAURANT: 'work,secondary', + cte.HOTEL: 'work,secondary', + cte.HOTEL_MEDIUM_CLASS: 'work,secondary', + cte.SMALL_HOTEL: 'work,secondary', + cte.LARGE_HOTEL: 'work,secondary', cte.DORMITORY: 'work', - cte.EVENT_LOCATION: 'secondary', - cte.CONVENTION_CENTER: 'secondary', - cte.HALL: 'secondary', - cte.GREEN_HOUSE: 'secondary', + cte.EVENT_LOCATION: 'work,secondary', + cte.CONVENTION_CENTER: 'work,secondary', + cte.HALL: 'work', + cte.GREEN_HOUSE: 'work', cte.INDUSTRY: 'work', cte.WORKSHOP: 'work', cte.WAREHOUSE: 'work', cte.WAREHOUSE_REFRIGERATED: 'work', - cte.SPORTS_LOCATION: 'secondary', - cte.SPORTS_ARENA: 'secondary', - cte.GYMNASIUM: 'secondary', - cte.MOTION_PICTURE_THEATRE: 'secondary', - cte.MUSEUM: 'secondary', - cte.PERFORMING_ARTS_THEATRE: 'secondary', - cte.TRANSPORTATION: 'secondary', + cte.SPORTS_LOCATION: 'work,secondary', + cte.SPORTS_ARENA: 'work,secondary', + cte.GYMNASIUM: 'work,secondary', + cte.MOTION_PICTURE_THEATRE: 'work,secondary', + cte.MUSEUM: 'work,secondary', + cte.PERFORMING_ARTS_THEATRE: 'work,secondary', + cte.TRANSPORTATION: 'work', cte.AUTOMOTIVE_FACILITY: 'work', cte.PARKING_GARAGE: 'work', - cte.RELIGIOUS: 'secondary', - cte.NON_HEATED: 'secondary', + cte.RELIGIOUS: 'work,secondary', + cte.NON_HEATED: 'work', cte.DATACENTER: 'work', cte.FARM: 'work' } diff --git a/main.py b/main.py index 9aedeeb..5dc16de 100644 --- a/main.py +++ b/main.py @@ -6,6 +6,7 @@ from hub.imports.usage_factory import UsageFactory from hub.helpers.dictionaries import Dictionaries from matsim_engine import MatSimEngine +from matsim_visualizer import MatSimVisualizer try: file_path = (Path(__file__).parent / 'input_files' / 'summerschool_all_buildings.geojson') @@ -32,6 +33,9 @@ try: MatSimEngine(city, 'output_files') + # visualizer = MatSimVisualizer('output/output_network.xml.gz', 'output/output_events.xml.gz') + # visualizer.visualize() + except Exception as ex: print('error: ', ex) print('[simulation abort]') diff --git a/matsim_engine.py b/matsim_engine.py index 967ea66..237f945 100644 --- a/matsim_engine.py +++ b/matsim_engine.py @@ -30,11 +30,13 @@ class MatSimEngine: # 1- Facilities # TODO: Add the ability for a building to have multiple activities for building in city.buildings: - activity = hub_function_to_matsim.dictionary[building.function] - schedule = matsim_schedule.dictionary[activity] + activity = hub_function_to_matsim.dictionary[building.function].split(',') + schedule = matsim_schedule.dictionary[activity[0]] start_time, end_time = schedule.split('-') new_id = 0 + + # TODO: building.grounds (TBD) for surface in building.surfaces: for coord in surface.solid_polygon.coordinates: new_id += 1 @@ -45,18 +47,21 @@ class MatSimEngine: '@id': building.name, '@x': str(building.centroid[0]), '@y': str(building.centroid[1]), - 'activity': { - '@type': activity, + 'activity': [] + } + for new_activity in activity: + facility['activity'].append({ + '@type': new_activity, 'capacity': { - '@value': '4.0' # TODO: Replace with a proper value taken from function + '@value': '4.0' # TODO: Replace with a proper value taken from function }, 'opentime': { '@day': 'wkday', '@start_time': start_time, '@end_time': end_time } - } - } + }) + facilities_dict['facilities']['facility'].append(facility) gdf = gpd.GeoDataFrame( @@ -81,6 +86,9 @@ class MatSimEngine: subprocess.run(command) # 3- Population + + # 3.1 - Public Transport + # 4- Config Generation def run(self): diff --git a/matsim_visualizer.py b/matsim_visualizer.py new file mode 100644 index 0000000..4163385 --- /dev/null +++ b/matsim_visualizer.py @@ -0,0 +1,113 @@ +import gzip + +import xmltodict +import networkx as nx +import matplotlib.pyplot as plt +from matplotlib.animation import FuncAnimation +from collections import defaultdict +import matplotlib.cm as cm +import matplotlib.colors as colors + +class MatSimVisualizer(): + def __init__(self, network_file_path, events_file_path): + self.network_file_path = network_file_path + self.events_file_path = events_file_path + self.G = nx.Graph() + self.pos = None + self.traffic_per_tick = defaultdict(lambda: defaultdict(int)) + self.cumulative_traffic = defaultdict(lambda: defaultdict(int)) + self.cmap = cm.viridis + self.norm = None + + def load_data(self): + # Load network data + with gzip.open(self.network_file_path, 'rb') as file: + network_doc = xmltodict.parse(file.read().decode('utf-8')) + + # Parse nodes + self.nodes = {node['@id']: (float(node['@x']), float(node['@y'])) for node in + network_doc['network']['nodes']['node']} + + # Parse links + self.links = [{ + 'id': link['@id'], + 'from': link['@from'], + 'to': link['@to'] + } for link in network_doc['network']['links']['link']] + + link_state = defaultdict(list) + + # Load and parse the events file + with gzip.open(self.events_file_path, 'rb') as file: + events_doc = xmltodict.parse(file.read().decode('utf-8')) + + for event in events_doc['events']['event']: + link_id = event.get('@link') + event_type = event.get('@type') + tick = float(event.get('@time')) + vehicle_id = event.get('@vehicle') + + if link_id is not None and event_type is not None and tick is not None: + if event_type == 'entered link' or event_type == 'vehicle enters traffic': + self.traffic_per_tick[tick][link_id] += 1 + link_state[link_id].append(vehicle_id) + elif event_type == 'left link' or event_type == 'vehicle leaves traffic': + self.traffic_per_tick[tick][link_id] -= 1 + link_state[link_id].remove(vehicle_id) + + for link in self.links: + self.cumulative_traffic[0][link['id']] = 0 + + # Accumulate the counts to get the total number of vehicles on each link up to each tick + actual_tick = 0 + sorted_ticks = sorted(self.traffic_per_tick.keys()) + for tick in sorted_ticks: + if actual_tick not in self.cumulative_traffic: + # Start with the vehicle counts of the previous tick + self.cumulative_traffic[actual_tick] = defaultdict(int, self.cumulative_traffic.get(actual_tick - 1, {})) + + # Apply the changes recorded for the current tick + for link_id, change in self.traffic_per_tick[tick].items(): + self.cumulative_traffic[actual_tick][link_id] += change + + actual_tick += 1 # Move to the next tick + + def create_graph(self): + for node_id, coords in self.nodes.items(): + self.G.add_node(node_id, pos=coords) + for link in self.links: + self.G.add_edge(link['from'], link['to']) + self.pos = nx.get_node_attributes(self.G, 'pos') + + def setup_color_mapping(self): + # Find max traffic to setup the normalization instance + max_traffic = max(max(self.cumulative_traffic[tick].values()) for tick in self.cumulative_traffic) + self.norm = colors.Normalize(vmin=0, vmax=max_traffic) + + def update(self, frame_number): + tick = sorted(self.cumulative_traffic.keys())[frame_number] + traffic_data = self.cumulative_traffic[tick] + + edge_colors = [self.cmap(self.norm(traffic_data.get(link['id'], 0))) for link in self.links] + edge_widths = [2 + self.norm(traffic_data.get(link['id'], 0)) * 3 for link in self.links] + + plt.cla() + nx.draw(self.G, self.pos, node_size=10, node_color='blue', width=edge_widths, edge_color=edge_colors, with_labels=False, + edge_cmap=self.cmap) + + plt.title(f"Time: {tick}") + + def visualize(self): + self.load_data() + self.create_graph() + self.setup_color_mapping() + + fig, ax = plt.subplots() + + sm = plt.cm.ScalarMappable(cmap=self.cmap, norm=self.norm) + sm.set_array([]) + plt.colorbar(sm, ax=ax, label='Traffic Density') + + ani = FuncAnimation(fig, self.update, frames=len(self.cumulative_traffic), repeat=False) + ani.save('traffic_animation.gif', writer='ffmpeg', fps=5) + plt.show()