Visualizer upgrades

This commit is contained in:
Ruben1729 2024-03-05 15:14:36 -05:00
parent d0bb062e2e
commit 2ab91cfaeb
7 changed files with 95 additions and 43 deletions

Binary file not shown.

10
main.py
View File

@ -10,7 +10,7 @@ from matsim import Matsim
from matsim_visualizer import MatsimVisualizer from matsim_visualizer import MatsimVisualizer
try: try:
file_path = (Path(__file__).parent / 'input_files' / 'summerschool_all_buildings.geojson') file_path = (Path(__file__).parent / 'input_files' / 'mtlbld_v4.geojson')
construction_format = 'nrcan' construction_format = 'nrcan'
usage_format = 'nrcan' usage_format = 'nrcan'
energy_systems_format = 'montreal_custom' energy_systems_format = 'montreal_custom'
@ -18,7 +18,7 @@ try:
out_path = (Path(__file__).parent / 'output_files') out_path = (Path(__file__).parent / 'output_files')
city = GeometryFactory('geojson', city = GeometryFactory('geojson',
path=file_path, path=file_path,
height_field='citygml_me', height_field='ETAGE_HORS',
year_of_construction_field='ANNEE_CONS', year_of_construction_field='ANNEE_CONS',
function_field='CODE_UTILI', function_field='CODE_UTILI',
function_to_hub=Dictionaries().montreal_function_to_hub_function).city function_to_hub=Dictionaries().montreal_function_to_hub_function).city
@ -28,7 +28,11 @@ try:
Matsim(city, 'output_files').export() Matsim(city, 'output_files').export()
MatSimEngine('output_files/Montreal_config.xml').run() MatSimEngine('output_files/Montreal_config.xml').run()
visualizer = MatsimVisualizer('output_files/Montreal/output_network.xml.gz', 'output_files/Montreal/output_events.xml.gz', 'output_files', (3830263.051512, 6043256.513982, 3866288.881858, 6012487.188767)) visualizer = MatsimVisualizer(
'output_files/Montreal',
'output_files',
(3854635.608067, 6040255.047194, 3855550.392442, 6039416.667303)
)
visualizer.visualize() visualizer.visualize()
except Exception as ex: except Exception as ex:

View File

@ -11,7 +11,7 @@ import gzip
import shutil import shutil
import geopandas as gpd import geopandas as gpd
from shapely.geometry import Point from shapely.geometry import Polygon, MultiPolygon
import hub.helpers.constants as cte import hub.helpers.constants as cte
from lxml import etree from lxml import etree
@ -19,6 +19,12 @@ CONFIG_DTD = "http://www.matsim.org/files/dtd/config_v2.dtd"
FACILITIES_DTD = "http://www.matsim.org/files/dtd/facilities_v1.dtd" FACILITIES_DTD = "http://www.matsim.org/files/dtd/facilities_v1.dtd"
POPULATION_DTD = "http://www.matsim.org/files/dtd/population_v5.dtd" POPULATION_DTD = "http://www.matsim.org/files/dtd/population_v5.dtd"
# EPSG 26911 reprojected
MONTREAL_TOP = 6070286.884310
MONTREAL_LEFT = 3829009.402623
MONTREAL_BOTTOM = 6007714.543057
MONTREAL_RIGHT = 3863759.595656
class Matsim: class Matsim:
def __init__(self, city, output_file_path): def __init__(self, city, output_file_path):
@ -57,11 +63,6 @@ class Matsim:
facilities_xml = etree.Element('facilities', name=self._facilities['name']) facilities_xml = etree.Element('facilities', name=self._facilities['name'])
for building in self._city.buildings: 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 = { facility = {
'id': building.name, 'id': building.name,
'x': str(building.centroid[0]), 'x': str(building.centroid[0]),
@ -109,11 +110,13 @@ class Matsim:
facility['activity'].append(activity_info) facility['activity'].append(activity_info)
self._facilities['facility'].append(facility) self._facilities['facility'].append(facility)
viewport = Polygon([(MONTREAL_LEFT, MONTREAL_TOP), (MONTREAL_RIGHT, MONTREAL_TOP), (MONTREAL_RIGHT, MONTREAL_BOTTOM), (MONTREAL_LEFT, MONTREAL_BOTTOM)])
gdf = gpd.GeoDataFrame( gdf = gpd.GeoDataFrame(
buildings_shape_data, geometry=[viewport],
crs=self._city.srs_name crs=self._city.srs_name
) )
gdf.to_file("input_files/buildings_shapefile.shp") gdf.to_file(f"{self._output_file_path}/buildings_shapefile.shp")
# Write xml content to file # Write xml content to file
xml_content = etree.tostring(facilities_xml, pretty_print=True, encoding='UTF-8').decode('utf-8') xml_content = etree.tostring(facilities_xml, pretty_print=True, encoding='UTF-8').decode('utf-8')
@ -130,7 +133,7 @@ class Matsim:
java_path, java_path,
"-jar", jar_path, "-jar", jar_path,
"input_files/merged-network.osm.pbf", "input_files/merged-network.osm.pbf",
"input_files/buildings_shapefile.shp", f"{self._output_file_path}/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) subprocess.run(command)

View File

@ -3,14 +3,14 @@ import gzip
from lxml import etree from lxml import etree
import networkx as nx import networkx as nx
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from collections import defaultdict
import matplotlib.cm as cm import matplotlib.cm as cm
import matplotlib.colors as colors import matplotlib.colors as colors
from matplotlib.animation import FuncAnimation
from collections import defaultdict
class MatsimVisualizer(): class MatsimVisualizer:
def __init__(self, network_file_path, events_file_path, output_file_path, viewport): def __init__(self, input_file_path, output_file_path, viewport, show_facilities=False, show_nearby_nodes=False):
self._nodes = {} self._nodes = {}
self._links = [] self._links = []
self._pos = None self._pos = None
@ -23,19 +23,34 @@ class MatsimVisualizer():
self._viewport = viewport self._viewport = viewport
self._output_file_path = output_file_path self._output_file_path = output_file_path
self._network_file_path = network_file_path self._network_file_path = f"{input_file_path}/output_network.xml.gz"
self._events_file_path = events_file_path self._events_file_path = f"{input_file_path}/output_events.xml.gz"
self._facilities_file_path = f"{input_file_path}/output_facilities.xml.gz"
self._G = nx.Graph() self._G = nx.Graph()
self._traffic_per_tick = defaultdict(lambda: defaultdict(int)) self._traffic_per_tick = defaultdict(lambda: defaultdict(int))
self._cmap = cm.viridis self._close_to_facility_nodes = set()
self._tick = 0 self._tick = 0
self._max_traffic = 0 self._max_traffic = 0
self._max_traffic_link = None self._max_traffic_link = None
self._max_traffic_tick = None self._max_traffic_tick = None
def load_data(self): self._show_facilities = show_facilities
self._show_nearby_nodes = show_nearby_nodes
def _load_data(self):
# ====== LOAD FACILITIES ====== #
if self._show_facilities:
with gzip.open(self._facilities_file_path, 'rb') as file:
facilities_doc = etree.parse(file)
for node in facilities_doc.xpath('/facilities/facility'):
x = float(node.get('x'))
y = float(node.get('y'))
if self._viewport[0] < x < self._viewport[2] and self._viewport[1] > y > self._viewport[3]:
self._nodes[f"facility_{node.get('id')}"] = (x, y)
# ====== LOAD NETWORK ====== # # ====== LOAD NETWORK ====== #
with gzip.open(self._network_file_path, 'rb') as file: with gzip.open(self._network_file_path, 'rb') as file:
network_doc = etree.parse(file) network_doc = etree.parse(file)
@ -103,54 +118,86 @@ class MatsimVisualizer():
if ticked: if ticked:
self._tick += 1 self._tick += 1
def create_graph(self): def _create_graph(self):
for node_id, coords in self._nodes.items(): for node_id, coords in self._nodes.items():
self._G.add_node(node_id, pos=coords) self._G.add_node(node_id, pos=coords, is_facility=node_id.startswith('facility_'))
for link in self._links: for link in self._links:
self._G.add_edge(link['from'], link['to']) self._G.add_edge(link['from'], link['to'], id=link['id'])
self._pos = nx.get_node_attributes(self._G, 'pos') self._pos = nx.get_node_attributes(self._G, 'pos')
def setup_color_mapping(self): if self._show_nearby_nodes:
self.norm = colors.Normalize(vmin=0, vmax=self._max_traffic) self._identify_close_nodes(radius=100) # TODO: move to load data
def update(self, frame): def _identify_close_nodes(self, radius):
facility_coords = {node: self._pos[node] for node in self._G.nodes if self._G.nodes[node].get('is_facility')}
for node, coords in self._pos.items():
if self._G.nodes[node].get('is_facility'):
continue
for facility_node, facility_coord in facility_coords.items():
if self._euclidean_distance(coords, facility_coord) <= radius:
self._close_to_facility_nodes.add(node)
break
def _setup_color_mapping(self):
self.norm = colors.Normalize(vmin=0, vmax=self._max_traffic/2)
def _update(self, frame):
traffic_change = self._traffic_per_tick[self._tick] traffic_change = self._traffic_per_tick[self._tick]
edge_colors = [] edge_colors_dict = {}
edge_widths = [] edge_widths_dict = {}
for link in self._links: for link in self._links:
for link_id, change in traffic_change.items(): for link_id, change in traffic_change.items():
if link_id == link['id']: if link_id == link['id']:
link['vehicles'] += change link['vehicles'] += change
edge_colors.append(self._cmap(link['vehicles'])) edge_colors_dict[link['id']] = self._cmap(self.norm(link['vehicles']))
edge_widths.append(1 + self.norm(link['vehicles']) * 10) edge_widths_dict[link['id']] = 1 + self.norm(link['vehicles'])
edgelist = [(link['from'], link['to']) for link in self._links]
edge_colors = [edge_colors_dict[link["id"]] for link in self._links]
edge_widths = [edge_widths_dict[link["id"]] for link in self._links]
plt.cla() 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)
# Draw Facilities
if self._show_facilities:
facility_nodes = {node: pos for node, pos in self._pos.items() if self._G.nodes[node]['is_facility']}
nx.draw_networkx_nodes(self._G, facility_nodes, facility_nodes.keys(), node_size=1, node_color='red')
# Draw Network
if self._show_nearby_nodes:
close_nodes = {node: self._pos[node] for node in self._close_to_facility_nodes if node in self._pos}
nx.draw_networkx_nodes(self._G, self._pos, close_nodes.keys(), node_size=1, node_color='blue')
nx.draw_networkx_edges(self._G, self._pos, edgelist=edgelist, width=edge_widths, edge_color=edge_colors, edge_cmap=self._cmap)
plt.title(f"Time: {self._tick}") plt.title(f"Time: {self._tick}")
self._tick += 1 self._tick += 1
def visualize(self): def visualize(self, fps=15, colormap='inferno'):
self.load_data() self._load_data()
self.create_graph() self._create_graph()
self.setup_color_mapping() self._setup_color_mapping()
fig, ax = plt.subplots() fig, ax = plt.subplots()
ax.set_aspect((self._max_x - self._min_x) / (self._max_y - self._min_y)) ax.set_aspect((self._max_x - self._min_x) / (self._max_y - self._min_y))
if hasattr(cm, colormap):
self._cmap = getattr(cm, colormap)
else:
print(f"Colormap '{colormap}' not recognized. Falling back to 'inferno'. Please select from: 'inferno', 'plasma', 'magma', 'spring', 'summer', 'autumn', or 'winter'.")
self._cmap = cm.inferno
sm = plt.cm.ScalarMappable(cmap=self._cmap, norm=self.norm) sm = plt.cm.ScalarMappable(cmap=self._cmap, norm=self.norm)
sm.set_array([]) sm.set_array([])
plt.colorbar(sm, ax=ax, label='Traffic Density') plt.colorbar(sm, ax=ax, label='Traffic Density')
self._tick = 0 self._tick = 0
ani = FuncAnimation(fig, self.update, frames=len(self._traffic_per_tick), repeat=False) 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) ani.save(f"{self._output_file_path}/traffic_animation.gif", writer='ffmpeg', fps=fps)
plt.show() plt.show()
print(f"Largest amount of vehicles ({self._max_traffic}) seen at {self._max_traffic_tick} on link {self._max_traffic_link}") def _euclidean_distance(self, coord1, coord2):
return ((coord1[0] - coord2[0]) ** 2 + (coord1[1] - coord2[1]) ** 2) ** 0.5

View File

@ -1,2 +0,0 @@
*
!.gitignore