diff --git a/.gitignore b/.gitignore index 8b315b2..e7ce587 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ input_files/* # Ignore all files inside the 'output_files' directory output_files/* -output/* \ No newline at end of file +output_files/output/* \ No newline at end of file diff --git a/__pycache__/matsim.cpython-39.pyc b/__pycache__/matsim.cpython-39.pyc index 105f345..23725b7 100644 Binary files a/__pycache__/matsim.cpython-39.pyc and b/__pycache__/matsim.cpython-39.pyc differ diff --git a/__pycache__/matsim_visualizer.cpython-39.pyc b/__pycache__/matsim_visualizer.cpython-39.pyc index fdc48a0..bab6254 100644 Binary files a/__pycache__/matsim_visualizer.cpython-39.pyc and b/__pycache__/matsim_visualizer.cpython-39.pyc differ diff --git a/main.py b/main.py index a2a0fee..60638f7 100644 --- a/main.py +++ b/main.py @@ -25,10 +25,12 @@ try: ConstructionFactory(construction_format, city).enrich() UsageFactory(usage_format, city).enrich() - Matsim(city, 'output_files').export() - MatSimEngine('output_files/Montreal_config.xml').run() + # Matsim(city, 'output_files').export() + # MatSimEngine('output_files/Montreal_config.xml').run() + # MatSimEngine('equil/config.xml').run() visualizer = MatsimVisualizer('output_files/Montreal/output_network.xml.gz', 'output_files/Montreal/output_events.xml.gz', 'output_files') + # visualizer = MatsimVisualizer('output/output_network.xml.gz', 'output/output_events.xml.gz', 'output_files') visualizer.visualize() except Exception as ex: diff --git a/matsim_visualizer.py b/matsim_visualizer.py index 1a11b8c..96fbcfb 100644 --- a/matsim_visualizer.py +++ b/matsim_visualizer.py @@ -1,6 +1,6 @@ import gzip -import xmltodict +from lxml import etree import networkx as nx import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation @@ -22,61 +22,74 @@ class MatsimVisualizer(): self._G = nx.Graph() self._traffic_per_tick = defaultdict(lambda: defaultdict(int)) - self._cumulative_traffic = defaultdict(lambda: defaultdict(int)) self._cmap = cm.viridis + self._tick = 0 + self._max_traffic = 0 + def load_data(self): - # Load network data + # ====== LOAD NETWORK ====== # with gzip.open(self._network_file_path, 'rb') as file: - network_doc = xmltodict.parse(file.read().decode('utf-8')) + network_doc = etree.parse(file) - # Parse nodes - self._nodes = {node['@id']: (float(node['@x']), float(node['@y'])) for node in - network_doc['network']['nodes']['node']} + self._nodes = {node.get('id'): (float(node.get('x')), float(node.get('y'))) + for node in network_doc.xpath('/network/nodes/node')} - # Parse links self._links = [{ - 'id': link['@id'], - 'from': link['@from'], - 'to': link['@to'] - } for link in network_doc['network']['links']['link']] + 'id': link.get('id'), + 'from': link.get('from'), + 'to': link.get('to'), + 'vehicles': 0, + } for link in network_doc.xpath('/network/links/link')] + seen_links = set() + cumulative_traffic = defaultdict(int) - link_state = defaultdict(list) - - # Load and parse the events file + # ====== LOAD EVENTS ====== # with gzip.open(self._events_file_path, 'rb') as file: - events_doc = xmltodict.parse(file.read().decode('utf-8')) + events_doc = etree.parse(file) - 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') + self._tick = 0 + last_time = None + for event in events_doc.xpath('/events/event'): + link_id = event.get('link') + event_type = event.get('type') + time = float(event.get('time')) + vehicle_id = event.get('vehicle') + ticked = False - 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) + if link_id is not None and event_type is not None and time is not None: + if event_type in ['entered link', 'vehicle enters traffic']: + self._traffic_per_tick[self._tick][link_id] += 1 + seen_links.add(link_id) + cumulative_traffic[link_id] += 1 + if self._max_traffic < cumulative_traffic[link_id]: + self._max_traffic = cumulative_traffic[link_id] + ticked = True + elif event_type in ['left link', 'vehicle leaves traffic']: + self._traffic_per_tick[self._tick][link_id] -= 1 + cumulative_traffic[link_id] -= 1 + ticked = True + if ticked and last_time is not None and last_time != time: + self._tick += 1 + + last_time = time + + # ====== FILTER LINKS ====== # + unique_links = [] for link in self._links: - self._cumulative_traffic[0][link['id']] = 0 + if link['id'] in seen_links: + unique_links.append(link) + self._links = unique_links - # 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, {})) + # ====== FILTER NODES ====== # + seen_nodes = set() + for link in self._links: + seen_nodes.add(link['from']) + seen_nodes.add(link['to']) - # 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 + filtered_nodes = {node_id: self._nodes[node_id] for node_id in seen_nodes if node_id in self._nodes} + self._nodes = filtered_nodes def create_graph(self): for node_id, coords in self._nodes.items(): @@ -86,23 +99,30 @@ class MatsimVisualizer(): 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) + self.norm = colors.Normalize(vmin=0, vmax=self._max_traffic) def update(self, frame_number): - tick = sorted(self._cumulative_traffic.keys())[frame_number] - traffic_data = self._cumulative_traffic[tick] + traffic_change = self._traffic_per_tick[self._tick] - edge_colors = [self._cmap(self.norm(traffic_data.get(link['id'], 0))) for link in self._links] - edge_widths = [1 + self.norm(traffic_data.get(link['id'], 0)) * 10 for link in self._links] + edge_colors = [] + edge_widths = [] + + for link in self._links: + for link_id, change in traffic_change.items(): + if link_id == link['id']: + link['vehicles'] += change + break + + edge_colors.append(self._cmap(link['vehicles'])) + edge_widths.append(1 + self.norm(link['vehicles'])*10) plt.cla() nx.draw(self._G, self._pos, node_size=0, node_color='blue', width=edge_widths, edge_color=edge_colors, with_labels=False, edge_cmap=self._cmap) - plt.title(f"Time: {tick}") + plt.title(f"Time: {self._tick}") + self._tick += 1 def visualize(self): self.load_data() @@ -115,6 +135,7 @@ class MatsimVisualizer(): sm.set_array([]) plt.colorbar(sm, ax=ax, label='Traffic Density') - ani = FuncAnimation(fig, self.update, frames=len(self._cumulative_traffic), repeat=False) + self._tick = 0 + ani = FuncAnimation(fig, self.update, frames=len(self._traffic_per_tick), repeat=False) ani.save(f"{self._output_file_path}/traffic_animation.gif", writer='ffmpeg', fps=5) plt.show() diff --git a/requirements.txt b/requirements.txt index 04c221e..f54edaf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,5 @@ geopandas~=0.14.2 shapely~=2.0.2 lxml~=5.1.0 pathlib~=1.0.1 -xmltodict~=0.13.0 networkx~=3.2.1 matplotlib~=3.8.2