当前位置: 首页 > news >正文

第7章:网络分析与可达性评估

7.1 引言:把“路网”变成可计算的图

网络分析把道路等线性要素转换为图(节点/边),用“成本”来度量出行(距离、时间、费用)。本章在原有基础上扩展:构建多模式(步行/驾车/骑行)可达性比较、生成等时圈服务区、计算 OD 旅行时间矩阵,并给出工程化与质量清单,满足更高的深度与行数要求。

7.2 学习目标

  • 理解路网图建模与权重设置,掌握最短路径与服务区(等时圈)
  • 能进行多模式可达性比较并输出指标(覆盖人口、平均时间、面积比例)
  • 会构建 OD 旅行时间矩阵,支持选址评估与公平性分析
  • 掌握工程化实践(数据质量、缓存/索引、参数记录)与 QA 检查

7.3 先修要求

  • 熟悉矢量数据与坐标系;理解节点/边的图结构
  • 具备 osmnx/networkx 基础;了解 geopandas 与制图表达

7.4 方法与流程(Mermaid)

flowchart LRA[输入:OSM道路/模式参数/设施点] --> B[构建图与权重]B --> C[最短路径/等时圈服务区]C --> D[OD 矩阵与可达性指标]D --> E[可视化与导出]E --> F[质量检查与说明文档]

7.5 核心概念与工程要点

  • 图构建:节点为路口,边为路段;方向性/单行/转弯约束决定路径合法性
  • 权重选择:驾车用 travel_time,步行/骑行可用距离或估计速度;统一单位(秒/米)
  • 算法选择:Dijkstra(非负权重最短路径)、A*(启发式加速)、CH(收缩层级,适合大路网)
  • 服务区构面:基于可达节点集合生成面(alpha-shape/栅格等值);凸包仅为近似
  • 数据质量:断裂与孤岛、缺失限速、单行与转向限制;需预处理或明确边界假设

7.6 代码示例一:驾车模式等时圈(10/20/30分钟)

import osmnx as ox
import networkx as nx
import geopandas as gpd
from shapely.geometry import Pointplace = "Shanghai, China"
G = ox.graph_from_place(place, network_type="drive")
G = ox.add_edge_speeds(G)
G = ox.add_edge_travel_times(G)origin_latlon = (31.2304, 121.4737)  # 上海中心(示意)
origin_node = ox.distance.nearest_nodes(G, origin_latlon[1], origin_latlon[0])lengths = nx.single_source_dijkstra_path_length(G, origin_node, weight="travel_time")
thresholds = [10*60, 20*60, 30*60]  # 秒# 收集各阈值下的可达节点坐标
nodes_df = gpd.GeoDataFrame({"node": list(lengths.keys())},geometry=[Point((G.nodes[n]["x"], G.nodes[n]["y"])) for n in lengths.keys()],crs="EPSG:4326"
).to_crs(epsg=3857)iso_polys = []
for t in thresholds:pts = nodes_df[nodes_df["node"].isin([n for n, tt in lengths.items() if tt <= t])].geometryif len(pts) >= 3:poly = gpd.GeoSeries(pts).unary_union.convex_hulliso_polys.append({"threshold_s": t, "geometry": poly})iso_gdf = gpd.GeoDataFrame(iso_polys, crs="EPSG:3857")
iso_gdf.to_file("outputs/drive_isochrones_10_20_30min.geojson", driver="GeoJSON")

说明:凸包会“鼓包”覆盖范围,适合初步可视化。更准确的服务区边界可使用 alpha-shape 或基于栅格的等值面方法。

7.7 代码示例二:多模式可达性比较(步行 vs 驾车)

import osmnx as ox
import networkx as nx
import geopandas as gpdplace = "Shanghai, China"
origin_latlon = (31.2304, 121.4737)# 步行网络(按距离近似时间,速度 ~ 4.5 km/h)
Gw = ox.graph_from_place(place, network_type="walk")
for u, v, d in Gw.edges(data=True):d["travel_time"] = d.get("length", 0) / (4.5 * 1000 / 3600)  # 秒
origin_w = ox.distance.nearest_nodes(Gw, origin_latlon[1], origin_latlon[0])
len_w = nx.single_source_dijkstra_path_length(Gw, origin_w, weight="travel_time")# 驾车网络(使用限速计算时间)
Gd = ox.graph_from_place(place, network_type="drive")
Gd = ox.add_edge_speeds(Gd)
Gd = ox.add_edge_travel_times(Gd)
origin_d = ox.distance.nearest_nodes(Gd, origin_latlon[1], origin_latlon[0])
len_d = nx.single_source_dijkstra_path_length(Gd, origin_d, weight="travel_time")# 10分钟等时圈面积比较
def isochrone_area(lengths, G):gdf = gpd.GeoDataFrame({"node": list(lengths.keys())},geometry=[gpd.points_from_xy([G.nodes[n]["x"]], [G.nodes[n]["y"]])[0] for n in lengths.keys()],crs="EPSG:4326").to_crs(epsg=3857)pts = gdf[gdf["node"].isin([n for n, tt in lengths.items() if tt <= 10*60])].geometryif len(pts) < 3:return 0.0return float(gpd.GeoSeries(pts).unary_union.convex_hull.area)area_walk = isochrone_area(len_w, Gw)
area_drive = isochrone_area(len_d, Gd)metrics = gpd.GeoDataFrame([{"mode": "walk", "area_m2": area_walk},{"mode": "drive", "area_m2": area_drive}
])
metrics.to_file("outputs/mode_accessibility_area_10min.geojson", driver="GeoJSON")

说明:步行与驾车可达范围差异明显;在报告中解释速度假设与道路限制(单行/转向)对结果的影响。

7.8 代码示例三:OD 旅行时间矩阵(小规模)

import osmnx as ox
import networkx as nx
import geopandas as gpdplace = "Shanghai, China"
G = ox.graph_from_place(place, network_type="drive")
G = ox.add_edge_speeds(G)
G = ox.add_edge_travel_times(G)# 示例:3 个起点与 3 个终点(经纬度)
origins = [(31.23,121.47),(31.21,121.44),(31.25,121.50)]
targets = [(31.22,121.45),(31.26,121.46),(31.20,121.52)]origin_nodes = [ox.distance.nearest_nodes(G, lon, lat) for lat, lon in origins]
target_nodes = [ox.distance.nearest_nodes(G, lon, lat) for lat, lon in targets]rows = []
for oi, o in enumerate(origin_nodes):lengths = nx.single_source_dijkstra_path_length(G, o, weight="travel_time")for tj, t in enumerate(target_nodes):rows.append({"origin": oi, "target": tj, "tt_seconds": float(lengths.get(t, float("inf")))})mat = gpd.GeoDataFrame(rows)
mat.to_file("outputs/od_travel_time_matrix_3x3.geojson", driver="GeoJSON")

提示:中大规模 OD 建议采用批处理与缓存路径树,或使用 OSRM/pgRouting 等专用引擎。

7.9 性能与工程实践

  • 缓存与复用:图与权重构建成本高,缓存 GraphML 与权重映射;复用最短路径树
  • 范围裁剪:先裁剪研究区道路,减少无关节点与边,提高计算效率
  • 算法加速:选择 A*/CH;对超大图使用专用路由引擎(OSRM/Valhalla/pgRouting)
  • 参数记录:速度假设、限制规则、网络版本与时间,写入元数据便于复审
  • 与人口叠加:将等时圈与人口栅格做分区统计,输出覆盖人口与密度指标

7.10 质量检查清单(QA)

  • 权重单位是否统一(秒/米);限速是否合理;单行/转向是否生效
  • 图是否连通;是否存在孤岛或断裂;关键区域是否可达
  • 服务区构面方法是否说明;是否与真实道路形态一致
  • OD 结果是否合理(样本抽查与可视化路径核对)
  • 输出是否包含必要元数据:模式、速度假设、网络版本、时间戳

7.11 常见错误与排障

  • 成本字段未统一;单位混乱 → 统一单位并在元数据中标注
  • 路网断裂;孤立节点 → 预处理修复,裁剪研究区并检查连通性
  • 服务区边界过粗 → 换用 alpha-shape/栅格等值构面;提高点密度
  • 过度简化或误导 → 在说明中界定边界(未考虑拥堵/红绿灯等),避免误解

7.12 延伸阅读与资源

  • OSRM、Valhalla、pgRouting 文档;交通可达性与公平性研究综述
  • OSMnx/NetworkX 指南;等时圈构面方法(alpha-shape、栅格等值)

7.14 公共交通(GTFS)换乘成本建模(最小示例)

本小节基于最小 GTFS 样例(位于 gis_examples/datasets/gtfs_minimal/),展示如何将 GTFS 时间表构造成图,近似考虑换乘惩罚与乘车时间,计算从起点站到终点站的最短时间路径。

数据文件:stops.txtroutes.txttrips.txtstop_times.txtcalendar.txt

核心思路:

  • 站点作为图节点(stop_id
  • 同一 trip 的相邻停靠构成“乘车边”,权重为时间差(秒)
  • 同一站点不同 trip 之间加入“换乘边”,权重为固定惩罚(例如 300 秒)
  • 使用 Dijkstra 在“乘车+换乘”合成图上求最短时间路径
import pandas as pd
import networkx as nxgtfs_dir = "gis_examples/datasets/gtfs_minimal/"
stops = pd.read_csv(gtfs_dir + "stops.txt")
trips = pd.read_csv(gtfs_dir + "trips.txt")
stop_times = pd.read_csv(gtfs_dir + "stop_times.txt")def hhmm_to_seconds(t):h, m, s = map(int, t.split(":"))return h*3600 + m*60 + s# 构建图:节点为 stop_id
G = nx.DiGraph()
for _, row in stops.iterrows():G.add_node(row["stop_id"], name=row["stop_name"])  # 可加入坐标辅助可视化# 乘车边:同一trip相邻stop,以时间差为权重
for trip_id, group in stop_times.groupby("trip_id"):grp = group.sort_values("stop_sequence")for i in range(len(grp)-1):a = grp.iloc[i]b = grp.iloc[i+1]dt = hhmm_to_seconds(b["arrival_time"]) - hhmm_to_seconds(a["departure_time"])if dt < 0:  # 跨日情况可按需处理continueG.add_edge(a["stop_id"], b["stop_id"], weight=float(dt), kind="ride", trip_id=trip_id)# 换乘边:同站点不同trip之间给定固定惩罚(此最小样例只有一条trip,演示用)
TRANSFER_PENALTY = 300.0  # 5分钟
stop_to_trips = stop_times.groupby("stop_id")["trip_id"].unique()
for stop_id, trips_at_stop in stop_to_trips.items():trips_at_stop = list(trips_at_stop)for i in range(len(trips_at_stop)):for j in range(i+1, len(trips_at_stop)):# 在相同stop上通过“虚拟换乘”边体现换乘成本G.add_edge(stop_id, stop_id, weight=TRANSFER_PENALTY, kind="transfer",from_trip=trips_at_stop[i], to_trip=trips_at_stop[j])# 计算最短时间:从 S1 到 S3
path = nx.shortest_path(G, source="S1", target="S3", weight="weight")
cost = nx.shortest_path_length(G, source="S1", target="S3", weight="weight")
print("best path:", path)
print("travel time (seconds):", cost)

说明与局限:

  • 以上示例为静态近似(未处理具体出发时刻与等待时间),适用于入门演示。
  • 若考虑“出发时间”“班次等待”“真实换乘”,需构建时间依赖图(节点扩展到 (stop_id, time) 或使用 RAPTOR/CSA 算法),或采用 OSRM/Valhalla 的 Transit 扩展。

流程图(GTFS 最小示例)

flowchart TDA[输入:GTFS stops/trips/stop_times] --> B[构建节点:stop_id]B --> C[乘车边:同一trip相邻stop\n权重=到达-发车秒差]B --> D[换乘边:同站点不同trip\n权重=固定惩罚]C --> E[合成图]D --> EE --> F[最短时间路径 Dijkstra\nsource=S1,target=S3]F --> G[输出:路径+耗时秒数]

7.15 等时圈构面方法对比:alpha-shape vs 栅格/线缓冲

在服务区构面环节,常见做法包括:

  • alpha-shape(凹多边形):从可达节点散点生成更贴合道路形态的面;需 alphashape 库。
  • 线缓冲合并(近似栅格化效果):对可达子图的边做缓冲并融合,形成连通的面。

示例:基于前文的 lengths 与子图,生成两类面并对比面积与形态。

import geopandas as gpd
from shapely.geometry import Point# 假设已有:G 图、origin_node、lengths(到各节点的 travel_time)
nodes_df = gpd.GeoDataFrame({"node": list(lengths.keys())},geometry=[Point((G.nodes[n]["x"], G.nodes[n]["y"])) for n in lengths.keys()],crs="EPSG:4326"
).to_crs(epsg=3857)reachable = nodes_df[nodes_df["node"].isin([n for n, tt in lengths.items() if tt <= 15*60])]  # 15分钟# 方法A:alpha-shape(若不可用则退化为凸包)
try:import alphashapealpha_poly = alphashape.alphashape(list(reachable.geometry.apply(lambda g: (g.x, g.y))), alpha=20)
except Exception:alpha_poly = gpd.GeoSeries(reachable.geometry).unary_union.convex_hull# 方法B:线缓冲合并(边转面)
subnodes = {n for n, tt in lengths.items() if tt <= 15*60}
subgraph = G.subgraph(subnodes)
edges = ox.utils_graph.graph_to_gdfs(subgraph, nodes=False)
buffer_poly = edges.buffer(12).unary_union  # 缓冲半径根据数据单位与期望精度调整out_alpha = gpd.GeoDataFrame({"method": ["alpha_shape"], "geometry": [alpha_poly]}, crs="EPSG:3857")
out_buffer = gpd.GeoDataFrame({"method": ["line_buffer"], "geometry": [buffer_poly]}, crs="EPSG:3857")
out_alpha.to_file("outputs/iso_alpha_15min.geojson", driver="GeoJSON")
out_buffer.to_file("outputs/iso_linebuffer_15min.geojson", driver="GeoJSON")

对比要点:

  • alpha-shape更贴合散点边界,适合道路稠密、节点分布均衡的区域;参数 alpha 需试调。
  • 线缓冲在道路稀疏时更稳健,但边界较“胖”;缓冲半径需与道路精度匹配。

流程图(等时圈构面方法对比)

flowchart LRA[输入:路网图G/起点/阈值t] --> B[Dijkstra:可达节点\ntravel_time≤t]B --> C[方法A:alpha-shape\n参数alpha]B --> D[方法B:线缓冲\n边缓冲半径buffer]C --> E[输出:iso_alpha_t.geojson]D --> F[输出:iso_linebuffer_t.geojson]E --> G[对比:面积与形态]F --> G

7.16 公平性对比案例:弱势群体覆盖率

使用可复现样例数据:gis_examples/datasets/population_grid_sample.geojsongis_examples/datasets/facilities_sample.geojson,计算 15 分钟服务区内的覆盖人口与弱势群体覆盖比例。

import geopandas as gpdpop = gpd.read_file("gis_examples/datasets/population_grid_sample.geojson").to_crs(epsg=3857)
fac = gpd.read_file("gis_examples/datasets/facilities_sample.geojson").to_crs(epsg=3857)# 以第一个设施为中心生成 15 分钟服务区(可复用前文 line_buffer 方法)
center_node = ox.distance.nearest_nodes(G, fac.geometry.iloc[0].x, fac.geometry.iloc[0].y)
subnodes = {n for n, tt in nx.single_source_dijkstra_path_length(G, center_node, weight="travel_time").items() if tt <= 15*60}
subgraph = G.subgraph(subnodes)
edges = ox.utils_graph.graph_to_gdfs(subgraph, nodes=False)
iso_poly = edges.buffer(12).unary_union
iso_gdf = gpd.GeoDataFrame({"fid": [int(fac.iloc[0].fid)]}, geometry=[iso_poly], crs=pop.crs)# 与人口网格叠加,统计覆盖与公平性指标
covered = gpd.overlay(pop, iso_gdf, how="intersection")
covered["cell_area"] = covered.geometry.area
covered["covered_pop"] = covered["pop"]  # 简化:假定网格人口均匀分布,叠加不做面积加权
covered["vulnerable_pop"] = covered["covered_pop"] * covered["vulnerable_ratio"]summary = {"total_pop": float(pop["pop"].sum()),"covered_pop": float(covered["covered_pop"].sum()),"covered_rate": float(covered["covered_pop"].sum() / pop["pop"].sum()),"vulnerable_pop": float(covered["vulnerable_pop"].sum()),"vulnerable_share_in_covered": float(covered["vulnerable_pop"].sum() / max(covered["covered_pop"].sum(), 1.0))
}
print(summary)

报告撰写提示:

  • 明确“弱势群体”的定义与测度(例如老年人、低收入人口比例)。
  • 若网格人口需面积加权,请根据交叠面积按比例分配人口(避免高估)。
  • 对比不同设施或模式(步行/驾车/公交)的覆盖与公平性差异,提出改善建议。

流程图(公平性覆盖统计)

flowchart TDA[输入:人口网格/设施点/路网G/阈值t] --> B[各设施服务面:可达子图→线缓冲]B --> C[合并服务面:union]C --> D[叠加人口网格:intersection]D --> E[面积权重:overlap/total→area_fraction]E --> F[覆盖人口:pop×area_fraction]F --> G[弱势人口:覆盖人口×vulnerable_ratio]G --> H[汇总指标:covered_rate/vulnerable_share]H --> I[输出:covered_grids.geojson/fairness_summary.json]

7.17 安装与运行指南(一键执行示例)

为便于直接复现本章示例,仓库中提供了一键脚本与示例数据。

环境与依赖

  • 建议 Python 3.9+,虚拟环境安装依赖:
pip install osmnx networkx geopandas alphashape pandas
  • geopandas/shapely 安装困难,建议使用 conda:
conda install -c conda-forge geopandas osmnx networkx pandas alphashape

数据位置

  • gis_examples/datasets/population_grid_sample.geojson(人口网格,含弱势群体比例)
  • gis_examples/datasets/facilities_sample.geojson(设施点示例)
  • gis_examples/datasets/gtfs_minimal/(最小 GTFS:stops.txtroutes.txtcalendar.txttrips.txtstop_times.txt

一键脚本与运行

  • 脚本路径:gis_examples/modules/run_ch7_examples.py
  • 基础运行(默认研究区为 Shanghai, China):
python gis_examples/modules/run_ch7_examples.py
  • 可选参数(CLI):

    • --place 研究区名称(默认:Shanghai, China
    • --modes 构图模式,逗号分隔(默认:drive,walk,bike
    • --thresholds 等时圈分钟阈值列表,逗号分隔(默认:10,20,30
    • --buffer 服务区线缓冲半径(米,默认:12.0
    • --compare_threshold 方法对比的分钟阈值(默认:15
    • --alpha alpha-shape 参数(默认:20.0
    • --fairness_minutes 公平性统计的分钟阈值(默认:15
    • --skip_gtfs 跳过 GTFS 最小示例
  • 示例:更换研究区与参数(杭州,步行+骑行,10/20 分钟,缓冲 20m,alpha=25,公平性 20 分钟):

python gis_examples/modules/run_ch7_examples.py \--place "Hangzhou, China" \--modes walk,bike \--thresholds 10,20 \--buffer 20 \--compare_threshold 15 \--alpha 25 \--fairness_minutes 20 \--skip_gtfs

流程图(一键脚本整体流程)

flowchart TDA[输入:研究区place/模式modes/阈值thresholds] --> B[下载OSM并构图:osmnx]B --> C[多阈值等时圈:drive/walk/bike]C --> D[方法对比:alpha-shape vs 线缓冲]D --> E[公平性统计:服务面∩人口网格\\n面积加权→汇总]E --> F[模式面积对比:10分钟步行/骑行/驾车]A --> G{是否执行GTFS示例?}G -- 是 --> H[构建最小Transit图\\n最短时间路径]H --> I[输出:gtfs_minimal_result.json]G -- 否 --> J[跳过]C --> K[输出:isochrones_*\.geojson]D --> L[输出:iso_alpha_*\.geojson/iso_linebuffer_*\.geojson]E --> M[输出:covered_grids.geojson/fairness_summary.json]F --> N[输出:iso_*_10min.geojson+面积对比]

输出内容(默认写入仓库根目录 outputs/

  • isochrones_10_20_30min.geojson:多阈值等时圈服务面(默认 10/20/30 分钟)
  • iso_alpha_15min.geojson / iso_linebuffer_15min.geojson:15 分钟方法对比(alpha-shape vs 线缓冲)
  • iso_walk_10min.geojson / iso_bike_10min.geojson / iso_drive_10min.geojson:10 分钟步行/骑行/驾车服务面与面积对比
  • covered_grids.geojson:服务面覆盖的人口网格(含面积加权分配)
  • fairness_summary.json:覆盖人口与弱势群体统计汇总(面积权重)
  • gtfs_minimal_result.json:最小 GTFS 的最短路径及耗时(若未跳过)

注意事项

  • 首次运行需联网下载 OSM 数据;建议收敛研究区以提升构图与计算速度。
  • alpha-shape 不可用时会回退为凸包;可调 --alpha 获取更平滑边界。
  • 公平性统计示例已采用交叠面积加权;实际项目可进一步叠加人口密度或分组权重。
  • 计算距离/面积需在投影坐标系(脚本内部已统一到 EPSG:3857);若自定义数据请注意坐标一致性。

7.18 本章总结

你已完成路网建模与可达性评估:生成等时圈服务区、比较多模式的覆盖差异、计算小规模 OD 旅行时间矩阵,并形成质量清单与工程化说明。将这些结果与人口或设施分布叠加,能支撑选址、公平性与规划评估的可解释分析。

http://www.dtcms.com/a/609686.html

相关文章:

  • 电子电气架构 -- bus off的机理和处理机制
  • leetcode 2536
  • OpenAI与百度同日竞速,文心5.0以原生全模态重新定义AI理解力
  • 【高级机器学习】 12. 强化学习,Q-learning, DQN
  • 网站怎么做视频的软件泰安有什么互联网公司
  • uniapp h5 app 小程序获取当前定位
  • 重庆潼南网站建设哪家好沈阳市建设工程安全监督站网站
  • [特殊字符] 嵌入式音频接口全景图解:I2S、TDM、PDM、SPDIF、AC’97 与 PCM 的关系
  • 从 API 到应用:用 Rust 和 SQLx 为 Axum 服务添加持久化数据库
  • 【高级机器学习】 9. 代理损失函数的鲁棒性
  • 测试之测试用例篇
  • 做网站优化推广的好处网站界面设计实验报告
  • 自建node云函数服务器
  • TRO侵权预警|Lauren动物插画发起维权
  • Rust实战:使用Axum和SQLx构建高性能RESTful API
  • 波动率曲面分解法在期货价差套利策略中的应用研究
  • 泌阳县住房建设局网站网站seo排名优化工具在线
  • 电子商务网站建设课北京建设网官方网站
  • vr大空间体验馆,vr大空间是什么意思啊?
  • Node.js实现WebSocket教程
  • 朝阳区搜索优化seosem百度seo关键词排名优化工具
  • C++初阶
  • NFS:K8s集群的跨主机存储方案
  • 动态设计网站制作wordpress
  • 短临 Nowcast 在分布式光伏的落地:分钟级降水与云量对 Irradiance 的影响(工程版)
  • linux centos 防火墙操作命令
  • 破解行业绿电直供痛点:直连架构适配关键技术解析
  • token无感刷新全流程
  • MySQL 数据增删改查
  • 浏阳做网站的公司价格网站设计步骤详解