77standardization of attributes.
88"""
99
10+ import folium
1011import geopandas as gpd
1112import networkx as nx
1213import numpy as np
@@ -21,8 +22,7 @@ def get_cartography(
2122 consolidate_intersections : bool | float = 10 ,
2223 dead_ends : bool = False ,
2324 infer_speeds : bool = False ,
24- return_type : str = "gdfs" ,
25- ) -> tuple | nx .DiGraph :
25+ ) -> tuple [nx .DiGraph , gpd .GeoDataFrame , gpd .GeoDataFrame ]:
2626 """
2727 Retrieves and processes cartography data for a specified place using OpenStreetMap data.
2828
@@ -44,16 +44,14 @@ def get_cartography(
4444 infer_speeds (bool, optional): Whether to infer edge speeds based on road types. Defaults to False.
4545 If True, calls ox.routing.add_edge_speeds using np.nanmedian as aggregation function.
4646 Finally, the "maxspeed" attribute is replaced with the inferred "speed_kph", and the "travel_time" attribute is computed.
47- return_type (str, optional): Type of return value. Options are "gdfs" (GeoDataFrames) or
48- "graph" (NetworkX DiGraph). Defaults to "gdfs".
4947
5048 Returns:
51- tuple | nx.DiGraph: If return_type is "gdfs", returns a tuple containing two GeoDataFrames:
49+ tuple[nx.DiGraph, gpd.GeoDataFrame, gpd.GeoDataFrame]: Returns a tuple containing:
50+ - NetworkX DiGraph with standardized attributes.
5251 - gdf_edges: GeoDataFrame with processed edge data, including columns like 'source',
5352 'target', 'nlanes', 'type', 'name', 'id', and 'geometry'.
5453 - gdf_nodes: GeoDataFrame with processed node data, including columns like 'id', 'type',
5554 and 'geometry'.
56- If return_type is "graph", returns the NetworkX DiGraph with standardized attributes.
5755 """
5856 if bbox is None and place_name is None :
5957 raise ValueError ("Either place_name or bbox must be provided." )
@@ -223,32 +221,26 @@ def get_cartography(
223221 ): # Check for NaN
224222 G .nodes [node ]["type" ] = "N/A"
225223
226- # Return graph or GeoDataFrames based on return_type
227- if return_type == "graph" :
228- return G
229- elif return_type == "gdfs" :
230- # Convert back to MultiDiGraph temporarily for ox.graph_to_gdfs compatibility
231- gdf_nodes , gdf_edges = ox .graph_to_gdfs (nx .MultiDiGraph (G ))
224+ # Convert back to MultiDiGraph temporarily for ox.graph_to_gdfs compatibility
225+ gdf_nodes , gdf_edges = ox .graph_to_gdfs (nx .MultiDiGraph (G ))
232226
233- # Reset index and drop unnecessary columns (id, source, target already exist from graph)
234- gdf_edges .reset_index (inplace = True )
235- # Move the "id" column to the beginning
236- id_col = gdf_edges .pop ("id" )
237- gdf_edges .insert (0 , "id" , id_col )
227+ # Reset index and drop unnecessary columns (id, source, target already exist from graph)
228+ gdf_edges .reset_index (inplace = True )
229+ # Move the "id" column to the beginning
230+ id_col = gdf_edges .pop ("id" )
231+ gdf_edges .insert (0 , "id" , id_col )
238232
239- # Ensure length is float
240- gdf_edges ["length" ] = gdf_edges ["length" ].astype (float )
233+ # Ensure length is float
234+ gdf_edges ["length" ] = gdf_edges ["length" ].astype (float )
241235
242- gdf_edges .drop (columns = ["u" , "v" , "key" ], inplace = True , errors = "ignore" )
236+ gdf_edges .drop (columns = ["u" , "v" , "key" ], inplace = True , errors = "ignore" )
243237
244- # Reset index for nodes
245- gdf_nodes .reset_index (inplace = True )
246- gdf_nodes .drop (columns = ["y" , "x" ], inplace = True , errors = "ignore" )
247- gdf_nodes .rename (columns = {"osmid" : "id" }, inplace = True )
238+ # Reset index for nodes
239+ gdf_nodes .reset_index (inplace = True )
240+ gdf_nodes .drop (columns = ["y" , "x" ], inplace = True , errors = "ignore" )
241+ gdf_nodes .rename (columns = {"osmid" : "id" }, inplace = True )
248242
249- return gdf_edges , gdf_nodes
250- else :
251- raise ValueError ("Invalid return_type. Choose 'gdfs' or 'graph'." )
243+ return G , gdf_edges , gdf_nodes
252244
253245
254246def graph_from_gdfs (
@@ -460,6 +452,52 @@ def create_manhattan_cartography(
460452 return gdf_edges , gdf_nodes
461453
462454
455+ def to_folium_map (
456+ G : nx .DiGraph ,
457+ which : str = "edges" ,
458+ ) -> folium .Map :
459+ """
460+ Converts a NetworkX DiGraph to a Folium map for visualization.
461+ Args:
462+ G (nx.DiGraph): The input DiGraph.
463+ which (str): Specify whether to visualize 'edges', 'nodes', or 'both'. Defaults to 'edges'.
464+ Returns:
465+ folium.Map: The Folium map with the graph visualized.
466+ """
467+
468+ # Compute mean latitude and longitude for centering the map
469+ mean_lat = np .mean ([data ["geometry" ].y for _ , data in G .nodes (data = True )])
470+ mean_lon = np .mean ([data ["geometry" ].x for _ , data in G .nodes (data = True )])
471+ folium_map = folium .Map (location = [mean_lat , mean_lon ], zoom_start = 13 )
472+
473+ if which in ("edges" , "both" ):
474+ # Add edges to the map
475+ for _ , _ , data in G .edges (data = True ):
476+ line = data .get ("geometry" )
477+ if line :
478+ folium .PolyLine (
479+ locations = [(point [1 ], point [0 ]) for point in line .coords ],
480+ color = "blue" ,
481+ weight = 2 ,
482+ opacity = 0.7 ,
483+ popup = f"Edge ID: { data .get ('id' )} " ,
484+ ).add_to (folium_map )
485+ if which in ("nodes" , "both" ):
486+ # Add nodes to the map
487+ for _ , data in G .nodes (data = True ):
488+ folium .CircleMarker (
489+ location = (data ["geometry" ].y , data ["geometry" ].x ),
490+ radius = 5 ,
491+ color = "red" ,
492+ fill = True ,
493+ fill_color = "red" ,
494+ fill_opacity = 0.7 ,
495+ popup = f"Node ID: { data .get ('id' )} " ,
496+ ).add_to (folium_map )
497+
498+ return folium_map
499+
500+
463501# if __name__ == "__main__":
464502# # Produce data for tests
465503# edges, nodes = get_cartography(
0 commit comments