如何在Flopy中为MF6 DISV网格创建SFR输入?基于Shapefile/Shapely LineString的实现方案咨询
处理MF6 DISV网格的SFR输入:从Shapefile到Flopy的实操方案
我刚好有过在MF6 DISV网格上搭建SFR模型的实操经验,针对你遇到的Shapefile/Shapely LineString转SFR输入的问题,分享几个可行的思路和具体实现方法:
一、Flopy中DISV网格创建SFR的通用逻辑
和结构化网格(DISU/DIS)不同,DISV的SFR是基于不规则单元格来定义河段(reach)的,核心逻辑是:
- 确定河流路径穿过的所有DISV单元格,把河流拆分成每个单元格内的子河段(每个子河段对应一个SFR reach);
- 为每个reach配置必要的水文地质属性(河床高程、水力传导系数、河流宽度等);
- 根据河流流向,建立reach之间的上下游连接关系;
- 用Flopy的
ModflowGwfSfr类组装这些数据,生成MF6的SFR输入文件。
二、从Shapefile/Shapely LineString生成SFR输入的Python实现
下面是用geopandas、shapely和flopy组合实现的具体步骤:
步骤1:读取网格与河流数据
首先读取DISV单元格的多边形数据(可以从已有的Shapefile读取,或者直接从Flopy的DISV对象中提取),以及河流的Shapefile:
import geopandas as gpd import shapely import flopy # 读取DISV单元格多边形(假设已导出为shapefile) disv_cells = gpd.read_file("disv_cells.shp") # 读取河流shapefile(确保是LineString类型) river_lines = gpd.read_file("river_network.shp")
如果是从Flopy模型中提取单元格多边形,可以用以下方式生成:
# 假设gwf是已创建的MF6 GWF模型对象 disv = gwf.disv cell_polygons = [] for cell_idx in range(disv.ncpl.get_data()): # 获取单元格的顶点坐标 vertices = disv.vertices.get_data()[disv.cell2vert.get_data()[cell_idx]] polygon = shapely.Polygon(vertices) cell_polygons.append({"cellid": cell_idx, "geometry": polygon}) disv_cells = gpd.GeoDataFrame(cell_polygons, crs="EPSG:4326") # 替换为你的坐标系
步骤2:匹配河流与单元格,拆分河段
对每条河流LineString,找到它相交的所有单元格,再把河流拆分成每个单元格内的子线段(每个子线段对应一个SFR reach):
from shapely.ops import split all_reaches = [] current_reach_id = 1 # 遍历每条河流 for _, river in river_lines.iterrows(): river_line = river.geometry # 筛选出与河流相交的单元格 intersecting_cells = disv_cells[disv_cells.intersects(river_line)] # 用单元格边界分割河流线段,得到每个单元格内的子河段 cell_boundaries = shapely.MultiPolygon(intersecting_cells.geometry.tolist()).boundary split_segments = split(river_line, cell_boundaries) # 为每个子河段关联对应的单元格和属性 for seg in split_segments.geoms: # 通过子河段的质心找到所属单元格 cell = intersecting_cells[intersecting_cells.contains(seg.centroid)].iloc[0] # 构建reach字典(属性根据你的Shapefile字段调整) reach = { "reachid": current_reach_id, "cellid": cell["cellid"], "length": seg.length, "strtop": river.get("river_elev", 100), # 河流顶部高程,可从Shapefile取或设默认值 "strbot": river.get("river_bed_elev", 95), # 河床底部高程 "strhc1": river.get("bed_k", 1e-4), # 河床水力传导系数 "width1": river.get("river_width", 10), # 河流宽度 "mannings": river.get("mannings", 0.03) # 曼宁糙率系数 } all_reaches.append(reach) current_reach_id += 1
步骤3:建立Reach上下游连接
根据河流的流向(拆分后的子线段顺序就是河流的流向),为每个reach设置上下游关联:
# 按河流分组处理连接(如果有多条河流,需要先按河流分组) # 这里假设all_reaches是按单条河流的顺序排列的,若有多条河流需先分组 for i in range(len(all_reaches)): if i == 0: all_reaches[i]["upstream"] = 0 # 首段无上游 else: all_reaches[i]["upstream"] = all_reaches[i-1]["reachid"] if i == len(all_reaches)-1: all_reaches[i]["downstream"] = 0 # 末段无下游 else: all_reaches[i]["downstream"] = all_reaches[i+1]["reachid"]
步骤4:用Flopy创建SFR包
把整理好的reach数据转换成Flopy要求的格式,然后创建SFR包:
# 假设gwf是已初始化的MF6 GWF模型对象 # 整理reach_data:按MF6 SFR的参数顺序排列 reach_data = [] for r in all_reaches: reach_data.append(( r["reachid"], r["cellid"], r["length"], r["strtop"], r["strbot"], r["strhc1"], r["width1"], r["mannings"], r["upstream"], r["downstream"] )) # 创建SFR包 sfr = flopy.mf6.ModflowGwfSfr( gwf, nreaches=len(all_reaches), reach_data=reach_data, # 设置packagedata:定义SFR的整体配置,比如下游边界类型 packagedata=[(0, "downstream", "free")], # 如果需要设置初始流量,可添加period_data period_data=[[(0, "flow", 100.0)]] # 示例:所有reach初始流量100 m³/d ) # 写入SFR输入文件 sfr.write_file()
三、注意事项与优化建议
- 空间匹配精度:如果河流线段和单元格边界的匹配有误差,可以给河流线段加微小的buffer(比如
river_line.buffer(0.1)),确保能正确匹配到单元格; - 属性补全:如果Shapefile缺少部分属性(比如河床高程),可以用DEM数据插值获取,或者参考区域水文地质资料设置合理默认值;
- 参数验证:MF6的SFR参数较多,建议对照官方文档确认每个参数的含义和格式,避免参数顺序或类型错误;
- 参考SFRmaker源码:虽然SFRmaker对DISV支持不完善,但可以参考它的空间匹配逻辑(比如线段拆分、单元格关联),修改后适配DISV网格。
内容的提问来源于stack exchange,提问作者nikobenho
相关产品推荐
相关产品推荐

