如何用OSMNX/Momepy生成带街道连续性的Dual NetworkX图?
生成具备街道连续性的对偶图(Dual Graph)方案(适配OSMNX/Momepy)
传统对偶图会把每条独立路段转成节点,但我们需要把同一条街道的所有路段合并成单个节点,核心思路是先给同街道的路段打上统一标识,再基于这个标识合并对偶图节点。以下是分别用OSMNX和Momepy实现的具体步骤:
用OSMNX实现
步骤1:获取街道网络并标记同街道路段
先拉取目标区域的街道网络,提取路段数据后,给同属一条街道的路段分配统一的street_id(处理无名道路和重名情况):
import osmnx as ox import networkx as nx import geopandas as gpd import pandas as pd # 拉取目标区域的驾车网络(以曼哈顿为例) G = ox.graph_from_place("Manhattan, New York, USA", network_type="drive") # 把网络转成GeoDataFrame,只保留路段数据 edges = ox.graph_to_gdfs(G, nodes=False, edges=True) # 处理无名道路:给每条无名路分配唯一ID edges["street_id"] = edges["name"].fillna(f"unnamed_{edges.index}") # 优化重名:结合道路编号(ref)生成唯一标识,避免重名街道被错误合并 edges["street_id"] = edges.apply( lambda x: f"{x['name']}_{x['ref']}" if pd.notna(x["ref"]) else x["street_id"], axis=1 )
步骤2:构建初始对偶图
先把每个路段作为对偶图的节点,再根据原始网络的交叉口(节点),给共享同一交叉口的路段节点添加连接边:
# 初始化有向对偶图(如果不需要方向,改用nx.Graph) dual_G = nx.DiGraph() # 给对偶图添加节点:每个路段对应一个节点,存储street_id和几何信息 for idx, row in edges.iterrows(): dual_G.add_node(idx, street_id=row["street_id"], geometry=row["geometry"]) # 记录每个原始交叉口节点连接的所有路段 node_edges = {} for u, v, key in G.edges(keys=True): node_edges.setdefault(u, []).append((u, v, key)) node_edges.setdefault(v, []).append((u, v, key)) # 遍历每个交叉口,给共享该交叉口的路段节点添加边 for node, edge_list in node_edges.items(): if len(edge_list) >= 2: # 两两连接该交叉口下的所有路段节点 for i in range(len(edge_list)): for j in range(i + 1, len(edge_list)): edge1_key = edge_list[i][2] edge2_key = edge_list[j][2] dual_G.add_edge(edge1_key, edge2_key, intersection_node=node)
步骤3:合并同街道的节点
用NetworkX的contracted_nodes方法,把同一street_id下的所有节点合并成一个,同时更新合并后的街道几何:
# 按street_id分组,获取每组对应的路段节点列表 street_groups = edges.groupby("street_id").index.tolist() # 遍历每组,合并同街道节点 for street_id, node_list in street_groups.items(): if len(node_list) > 1: # 选第一个节点作为主节点,合并其他节点 main_node = node_list[0] for node in node_list[1:]: dual_G = nx.contracted_nodes(dual_G, main_node, node, self_loops=False) # 合并主节点和被合并节点的几何,得到整条街道的连续几何 merged_geom = dual_G.nodes[main_node]["geometry"].union(dual_G.nodes[node]["geometry"]) dual_G.nodes[main_node]["geometry"] = merged_geom
用Momepy实现(更适配城市形态分析)
Momepy专门针对城市网络设计,处理街道合并和对偶图更高效:
步骤1:加载并合并同街道路段
先获取街道数据,基于name和ref合并同一条街道的所有路段:
import momepy import geopandas as gpd import networkx as nx import pandas as pd # 从OSM拉取目标区域的驾车网络 gdf = momepy.download_osm("Manhattan, New York, USA", network_type="driving") # 生成唯一street_id,处理无名和重名情况 gdf["street_id"] = gdf.apply( lambda x: f"{x['name']}_{x['ref']}" if pd.notna(x["ref"]) else x["name"], axis=1 ) gdf["street_id"] = gdf["street_id"].fillna(f"unnamed_{gdf.index}") # 按street_id合并路段,得到整条街道的连续几何 merged_streets = gdf.dissolve( by="street_id", aggfunc={"geometry": "union", "highway": "first"} )
步骤2:构建基于合并后街道的对偶图
直接以合并后的整条街道为节点,根据街道间的交叉口添加连接边:
# 初始化无向对偶图(需要方向则用nx.DiGraph) dual_G = nx.Graph() # 添加节点:每个合并后的街道对应一个节点 for idx, row in merged_streets.iterrows(): dual_G.add_node(idx, street_id=idx, geometry=row["geometry"], highway=row["highway"]) # 遍历所有街道对,检查是否有交叉口,有则添加边 for i in range(len(merged_streets)): street1 = merged_streets.iloc[i] for j in range(i + 1, len(merged_streets)): street2 = merged_streets.iloc[j] # 检查两条街道是否相交,且交点是单个点(即交叉口) if street1["geometry"].intersects(street2["geometry"]): intersection = street1["geometry"].intersection(street2["geometry"]) if intersection.type == "Point": dual_G.add_edge(street1.name, street2.name, intersection=intersection)
关键注意事项
- 重名街道处理:仅用
name容易出错,结合ref(道路编号)、highway类型或原始osmid集合生成street_id更准确。 - 无名道路:必须给每条无名道路分配唯一ID,避免不同的无名路被错误合并。
- 方向控制:如果需要保留单向通行信息,使用有向图(
nx.DiGraph);无向图适合分析街道连通性。 - 几何准确性:合并后的街道几何用
union确保是连续的线串,后续可根据需求简化或平滑。
内容的提问来源于stack exchange,提问作者Frôis
相关产品推荐
相关产品推荐

