Visualizer upgrades
This commit is contained in:
parent
d0bb062e2e
commit
2ab91cfaeb
Binary file not shown.
Binary file not shown.
Binary file not shown.
10
main.py
10
main.py
|
@ -10,7 +10,7 @@ from matsim import Matsim
|
|||
from matsim_visualizer import MatsimVisualizer
|
||||
|
||||
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'
|
||||
usage_format = 'nrcan'
|
||||
energy_systems_format = 'montreal_custom'
|
||||
|
@ -18,7 +18,7 @@ try:
|
|||
out_path = (Path(__file__).parent / 'output_files')
|
||||
city = GeometryFactory('geojson',
|
||||
path=file_path,
|
||||
height_field='citygml_me',
|
||||
height_field='ETAGE_HORS',
|
||||
year_of_construction_field='ANNEE_CONS',
|
||||
function_field='CODE_UTILI',
|
||||
function_to_hub=Dictionaries().montreal_function_to_hub_function).city
|
||||
|
@ -28,7 +28,11 @@ try:
|
|||
Matsim(city, 'output_files').export()
|
||||
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()
|
||||
|
||||
except Exception as ex:
|
||||
|
|
21
matsim.py
21
matsim.py
|
@ -11,7 +11,7 @@ import gzip
|
|||
import shutil
|
||||
|
||||
import geopandas as gpd
|
||||
from shapely.geometry import Point
|
||||
from shapely.geometry import Polygon, MultiPolygon
|
||||
import hub.helpers.constants as cte
|
||||
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"
|
||||
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:
|
||||
def __init__(self, city, output_file_path):
|
||||
|
@ -57,11 +63,6 @@ class Matsim:
|
|||
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]),
|
||||
|
@ -109,11 +110,13 @@ class Matsim:
|
|||
facility['activity'].append(activity_info)
|
||||
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(
|
||||
buildings_shape_data,
|
||||
geometry=[viewport],
|
||||
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
|
||||
xml_content = etree.tostring(facilities_xml, pretty_print=True, encoding='UTF-8').decode('utf-8')
|
||||
|
@ -130,7 +133,7 @@ class Matsim:
|
|||
java_path,
|
||||
"-jar", jar_path,
|
||||
"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"
|
||||
]
|
||||
subprocess.run(command)
|
||||
|
|
|
@ -3,14 +3,14 @@ import gzip
|
|||
from lxml import etree
|
||||
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
|
||||
from matplotlib.animation import FuncAnimation
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
class MatsimVisualizer():
|
||||
def __init__(self, network_file_path, events_file_path, output_file_path, viewport):
|
||||
class MatsimVisualizer:
|
||||
def __init__(self, input_file_path, output_file_path, viewport, show_facilities=False, show_nearby_nodes=False):
|
||||
self._nodes = {}
|
||||
self._links = []
|
||||
self._pos = None
|
||||
|
@ -23,19 +23,34 @@ class MatsimVisualizer():
|
|||
self._viewport = viewport
|
||||
|
||||
self._output_file_path = output_file_path
|
||||
self._network_file_path = network_file_path
|
||||
self._events_file_path = events_file_path
|
||||
self._network_file_path = f"{input_file_path}/output_network.xml.gz"
|
||||
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._traffic_per_tick = defaultdict(lambda: defaultdict(int))
|
||||
self._cmap = cm.viridis
|
||||
self._close_to_facility_nodes = set()
|
||||
|
||||
self._tick = 0
|
||||
self._max_traffic = 0
|
||||
self._max_traffic_link = 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 ====== #
|
||||
with gzip.open(self._network_file_path, 'rb') as file:
|
||||
network_doc = etree.parse(file)
|
||||
|
@ -103,54 +118,86 @@ class MatsimVisualizer():
|
|||
if ticked:
|
||||
self._tick += 1
|
||||
|
||||
def create_graph(self):
|
||||
def _create_graph(self):
|
||||
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:
|
||||
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')
|
||||
|
||||
def setup_color_mapping(self):
|
||||
self.norm = colors.Normalize(vmin=0, vmax=self._max_traffic)
|
||||
if self._show_nearby_nodes:
|
||||
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]
|
||||
|
||||
edge_colors = []
|
||||
edge_widths = []
|
||||
edge_colors_dict = {}
|
||||
edge_widths_dict = {}
|
||||
|
||||
for link in self._links:
|
||||
for link_id, change in traffic_change.items():
|
||||
if link_id == link['id']:
|
||||
link['vehicles'] += change
|
||||
|
||||
edge_colors.append(self._cmap(link['vehicles']))
|
||||
edge_widths.append(1 + self.norm(link['vehicles']) * 10)
|
||||
edge_colors_dict[link['id']] = self._cmap(self.norm(link['vehicles']))
|
||||
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()
|
||||
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}")
|
||||
self._tick += 1
|
||||
|
||||
def visualize(self):
|
||||
self.load_data()
|
||||
self.create_graph()
|
||||
self.setup_color_mapping()
|
||||
def visualize(self, fps=15, colormap='inferno'):
|
||||
self._load_data()
|
||||
self._create_graph()
|
||||
self._setup_color_mapping()
|
||||
|
||||
fig, ax = plt.subplots()
|
||||
|
||||
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.set_array([])
|
||||
plt.colorbar(sm, ax=ax, label='Traffic Density')
|
||||
|
||||
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)
|
||||
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=fps)
|
||||
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
|
||||
|
|
2
output_files/.gitignore
vendored
2
output_files/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
*
|
||||
!.gitignore
|
Loading…
Reference in New Issue
Block a user