首页 / 资讯中心 / 文章详情

QGIS+OSM+Python:开源交通分配分析实战全流程指南

QGIS+OSM+Python:开源交通分配分析实战全流程指南 ★ FEATURED ARTICLE
跟QGIS、OSM和Python打了这么多年交道真正让我觉得“这套组合拳打出了价值”的是前段时间做的一次指定区域交通分配分析。这个课题看起来很专业但拆开看其实就是三件事从OpenStreetMap拿到某片城区的真实路网用QGIS把路网收拾干净、建好拓扑最后写Python脚本把交通需求分配到路上算出每条路该承担多少流量。这套流程放在商业软件里动辄几十万咨询费但用开源工具自己搭数据、算法、结果全在自己手里可控性强得多。这篇东西我尽量按实操顺序写思路选型、环境准备、路网处理、算法实现、结果输出最后附上我踩过的坑和排查经验。无论你是做交通规划、物流路径分析、还是城市研究这套链路都值得照着一遍改改数据就能用在自己项目里。1. 整体设计与技术选型思路1.1 交通分配到底在解决什么问题交通分配Traffic Assignment是交通规划四阶段法里的最后一个阶段。前三个阶段分别是出行生成、出行分布和方式划分最终会输出一张OD表Origin-Destination起讫点矩阵代表某个区域里有多少车要从A点去B点。交通分配的任务就是把这张OD表里的出行需求按照一定的路径选择规则加载到具体的路网路段上最终算出每条路的流量、饱和度、服务水平用来支撑道路规划、信号配时、拥堵治理等决策。用通俗的话说出行者从家去公司不会所有人都走同一条路有人选最短的、有人选最快的、有人可能更习惯走熟悉的干道。分配给每条路的车辆数就是道路流量。如果所有车都按“当前路况下最短时间路径”分配这叫全有全无分配All-or-Nothing最简单但偏理想。如果再进一步考虑随着道路越来越堵走这条路的时间会变长一部分车会转向次优路径最终达到所有被使用路径的行驶时间相等、没有车愿意换路的状态这就是用户均衡分配User EquilibriumUE。后者更贴近现实也是我这次实践的重点。1.2 为什么是QGIS OSM Python这个组合这个组合并非偶然每一项都有明确分工。OSM解决“数据从哪来”的问题QGIS解决“数据怎么变成可用路网”的问题Python解决“模型怎么算”的问题。先说OSM。全球任何一个地方只要有OpenStreetMap的贡献者画过路就能免费下载到完整的路网数据包含道路等级、道路名称、单行道属性、限速、车道数等信息。平均质量在大城市甚至不输商业电子地图。商业路网数据比如Navteq、TomTom不是不好但采购流程长、价格高、更新周期固定对于个人或小团队做研究OSM是零成本且够用的最优解。再说QGIS。它是开源GIS界的桌面担当支持Shapefile、GeoJSON、PostGIS等几乎所有常见格式。QGIS最大的优势不光是能看数据它自带了一套完整的拓扑修复工具箱捕捉端点、检测悬挂线、合并重复要素、按字段拆分路网这些处理正是交通分配前最繁琐也最关键的一步。还有一个理由QGIS里可以加载OSM的在线底图做参考直接边看边改非常直观。最后是Python。为什么不用QGIS自带的网络分析工具比如短路径算法插件因为那些工具做单条路径规划很顺手但做全网络的交通分配不行。交通分配本质上是“大量OD对 × 每个OD的最短路/均衡路径”需要批量计算、迭代收敛、结果汇总这个逻辑只有放进Python里跑才灵活。加上networkx、geopandas、shapely这些库Python做复杂网络计算几乎是标配。1.3 工作流程的整体分层我把整个项目拆成四个层次这个思维框架适用于大多数GIS网络的课题不只是交通分配数据层从OSM下载某指定区域的路网数据梳理出需要的字段道路等级、单行道、速度、长度等导入QGIS。网络构建层在QGIS里对路网做拓扑修复保证每条路段的端点正好连着其他路段的端点切断冗余的长线建立“路段—节点”模型把道路等级转化为通行速度计算每个路段的时间成本。计算层用Python加上networkx构建一张有向加权图节点是路口边是路段权重是时间。读入OD需求矩阵执行全有全无分配和用户均衡分配输出每个路段承担的流量。输出层将计算结果导出为GeoJSON回到QGIS里做可视化流量分级渲染、饱和度热力生成可汇报的图片和表格。这个分层的好处是每一层都是独立的比如以后想换数据源只影响第一层想换算法只改第三层想让结果更好看只动第四层。层与层之间用标准的GeoJSON或CSV接口衔接调试起来非常省心。2. 环境准备与OSM数据获取2.1 QGIS和Python环境搭建的注意点QGIS的安装基本是傻瓜式去官网下载对应系统的安装包即可。但有几个细节值得留意都是踩过坑总结的。版本选择建议LTR版本长期支持版稳定性最高。开发版功能多但插件兼容性容易出问题。做数据处理稳定性永远排在第一位。安装路径如果电脑上同时装了ArcGIS注意两个软件的Python环境和一些底层库如GDAL可能冲突。建议QGIS装默认路径尽量让系统环境变量用各软件自带的Python不要手动把QGIS的bin目录塞进全局PATH否则可能出现“import qgis”成功的假象但实际版本错乱。Python环境我推荐直接装Anaconda或者Miniconda不是因为它多高大上而是因为它把Python、pip、虚拟环境一起管好了尤其适合这类数据密集型项目。我的环境是Python 3.10 conda管理的独立env避免了系统Python被搞乱的风险。核心库geopandas读取和操作矢量数据的核心。shapely处理几何对象。networkx构建路网图、执行最短路和均衡分配。osmnx如果你只想快速下载OSM数据这个库可以一行代码下载整个区域的路网。不过我们这次要讲清楚全流程所以我会演示原始OSM文件的处理办法osmnx只在对比时提一句。matplotlib / contextily辅助绘图。安装命令conda create -n traffic_assign python3.10 -y conda activate traffic_assign pip install geopandas shapely networkx osmnx matplotlib contextily如果下载速度慢可以给pip配国内镜像源pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple2.2 从OpenStreetMap导出指定区域路网OSM数据获取方式分四种我列举一下使用场景整包下载Geofabrik适合国家/省份级的分析文件大需要裁切。Overpass API通过QGIS的QuickOSM插件适合城市级、有明确边界范围的下载最常用。OSMnx的graph_from_place适合程序化、重复性下载直接得到networkx图。在线导出只能导出小范围实用意义不大。我最常用的是QuickOSM因为可以直接框选范围还能按“highway”标签过滤所有道路。操作也很简单安装QuickOSM插件在“查询”里选“highway”范围选“绘制范围”然后在地图上拉矩形。导出后得到的是OSM原始XML数据里面包含节点node、路径way和关系relation。在QGIS里如果不处理直接加载会很混乱因为XML不是标准GIS矢量格式。正确的做法是安装插件“QuickOSM”后它会把OSM数据导入为QGIS的三个图层点层节点、线层路网、多边形层建筑等。我们主要用线层。2.3 路网数据的关键字段概览拿到路网线层后属性表里最核心的字段是highway道路类型。motorway、trunk、primary、secondary、tertiary、residential、service、footway、cycleway等。oneway是否单行道“yes”表示单行“no”表示双向也可能为空一般默认双向。maxspeed速度限制可能没有填写。name道路名主要用于参考不参与计算。geometry线几何对象。在导入后建议先看一眼整体的道路等级分布。方法很简单右键图层属性在“符号化”里按“highway”字段做分类渲染不同等级的道路颜色区分开。这样很快能发现数据质量有没有重大问题比如有没有明显的断头路、某些不该出现在路网里的道路比如高速公路匝道、步行街等。截取一个关键知识点OSM里面的highway标签并不完全等同于道路通行能力等级。比如一条tagged as “secondary”的市区道路其设计速度可能和“tertiary”差别很大。但作为初始数据OSM等级已经是全球覆盖度最好、一致性不错的数据源。实际项目中如果想更精细可以再叠加本地限速数据这个就是后话了。3. 路网清洗与拓扑修正最关键的实操环节3.1 按道路等级筛选路网第一步把对机动车不开放或意义不大的道路类型过滤掉。我在QGIS里通常保留这些等级motorway高速公路trunk快速路primary主干道secondary次干道tertiary支路residential居住区道路living_street生活街区道路unclassified未分类道路通常也能走车需要剔除的footway、cycleway、path、steps、pedestrian步行和非机动车道service服务性道路除非是影响区域连接的关键通道track农用/林间土路多数不可驾车通行操作方式在QGIS里打开属性表使用表达式选择。highway IN (motorway, trunk, primary, secondary, tertiary, residential, living_street, unclassified)然后导出选中的要素为新图层。这一步不仅能大幅削减数据量还能避免后续在网络构建时出现大量步行巷道、死胡同节点干扰分配逻辑。3.2 拓扑修正端点捕捉与悬挂线处理这里的“拓扑”特指路网几何的连通性。交通分配要求整个路网构成一张连通的图每一条路段的端点必须和其他路段的端点精确重合。然而OSM原始数据里经常出现两条路在交叉口明明相交但它们的几何线没有真正在交叉点处断开也就是一条路“跨过”了另一条却没有被打断车辆没法在这里转弯。在QGIS中处理拓扑问题核心工具是“拓扑检查工具”Topology Checker插件。新建一个拓扑检查规则选择路网图层规则选“不能有悬挂线”must not have dangles和“不能有伪节点”must not have pseudo-nodes。悬挂线就是路网的“断头”即一条线的端点没有接到任何其他线上。这种情况在真实路网里可能是小区尽头路但更多时候是数据采集时漏了连接。伪节点则是一条线被切成两段但中间没有第三条线汇入这种节点对交通流向没有影响甚至可能让最短路计算时多绕一个节点通常合并掉。处理流程运行拓扑检查生成问题列表。对悬挂线先人工判断是真实断头路还是数据缺失。如果旁边明显还有道路应该相连就需要手动编辑线层。点击“启用编辑”用“捕捉线段端点”工具把线端点拽到相邻线的端点或交叉点上。如果距离较大超过0.5米通常不直接拽线而是用“重算几何”或者“修复几何”v.clean插件GRASS算法批量处理。我实际操作中大批量数据直接用GRASS的v.clean break插件处理。这个插件的功能是把所有线在交点处打断同时可以设置捕捉容差。参数一般设0.1米到0.5米设置太大会把平行很近但实际不相交的两条路错误连接。3.3 方向性处理与通行规则单行道是交通分配里必须处理好的属性否则双向流量算出来可能严重离谱。在OSM数据里oneway字段通常有三个值“yes”道路只能按线段几何方向行驶“-1”道路只能逆着线段几何方向行驶“no”或空双向通行处理步骤先在属性表中新增一个字段“direction”整型1表示顺向单行-1表示逆向单行0表示双向。用字段计算器填入CASE WHEN oneway yes THEN 1 WHEN oneway -1 THEN -1 WHEN oneway no THEN 0 WHEN oneway IS NULL THEN 0 ELSE 0 END注意很多OSM道路的oneway字段没填此时默认按双向处理这点符合一般假设。另外高速公路的匝道motorway_link在很多情况下也是单行的需要检查。如果将来导入networkx时发现某些路段“不能到达”先查oneway字段是不是填了-1。3.4 路段成本的确定速度、长度与时间交通分配里的路阻函数最常用的是时间阻抗即走完某条路段需要多少分钟。公式很简单[ t_0 \frac{L}{V} ]其中L是路段长度公里V是平均速度公里/小时t0是自由流时间小时或分钟。OSM本身提供maxspeed字段但覆盖率不高。没填的时候我一般按highway等级来设定一个默认速度这个经验值对于任何项目都是先跑起来的好起点highway等级默认速度km/hmotorway100trunk85primary60secondary50tertiary40residential30living_street20unclassified30在QGIS里建立两个字段“length_km”和“speed_kmh”。“length_km”可以自动计算在字段计算器里选“$length”注意要在地图层坐标系为投影坐标如EPSG:3857或区域局部投影下算如果原始坐标是WGS84经纬度EPSG:4326$length的单位是度不是米算出来毫无意义。解决方法是先把图层重新投影为适合的投影坐标系或者用“$length”前加一句 transform($geometry, EPSG:4326, EPSG:3857)。我一般提前把QGIS工程CRS设置为目标投影线层也随之变换。之后“time_min”字段 length_km / (speed_kmh / 60)这一步完成后路网数据基本就达到可计算的标准了拓扑干净、方向明确、速度合理、每段有时间权重。4. Python里的网络建模与交通分配实现4.1 把QGIS处理好的路网读入Python构建图QGIS处理完毕导出为GeoJSON。然后进入Python环节。先写一段基础代码把GeoJSON读进来同时构建有向图。import geopandas as gpd import networkx as nx from shapely.geometry import LineString, Point # 读取路网数据 gdf gpd.read_file(road_network_clean.geojson) # 创建有向图 G nx.DiGraph() # 给每一个节点赋一个id用经纬度或xy坐标字符串 for idx, row in gdf.iterrows(): geom row.geometry if geom is None or not isinstance(geom, LineString): continue coords list(geom.coords) u (round(coords[0][0], 6), round(coords[0][1], 6)) v (round(coords[-1][0], 6), round(coords[-1][1], 6)) length_km row.get(length_km, 0) speed_kmh row.get(speed_kmh, 30) time_min row.get(time_min, length_km / (speed_kmh / 60)) direction row.get(direction, 0) highway row.get(highway, unknown) # 根据方向添加边 if direction 1: G.add_edge(u, v, weighttime_min, length_kmlength_km, highwayhighway) elif direction -1: G.add_edge(v, u, weighttime_min, length_kmlength_km, highwayhighway) else: G.add_edge(u, v, weighttime_min, length_kmlength_km, highwayhighway) G.add_edge(v, u, weighttime_min, length_kmlength_km, highwayhighway)这里的关键点是节点ID。OSM的路网数据量大直接用几何坐标作为节点ID虽然可行但要注意浮点精度问题所以我用round函数统一到小数点后6位约合0.1米精度足够避免相邻路口被错误合并。还有一个容易忽略的点极少数情况下一条路的起点和终点可能在几何上非常接近但在拓扑上不是同一个节点QGIS里没捕捉完全。这会导致图变成多个不连通的子图。遇到这种问题最直接的办法是先找出最大连通子图把孤岛去掉。# 找最大弱连通子图 components list(nx.weakly_connected_components(G)) largest max(components, keylen) G G.subgraph(largest).copy() print(f节点数: {G.number_of_nodes()}, 边数: {G.number_of_edges()})这样处理之后图就是完全连通的可以放心做路径搜索。4.2 构建OD需求矩阵OD需求矩阵是交通分配模型的输入。它的每一行每一列都代表一个交通小区Zone而矩阵值表示从行区到列区的出行量。实际项目中OD数据通常是拿居民出行调查、手机信令数据、交通模型宏观标定得到的。在这个演示项目里没有真实OD数据我采用简化方法在指定区域里挑几个关键节点作为需求点比如大型居住区、商业中心、工业园用随机数或经验比例构造一个稀疏OD矩阵。import pandas as pd import numpy as np # 假设选了5个节点作为需求点 zone_nodes [ (120.123456, 30.123456), # 居住区A (120.234567, 30.234567), # 商业中心B (120.345678, 30.345678), # 工业园C # ... 其他 ] # 生成一个5x5需求矩阵单位辆/小时 od_df pd.DataFrame([ [0, 300, 200, 150, 100], [200, 0, 400, 100, 80], [180, 350, 0, 90, 60], [80, 60, 120, 0, 50], [50, 70, 90, 40, 0] ], indexzone_nodes, columnszone_nodes)真实场景下OD矩阵如何标定是一个很大的课题常见方法有重力模型、增长系数法、OD反推等。但交通分配这步本身不关心OD是怎么来的它只负责把OD加载到路网上。所以这里我们只要保证矩阵格式正确、流量总和大致符合区域规模就行。4.3 全有全无分配All-or-Nothing的实现全有全无分配是最简单的分配算法。对每个OD对求当前路网权重下的最短路径这里用自由流时间然后把OD流量全部加到该路径经过的所有边上。实现代码def all_or_nothing(G, od_df): # 深拷贝图因为要修改边上的流量 G_aon G.copy() # 给所有边加流量字段 for u, v, data in G_aon.edges(dataTrue): data[flow] 0.0 for origin in od_df.index: for dest in od_df.columns: demand od_df.loc[origin, dest] if demand 0: continue if origin dest: continue try: path nx.dijkstra_path(G_aon, origin, dest, weightweight) except nx.NetworkXNoPath: continue # 沿路径累加流量 for i in range(len(path) - 1): u, v path[i], path[i1] # 注意可能需要处理双向边的流量的方向这里简单按正向累加 if G_aon.has_edge(u, v): G_aon[u][v][flow] demand elif G_aon.has_edge(v, u): G_aon[v][u][flow] demand * (-1) # 这种情况说明路径走向与边方向相反 # 不推荐这样处理应该在构建图时保证路径方向与边方向一致 else: print(边不存在, u, v) return G_aon注意这段代码里我故意留了一个“边方向不一致”的坑。实际中如果图构建正确dijkstra_path返回的路径节点肯定是沿着有向边走的所以不会出现反向累加。但如果发生说明你的图里存在平行边或方向字段有异常排查方向应该是数据方向字段而不是算法。全有全无分配实现简单、计算快但不考虑拥堵效应所有车都走最短路径最短路径上的拥堵会更严重而次短路线可能空着。所以它通常用于快速筛选或者作为更高级分配算法的初始解。4.4 用户均衡分配User Equilibrium的迭代实现用户均衡分配基于Wardrop第一原理所有出行者都选择自己的最小阻抗路径且任何出行者都无法通过单方面改变路径来减少自己的行程时间。数学上这个问题的解对应一个最优化问题可以用Frank-Wolfe算法或连续平均法MSAMethod of Successive Averages求解。在中小规模路网中我用的是MSA。它的思路是每一轮迭代用当前行驶时间作为权重做一次全有全无分配得到一个辅助流量然后用上一轮的流量和本轮辅助流量的加权平均去更新流量。权重系数一般取1/ii是迭代轮次。这里路由阻函数阻抗函数用的是经典的BPRBureau of Public Roads函数[ t t_0 \times (1 \alpha \times \left(\frac{Q}{C}\right)^\beta) ]其中t是实际行驶时间t0是自由流行驶时间Q是当前流量C是道路通行能力α和β是参数通常取0.15和4。可以这样理解这个公式流量从0开始增加到道路通行能力道路的时间只增加15%但当流量超过通行能力时间会急剧增长模拟出拥堵排队效应。实现代码def bpr_time(t0, flow, capacity, alpha0.15, beta4.0): if capacity 0: return t0 * 100 # 容量为0的路段给一个极大阻抗 return t0 * (1 alpha * (flow / capacity) ** beta)capacity通行能力怎么定也要按道路等级给经验值。单位是辆/小时pcu/h表如下道路等级通行能力pcu/h/车道motorway2000trunk1800primary1600secondary1400tertiary1200residential800living_street500M S A 迭代流程如下def user_equilibrium(G, od_df, max_iter100, tol1e-4): G_ue G.copy() # 初始化流量为0 for u, v, data in G_ue.edges(dataTrue): data[flow] 0.0 data[capacity] get_capacity(data.get(highway, tertiary)) data[t0] data.get(weight, 1.0) for iteration in range(1, max_iter 1): # 1. 按当前流量更新边权 for u, v, data in G_ue.edges(dataTrue): flow data.get(flow, 0.0) cap data.get(capacity, 1200) t0 data.get(t0, 1.0) data[weight] bpr_time(t0, flow, cap) # 2. 全有全无分配得到辅助流量 aux_flow {edge: 0.0 for edge in G_ue.edges()} for origin in od_df.index: for dest in od_df.columns: demand od_df.loc[origin, dest] if demand 0 or origin dest: continue try: path nx.dijkstra_path(G_ue, origin, dest, weightweight) except nx.NetworkXNoPath: continue for i in range(len(path) - 1): u, v path[i], path[i1] if G_ue.has_edge(u, v): aux_flow[(u, v)] demand # 3. MSA更新流量 alpha 1.0 / iteration flow_diff 0.0 for edge in G_ue.edges(): old G_ue[edge[0]][edge[1]].get(flow, 0.0) new (1 - alpha) * old alpha * aux_flow[edge] G_ue[edge[0]][edge[1]][flow] new flow_diff abs(new - old) # 4. 判断收敛 if flow_diff tol: print(f迭代 {iteration} 轮后收敛流量变化: {flow_diff:.6f}) break return G_ue这里最关键的是alpha的递减策略。早期迭代alpha大允许流量大幅摆动后期alpha小流量稳定收敛。用1/iteration是一个简单经典的实现也被证明在大规模网络上可以收敛到不错的结果。需要注意MSA收敛速度偏慢通常几百次迭代才能达到很精细的收敛。实践中我一般设max_iter200用流量变化量flow_diff小于某个阈值来判断是否提前终止。如果200轮还不收敛大概率是OD量太大或者路网连通性有问题不是算法的问题。4.5 结果输出与格式化分配完毕把每条路段的流量以字段形式写回GeoJSON方便回到QGIS里可视化。def export_results(G_ue, gdf, output_path): # 构建边的流量字典 flow_dict {} for u, v, data in G_ue.edges(dataTrue): key (u, v) flow_dict[key] data.get(flow, 0) # 给gdf加一个流量字段 gdf[flow] 0.0 for idx, row in gdf.iterrows(): geom row.geometry if geom is None or not isinstance(geom, LineString): continue coords list(geom.coords) u (round(coords[0][0], 6), round(coords[0][1], 6)) v (round(coords[-1][0], 6), round(coords[-1][1], 6)) direction row.get(direction, 0) if direction 1: gdf.at[idx, flow] flow_dict.get((u, v), 0) elif direction -1: gdf.at[idx, flow] flow_dict.get((v, u), 0) else: # 双向道路流量是两个方向的叠加 gdf.at[idx, flow_forward] flow_dict.get((u, v), 0) gdf.at[idx, flow_backward] flow_dict.get((v, u), 0) gdf.at[idx, flow] flow_dict.get((u, v), 0) flow_dict.get((v, u), 0) gdf.to_file(output_path, driverGeoJSON)这里要注意双向道路的流量怎么处理。如果只关注“总流量”可以把两个方向的流量相加用颜色渲染。但如果要精确到方向比如分析车道方向饱和度就需要分别保存flow_forward和flow_backward两个字段。按需输出不要一股脑全塞。5. 结果可视化与常见问题排查5.1 在QGIS中制作流量专题图拿到带流量字段的GeoJSON在QGIS打开。关键操作就三步符号化选“分级”渲染值选“flow”字段。选择“按大小”或“按宽度”的符号样式宽度根据流量值分级。比如流量0-100画1px100-500画2px500以上画4-6px这样一眼能看出主干道的拥堵趋势。添加OSM底图在XYZ Tiles里选OpenStreetMap或者用国产可用的底图源把渲染结果叠加在真实地图上检查结果是否符合常理。流量的检查逻辑通常是市中心或干道流量应该大边缘小路流量应该小跨越河流、铁路的桥梁或隧道往往流量异常大因为可选的过河通道少分配流量集中是正常现象。如果需要出图可以再加一个V/C比流量/通行能力字段用红黄绿分级直观表达饱和度。饱和度大于1意味着路该拓宽或改道了饱和度0.5-0.8说明通行状态良好小于0.5说明道路富余。5.2 常见问题与排查技巧我把这次项目里遇到的各类问题整理成一张速查表这些坑新手几乎都会踩一遍。问题现象可能原因解决方案有路段永远没有流量该路段是孤立子图OD到不了检查连通性排除未连上的线段某个节点大量流量聚集该节点是唯一通道本身正常如果不符合常识检查是否有平行路被误删结果流量异常偏大OD矩阵量级设置太大检查OD单位确保是pcu/小时而不是pcu/天最短路路径明显绕路路权time_min计算错误检查length_km/speed_kmh字段看单位是否统一QGIS里线方向乱了道路几何方向随机用oneway字段和direction字段校准不依赖几何方向导入networkx报错有空的几何对象构建图前先过滤 gdf[gdf.geometry.notnull()]部分OD对无路径路网有向边不连通用弱连通子图查看检查单行道方向是否是配置错了迭代不收敛OD需求远超路网容量减小OD比例或者把默认通行能力调大速度字段缺失OSM maxspeed覆盖不足按道路等级赋默认速度后面再精修还有一个经常被忽略的点数据下载时间不同OSM的道路会存在细微差异。同一片区三月份下载的数据和九月份下载的数据可能差几十条路。这在研究里必须写明数据版本和下载日期否则结果难以复现。5.3 这套流程还能怎么扩展做完基础版之后我其实还做了一些扩展比如多方式分配在路网上加公共交通的访问时间把OD需求按比例拆分到“汽车”和“公交”两种网络上。动态OD加载把小时级OD矩阵拆成15分钟或5分钟的时间片模拟早晚高峰车流变化。拥堵收费模拟调节特定路段比如跨河桥梁的收费属性观察流量是否会有效地转移到其他通道上。与真实检测数据对比如果你能弄到卡口、线圈或手机信令的路段流量数据可以用来标定BPR参数里的alpha和beta标定后再算结果会更贴近实际。这部分虽然工作量不小但也是交通模型最核心的价值所在。这些扩展其实都不难核心就是把“分配”这个引擎搭好后换OD数据、换路网数据、换阻抗函数就能应对各种场景。6. 写代码时最容易翻车的三个细节补充一个章节因为这几个细节我几乎每次都要提醒自己一遍6.1 GeoJSON的坐标系陷阱GeoJSON标准推荐用WGS84经纬度。但很多人在QGIS里处理时喜欢用Web墨卡托EPSG:3857导出的GeoJSON还是3857坐标。这种做法不是不行但容易在Python里出错。因为networkx本身不关心坐标但如果之后要叠加底图或者把节点的xy坐标当成地理位置计算坐标系的数字就是错的。我的习惯是所有中间文件统一用EPSG:4326WGS84保存只在使用“$length”算长度时临时转换坐标系。这个习惯帮我省了不少事。6.2 路段方向与流量符号flow用正负值还是绝对值很容易让人混乱。我建议定义清楚flow始终是非负值表示该方向上的流量双向路段的几何方向不一定代表车流方向。可视化时如果要直观地展示方向流量可以用箭头线符号如果只看总量把双向加起来就行。不要试图在一个字段里用正负同时表示方向和流量那样后面统计时经常出bug。6.3 交通流量与人口数量的量级匹配很多人第一次跑交通分配OD矩阵里的数量级完全凭感觉导致出来的流量数值非常夸张。正确做法是预估研究区域总机动车出行量然后分配到你设定的OD对里。比如某片区高峰时期大约有5万辆机动车出行那么OD矩阵所有非对角元素之和应该约等于50000而不是随手写几百万。量级不对后面所有饱和度分析都是空中楼阁。最后再分享一个实用小技巧如果你之后想快速上手OSM数据可以在Python里直接用osmnx这个库三行代码就能下载指定城市或矩形范围的路网并直接转为networkx图import osmnx as ox G ox.graph_from_place(某城区, 某市, network_typedrive)它会自动做好拓扑、方向、速度估算省掉很大一部分QGIS手工清洗的功夫。但我不建议完全跳过QGIS那一步因为实际工作中很多数据问题光靠自动化清洗是发现不了的必须肉眼检查一遍路网的等级和连通性才放心。用osmnx做快速原型、用QGIS做精细数据准备两者结合才是效率与质量的最优解。做交通分配这件事说难不难说容易也容易踩坑。难的地方在于数据清洗和参数标定容易的地方在于算法本身其实已经非常成熟。只要你掌握了真实路网数据的准备流程和一套能跑通的Python分配代码换城市、换时间、换需求都是改改参数的功夫。希望这份从零到一的实践记录能帮你少走点弯路。
阅读完成 · 觉得有帮助?
咨询建站