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
|
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:
|
||||||
|
|
21
matsim.py
21
matsim.py
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
2
output_files/.gitignore
vendored
2
output_files/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
||||||
*
|
|
||||||
!.gitignore
|
|
Loading…
Reference in New Issue
Block a user