简介:这套ArcGIS河流平均比降计算工具,定位为水文分析与数字地形处理场景下的实用插件,面向有一定ArcGIS基础的水文学、地理信息系统(GIS)研究者与相关行业技术人员,帮助使用者基于约翰斯通-克罗斯法快速完成河流平均比降测算,支撑河流水流特性分析、地貌变化研究和洪水预测等工作。压缩包内共2个文件,包含ArcGIS自定义工具箱(.tbx)和配套Python脚本(.py),前者提供可视化工具入口,后者承载核心计算逻辑,可将DEM处理、河网提取、长度与落差计算及加权平均比降输出等环节封装为自动化流程,减少手动操作与重复建模。资源包整体仅5KB,结构精简,适合在已有ArcGIS环境下直接加载使用;目前已吸引3652人学习或下载,具备一定实用参考价值。工具简洁地呈现了约翰斯通-克罗斯法在ArcGIS中的落地方式,使用者可结合自身流域数据快速获得平均比降结果,为水文模型建立、水资源管理和防洪规划等提供数据基础,也可作为相关教学或二次开发的参考。
1. 平均比降到底怎么算:先搞清算法再动手
河流平均比降,也叫河流平均坡降,是河道纵剖面分析里绕不过去的基础参数。做洪水计算、水库回水推算、河道整治设计或者水土保持评价时,都要用到它。ArcGIS里提取河网线的教程一抓一大把,但真正卡住大多数人的反而是最后一步:线出来了,怎么把"平均比降"这个数值算出来?这个问题我前前后后折腾了不少回,今天把整套方法从头到尾捋一遍。
先明确一个概念:河流平均比降指的是单位河长上的高差变化,一般用千分率(‰)表示。计算公式并不复杂,最常见的是下面两种:
- 简单法,也叫起讫点法:J = (Z上游 - Z下游) / L,其中Z是河道起端和末端的河底高程,L是河道平面长度。算出来的结果如果乘以1000,就是千分率。
- 加权分段法:把河道按长度分成若干段,逐段量测比降,再按河段长度加权平均:J加权 = Σ(li × Ji) / Σli,其中li是第i段河长,Ji是第i段比降。
从数学上看,加权分段法化简之后和简单法结果是一样的,因为Σli×Ji正好等于总高差。那为什么还要分段?因为在真实河道里,起点和终点之间可能有深潭、跌水、缓滩,简单法只能得到一个"整体平均",完全看不出比降沿程变化。而分段后你能看到哪一段是陡坡段、哪一段是缓流段,这对于分析洪水演进、判定冲刷部位非常关键。我的建议是:如果只是要一个交差用的参数,简单法足够;如果要用于工程分析,务必做分段加权,并保留逐段比降表。
在ArcGIS里算比降,核心难点不是公式,而是"如何准确地从DEM上取出河道各位置的高程"。选错方法和坐标系,结果能差出好几倍。后面我会把每一步的参数设置和坑都写清楚。
2. 数据准备与河网提取:数据不干净,算出来的比降全是废的
2.1 填洼、流向、累积流量一条龙
比降计算的前提是有一条连续、拓扑正确的河网线。很多新手直接在原始DEM上跑水文分析,结果河网断成一段一段,或者出现莫名其妙的"倒流"现象。这通常是因为DEM里存在洼地——真实地形中的坑、采集误差形成的伪洼地都会截断水流路径。
所以第一步必须是填洼。用Spatial Analyst的Fill工具,把洼地深度填平,确保地表径流能连续流动。如果研究区是平坦的冲积平原,还可以再叠加一次Fill,避免平原区水流方向随机。
填洼之后依次执行:
- Flow Direction(流向):用D8算法确定每个像元的水流方向。D8的意思是只允许水流流向周围8个邻域像元中坡度最陡的一个,这个算法简单稳定,也是后续工具依赖的基础。
- Flow Accumulation(累积流量):统计每个像元上游汇入的像元数量。某个像元的累积值越大,说明它越处于沟谷底部。
- 栅格计算器提取河网:比如 Con("facc" >= 1000, 1)。这个阈值不是你随便拍的,它直接决定河网密度。阈值为1000,意味着只有汇水面积超过1000个像元面积的位置才成为河道。如果DEM分辨率是30米,一个像元代表900平方米,那么1000个像元的阈值对应的最小汇水面积就是0.9平方公里。这个面积应该与研究区实际的水文特征匹配,山区可以取小一点,平原区取大一点。
2.2 河网栅格转矢量:这一步很多人漏了坐标系检查
拿到河网栅格后,用Stream to Feature工具把它转成矢量线。工具本身不难,难的是转完之后你会得到一堆很长很碎的线,而且它们的属性表是空的——没有长度、没有高程、什么都没有。
转矢量之前必须检查两件事:
第一,所有栅格数据的环境设置必须一致。在ArcGIS的"环境设置"里,把"处理范围"设为DEM的范围,把"像元大小"设为DEM的像元大小,把"捕捉栅格(Snap Raster)"设为原始DEM。否则就会出现热词里大家经常搜的"范围不一致""像元个数被更改"这类问题,栅格之间错位半个像元,后面提高程全都会偏。
第二,坐标系必须投影坐标。像元大小和距离只能在投影坐标系(比如高斯-克吕格、UTM)下计算,经纬度坐标的单位是度,用它算长度和面积完全是错的。我见过有人拿着WGS84的WGS84经纬度DEM直接算河长,得出来的"长度"连单位都不知道是啥,比降自然毫无意义。建议统一用与区域匹配的区域投影坐标系,国内一般用CGCS2000 / 3-degree Gauss-Kruger,或者对应UTM分带。
3. 核心实操:从DEM到平均比降的完整流程
3.1 简单法整段计算:5分钟跑通第一版结果
这里以ArcGIS Pro 3.x为例,ArcMap 10.8的界面略有差异但工具名称基本一致。
第一步,准备好前面提取出来的河网线数据,确认它和DEM在同一投影坐标系下。如果还不是投影坐标,先用Project工具转换。
第二步,给河网线赋高程。打开3D Analyst工具下的"Interpolate Shape(插值形状)",输入河网线和DEM,输出的线要素就带上了Z值,每个折点的高程都从DEM上插值得到。这个工具会把DEM当作连续表面,按折点位置线性内插,精度比直接提取栅格值更好。
如果没有3D Analyst许可,也可以在线条端点生成点,再用Extract Values to Points提取高程,效果类似。
第三步,计算高差和长度。打开线要素属性表,添加四个双精度字段:Length_M、Z_Start、Z_End、Gradient_PT。用Calculate Geometry算出线的投影长度;Z_Start和Z_End用Python表达式取线几何的起点和终点Z值,在字段计算器里写:
!shape!.firstPoint.Z
Z_End同理:
!shape!.lastPoint.Z
需要注意,Stream to Feature生成的线方向理论上是从上游指向下游,但实际经常出现个别线方向反转的情况。这时候直接用firstPoint减lastPoint会得到负的高差。稳妥做法是统一用绝对值:
abs(!shape!.firstPoint.Z - !shape!.lastPoint.Z)
第四步,计算比降千分率:
Gradient_PT = abs(!shape!.firstPoint.Z - !shape!.lastPoint.Z) / !Length_M! * 1000
这一步就出结果了。我实测过一个项目里的山区河段,DEM为12米分辨率,用简单法算出来比降约28.6‰,手算校核也在29‰附近,符合预期。
3.2 加权分段法:等距取点、批量提取高程、字段计算
简单法只能给一个整体数值,遇到需要逐段分析的情况就要上分段法。操作流程分四步:
第一步,在河网线上生成等距采样点。使用"Generate Points Along Lines"工具,按河线长度每100米生成一个点(间距可自己定——山区陡坡河流建议50米,平原缓流河流可以放宽到200米,原则是能分辨出比降的沿程变化)。工具输出会自动带一个DISTANCE字段,表示该点距离起点的沿线长度,这个字段后面要用。
第二步,把DEM高程提取到采样点上。用"Extract Multi Values to Points"工具,输入采样点和高程栅格,输出后每个点会带上DEM高程值,我通常把字段名改成Z。
第三步,按"线ID + DISTANCE"排序。采样点属性表里有原始河线的ObjectID字段和DISTANCE字段,按这两个字段排序,保证同一河段上的点是按上下游顺序排列的。
第四步,用Python脚本逐段计算比降并汇总。计算逻辑是:对每个河段,从下游相邻点的高程差除以两点间的沿河距离,得到逐段比降,再按段长加权汇总。
脚本核心片段大概是这样:
import arcpy # 输入采样点要素类 points = r"路径\采样点" fields = ["RiverID", "DISTANCE", "Z"] # 按河段ID分组处理 data = {} with arcpy.da.SearchCursor(points, fields) as cursor: for rid, dist, z in cursor: data.setdefault(rid, []).append((dist, z)) # 汇总结果 total_length = 0.0 weighted_sum = 0.0 segments = [] for rid, pts in data.items(): pts.sort() # 按DISTANCE升序,即从上游到下游 for i in range(1, len(pts)): d1, z1 = pts[i-1] d2, z2 = pts[i] seg_len = d2 - d1 seg_slope = abs(z2 - z1) / seg_len total_length += seg_len weighted_sum += seg_slope * seg_len segments.append((rid, d1, z1, d2, z2, seg_len, seg_slope * 1000)) overall_gradient_permil = weighted_sum / total_length * 1000把每个采样点的字段名、河段ID字段名替换成你数据里实际的名称就能跑。脚本跑完,不仅得到整体平均比降,segments列表里还保存了每一段的比降,可以直接导出成表格,用于画河道纵剖面比降变化图。
3.3 顺手验证:比降结果合理吗?
拿到结果先别急着用,做两个快速验证:
一是方向检查。随机抽取几条河段,把采样点的Z值按DISTANCE排序打印出来,上游点的Z值一定要大于下游点。如果出现大量上游Z比下游低的情况,说明线的方向或者提取高程的步骤出错了。
二是量级对照。山地河流比降通常在10‰到100‰之间,平原河流在0.1‰到5‰之间。如果你的山区河道算出来只有0.5‰,先检查投影坐标系和DEM单位是不是有问题;如果算出来几百‰,检查是不是把DEM高程单位(米)和平面坐标单位搞混了。
4. 把流程封装成工具:模型构建器与Python脚本
4.1 模型构建器:拖拖拽拽也能做,关键是参数化
如果只是偶尔算一两条河,手动操作完全够用。但实际项目里往往有几十条河、多个小流域要批量计算,这时候就必须把流程封装成可复用工具。
用ArcGIS的模型构建器可以做到,我的封装思路是:
- 新建模型,把Fill、Flow Direction、Flow Accumulation、栅格计算器、Stream to Feature依次拖进来连线。
- 把DEM和累积流量阈值设置为模型参数(右键各工具 → 参数 → 勾选),这样使用者只需要输入DEM和阈值就能跑。
- 再加一个Interpolate Shape工具,把河网线输入接进去,输出带Z值的线。
- 最后加一个Python脚本工具,把前面那个分段计算脚本封装进去,输入带Z值的河网线和采样间距,输出整体比降和逐段比降表。
模型构建器的好处是可视、可改、好交付,项目里其他同事也能直接拿来用。缺点是模型跑批处理时无法动态跳过某个失败的子流域,整体容错性差一些。
4.2 Python脚本:一次跑完所有子流域
我自己更倾向用纯Python脚本,把所有子流域DEM放在同一个文件夹里,循环处理。定义一个函数,输入DEM路径、输出GDB路径、阈值、采样间距,返回平均比降结果。核心流程就是第3章的操作顺序,但中间用arcpy环境设置对齐栅格:
import arcpy from arcpy.sa import * arcpy.env.workspace = r"路径\工作空间.gdb" arcpy.env.overwriteOutput = True arcpy.env.snapRaster = dem arcpy.env.cellSize = dem arcpy.env.extent = dem fill_dem = Fill(dem) flow_dir = FlowDirection(fill_dem) flow_acc = FlowAccumulation(flow_dir) threshold = 1000 stream_ras = Con(flow_acc > threshold, 1) streams = StreamToFeature(stream_ras, flow_dir, "streams_10k")封装成函数之后,配合一个记录所有DEM路径的Excel清单,就能一行行批量出结果。这个方案我跑过50多个小流域,一次性跑完没出问题,比一个一个手动操作高效太多。
4.3 许可和环境问题提醒
封装工具时最容易踩的坑是许可。Free落、Flow Direction这类工具需要Spatial Analyst扩展许可;Stream to Feature在高版本Pro里可以用,但旧版ArcMap有时会报"you are not licensed for ArcGIS for Desktop Advanced"的错误,因为该工具需要Advanced许可级别。如果你遇到这个报错,有两个办法:一是申请Advanced许可;二是绕过该工具,用栅格计算器把河网栅格重分类成1,再用Raster to Polygon转面,最后用Polygon to Line转线。效果一样,只是多几步。
运行模型时还需要注意,Python脚本里的arcpy.env设置会影响所有后续输出。如果模型里同时处理多个栅格,务必把Snap Raster设为基础DEM,否则各个中间结果的范围和像元对齐会随机漂移,这也是"栅格范围不一致"报错最常见的来源。
5. 常见问题与排查记录:我踩过的坑都在这了
5.1 计算长度结果明显不对:投影坐标系的锅
大概率是数据框或者数据本身还在经纬度坐标系下。判断方法很简单:看长度字段的值,如果一条几十公里的河道,算出来的"长度"只有几十到几百,那单位一定是"度"而不是"米"。解决办法是把数据投影到合适的投影坐标系,再重新计算几何长度。另外要注意,Calculate Geometry的时候要选对坐标系,默认用的是数据框坐标系,如果你把数据框设成了Web Mercator,长度也会被扭曲,尤其是高纬度地区。
5.2 河网断断续续,转出来的线零碎不堪
先检查是否做了填洼;再检查累积流量阈值是否偏高。如果阈值已经很低了还是断,说明DEM质量不行,可以考虑对DEM做一次低通滤波或重采样到更粗分辨率。还有一个容易被忽视的点:研究区范围必须比关心河道的最上游再向外扩展一定距离,不然源头区域会被范围边界截断,河流上游直接断头。
5.3 按掩膜提取报错Error 010568
这个报错出现在使用Extract by Mask时,通常原因是掩膜数据与待提取栅格的像元大小、范围或者空间参考不一致。解决方法是先检查环境设置中的"捕捉栅格"和"像元大小",把掩膜也重采样到与DEM一致。如果你的掩膜是矢量数据(shp或面要素),Extract by Mask可以直接输入矢量,反而更不容易出错。
5.4 ArcGIS一直未响应,跑大DEM时要特别注意
几十GB的全省DEM直接跑Fill,很容易让ArcGIS卡死。我的经验是:先用或Resample把DEM裁到研究区范围,再处理;输出格式用File Geodatabase,不要直接输出到shp;工作空间放本地SSD,不要放网络盘。如果仍然卡顿,就把填洼和流向分两步跑,每跑完一步先保存中间结果,避免崩溃后全功尽弃。
5.5 属性表字段和值显示乱码
这个主要是使用shapefile时出现的。编码文件(.cpg)缺失或与系统语言不匹配会导致中文乱码。如果项目允许,建议全程用File Geodatabase(gdb),从根本上避免编码问题;如果必须交付shp,可以在导出时手动指定编码UTF-8。比降计算过程中的字段名和值都是英文数字,通常不受影响,但一旦涉及样点编号等中文备注字段就要留意。
5.6 在线底图加载失败,河网没法对照检查
想用天地图影像底图做河网合理性检查,结果加载不出来。最常见的原因是工作环境的网络策略限制,或者动态投影设置不对。可以先确认数据框坐标系是Web Mercator(3857),再检查底图服务地址是否能直接浏览打开。如果底图确实加载不出来,也可以用高分辨率的卫星影像切片替代,或者干脆用DEM生成的山体阴影图做背景。
6. 最后再补充一个实用小技巧
我用这个流程跑了很多项目之后,最大的体会是:比降计算本身不复杂,真正的价值在于把"分段比降表"留下来。很多报告的评审专家不只看最终平均值,还会追问"哪一段比降最大""跌水位置在哪"。所以每次计算完,我都会把逐段比降结果导出成Excel,再用线要素的路径距离字段做一张简单的纵向剖面图——横轴是沿河距离,纵轴是比降值,哪一段陡、哪一段缓一目了然。这个小文件放到项目成果里,既能让报告显得专业,也方便后续复核。
另外,给同行们一个建议:DEM的垂直精度直接影响比降结果。能用12米或更高精度的DEM就不用30米的;如果只有粗分辨率DEM,计算前先对研究区内已知水文断面的高程做一次比对校准,误差太大就要谨慎使用结果。河流平均比降这种参数,算出来只是第一步,算得准、算得可复核才是真正体现功力的地方。
本文还有配套的精品资源,点击获取