简介:面向GIS与水文分析学习者的一份完整DEM河流提取实践包,聚焦如何从数字高程模型中自动识别与提取河网。内含可直接运行的DEM_Water_Analysis.exe、配套的dem-data.txt测试数据,以及用C#编写的核心算法源代码,帮助用户从数据准备、流向计算到阈值筛选走通全流程。包内共81个文件、约3.12MB,主要文件类型包括exe可执行程序、cs源码、jpg效果图、txt算法说明及dll依赖库,结构清晰,既适合课堂实验也方便二次开发。程序指南与算法说明文档详细讲解了D8流向法和Flow Accumulation累积量原理,并给出参数设置建议;结果截图对比了不同汇流阈值(20至10000)下的河流提取效果,便于直观评估算法参数的影响。目前已有538人学习下载,对于想掌握GIS水文分析、DEM地形分析或河网提取方法的用户,是一份可直接上手、可验证的参考资料。
1. 基于DEM数据的河流提取:不只是填洼和流向那点事
做GIS水文分析的人多半都有过这种经历:拿到一份DEM,想提取河网,结果跑出来的河道要么断断续续,要么在平原地带乱窜,甚至把山脊线当成了河流。市面上讲水文分析的教程不少,但真正能把手上的DEM数据变成可用河网、还能导出shp的完整流程,很多资料都只讲了半截。这份基于DEM数据的河流提取资源,解决的就是从原始DEM到矢量河网的全流程问题,包含填洼处理、流向计算、累积流量阈值调整、河网分级以及矢量化导出,适合正在做水文分析、洪水模拟或流域划分的GIS从业者,也适合刚接触ArcGIS水文模块但不想在参数上调来调去的新手。它不玩虚的,直接给出一条能跑通的路。
2. 水文分析的核心逻辑:为什么DEM不能直接用来提取河流
2.1 原始DEM里的“坑”:洼地、平地与伪河道
很多初学者第一步就栽在“直接用DEM算流向”上。原始DEM中通常存在两类问题:一是真实地形中的凹陷区域,比如喀斯特漏斗或冰川侵蚀坑;二是数据采集和插值过程中产生的伪洼地。这两类洼地都会让水流方向计算出现“死胡同”——水流到洼地里就停了,没法继续向低处汇流,提取出来的河网自然就是断的。常见做法是先做填洼(Fill),把凹陷区域抬升到与周围最低溢出口齐平。
但填洼不是无脑点一下就行。填洼有个关键参数叫“Z值限制”,默认情况下ArcGIS的Fill工具会填掉所有洼地,包括那些可能是真实地形的凹陷。如果研究区是喀斯特地貌或者人工改造过的平坦区域,填洼反而会让河道走向失真。我一般会先做一个试探性的流向分析,看看原始DEM里的洼地分布规模,再决定填洼阈值。填得太多,河网密度会失真;填得太少,流向计算会断流。
2.2 流向算法与累积流量:D8、MFD与阈值的关系
流向计算是河网提取的地基。ArcGIS默认用D8单流向算法,每个像元只流向周围8个像元中坡度最陡的那一个。D8算法在陡峭山区表现不错,但在缓坡或平原区域容易出现平行河道——因为坡度差异太小,流向容易被噪声主导。另一种常用的是MFD多流向算法,把水量按坡度比例分配给多个下游像元,适合平坦区域,但生成的河网边缘比较模糊,矢量化后毛刺多。
累积流量(Flow Accumulation)是把流向结果累加出每个像元的上游汇水面积。这里最难定的是阈值:累积流量值大于某个像素数的像元才被认定为河道。阈值定得越小,河网越密,河道数量爆炸;阈值越大,河网越稀疏,主干道明显但支流全没了。这个阈值没有固定标准,跟DEM分辨率直接挂钩。比如30米分辨率DEM,用500像素起步;12.5米分辨率,可能得用2000甚至更高。我一般会先用几个不同阈值试跑,对比河网与真实水系在空间上的吻合度,再定最终值。
2.3 一份资源该包含什么:从栅格到矢量的完整链条
一份能落地的DEM河流提取资源,至少得覆盖几步:数据预处理(填洼、消噪)、流向计算、累积流量、栅格河网提取、河网分级(Strahler或Shreve)、栅格转矢量、以及矢量河网的平滑与简化。很多教程只讲前四步,转矢量后就直接用,结果生成的shp线要素上有一堆锯齿和伪分支。真正实操过的工程师都知道,栅格河网转矢量之后,还得做简化(Simplify)和消除短小伪河道(Eliminate),才能用于制图或进一步的水文模拟。
这份资源的价值就在于它把上述链条完整串起来了,并且给出了每个工具的参数参考值。它不是那种只丢一个脚本或者PPT的素材包,而是把参数设置、处理顺序、常见报错和解决思路都整理好的实操笔记。拿到手后,只要你的DEM数据格式没问题,基本可以按图索骥,少走好几天的弯路。
3. 实操第一步:填洼与流向计算的参数设置细节
3.1 填洼工具的正确打开方式:Z值限制与条件填洼
打开ArcToolbox里的Spatial Analyst 工具→水文分析→填洼(Fill),界面很简单,输入DEM、输出栅格,还有一个可选参数“Z限制”。默认空着,表示所有洼地都会被填平。如果研究区的地形相对完整,只是数据有杂讯,用默认值就好。但我更推荐一个习惯:先用【焦点统计】或【栅格计算器】生成一份“疑似洼地”掩膜,看看洼地的数量和空间分布,再决定是不是全部填掉。
案例:某流域DEM有明显的大坝和水库区域,直接填洼会把水库填成一个平面,河流提取结果在水库下游断流或者错位。这种情况下不能全局填洼,得先把水库区域掩膜出来,填洼时用【提取分析】将水库区域排除,再对剩余区域填洼。还有人用条件填洼,即只填洼地深度小于某个值的凹陷,比如小于10米填掉,大于10米的保留。这个阈值没有定论,跟DEM垂直精度和研究目的有关。做洪水淹没模拟的,洼地通常保留;做河网提取的,填得干净些更好。
填洼完成后,别急着跑流向。我习惯先用栅格计算器做一个减法,把填洼前后的DEM相减,检查哪些地方被抬升了,抬升幅度是否合理。如果某个区域抬升了50米以上,那大概率有问题,要么是真实地形,要么是DEM有粗差。这一步虽然多花两分钟,但能省掉后面流向计算的整段返工。
3.2 流向计算:D8算法与边缘处理
流向工具位置:Spatial Analyst 工具→水文分析→流向(Flow Direction)。输入填洼后的DEM,输出流向栅格。这里有一个容易忽略的选项:输出法线方向是可选的,一般不勾选,默认输出的是“流向编码”8个方向(1,2,4,8,16,32,64,128),这不是普通的方向角数值,而是在学习栅格计算时容易搞混的点。后面做条件判断时,比如要筛选东向流向,不是找流向值等于90,而是找等于64的像元。
D8算法本身很简单:计算中心像元与周围8个像元的高程差,除以水平或对角线距离,取最大坡降方向。但DEM边缘像元周围没有8个邻居,ArcGIS会自动把它标记为无数据。如果你发现流向栅格边缘有一圈奇怪的值,多半不是算法问题,而是原始DEM的无效值没处理好。处理办法是在填洼前,先用【按掩膜提取】把DEM范围严格限制在流域边界内,或者用【栅格计算器】把无效值统一赋为NoData。边缘河网提取错误往往就是这一步埋下的雷。
3.3 实测参数参考:30米与12.5米DEM的设置差异
不同分辨率的DEM,推荐参数差异很大。以我处理过的几个项目为例:
| DEM类型 | 分辨率 | 填洼Z限制 | 流向算法 | 累积流量阈值(像素) |
|---|---|---|---|---|
| SRTM | 30米 | 默认(全部填平) | D8 | 500~1000 |
| ALOS | 12.5米 | 默认(全部填平) | D8 | 2000~4000 |
| 无人机雷达 | 2米 | 按需填洼(保留真实坑塘) | MFD | 5000以上 |
注意:这个表格是参考值,不是死规定。累积流量阈值跟DEM分辨率、地形坡度、降雨特征都有关系。坡度陡的山区,汇水面积小也能形成河道,阈值可以调小一点;平原地区,河道形成的汇水条件更严格,阈值要调大。我见过有人拿着一份5米DEM,阈值设500,结果提取出来的“河网”把农田排水沟全画进去,整个流域密密麻麻像毛细血管,根本不能用。
调阈值没有捷径,只能多次尝试。一个技巧是,先用一个较大的初值(比如像素数的5%),跑出主干河流,再逐步降低阈值,观察支流出现的趋势和断裂点。当阈值降到某个值之后,河网长度会突然增多,那往往就是伪河道的开始,退回到上一个阈值即可。
4. 河网提取与分级:把栅格水线变成有层次的河道
4.1 累积流量栅格的应用与阈值选取技巧
得到流向栅格后,用【水文分析】→【流量】工具计算累积流量。输入是流向栅格,输出累积流量栅格,单位为像元个数,代表每个像元接收的上游汇水像元数。这个数值大小与汇水面积成正比,换算成面积就是像元个数乘以单个像元面积。比如30米分辨率的DEM,一个像元面积是900平方米,累积流量1000,就代表汇水面积0.9平方公里。
阈值选取我一直用两步法:第一步,用【符号系统】把累积流量栅格按分位数分类显示,比如分成5类,看看累积流量在哪个区间突然稀疏,选择这个断点附近作为初值;第二步,生成河网后与高分辨率影像叠加目视检查,重点看河网是否穿过了山脊线、是否在平原区域消失。一个可复用的经验是:阈值取DEM分辨率的10到20倍。例如30米DEM,阈值取300到600像元;12.5米DEM取1250到2500像元。如果河网断头太多,降低阈值;如果河道乱窜,提高阈值。
4.2 栅格河网转矢量之前必须做的清理操作
用【栅格计算器】提取河网时,常用公式是 SetNull(“FlowAcc”< 阈值, “FlowAcc”),小于阈值的像元置空,大于阈值的保留。这一步得到的栅格河网通常是8像素宽的粗线,直接转矢量会得到面状要素或者带锯齿的线。正确的做法是先对河网栅格做【栅格河网矢量化】(ArcGIS 10.8及以后版本有专门的“栅格河网矢量化”工具),或者用旧版的“Stream to Feature”。
无论哪个工具,转换之前得先做两个操作:一是【条件函数】将河网赋为常数1,背景赋为NoData,让河网形成连续区域;二是用【区域连通性】检查河网是否有断裂弧段,如果有,需要调整阈值或者补做一次“填洼”。断裂的河网矢量化后会生成多条短线,自动合并很困难,只能人工在编辑里补线,那事情就复杂了。
4.3 河网分级方案选择:Strahler与Shreve的区别
河网分级有两个常见方案。Strahler分级规则:没有支流的河段为1级,两条同级河段汇合后级别加1,不同级河段汇合后级别保持较大者。Shreve分级则简单粗暴,所有源头河段为1级,汇合后级别相加。Strahler适合反映河网的等级关系,在地貌制图中常用;Shreve更适合做水文模拟,因为它能反映总汇水路径的贡献量。
ArcGIS中对应工具是“河网分级”(Stream Order),输入河网栅格和流向栅格,输出分级栅格。选哪种取决于你的用途。如果只是做流域边界或河道走向示意,Strahler就行;如果要做径流计算或洪峰分析,Shreve会更合理。参数设置上还有一个容易被忽略的点:输入河网栅格必须只包含河网像元(通常值为1),背景必须是NoData。很多人直接把累积流量栅格当作输入,结果跑出来的分级结果全是背景值,这是新手最容易翻车的地方。
# 如果不想手动操作ArcGIS界面,可以用arcpy脚本一键完成河网提取与分级 # 前提:已安装ArcGIS Desktop或Pro,并具备Spatial Analyst扩展许可 import arcpy from arcpy.sa import * # 设置工作环境 arcpy.env.workspace = r"D:/gis_data" arcpy.env.overwriteOutput = True # 输入文件——注意DEM必须是填洼后的 dem = "fill_dem.tif" # 1. 流向计算 flow_dir = FlowDirection(dem) flow_dir.save("flow_dir.tif") # 2. 累积流量计算 flow_acc = FlowAccumulation(flow_dir) flow_acc.save("flow_acc.tif") # 3. 提取河网栅格:阈值设为1000个像元(约0.9平方公里) threshold = 1000 river_rast = SetNull(flow_acc < threshold, 1) river_rast.save("river_rast.tif") # 4. 河网分级,采用Strahler stream_order = StreamOrder(river_rast, flow_dir, "STRAHLER") stream_order.save("stream_order.tif") # 5. 栅格河网转矢量 stream_vec = arcpy.sa.StreamToFeature(river_rast, flow_dir, "stream_vec.shp")这段脚本的逻辑顺序很关键:先算流向,再算累积流量,然后按阈值提取河网栅格,之后做分级和矢量化。注意第5步StreamToFeature输出的是线要素类,保存路径里不能用“stream_vec.shp”直接作为参数,准确写法是先设定输出要素类变量,再用arcpy.CopyFeatures复制。上面代码里的写法是为了清晰展示流程,实际运行时建议把第三步阈值单独拉出来,先用多个阈值试跑再落地脚本。另外,阈值调整是这条链路里最影响结果的一环——阈值设小了,河道太多;设大了,主干道断流。建议把阈值参数化,循环跑几个值,对比后再定。
5. 避开河流提取过程中最常见的五个坑
5.1 投影坐标系的错:平面坐标与地理坐标混用导致面积失真
现象:用WGS1984地理坐标系直接跑水文分析,提取出来的河网严重变形,流向方向斜得离谱。
原因:流量累积和流向计算都是基于水平和垂直距离的比例关系,地理坐标系的单位是度,而高程单位是米,两者直接计算坡降时,水平距离和垂直距离不在同一量纲下,算法认为几乎处处是陡坡。
解决:在做任何水文分析之前,先将DEM投影到适合研究区的投影坐标系,比如UTM或高斯克吕格。分辨率要留意一下,投影后的像元尺寸会发生变化,比如30米的SRTM投影到UTM北半球带,像元尺寸可能变成29.7米,累积流量的阈值也需要做相应微调。
5.2 填洼过头:把真实河槽填没了
现象:提取出来的河流路径平直,完全不像自然弯曲的河道,而且主流与明显河谷位置对不上。
原因:填洼工具的Z限制没设置,把所有洼地都抬平了。自然河道本身就是一种“洼地”,河槽的相对低洼被填掉后,水流方向找不到原本的汇水通道,只能沿着填平后的曲面乱走。
解决:改用带Z限制的条件填洼。在填洼工具中设置Z限制为比如10米(根据DEM竖直精度),只填埋深度小于10米的伪洼地,保留深度大于10米的真实河谷,然后重新计算流向。如果拿不准阈值,可以做一组对比实验,分别用0(不填)、5、10、20米,观察提取结果与影像中河道的匹配度。
5.3 累积流量阈值拍脑袋:河网密度失控
现象:同一份DEM,用1000阈值提取的河网有上百条支流,用5000阈值却只剩下几条主干道,两个结果都看起来不合理。
原因:阈值和DEM分辨率、地形起伏、研究区面积都有关系。有人用3000跑的太行山区很顺,但同样的值拿到平原水稻田区域,提取出来的全是沟渠网。
解决:不要直接拍一个阈值,先画一条“累积流量-河网长度”的关系曲线。方法是在【符号系统】中用分位数分级显示累积流量栅格,不断尝试阈值,每次记录河网栅格中的像元数,当阈值变化导致像元数跳变时,那个点就是地形上的“河道源头”与“坡面径流”的分界。研究区是山区的,这个跳变往往出现在累积流量500~2000之间;平原区域会明显靠后,可能到5000以上。实际项目中,我会按这个跳变点再乘0.5~2作为试验区间,对比影像决定。
5.4 矢量化后短线纵横交错:栅格河网没有先清理
现象:Stream to Feature生成的结果里,有大量细碎的短线,相互交叉,有的地方看起来像蜘蛛网,根本不是连续河网。
原因:栅格河网提取时,河道不是单像素宽的,而是多像素宽的条带。条带内部像元之间有多条可能的连通路径,转矢量时会产生多条平行短线。同时,水流在分叉处会生成多路径分支,如果没有做“缩窄”处理,短线自然满天飞。
解决:转矢量前,先对河网栅格做一次“细化”(Thin),或者使用“河流链接”(Stream Link)工具先给每条河段一个唯一编号,然后转矢量。另一种更稳妥的方法是直接把河网栅格转为“矢量化河网”工具(ArcGIS Pro 2.7+才有),它内部已经做了中心线提取,输出的线要素基本平滑。如果是老版本ArcGIS,就用“Stream Order”先合并河段再转矢量,能减少相当一部分交错短线。
5.5 河网断头:平原区提取结果频繁中断
现象:河道在平原区域走了一段就消失,再往前一段又出现,没法形成一个连续的河网。
原因:平原地区地形平坦,流向算法的D8只能在8个方向上选一个,可地形实际坡降方向往往介于两方向之间,导致流向不稳定,同一河段在不同像元上选出完全相反的方向。另外,平原地区DEM的高程精度往往不能满足水文分析要求,10米的误差就足以覆盖实际坡度。
解决:在填洼前先对DEM做平滑处理,比如【焦点统计】3x3窗口取均值,能抑制一些噪声。如果研究区确实地势平坦,可以考虑用多流向算法(MFD)替代D8,或者在流向计算前先对DEM进行“1米等高线平滑”之类的预处理。再一个土办法是:把累积流量阈值降低一档,让河道源头下探到低洼沟壑区域,往往能接上断头,但代价是支流增多,后期需要手工删除一部分。
6. 河网结果检验与实用技巧:让提取结果更贴近真实水系
6.1 与影像及现有水系数据叠加验证
河网提取完成后,第一件事不是急着出图,而是拿结果叠加到Google Earth或高分影像上,逐段对比。重点看几个地方:主流是否贴合河谷底部;支流源头是否出现在地形上的沟头位置;平原区域的曲线是否与旧河道吻合。如果主流偏离了河谷,大概率是流向计算阶段出了问题,回头检查DEM预处理。如果只有支流局部偏,通常是累积流量阈值偏低导致沟壑误判。
有一个我常用的检验方法:把提取河网和高精度遥感影像中的河流中心线做缓冲区对比,计算两者重合率。重合率在70%以上算基本合格,90%以上说明流程和参数设置很理想。重合率低于50%时,不要动阈值了,回归源头检查投影和填洼。
6.2 用坡度与汇水面积关系过滤伪河道
有些伪河道确实难以通过目视发现,尤其是在山地阴影区。一个更客观的过滤方法是使用“坡度-面积”阈值判断。原理是:真实河道的源头通常满足坡度与汇水面积的幂律关系,即坡度较大的区域需要较小汇水面积就能形成河道,而平坦区域需要很大汇水面积。如果你的河网里出现了一些既陡峭又汇水面积很小的短河道,那多半是算法噪声。
操作上,可以先从累积流量栅格中提取每个河网像元的汇水面积,再用【坡度】工具计算坡度,然后做散点图。如果散点图上有大量短河道落在“高坡度+低面积”区域,把它们单独提取出来分析,确认是噪声后,用【按属性选择】从矢量河网中删除。
6.3 批量提取多个小流域河网的自动化思路
当研究区覆盖范围很大,需要分多个子流域分别提取河网时,手工操作会累死人。建议把前面的arcpy脚本封装成一个函数,接收DEM路径、阈值、输出路径三个参数,然后在循环里遍历多个裁剪好的DEM子集。
# 封装一个单流域河网提取函数,用于批量处理 def extract_river(dem_path, threshold, output_dir): """ 简单封装:填洼->流向->累积流量->河网栅格->矢量化 dem_path: 单个子流域的DEM路径(已裁剪) threshold: 累积流量阈值 output_dir: 输出文件夹 """ import arcpy from arcpy.sa import * arcpy.env.workspace = output_dir arcpy.env.overwriteOutput = True # 填洼(默认填平全部洼地) dem_fill = Fill(dem_path) dem_fill.save("dem_fill.tif") # 流向 flow_dir = FlowDirection(dem_fill) flow_dir.save("flow_dir.tif") # 累积流量 flow_acc = FlowAccumulation(flow_dir) flow_acc.save("flow_acc.tif") # 河网栅格 river_rast = SetNull(flow_acc < threshold, 1) river_rast.save("river_rast.tif") # 转矢量 river_shp = r"%s/river_%s.shp" % (output_dir, str(threshold)) arcpy.sa.StreamToFeature(river_rast, flow_dir, river_shp) print("完成:", dem_path, "阈值:", threshold, "->", river_shp) # 批量调用,注意DEM子集要确保已在统一投影坐标系下 if __name__ == "__main__": dems = [ "D:/watersheds/sub1.tif", "D:/watersheds/sub2.tif", "D:/watersheds/sub3.tif" ] for dem in dems: extract_river(dem, threshold=1000, output_dir="D:/watersheds/out")这个脚本在实测中有一个值得注意的参数:Fill工具的默认行为会无条件填充所有洼地,在多个子流域同时提取时,某些子流域里可能存在大坝或水库,会导致提取结果异常。稳妥的做法是在循环外面先对所有子流域做统一的掩膜,将水体区域抠掉,再交给批量脚本。另外,阈值如果需要对不同子流域设置不同值,可以把循环里的threshold改成读取一个配置表,按DEM名称索引对应阈值。
批量处理结束之后,河网矢量文件会很多,建议统一使用GDB(地理数据库)存放,不要用散落的一个个shapefile。shapefile在编辑拓扑关系时容易出问题,线要素属性表里的字段长度也有限制。GDB能更好地保持线区间连续性,也方便后续做河网汇流累积等操作。
从第一次把DEM填洼到最终得到像样的河网,中间踩过的坑远不止上面列的那些。投影、填洼、阈值、矢量化、批量化,每个环节都有它的脾气。尤其是阈值这个问题,没有拿来即用的万能答案。从那以后,我每次拿到一份新DEM,都会强制自己先做一个快速试跑:投影检查、填洼对比、坡度-面积分析、不同阈值出图比对,整套流程走下来不超过半小时,但能确保后面正式处理时不会在河网形态上返工。希望这份U盘里的资源和这篇实操笔记,能帮你把DEM变成真正能出图、能建模的河网数据,也希望你在跑通一次完整流程后,能摸索出属于自己研究区的那套参数组合。希望帮到你。
本文还有配套的精品资源,点击获取