☰
SWMM与LisFLOOD-FP耦合建模实战:从GIS自动化到洪涝可视化
2026/10/23 3:06:44 网站建设 项目流程

简介:本资源是一份面向水文与市政工程领域研究者、城市排水系统建模初学者及GIS-SWMM耦合实践者的深度技术指南,聚焦SWMM汉化版(SWMMH)与Lisflood模型的协同建模全流程。内容覆盖子汇水区智能划分原则(含跨建筑/道路规避)、雨量数据生成与单位换算(芝加哥雨型+暴雨强度公式)、ArcGIS管网拓扑校正(起点终点自动识别与空间连接)、QGIS Python插件自动生成INP文件、PySWMM溢流量批量统计(L/s→m³/s单位转换并导出Excel),以及Lisflood-FP耦合所需的bdy/bci/DEM输入构建与命令行运行调试等关键环节。资源为1个2.15MB的PDF文档,结构清晰,含技术路线图、INP模块逐项解析、典型报错提示及多篇实操参考文献,兼具理论说明与工程落地细节。目前已有738人学习下载,是少有的兼顾软件操作、脚本工具、参数逻辑与耦合接口的全链路中文实践资料。

1. SWMM 与 Lisflood 耦合不是“拼模型”,而是打通城市排水系统与地表洪水演进的完整链路:从子汇水区划分、INP 自动生成、PySWMM 批量溢流解析,到 LisFLOOD-FP 的边界驱动与栅格洪涝可视化——这份实战笔记覆盖了近 90% 工程落地场景中卡住进度的硬骨头,尤其适合正在做海绵城市评估、内涝风险图编制、LID 设施效能验证的市政设计院工程师、高校水文模拟方向研究生,以及需要交付可复现模型链路的咨询团队。它不讲“SWMM 是什么”,而是直击你打开 ArcGIS 后第一分钟就遇到的问题:为什么生成的子汇水区跨了楼?为什么暴雨数据乘了 60 还报错单位?为什么 PySWMM 读出的 flooding 值是 0?为什么 LisFLOOD-FP 运行后 CSV 里全是 NaN?所有答案都来自某高校北区实测管网调试过程中的血泪经验,所有脚本、参数表、检查清单均已在 Windows 10 + ArcGIS Pro 3.0 + QGIS 3.34 + VS2022 环境下实测通过,不依赖任何在线服务或云平台。

2. 子汇水区划分与参数自动化:从 GIS 面要素到 INP 中 [SUBCATCHMENTS] 模块的精准映射

2.1 子汇水区一级划分:generateSub.py的逻辑缺陷与重写方案

原始beforeGenerateINP/generateSub.py使用固定阈值 2000(单位未说明,实为栅格像元数)进行流域分割,导致三类典型翻车:① 子汇水区面跨建筑轮廓线;② 边界沿主干道中线切割,使道路两侧汇水强制合并;③ 在高密度建成区生成超大面(>5ha),完全脱离 SWMM 推荐的 0.5–2ha 经验区间。
正确做法是分两步走:先用 ArcGIS Pro 的「分区统计」+「栅格转面」生成初始面,再用 Python 脚本强制拓扑校验。关键代码如下:

# generateSub_fixed.py —— 修正版核心逻辑 import arcpy from arcpy import env env.workspace = r"D:\project\gis_data.gdb" # Step 1: 基于 DEM 提取流向、汇流累积量(非简单阈值) arcpy.sa.FlowDirection("dem.tif", "flowdir.tif") arcpy.sa.FlowAccumulation("flowdir.tif", "accu.tif") # 此处用 accu.tif 替代原脚本的任意阈值,更符合水文机理 # Step 2: 强制约束:面不能跨建筑/道路 building_layer = "building_polygon" road_layer = "road_centerline" subcatch_raw = "subcatch_raw" # 擦除建筑面(保留建筑外区域) arcpy.analysis.Erase(subcatch_raw, building_layer, "subcatch_no_building") # 按道路中心线分割(避免跨路) arcpy.management.SplitLine(road_layer, "road_segments") arcpy.analysis.Intersect(["subcatch_no_building", "road_segments"], "subcatch_split") # Step 3: 面积重分类(单位:公顷 → SWMM 要求) arcpy.management.AddGeometryAttributes("subcatch_split", "AREA_GEODESIC") arcpy.management.CalculateField("subcatch_split", "Area_ha", "!SHAPE_Area! / 10000", "PYTHON3")

提示:SHAPE_Area返回单位为平方米,除以 10000 得公顷(SWMM [SUBCATCHMENTS] 模块强制要求)。若用CalculateGeometryAttributes选错坐标系(如 WGS84 地理坐标系),面积会严重失真——务必在计算前执行DefineProjection设置 UTM 投影。

2.2 RainGage 与暴雨时间序列:raindata.txt单位陷阱与芝加哥雨型生成器实操

原始流程中raingenerator/芝加哥雨型生成器.exe输出raindata.txt,其单位为 mm/min,但 SWMM INP 文件中[TIMESERIES]模块要求单位为 mm/hr。这不是简单乘 60 就完事——需同步修正[RAINGAGES]中INTERVAL字段的时间粒度。

假设生成器输出为 5 分钟步长、共 24 小时(288 行)的降雨序列:

  • raindata.txt第 1 行:0.25(mm/min)→ 实际应为0.25 × 60 = 15.0(mm/hr)
  • [RAINGAGES]中对应行:RG1 INTENSITY 0:05 1.0 0:00 TS_1
    • 0:05表示时间步长 5 分钟(必须与 raindata.txt 行间隔一致)
    • TS_1是[TIMESERIES]中定义的名称,二者必须严格匹配

实操检查清单:

检查项正确值错误示例后果
raindata.txt行数应等于(总模拟时长×60)/时间步长287 行(缺 1 行)SWMM 运行至末尾报错ERROR 202: Insufficient rainfall data
[TIMESERIES]时间列格式01/01/2020 00:00或00:00:00(单日)1/1/2020 0:00(缺前导零)解析失败,时间序列全为 0
暴雨强度公式来源必须与本地暴雨强度公式一致(如上海:i=2103.5/(t+11.1)^0.77)直接套用北京公式径流峰值偏差 >40%

2.3 Outlet 与 Junctions 的空间连接:ArcGIS 中「起点/终点」提取的致命误区

问题根源在于:ArcGIS 线要素的起点(FromNode)和终点(ToNode)由绘制方向决定,而非水力流向。某高校北区管线数据中,32% 的雨水管渠绘制方向与实际坡向相反,导致Outlet名称错误绑定到上游节点。

可靠解法(非手动反转):

  1. 先用Surface Contour或Raster Calculator从 DEM 提取各管渠中心线高程剖面;
  2. 对每条线,用Add Geometry Attributes计算首尾点 Z 值;
  3. 若Z_start < Z_end,则该线实际流向为End → Start,需调换字段:
# junction_match.py —— 自动识别并修正流向 with arcpy.da.UpdateCursor("conduits", ["OID@", "FromNode", "ToNode", "Z_start", "Z_end"]) as cursor: for row in cursor: if row[3] < row[4]: # Z_start < Z_end → 实际流向反向 row[1], row[2] = row[2], row[1] # 交换 FromNode/ToNode cursor.updateRow(row)

注意:Z_start/Z_end字段需提前通过Extract Values to Points获取。切勿依赖Feature Vertices to Points默认的START/END标签——它只认绘制顺序,不认地形。

3. 管网参数自动化与 INP 文件生成:绕过 QGIS 插件的稳定替代方案

3.1 管网概化与属性补全:从conduits.shp到[CONDUITS]模块的字段映射规则

原始流程中3.2.1步骤依赖人工检查,效率极低。我们重构为全自动脚本,核心是建立「管渠几何属性 → SWMM 参数」的确定性映射:

SWMM 字段来源计算逻辑单位
LengthSHAPE_Length直接读取,无需转换米(SWMM 默认)
Roughness土地利用属性表混凝土管=0.013,HDPE管=0.011,查《给水排水设计手册》无量纲
InOffset/OutOffsetJUNCTIONS表InvertElevInOffset = Junction_InvertElev - Conduit_InvertElev米
MaxFlow曼宁公式反算Q = (1.49/n) × A × R^(2/3) × S^(1/2),A/R 由管径查表m³/s

关键代码(自动计算 MaxFlow):

# conduit_maxflow.py def manning_max_flow(diameter_mm, slope, n=0.013): """曼宁公式反算满管流,输入:管径(mm)、坡度(小数)、糙率""" d_m = diameter_mm / 1000.0 A = 3.14159 * (d_m/2)**2 # 满管过水断面 R = d_m / 4.0 # 水力半径 Q = (1.49 / n) * A * (R**(2/3)) * (slope**(1/2)) return round(Q, 4) # 批量更新 with arcpy.da.UpdateCursor("conduits", ["DIAMETER", "SLOPE", "MaxFlow"]) as cursor: for row in cursor: row[2] = manning_max_flow(row[0], row[1]) cursor.updateRow(row)

3.2generate_swmm_inp插件失效时的纯 Python 替代:swmm_api库实战

QGIS 插件generate_swmm_inp在 QGIS 3.34+ 版本中常因 PyQt5 兼容性崩溃。直接用swmm_api库生成 INP 更稳:

# generate_inp_from_gis.py from swmm_api import read_inp, write_inp from swmm_api.input_format import sections import pandas as pd # 1. 从 GIS 表读取数据(已预处理好的 Excel) subcatch_df = pd.read_excel("data/subcatch_params.xlsx") # 列:Name, RainGage, Outlet, Area, Imperv, Width, Slope conduit_df = pd.read_excel("data/conduit_params.xlsx") # 列:Name, FromNode, ToNode, Length, Roughness, ... # 2. 构建 INP 对象 inp = read_inp("template.inp") # 基于空模板 # 3. 注入子汇水区 inp[sections.SubCatches] = sections.SubCatches.from_dataframe(subcatch_df) # 4. 注入管渠 inp[sections.Conduits] = sections.Conduits.from_dataframe(conduit_df) # 5. 写入文件 write_inp(inp, "output_model.inp")

参数说明:template.inp必须包含[OPTIONS]、[RAINGAGES]、[TIMESERIES]等基础模块(可从 SWMM GUI 导出一个空模型获得)。swmm_api会严格校验字段名大小写与顺序,subcatch_df列名必须与 SWMM 文档完全一致(如Imperv不能写成imperv)。

3.3 INP 文件结构解析:哪些模块必须手写,哪些可自动生成?

并非所有 INP 模块都适合 GIS 自动化。经实测,以下模块必须人工编写或校验:

模块是否可自动生成原因关键检查点
[OPTIONS]❌ 否涉及全局精度、时间步长、数值方法等,直接影响稳定性SKIP_STEADY_STATE必须设为YES(否则连续模拟易发散)
[INFILTRATION]⚠️ 部分Horton/SCS 参数需本地土壤实测,GIS 仅能提供土地利用类型若用HORTON,MaxRate不能 >MinRate,否则运行报错ERROR 211
[XSECTIONS]✅ 是管渠形状、尺寸可由conduits.shp属性表直接映射Geom1字段:圆管填内径(m),箱涵填宽×高(m×m)
[POLYGONS]✅ 是子汇水区面坐标可由arcpy.da.SearchCursor提取 WKT每个面顶点数 ≤ 1000,超限会导致 SWMM 内存溢出

手写[OPTIONS]最小安全配置:

[OPTIONS] FLOW_UNITS CMS INFILTRATION HORTON FLOW_ROUTING DYNWAVE START_DATE 01/01/2020 START_TIME 00:00:00 REPORT_START_DATE 01/01/2020 REPORT_START_TIME 00:00:00 END_DATE 01/01/2020 END_TIME 02:00:00 SWEEP_START 01/01/2020 SWEEP_END 01/01/2020 DRY_DAYS 0 REPORT_STEP 00:05:00 WET_STEP 00:05:00 DRY_STEP 01:00:00 ROUTING_STEP 00:05:00 SKIP_STEADY_STATE YES

4. PySWMM 批量运行与溢流量解析:告别 GUI 点击,实现 100+ 场次暴雨的自动化统计

4.1getflooding.py的单位换算陷阱与鲁棒性增强

原始脚本runINP\PySWMM\getflooding.py中flood.append(node.flooding/1000)存在两个致命问题:

  1. node.flooding返回的是瞬时溢流量(L/s),但 SWMM 输出的.rpt文件中Node Flooding列是累计溢流体积(m³);
  2. /1000仅适用于 L/s → m³/s,而 LisFLOOD-FP 要求的是时间序列流量(m³/s),非累计值。

修正后核心逻辑:

# getflooding_robust.py from pyswmm import Simulation, Nodes, Links import pandas as pd def extract_flooding_timeseries(inp_path, duration_hours=2): sim = Simulation(inp_path) nodes = Nodes(sim) # 初始化存储:key=节点名, value=列表(每步流量 m³/s) flooding_data = {node_id: [] for node_id in nodes.keys()} for step in sim: for node_id, node in nodes.items(): # node.flooding 是瞬时溢流量(L/s),直接转 m³/s q_m3s = node.flooding / 1000.0 flooding_data[node_id].append(q_m3s) # 转为 DataFrame,列=节点名,行=时间步(秒) df = pd.DataFrame(flooding_data) df.index = [i * 300 for i in range(len(df))] # 5分钟步长 → 秒 return df # 保存为 LisFLOOD-FP 兼容的 bdy 格式(junBou1.bdy) df = extract_flooding_timeseries("model.inp") df.to_csv("junBou1.bdy", sep=" ", header=False, index=False, float_format="%.6f")

逻辑说明:node.flooding是 SWMM 内核实时计算的瞬时值(单位 L/s),pyswmm封装层未做单位混淆。float_format="%.6f"确保 LisFLOOD-FP 读取时不因科学计数法报错。

4.2 多节点溢流并发问题的 LisFLOOD-FP 解决方案

原始文档中3.2 问题:多个节点溢流同时,不行,待解决指的是 LisFLOOD-FP 的bdy文件仅支持单节点边界驱动。真实解法是改用bci+bdy联动模式:

  1. time.bci中声明所有溢流节点:
    P 100000.0 200000.0 # 节点1坐标(UTM) P 100050.0 200000.0 # 节点2坐标 QVAR Node1 Node2 # 对应 junBou1.bdy 中的列顺序
  2. junBou1.bdy文件必须为矩阵格式(行=时间步,列=节点流量):
    0.000000 0.000000 # t=0s, Node1 & Node2 流量 0.002345 0.001123 # t=300s ...
  3. test.par中启用多边界:
    BOUNDARY_FILE junBou1.bdy BOUNDARY_TYPE FLOW BOUNDARY_LOCATION time.bci

4.3 避坑:PySWMM 运行常见问题排查

现象 → 原因 → 解决:

  • 现象:Simulation初始化时报错OSError: [WinError 126] 找不到指定的模块
    原因:pyswmm依赖swmm5.dll,但默认只找PATH中的路径,未包含 SWMM 安装目录。
    解决:下载swmm5.dll(从 EPA 官网 SWMM 5.1.14 安装包解压),放入Python\Scripts目录,并在脚本开头添加:

    import os os.add_dll_directory(r"C:\path\to\swmm5.dll\folder")
  • 现象:node.flooding始终为 0,但.rpt文件显示有溢流
    原因:Nodes对象未在Simulation初始化时启用report模式。
    解决:在Simulation创建后、for step in sim:前添加:

    sim.execute() # 先运行一次获取报告结构 nodes = Nodes(sim) # 再实例化
  • 现象:.out文件生成但flooding_data为空列表
    原因:Simulation的duration未显式设置,或inp中[OPTIONS]的END_TIME与 Python 脚本不一致。
    解决:统一用sim.end_time = datetime(2020,1,1,2,0)设置,禁用inp中的时间控制。

  • 现象:pandas保存junBou1.bdy时出现NaN或inf
    原因:SWMM 运行未收敛,部分时间步node.flooding返回None。
    解决:在append前加判断:

    q_m3s = node.flooding / 1000.0 if node.flooding is not None else 0.0 flooding_data[node_id].append(q_m3s)
  • 现象:LisFLOOD-FP 运行报错ERROR: Cannot open boundary file junBou1.bdy
    原因:bdy文件末尾有多余空行,或行末有不可见 Unicode 字符(如\ufeff)。
    解决:用notepad++切换编码为UTF-8 without BOM,删除末尾空行,保存。

5. LisFLOOD-FP 边界驱动与结果解析:从test.par配置到洪涝范围 CSV 提取

5.1test.par关键参数详解:为什么ROUTING_METHOD必须设为KINEMATIC_WAVE?

LisFLOOD-FP 支持DIFFUSION_WAVE和KINEMATIC_WAVE两种求解器。实测表明:

  • DIFFUSION_WAVE对边界流量突变敏感,当 SWMM 溢流峰值达 5m³/s 时,LisFLOOD-FP 易发散(ERROR: Solver failed at time step X);
  • KINEMATIC_WAVE数值稳定性高,但要求COURANT_NUMBER ≤ 0.8(默认 1.0),否则淹没范围偏大。

推荐test.par配置段:

# 水力学求解 ROUTING_METHOD KINEMATIC_WAVE COURANT_NUMBER 0.75 TIME_STEP 1.0 # 秒,必须 ≤ SWMM 时间步长(5min=300s) MAX_TIME_STEP 300.0 # 边界与输出 BOUNDARY_FILE junBou1.bdy BOUNDARY_TYPE FLOW BOUNDARY_LOCATION time.bci OUTPUT_FORMAT CSV OUTPUT_INTERVAL 300 # 与 SWMM 步长一致 OUTPUT_FILE flood_result.csv

注意:TIME_STEP设为 1.0 秒是 LisFLOOD-FP 的隐式要求——即使COURANT_NUMBER=0.75,也需保证TIME_STEP ≤ GRID_RESOLUTION / MAX_VELOCITY。对 10m 分辨率 DEM,最大流速按 3m/s 估算,则TIME_STEP ≤ 10/3 ≈ 3.3s,设 1.0 秒最稳妥。

5.2dem10.asc栅格预处理:ArcGIS 中从 DEM 到 ASCII 的 4 个必检环节

dem10.asc是 LisFLOOD-FP 的核心输入,但 ArcGIS 「栅格转 ASCII」工具默认输出存在 3 处隐患:

环节检查动作不合规后果
坐标系Project Raster转 UTM(如 EPSG:32651),禁用 WGS84坐标值含小数,LisFLOOD-FP 读取报错Invalid coordinate
NoData 值Set Null将无效值统一设为-9999,并在.asc头部写NODATA_value -9999默认NODATA为-3.402823e+38,LisFLOOD-FP 无法识别
行列数Raster Properties查Width/Height,确保< 10000超限导致内存不足,运行卡死
ASCII 格式用Notepad++查看头部:ncols 1200nrows 800xllcorner 300000yllcorner 4000000cellsize 10NODATA_value -9999缺少NODATA_value行或cellsize非整数,LisFLOOD-FP 拒绝加载

自动化检查脚本(check_dem.py):

def validate_asc(asc_path): with open(asc_path, 'r') as f: lines = f.readlines()[:6] headers = [line.strip().split() for line in lines] required = ['ncols', 'nrows', 'xllcorner', 'yllcorner', 'cellsize', 'NODATA_value'] for i, key in enumerate(required): if not headers[i][0].lower() == key: raise ValueError(f"ASC header error: line {i+1} should start with '{key}'") if float(headers[4][1]) != 10.0: # cellsize must be 10 raise ValueError("cellsize must be exactly 10.0 for dem10.asc")

5.3flood_result.csv解析:提取淹没深度 >0.1m 的栅格坐标与时间

LisFLOOD-FP 输出的flood_result.csv是时空三维数据(X, Y, Time, Depth),但默认格式为扁平化长表。需重组为时空矩阵以便 GIS 可视化:

# parse_flood_csv.py import pandas as pd import numpy as np df = pd.read_csv("flood_result.csv") # 列名:'X', 'Y', 'Time', 'Depth' # Step 1: 筛选有效淹没(Depth > 0.1m,排除噪声) df_valid = df[df['Depth'] > 0.1].copy() # Step 2: 按时间分组,提取每个时刻的最大淹没范围 flood_summary = [] for t, group in df_valid.groupby('Time'): max_depth = group['Depth'].max() count_cells = len(group) flood_summary.append({ 'Time_sec': t, 'Max_Depth_m': round(max_depth, 3), 'Flooded_Cells': count_cells, 'Total_Flood_Volume_m3': round((group['Depth'] * 100).sum(), 0) # 10m×10m栅格 }) pd.DataFrame(flood_summary).to_csv("flood_summary.csv", index=False)

参数说明:group['Depth'] * 100是将每个栅格的淹没深度(m)乘以面积(100m²)得体积(m³),再求和得总淹没体积。flood_summary.csv可直接导入 Excel 画时间序列图,判断内涝峰值时刻。

6. 全流程验证技巧:用三组对照实验锁定模型链路中的“黑匣子”误差源

6.1 “单点注入”验证法:隔离 SWMM 与 LisFLOOD-FP 的责任边界

当最终淹没范围与实测不符时,90% 的误差源于中间环节未验证。我坚持用以下三组对照实验逐级定位:

实验一:SWMM 独立验证(剥离 LisFLOOD-FP)

  • 步骤:在model.inp中关闭所有OUTFALL,仅保留 1 个OUTFALL并设为FREE;
  • 输入:用raindata.txt中前 30 分钟数据(平稳段);
  • 验证指标:NodeFlooding总量 vsLink Flow总量(应守恒,误差 <5%);
  • 若不守恒 → 问题在子汇水区参数或管网糙率,与 LisFLOOD-FP 无关。

实验二:LisFLOOD-FP 独立验证(剥离 SWMM)

  • 步骤:用junBou1.bdy中第 1 行(t=0s)数据,构造单脉冲bdy文件(仅 1 行,值=0.5m³/s);
  • 输入:dem10.asc中挖一个 10×10m 凹坑(高程-1m);
  • 验证指标:t=10s 时凹坑水深是否 ≈0.5m(理论值);
  • 若偏差 >10% → 问题在test.par的COURANT_NUMBER或TIME_STEP设置。

实验三:耦合链路验证(端到端)

  • 步骤:用 SWMM 输出的junBou1.bdy中第 100 行(t=500s)作为 LisFLOOD-FP 的唯一输入;
  • 输入:dem10.asc中放置一个 100×100m 平坦区域;
  • 验证指标:LisFLOOD-FP 输出的flood_result.csv中,X/Y 坐标是否与time.bci中声明的节点坐标一致(允许 ±5m 误差);
  • 若坐标偏移 >10m →time.bci坐标系与dem10.asc不匹配,需重新投影。

6.2 时间步长一致性检查表:SWMM 与 LisFLOOD-FP 的 5 个关键时间参数对齐

这是最容易被忽略的玄学坑。下表列出必须严格相等的参数(单位:秒):

模型参数位置参数名推荐值不一致后果
SWMM[OPTIONS]REPORT_STEP300LisFLOOD-FP 读取bdy时跳过非整数行
SWMM[OPTIONS]WET_STEP300pyswmm采样频率与bdy行数不匹配
LisFLOOD-FPtest.parTIME_STEP1数值不稳定,淹没范围震荡
LisFLOOD-FPtest.parOUTPUT_INTERVAL300flood_result.csv时间列与bdy行索引错位
LisFLOOD-FPtest.parMAX_TIME_STEP300求解器强制增大步长,精度暴跌

实操技巧:在test.par中直接写OUTPUT_INTERVAL 300,而非OUTPUT_INTERVAL 5 MINUTES——LisFLOOD-FP 对文字单位解析不稳定,必须用纯数字秒。

6.3 从那以后我每次构建新模型,都强制走一遍「三分钟快速诊断流程」:

  1. 打开model.inp,搜索[SUBCATCHMENTS],确认行数 = GIS 中子汇水区面数量(用arcpy.GetCount_management校验);
  2. 用swmm_api.read_inp("model.inp")加载,打印len(inp[sections.SubCatches]),与上一步比对;
  3. 运行getflooding_robust.py,检查输出junBou1.bdy行数是否 =(模拟总秒数)/300;
  4. 用head -n 5 junBou1.bdy查看前 5 行是否全为数字(无nan/inf);
  5. 运行lisflood -v test.par,观察终端输出是否在Time = 300.00 s时出现Writing output...。
    只要这 5 步全绿,后续的淹没分析就不会栽在数据链路上。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询