用Python玩转城市路网:OSMnx一键下载北京/上海街道数据并可视化分析(附完整代码)
当你在深夜打开导航软件,看着那些闪烁的路线和拥堵提示,是否好奇这些数据背后的秘密?城市路网就像人体的血管系统,承载着整个城市的活力与脉搏。而今天,我们将用Python这把"手术刀",解剖北京、上海这样超级城市的道路网络,揭示那些隐藏在数据中的城市密码。
对于城市规划师、交通工程师或是数据科学爱好者来说,获取和分析城市路网数据曾是一项耗时费力的工作。但有了OSMnx这个Python神器,我们只需几行代码就能下载完整的街道数据,并进行专业的网络分析和可视化。本文将带你从零开始,掌握这套高效的工作流,无论是学术研究还是商业分析,都能游刃有余。
1. 环境准备与OSMnx基础
在开始我们的城市探险之前,需要确保工具包准备就绪。OSMnx建立在几个强大的Python库之上,包括NetworkX用于网络分析,GeoPandas处理地理数据,以及Matplotlib进行可视化。
安装依赖库(推荐使用conda环境):
conda create -n street_analysis python=3.9 conda activate street_analysis conda install -c conda-forge osmnx geopandas matplotlib foliumOSMnx的核心优势在于它简化了从OpenStreetMap获取数据的流程。不同于传统GIS软件需要手动下载和处理数据,OSMnx提供了直观的Python接口。例如,获取北京市中心区域的路网只需一行代码:
import osmnx as ox beijing_graph = ox.graph_from_place("北京市, 中国", network_type="drive")提示:network_type参数支持"drive"(机动车)、"walk"(步行)、"bike"(骑行)三种模式,对应不同的路网筛选条件。
OSMnx下载的数据会自动转换为NetworkX的图结构,同时保留完整的几何信息和属性表。我们可以用以下方法快速查看数据摘要:
ox.basic_stats(beijing_graph)典型输出结果可能包括:
| 指标 | 数值 | 说明 |
|---|---|---|
| 节点数 | 12,345 | 交叉口或道路端点 |
| 边数 | 23,456 | 道路段数量 |
| 平均度 | 3.8 | 每个节点连接的道路数 |
| 街道总长 | 1,234 km | 路网总长度 |
2. 高级数据获取与预处理
实际分析中,我们往往需要更精确地控制下载区域和数据类型。OSMnx提供了多种灵活的数据获取方式:
2.1 按行政区划获取
# 获取上海市黄浦区路网 huangpu = ox.graph_from_place("黄浦区, 上海市, 中国", network_type="walk")2.2 按地理边界框获取
# 北京五环范围(近似) north, south, east, west = 40.02, 39.87, 116.47, 116.25 wuhuan = ox.graph_from_bbox(north, south, east, west, network_type="drive")2.3 按点半径范围获取
# 以上海人民广场为中心,3公里半径 renmin_square = ox.graph_from_point((31.2304, 121.4737), dist=3000)获取原始数据后,通常需要进行清洗和处理:
# 清理孤立节点和重复边 cleaned_graph = ox.consolidate_intersections( wuhuan, tolerance=15, # 合并15米内的交叉口 rebuild_graph=True ) # 计算街道长度并添加为边属性 wuhuan = ox.add_edge_lengths(wuhuan)对于大型城市,下载和处理可能耗时较长,建议将处理好的数据保存:
# 保存为GraphML格式(保留所有属性) ox.save_graphml(wuhuan, "beijing_wuhuan.graphml") # 也可导出为GIS标准格式 ox.io.save_graph_shapefile(wuhuan, filepath="./beijing_roads")3. 路网分析与指标计算
有了干净的路网数据,我们就可以进行各种专业分析了。NetworkX提供了丰富的图论算法实现,结合OSMnx的扩展功能,能计算出许多有实际意义的城市指标。
3.1 连通性分析
import networkx as nx # 检查是否为连通图 is_connected = nx.is_strongly_connected(wuhuan) print(f"路网是否强连通: {is_connected}") # 如果不连通,获取最大连通子图 largest_cc = max(nx.strongly_connected_components(wuhuan), key=len) main_graph = wuhuan.subgraph(largest_cc)3.2 中心性指标计算
中心性指标能识别路网中的关键节点,对交通规划尤为重要:
# 计算接近中心性 closeness = nx.closeness_centrality(main_graph, distance="length") # 计算介数中心性 betweenness = nx.betweenness_centrality(main_graph, weight="length") # 将结果添加到节点属性 nx.set_node_attributes(main_graph, closeness, "closeness") nx.set_node_attributes(main_graph, betweenness, "betweenness")3.3 街道方向分析
城市街道的走向往往反映了规划的特点:
# 计算所有街道的方位角 bearings = ox.bearing.calculate_bearing( ox.utils_graph.graph_to_gdfs(main_graph, nodes=False) ) # 生成极坐标直方图 fig, ax = ox.bearing.plot_orientation( main_graph, title="北京市五环内街道方向分布", color="#003366", show=False ) ax.set_theta_zero_location("N") # 北方为0度 ax.set_theta_direction(-1) # 顺时针方向典型城市可能呈现以下方向模式:
- 网格状规划城市(如纽约):在0°、90°方向有明显峰值
- 有机生长城市(如伦敦):分布相对均匀
- 混合型城市(如北京):主方向明显但分布较广
4. 高级可视化技巧
静态图表难以展现复杂的地理数据,我们需要更丰富的可视化手段。
4.1 带热力图的静态可视化
# 创建Figure和Axes对象 fig, ax = ox.plot_graph( main_graph, node_size=0, # 不显示节点 edge_linewidth=0.5, show=False, close=False ) # 提取节点位置和中心性值 nodes = ox.utils_graph.graph_to_gdfs(main_graph, edges=False) sc = ax.scatter( nodes.x, nodes.y, c=nodes["betweenness"], s=5, cmap="plasma", alpha=0.7, zorder=3 ) # 添加颜色条 cbar = fig.colorbar(sc, ax=ax, shrink=0.5) cbar.set_label("介数中心性")4.2 交互式地图
使用Folium创建可缩放的交互地图:
import folium # 计算图的地理中心 center = ox.utils_geo.graph_to_gdfs(main_graph, edges=False).unary_union.centroid m = folium.Map(location=[center.y, center.x], zoom_start=13) # 添加路网 ox.folium.plot_graph_folium(main_graph, graph_map=m, color="gray", weight=1) # 添加中心性热力图 points = ox.utils_geo.graph_to_gdfs(main_graph, edges=False) for _, row in points.iterrows(): folium.CircleMarker( location=(row.y, row.x), radius=2, color=None, fill=True, fill_color="red", fill_opacity=row["betweenness"]*10, popup=f"介数中心性: {row['betweenness']:.4f}" ).add_to(m) # 保存为HTML m.save("beijing_road_analysis.html")4.3 3D可视化
使用PyVista创建三维效果:
import pyvista as pv from pyvista import examples # 将路网转换为3D线条 edges = ox.utils_graph.graph_to_gdfs(main_graph, nodes=False) plotter = pv.Plotter() for _, edge in edges.iterrows(): line = pv.Line( pointa=(edge.geometry.coords[0][0], edge.geometry.coords[0][1], 0), pointb=(edge.geometry.coords[-1][0], edge.geometry.coords[-1][1], 0) ) plotter.add_mesh(line, color="white", line_width=0.3) # 添加地形背景(示例) dem = examples.download_crater_topo() plotter.add_mesh(dem, cmap="gist_earth") plotter.show()5. 实际应用案例:北京vs上海路网对比
让我们通过一个完整案例,对比两个超级城市的路网特征。
5.1 数据获取与清洗
# 下载数据 shanghai = ox.graph_from_place("上海市中心, 上海市, 中国", network_type="drive") beijing = ox.graph_from_place("北京市中心, 北京市, 中国", network_type="drive") # 标准化处理 def standardize_graph(G): G = ox.add_edge_lengths(G) G = ox.add_edge_speeds(G) # 添加默认速度 G = ox.add_edge_travel_times(G) # 计算行程时间 return ox.consolidate_intersections(G, tolerance=20) shanghai = standardize_graph(shanghai) beijing = standardize_graph(beijing)5.2 基础指标对比
# 计算统计指标 sh_stats = ox.basic_stats(shanghai) bj_stats = ox.basic_stats(beijing) # 创建对比表格 comparison = { "指标": ["节点数", "边数", "平均节点度", "街道总长(km)", "平均街道长度(m)"], "上海": [ sh_stats["n"], sh_stats["m"], round(sh_stats["average_degree"], 2), round(sh_stats["street_length_total"]/1000, 1), round(sh_stats["street_length_avg"], 1) ], "北京": [ bj_stats["n"], bj_stats["m"], round(bj_stats["average_degree"], 2), round(bj_stats["street_length_total"]/1000, 1), round(bj_stats["street_length_avg"], 1) ] } # 使用Pandas显示美观表格 import pandas as pd pd.DataFrame(comparison).set_index("指标")5.3 拓扑结构差异
绘制两个城市的街道方向分布:
import matplotlib.pyplot as plt fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5), subplot_kw={"polar": True}) # 上海 ox.bearing.plot_orientation( shanghai, ax=ax1, title="上海市中心街道方向", color="#e63946", show=False ) # 北京 ox.bearing.plot_orientation( beijing, ax=ax2, title="北京市中心街道方向", color="#457b9d", show=False ) plt.tight_layout()从方向分布图中,我们通常可以发现:
- 上海由于历史原因,中心城区呈现更有机的放射状模式
- 北京受传统城市规划影响,东西-南北走向更为明显
5.4 可达性分析
计算从中心点到周边区域的行程时间:
# 选择中心点(上海人民广场和北京天安门) sh_center = ox.distance.nearest_nodes(shanghai, 121.4737, 31.2304) bj_center = ox.distance.nearest_nodes(beijing, 116.3974, 39.9087) # 计算等时圈 def calculate_isochrones(G, center, trip_times=[5,10,15]): isochrones = [] for time in sorted(trip_times, reverse=True): subgraph = nx.ego_graph( G, center, radius=time*60, # 转换为秒 distance="travel_time" ) isochrones.append(subgraph) return isochrones sh_isochrones = calculate_isochrones(shanghai, sh_center) bj_isochrones = calculate_isochrones(beijing, bj_center)可视化等时圈:
# 创建底图 sh_map = ox.plot_graph(shanghai, show=False, close=False, node_size=0) bj_map = ox.plot_graph(beijing, show=False, close=False, node_size=0) # 绘制等时圈 colors = ["#ff0000", "#ff9900", "#ffff00"] for i, (sh_subg, bj_subg) in enumerate(zip(sh_isochrones, bj_isochrones)): # 上海 sh_nodes = ox.utils_graph.graph_to_gdfs(sh_subg, edges=False) sh_map.scatter( sh_nodes.x, sh_nodes.y, color=colors[i], s=3, label=f"{5*(i+1)}分钟可达" ) # 北京 bj_nodes = ox.utils_graph.graph_to_gdfs(bj_subg, edges=False) bj_map.scatter( bj_nodes.x, bj_nodes.y, color=colors[i], s=3, label=f"{5*(i+1)}分钟可达" ) sh_map.legend() bj_map.legend()这种分析可以直观展示不同城市的交通效率,为商业选址、公共服务设施规划等提供参考。