全国各省市县坡度数据,听起来像是一个“给个压缩包就行”的活儿,但真正接过这种需求的人都知道,这句话背后意味着什么。全国 34 个省级行政区、330 多个地级市、2800 多个县级行政区,每一个层级都要能拿出一份坡度栅格,还能随时统计出“哪个县坡地占比高”“哪个乡镇不适合搞建设”。2024 年这版数据,我前后折腾了小一个月,从底图选型、坐标系设计、批量裁剪到分级统计,踩了不少坑,也沉淀了一套可以直接复用的流程。这篇文章就把我做全国各省市县坡度数据的完整思路、实操步骤和翻车记录一次性讲清楚,给正打算做类似数据的同行一个参考。
1. 2024年版坡度数据构建:先把数据源和坐标系这两件“地基”定下来
1.1 高程底图选型:SRTM、ALOS、GDEM怎么选
做坡度数据,第一步不是打开软件,而是先问自己一个问题:用谁的DEM当底图?
坡度是基于DEM(数字高程模型)计算出来的,DEM的精度直接决定了坡度数据的上限。我在这个项目里对比了三套主流开源高程数据,分别是SRTM、ALOS PALSAR和ASTER GDEM,它们的核心差异主要集中在地面分辨率、高程精度和覆盖完整性上。
| 数据源 | 分辨率 | 高程精度(RMSE) | 覆盖范围 | 适合场景 |
|---|---|---|---|---|
| SRTM V3 | 30米 | 约4-6米 | 全球60°N-56°S | 全国大范围均匀计算,最均衡 |
| ALOS PALSAR | 12.5米 | 约5米 | 全球覆盖 | 局部高精度项目,数据量大 |
| ASTER GDEM V3 | 30米 | 约7-14米 | 全球覆盖 | 高纬和山区补充,需去噪 |
SRTM原本是航天飞机雷达地形测绘任务采集的,V3版本经过了多轮校正,是目前国内做全国尺度地形分析最常用的底图。它最大的优势是数据均匀、噪声小,30米分辨率对于“省市县级坡度统计”来说完全够用,而且在平缓地区的高程表现非常稳定。ALOS精度更高,细节更多,但全国范围拼接起来数据体量巨大,处理时间会成倍增加,除非客户明确要求“乡镇级细坡度”,否则优先级不高。
ASTER GDEM覆盖纬度更高,在某些山区细节上甚至比SRTM好,但它有个老毛病:局部云噪声和残余伪影比较多。我专门拿两个数据源在同一个山区县做过对比,ASTER在陡坡区域容易出现“阶梯状”假象,算出来的坡度在30°-45°区间会莫名出现条带。所以除非SRTM在某个边境区域存在空洞,否则我不推荐把它作为全国数据的主底图。
我的最终选择是:以SRTM V3 30米为全国基础底图,用ALOS 12.5米对几个重点县域做局部加密。
1.2 为什么2024年版必须以当年行政区划为基准重建
很多人在这一步会问:市面上不是已经有“全国坡度图”吗?直接下载来裁剪不行吗?
行,但有一个风险:老坡度图对应的可能是旧版行政区划边界。2024年版的意思,不仅仅是“数据是新算出来的”,更重要的是行政区划边界必须用2024年的乡镇、县、市、省四级边界来裁。国内这几年行政区划调整不算少,撤县设区、乡镇合并、新区设立都在持续发生。你如果用2020年的县级边界去裁2024年的坡度数据,某些县的范围对不上,面积统计表里就会冒出一些“无归属”的啰嗦数据。
我当时采用的做法是:直接从公开的全国地理信息资源目录服务系统里拉取2024年版省、市、县三级行政区划矢量边界,重点检查字段里是否包含标准的行政区划代码(省级两位、市级四位、县级六位),这个代码是后续做属性挂接和分文件夹命名的基础。
拿到边界之后千万别急着裁,先做一轮拓扑检查。手动跑一遍ArcGIS的“检查几何”和“拓扑”工具,重点看有没有窄长自相交的县界,有没有面积异常小的碎片多边形。全国边界数据里偶尔会出现个别县的边界因为制图综合被简化得太过头,和遥感影像对不上。我这次就遇到过一个县级边界在河谷地带呈现明显的“锯齿状”,裁剪出来的坡度栅格沿着河谷出现一大条直线异常,最后重新下载了该县的新版边界才解决。
1.3 全国统一投影 vs 分带投影:Albers投影的取舍
投影坐标系是整个项目里最容易忽略、出了问题最难查的环节。做全国坡度数据,投影选不对,后面全盘皆输。
坡度计算必须基于“水平距离和垂直距离的单位一致”这一前提。如果你用经纬度坐标(GCS_WGS_1984)直接跑坡度工具,水平方向单位是度,垂直方向单位是米,算出来的坡度数值完全是错的。所以必须先给DEM定义一个合适的投影坐标系。
全国尺度的坡度分析,我最推荐的是Albers等积圆锥投影。它是一条或两条标准纬线,在保持面积变形最小的同时,能让全国范围内每个县的坡度都有自己的实际面积意义。中心经线取105°E,两条标准纬线取25°N和47°N,这是国内制图惯用的参数组合。我用这组参数做完整套坡度统计之后,再用县界去做面积核对,误差能控制在0.5%以内。
个别同事喜欢用UTM分带投影来处理分县数据,这个思路没错,但如果你的目标是“全国统一坡度栅格”,分带投影会让相邻县的坡度栅格在接边处产生明显差异——同一个山体,西半部分在49带、东半部分在50带,拐带的地方坡度会有肉眼可见的断裂。我在项目启动前就统一规定了:所有处理过程中的坡度栅格一律用Albers投影,只有最后给甲方出图时才会根据指定区域转成对应分带投影。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从分幅DEM到全国坡度栅格:一条可以复用的生产流水线
2.1 分幅数据下载与统一文件命名规则
确定完底图和数据源,接下来的工作是从分幅DEM开始,一步一步加工成全国坡度栅格。这一步最怕的不是技术,而是文件管理混乱。
SRTM V3 30米数据在国内通常是按经纬度1°×1°分幅存储的,全国范围大概几百个分幅文件。我从地理空间数据云按分幅编号批量下载,下载后立刻做统一重命名。我的命名规则是固定的:srtm_30m_北纬_东经.tif,比如srtm_30m_N30_E105.tif。不要小看这个细节,几百个文件如果命名不统一,后面写批量处理脚本的时候光是处理文件名就要浪费一天。
下载完所有分幅后,还要做一步“分幅完整性校验”。我用Python的rasterio写了个简单脚本,遍历所有分幅,读取每个文件的宽度、高度、波段数、nodata值、空间范围,把异常文件揪出来。常见问题有两种:一种是文件下载不完整,导致栅格某一行全是0;另一种是少数分幅的nodata值没有统一识别,导致后续拼接后出现“黑斑”。这一步花不了十分钟,但能省下后面数小时的排查时间。
如果你用的是GDAL环境,一条命令就能完成批量检查:
bash复制gdalinfo -json srtm_30m_N30_E105.tif | jq '.size, .cornerCoordinates, .band[0].noDataValue'
用遍历脚本把几百个文件名全部跑一遍,输出一个清单。我这次跑完发现有两个分幅的nodata值是-9999,其余都是-32768,如果不修正,拼接后的全国坡度栅格会在这些区域出现大片异常值。修正办法很简单,统一用gdal_translate把nodata值重设为-32768即可。
2.2 用VRT+分块裁剪避开全国数据拼大图的坑
很多人一上来就打算把所有分幅拼接成一个“全国超大栅格”,然后再开始计算坡度。这个思路对于低分辨率数据可行,30米分辨率的全国栅格轻则十几GB,重则几十GB,如果你用个人电脑做,大概率在拼接那一步就卡死了。
我的做法是:不拼大图,用VRT(虚拟栅格)代替物理拼接。VRT就像是一个“索引文件”,它不真正拷贝所有像元数据,而是记录每个分幅文件的路径和空间位置。GDAL在读取VRT时才会按需拉取实际数据,这样既节省硬盘空间,又不会让软件一次性加载几十GB。
bash复制gdalbuildvrt -resolution highest -r nearest -nodata -32768 all_srtm.vrt N30_E105.tif N30_E106.tif ...
构建VRT之后,坡度计算也直接在VRT上跑。ArcGIS的Slope工具可以读取VRT吗?可以,但更稳妥的方式是用GDAL直接算:
bash复制gdaldem slope all_srtm.vrt china_slope_deg.tif -p -s 111120 -compute_edges
解释一下这几个参数:-p表示输出坡度百分比,如果不加则默认输出度数;-s 111120是水平距离缩放因子,因为VRT原始数据是WGS84经纬度坐标,需要把度换算成米,赤道附近1度约等于111120米;-compute_edges是让边缘像元也参与计算,避免在分幅边缘出现一圈空值。
确定好投影后,先用gdalwarp把VRT重投影到Albers,再做坡度计算。顺序不要搞反:先重投影、后算坡度,才能保证水平距离是正确的。如果你先算坡度再重投影,像元值会被重采样,坡度信息会失真。
2.3 坡度计算参数设置与公式校验
坡度计算的核心原理是:在一个3×3的像元窗口中,比较中心像元与周围8个像元的高程差,通过拟合平面来得到最大坡度方向。ArcGIS的Slope工具默认使用Zevenbergen-Thorne算法,GDAL的gdaldem slope底层也使用类似的三阶反距离平方权方法。对于大多数应用场景,这两者的结果差异极小,可以忽略。
如果你想知道坡度数值对不对,可以找一个已知坡度的样区做校验。比如找一个山坡,已知高差和水平距离,算出理论坡度,再和软件算出来的结果对比。我在项目里用了一个简单办法:找一个河道的纵断面,坡度应该非常平缓;再找一个陡崖,坡度应该接近90°。拿这两个极端值去反查全国坡度栅格,数值在合理范围即可。
坡度单位的选择也很关键。很多行业用户习惯用“度”,但农业、林业项目里也有不少要求用“百分比坡度”(即升高除以水平距离乘以100)。两个数值之间不能直接线性换算,比如45°等于100%坡,而30°等于57.7%坡。我在交付时同时保留了两套栅格:一套是坡度度数,一套是坡度百分比,文件名里明确标注_unit_deg和_unit_pct。别看这个动作很小,后来甲方拿去做生态红线评估时,直接用了百分比版本,省了一轮返工。
3. 省、市、县三级裁剪中的边界咬合与性能问题
3.1 像元边界与矢量边界不咬合的处理方式
拿到全国坡度栅格之后,接下来是最核心的一步:按省、市、县三级行政区划裁剪。
如果你直接用“按掩膜提取”(Extract by Mask)去裁剪,大概率会遇到一个常见问题:矢量边界和栅格像元的中心点并不对齐。栅格是一个个正方形像元组成的,像元中心落在边界内才算该像元被保留,这导致裁剪出来的县域栅格边缘会略微“缩水”或“膨胀”,和实际行政边界存在半个像元左右的偏差。
处理这个问题有两种思路。第一种是“先栅格化再提取”:把县级边界矢量转成与坡度栅格完全一致的像元网格,生成一个“区域栅格”,然后交给Tabulate Area做面积统计,能保证每个县统计到的像元数量和它实际的像元归属一致。第二种是“先缓冲再裁剪”:把县界向外缓冲一个像元大小,裁剪后再用县界做二次掩膜,去掉边缘多余像元。这个方案更贴近“精确边界的坡度值”,但计算量稍大。
实际操作中大家更关心的是:边缘那半个像元的误差对统计结果影响大不大?我做了一组对比,某个面积约1800平方公里的县,两种方法统计的面积误差在0.2平方公里左右,按百分比算只有0.01%。对于绝大多数行业应用,这个误差可以接受。但如果你做的是不动产评估、宅基地选址这种需要精确到地块尺度的项目,就不能只靠裁剪,得直接使用厘米级的高精度DEM。
3.2 飞地、海岛和飞地小斑块的县级裁剪
全国边界数据中总有那么几个“特殊情况”:某个县在另一个县境内有一块飞地;某些市辖区包含海岛,海岛的边界在矢量数据里是单独的多边形;还有一些县的边界极不规则,比如沿着山脊线走,产生大量细碎的锯齿。
在做批量裁剪时,这些特殊情况容易导致两个问题:一是裁剪后某个县出现“多快不相连的栅格碎片”,二是某些飞地面积非常小,像元数量不足一个。
我的处理经验是:对县界矢量先做一个预处理,把同一个县的多个面要素合并(Dissolve),属性表保留县级行政区划代码,然后在合并后的面上做“按掩膜提取”。这样飞地和主辖区就能合并到一个文件里。合并之后生成的坡度栅格不会出现“某一县缺失”的情况,统计面积时也是整体统计。
如果存在特别小的飞地,比如面积不到一个30米像元(约900平方米)的,就需要单独评估。这种情况通常是岛屿或插花地,统计时面积可以忽略不计,但如果客户要求“每个县必须完整覆盖”,就需要用最近邻重采样把该飞地的坡度值从邻近陆地像元补齐,同时在工作报告中注明该处理方式。
3.3 大区域按需分块:不让一次裁剪拖垮电脑
全国坡度栅格已经不算小,当你需要按省裁剪时,一整块全国栅格直接喂给工具,电脑内存占用会非常夸张。尤其是到了市、县级别,每个县都要跑一遍提取,如果每次都从全国大栅格里裁,浪费时间不说,还容易中途“崩溃”。
我的做法是“分层分块、逐级缩小”。具体来说:
- 先用省级边界把全国坡度栅格裁剪成34个省级坡度栅格文件。
- 再用市界去省级栅格里裁剪,得到市级坡度栅格。
- 最后用县界去市级栅格里裁剪,得到最终的县级坡度栅格。
这样每一级裁剪的范围都缩小了,计算速度大幅提升。尤其是最后一步,每个县只需要在一个几十MB级别的栅格里做提取,几秒钟就能完成。别嫌麻烦,这个“缩圈”策略在数据量大的时候能救你命。
我在实际运行中,用Python脚本配合arcpy循环处理了全国2800多个县级行政区,整个裁剪流程跑下来不到一小时,中途没有出现内存溢出。如果你不用ArcGIS,也可以用GDAL的gdalwarp加-cutline参数实现相同的效果:
bash复制gdalwarp -cutline county.shp -crop_to_cutline -dstnodata -32768 -of GTiff china_slope_deg.tif county_slope.tif
这条命令会根据县界裁剪,并把裁剪范围外的区域设为nodata,输出文件精准对应该县范围。唯一的坑是,-crop_to_cutline必须加,否则输出范围还是全国范围,空值区域会被nodata填充,白占空间。
4. 坡度分级与分县级统计:把栅格变成一张能汇报的Excel
4.1 不同行业的分级阈值怎么定
坡度数据光是有栅格还不够,大多数业务场景需要的是“不同坡度等级的面积统计”。这时候就要做坡度分级重分类。
分级阈值没有统一标准,完全看使用场景。我整理了几种常用的分级口径,供大家参考:
| 应用方向 | 分级方案 | 典型用途 |
|---|---|---|
| 农业耕地坡度分级 | 0-2°、2-6°、6-15°、15-25°、>25° | 高标准农田建设、耕地等级评价 |
| 建设用地适宜性 | 0-5%、5-8%、8-15%、15-25%、>25% | 城镇开发边界、宅基地选址 |
| 林业/水土保持 | 0-5°、5-15°、15-25°、25-35°、>35° | 退耕还林、水土流失防治 |
| 地质灾害风险 | 0-15°、15-30°、30-45°、>45° | 滑坡、崩塌易发区划 |
我在项目里同时输出了两套分级结果:一套是按“度”分级,一套是按“百分比”分级。原因很简单,不同部门习惯不同,你做成Excel统计表时,两套都放上,甲方自己挑口径,省得来回确认。
ArcGIS里用Reclassify工具,GDAL环境可以用gdal_calc或者numpy重分类。我用的是arcpy.Reclassify,设置好重映射表,把全国坡度栅格一次性重分类成1到5的整型栅格。整型栅格占用空间小,统计速度也快。
4.2 用Tabulate Area实现省、市、县坡度面积快速统计
重分类之后,真正让数据“能说话”的是面积统计。每个县,哪个坡度等级占了多少平方公里,这才是有价值的成果。
ArcGIS的Tabulate Area工具是干这个的最优选择。它接收两个输入:一个是区域栅格(把行政区划矢量转成栅格,像元值和行政区划代码对应),另一个是分类栅格(坡度重分类后的整型栅格),输出是一个“区域×分类”的交叉面积表。
我实际操作时,先把全国县级边界矢量转成和坡度栅格完全一致的栅格:
python复制import arcpy
arcpy.env.snapRaster = "china_slope_reclass.tif"
arcpy.PolygonToRaster_conversion("county_2024.shp", "code", "county_ras.tif", "CELL_CENTER")
然后跑Tabulate Area:
python复制arcpy.TabulateArea_analysis("county_ras.tif", "Value", "china_slope_reclass.tif", "Value", "slope_area_table.dbf")
这里有个关键点:必须设置环境变量snapRaster为坡度栅格,这样区域栅格和坡度栅格才能在像元上完全对齐,统计出的面积才准确。
Tabulate Area输出的是dbf表,我直接用pandas读取,把行政区划代码关联上省市县名称,再换算成平方公里,最后导出成Excel。一张带“省份、城市、区县、坡度等级1面积、坡度等级2面积…”的统计总表就完成了。
4.3 成果目录和属性表怎么整理才能方便下游引用
数据做完之后,成果目录的整理是一门“被低估的加分项”。甲方拿到一个乱糟糟的文件夹,哪怕数据质量再好,印象分也会大打折扣。我这次交付的目录结构如下:
text复制全国各省市县坡度数据_2024/
├─ 01_全国坡度栅格/
│ ├─ china_slope_deg.tif
│ └─ china_slope_percent.tif
├─ 02_省级坡度栅格/
│ ├─ 北京市_slope.tif
│ ├─ 上海市_slope.tif
│ └─ ...
├─ 03_市级坡度栅格/
│ ├─ 北京市_朝阳区_slope.tif
│ └─ ...
├─ 04_县级坡度栅格/
│ ├─ 北京市_朝阳区_XXX乡_slope.tif
│ └─ ...
├─ 05_坡度分级栅格/
│ ├─ 全国_slope_reclass_1_5.tif
│ └─ ...
├─ 06_坡度统计表/
│ ├─ 全国各省坡度面积统计.xlsx
│ ├─ 全国各市坡度面积统计.xlsx
│ └─ 全国各县坡度面积统计.xlsx
└─ 数据说明文档.pdf
每一级栅格文件命名里,我都包含了“行政区划名称+数据类型+分辨率+投影+版本号”。比如“四川省_slope_deg_30m_albers_2024.tif”,这样任何一个人拿到文件不看说明文档也能知道这份数据的关键信息。
统计表里我额外加了三列:总面积、平均坡度、最大坡度。这三列数据是用Zonal Statistics区域统计跑出来的,虽然不属于分级面积,但很多项目初期筛选时都会用到“平均坡度”这个指标来快速判断一个县的地形特征。
5. 交付前的数据自检:防呆清单和几个实际翻车案例
5.1 空值、负值、锯齿、白边:四个必须过的检查
全国数据量大,难免有漏网之鱼。交付之前,我自己定了一份“防呆清单”,每一项都跑一遍,不通过不许交付。
第一项:空值检查。用GIS打开每个县级坡度栅格,检查是否存在像元值为nodata的“洞”。尤其是水系、湖泊、水库区域,SRTM会表现为平面,但偶尔会因为水体反射导致局部空值。如果某个县湖面巨大,空值面积超过1%,就要考虑用周边像元插值补洞。
第二项:负值检查。坡度在数学上不可能为负,但DEM原始数据中的残余噪声可能在计算坡度时产生极小负值。我的阈值是:负值面积占比超过0.01%就需要处理。处理方法很简单,用条件函数把负值强制替换为0。
第三项:边缘锯齿检查。打开省级坡度栅格,放大到边界处,检查是否存在明显的一排排直线切割痕迹。如果发现裁剪边界处有规则的“台阶”,说明栅格像元和矢量边界没有对齐,需要检查投影是否一致。
第四项:白边检查。这一步主要是看县级栅格和县级边界是否完全重合。我用“栅格转面”工具把县级坡度栅格中非空值区域转成面状要素,和县级边界求交集,计算两者的面积差异。差异大于0.5%的县单独拉出来处理。绝大多数差异来自湖面空值或飞地,少数来自边界不咬合。
5.2 抽样验证:坡度和真实地形差距多大算合格
数据自查只能说明“没有明显异常”,但数据精度是否达标,还得靠外部验证。
我的做法是:选取分布在东、中、西部不同地形区的6个县,每个县随机抽取20个样点,利用GNSS接收机在实地测量这些点位的高程(精度约±0.5米),再和SRTM DEM上对应位置的高程做对比,计算高差中误差。如果高差中误差在6米以内,用这个DEM算出的坡度数据基本满足区域尺度分析要求。
还有一个更省时省力的抽查办法:用更高精度的ALOS 12.5米数据作为参考,在6个样区各裁剪一块10公里×10公里的范围,计算SRTM坡度和ALOS坡度的相关系数。相关系数达到0.9以上,说明在区域尺度上,两套数据的变化趋势高度一致,可以放心交付。
我在这个项目里做了两个样区的对比,相关系数分别达到0.93和0.88。0.88的那个样区是典型的喀斯特地貌,峰丛洼地多,地形破碎度高,30米分辨率确实难以完全刻画出细碎的坡度变化。这种情况我会在数据说明文档中如实注明:“喀斯特地貌区小尺度坡度存在低估可能”,让下游分析人员心里有数。
5.3 我的一次翻车复盘:坐标偏差让县域平均坡度整整高了3度
再分享一个我实际踩过的坑,这个坑非常隐蔽,值得所有人注意。
当时处理到某省的一个山区县,做完县级坡度栅格后,我顺手跑了一眼Zonal Statistics,发现这个县的平均坡度竟然有28.7度。当时直觉告诉我“不对劲”,因为该县虽然以山地为主,但县城周边有大片河谷平地,全县平均坡度普遍在22度上下,28度明显偏高。
排查了一圈,最终发现原因是:县级边界矢量数据的坐标系是CGCS2000地理坐标系,而我处理坡度栅格用的Albers投影参数中,中心经线设成了120°E,而不是全国通用的105°E。这个县的经度大概在119°E左右,正好处于新投影的中心经线附近,看似影响不大。但县界矢量在从CGCS2000转到Albers时,由于我操作时忘记勾选“数据的原始坐标系”,软件默认把WGS84当作CGCS2000直接转换,造成了接近两公里的横向偏移。
两公里偏移对于邻县之间的距离可能不算什么,但对于一个面积几百平方公里的县来说,相当于把旁边高山地区的坡度大量“拉”进了这个县的范围,平均坡度自然被拉高了3度。这个坑的教训是:任何一次投影转换,都必须先确认源坐标系和目标坐标系是否正确,再执行转换,绝不能“默认下一步”。
后来我把所有处理脚本统一改成显式声明坐标系参数,并且在每次投影转换后做一次“与原始边界叠加检查”的动作,确保边界位置没有发生位移。从那之后,这类坐标系问题再也没出现过。数据交付前的自检清单也长期保留着“边界叠加抽查”这一项。
全国各省市县坡度数据看着朴素,做起来细节量非常大。数据源、投影、裁剪、分级、统计、验证,每一环都不能省。希望这篇文章能把你在做同类数据时可能踩的坑提前拆掉,帮你省下几个加班的夜晚。如果你也在处理全国尺度的地形数据,欢迎按这套流程试一遍,有问题可以再交流。
