☰
30m DEM数据处理实战:从预检裁剪到填洼坡度等高线提取
2026/10/8 5:03:07 网站建设 项目流程

简介:湖北省宜昌市30米分辨率DEM数字高程数据,面向GIS学习者、地理信息从业者与规划研究人员,可用于地形制图、坡度坡向分析、洪水淹没模拟、可视域分析及区域环境评估等场景。压缩包共12个文件,包含TIFF格式高程栅格、Shapefile格式市域边界矢量、投影坐标系定义文件、金字塔预览文件、空间索引与XML元数据等,总大小约63.67MB,文件组织规范,便于在ArcGIS、QGIS等平台直接加载使用。包内单独提供宜昌市范围shp边界,配合tfw地理参考文件可快速将DEM影像与真实坐标对齐,裁剪出完整市域地形图层,且覆盖范围含周边部分区域。已有825人学习下载,适用于教学演示、课程设计与中小尺度地理建模实践,能为后续坡度计算、流域划分等分析提供标准化基础数据。

1. 这个 30m DEM 包不是拿来就能出图:先把它修成能分析的地形底图

很多朋友用 ArcGIS 双击打开宜昌市 DEM 数字高程数据包里的 tif,看到的不是彩色地形,而是一片灰黑,或者整块绿得发假。这不是数据坏了,而是拉伸方式和无效高程没处理好。30m 分辨率意味着一个栅格像元约对应 900 平方米的地面范围,做市县尺度、小流域分析、规划选址够用,做单体场地设计就偏粗。藏在 zip 里的市级范围 shp 文件不是装饰,它既是裁剪边界,也是后续填洼、坡度、坡向统计的掩膜范围。这篇按预检、裁剪、派生、排错、交付的顺序,把这套 30m DEM 数据用透。

2. DEM 预检比实际操作更耗时:30 分钟搞定文件结构、坐标系与无效高程

拿到 zip 包后最常见的冲动是直接解压、往地图里一拖、随手做个坡度出图。但 30m 栅格和市级范围 shp 的组合里,真正耽误时间的从来不是裁剪那一下,而是坐标系对不上、NoData 值被当成 0 米、shp 缺投影文件这类前置问题。预检做扎实,后面裁剪和填洼一次过;预检跳过,后面每一步都在给前面的偷懒还债。

2.1 解压后的文件清单:shp 为什么是五个文件而不是一个

先不要双击 zip 再拖拽,解压到一个干净目录,然后按文件清单核对。zip 解压后至少有两类东西:一类是栅格 DEM,通常以 .tif 或 .img 结尾;另一类是宜昌市市级范围的 shp 矢量边界。很多人以为 shp 就是单个文件,实际它是一组文件:.shp 存几何、.dbf 存属性、.shx 存索引、.prj 存投影信息。有的包还带 .cpg 字符集和 .sbn/.sbx 空间索引。缺 .dbf 会打不开属性表,缺 .prj 则会让 ArcMap 和 QGIS 猜坐标系,后面裁剪整体错位。

unzip -q "湖北省宜昌市DEM数字高程数据30m(含本市级范围shp文件).zip" -d ./yichang_dem ls -lh ./yichang_dem # 期望看到 1 个 tif 栅格,以及同名 shp 派生出的 5 个以上文件

解压参数里-q是安静模式,-d指到指定目录。Windows 上建议解压到D:\work\yichang这种全英文路径。这一步是后续 arcpy 和 GDAL 能稳定运行的前提,路径带中文时各工具编码行为不一致,经常出现“数据明明在却打不开”的奇怪报错。解压完再看有没有 .tfw 世界文件和 .aux.xml 辅助文件,前者记录地理配准参数,后者存栅格统计信息。老版本 ArcMap 缺了 .aux.xml 可能不显示有效范围,整张图看起来像被压成一个点。

2.2 坐标系一致性核对:先统一再裁剪,还是先裁剪再统一?

这个问题的答案是:先统一坐标系,再裁剪。先裁剪后投影会把栅格重采样两次,第一次按 shp 范围切,第二次按目标坐标系抽点,边缘像元会多一层锯齿和错位。

gdalinfo ./yichang_dem/dem.tif | head -20 ogrinfo ./yichang_dem/宜昌市界.shp -so -al | grep -E "EXTENSION|PROJCS|GEOGCS" | head -5

gdalinfo会输出栅格的 EPSG 代码、像元大小和四个角坐标;ogrinfo输出 shp 的几何类型和空间参考。宜昌市常见的数据组合是 WGS84 或 CGCS2000 地理坐标(经纬度显示,单位通常是度和分),也可能是高斯-克吕格投影的米制坐标。判断标准很简单:看坐标值——数值在 110 到 112 之间且带小数点,是经纬度;数值在几十万到几百万量级,是投影坐标。两者不一致时,用 ArcMap 的 Project Raster 和 Project 工具统一到同一个坐标系,TIN 地形和坡度计算之前尽量选米制投影坐标,否则坡度结果会按经纬度度数去解释,数值完全不可用。

如果 .prj 文件缺失,旧版 ArcMap 会弹出“未知空间参考”提示。不要点确定硬算,正确做法是先用 Define Projection 工具手动指定数据来源坐标系,再用 Project 工具转换。指定错了虽然也能出图,但所有距离、面积、坡度结果都会错,且很难回头排查。

2.3 无效高程筛查:负值不一定是错误,0 值不一定能用

DEM 栅格里的 NoData 与 0 是完全不同的概念。NoData 表示该像元没有有效观测值,0 表示真实海拔为 0 米左右。但很多数据生产环节会把无效值写成 -9999、-32768,甚至直接写成 0。如果直接把 0 当真实高程参与填洼,宜昌长江沿岸的河谷地带会被挖出一个假的“海平面凹陷”,后面所有汇水路径都跟着歪。

import rasterio import numpy as np with rasterio.open("./yichang_dem/dem.tif") as src: arr = src.read(1).astype(np.float32) nodata = src.nodata valid = arr[arr != nodata] if nodata is not None else arr print("nodata:", nodata) print("min:", valid.min(), "max:", valid.max()) print("zero_count:", int((arr == 0).sum())) print("negative_count:", int((arr < 0).sum()))

这段脚本用 rasterio 读栅格,先取元数据里的 nodata 标记,再统计有效值的最小、最大值以及 0 值、负值数量。如果零值数量占全图比例很低,通常是无数据填充;如果负值集中出现在某一区域,需要对照地形判断是不是内陆湖底或河谷负海拔。ArcMap 里可以在栅格图层属性 - 符号系统里把 NoData 显示为单独颜色,QGIS 则可以在图层属性 - 透明度里单独设置。预检这一步把无效值范围定下来,第三章裁剪时才不会把地狱值带进派生分析。

3. 用市级范围 shp 裁剪 DEM:Arcmap 掩膜提取、QGIS 与 GDAL 的最小操作集

预检通过后进入核心操作:用面图层把大范围 DEM 裁到宜昌市市级边界内。这步工具选型决定了后续成果的边界形状和 NoData 表现。搜索这个标题的人,多半会搜到“arcmap 中依靠面图层裁剪 dem 栅格 tif 文件和依靠面图层掩码提取有啥区别”,下面先把这两个常用工具的关系讲透,再给出 QGIS 和 Python 的落地操作。

3.1 ArcMap 中 Clip 与掩膜提取:两个工具结果有什么不一样

ArcMap 里有两个工具看起来都能裁剪:数据管理工具箱里的 Clip(栅格裁剪)和 Spatial Analyst 工具箱里的 Extract by Mask(按掩膜提取)。两者的差别不在最终影像差多少,而在处理逻辑。Clip 是按照面要素的外包矩形或面本身去切栅格,直接改写输出范围的像元,边缘是否完全贴合边界取决于像元对齐;Extract by Mask 则是用掩膜面去“筛选”栅格像元,处于面内的像元被保留,面外的像元统一写成 NoData,同时受 ArcToolbox 环境设置里的掩膜和范围影响。

import arcpy from arcpy.sa import * arcpy.env.workspace = r"D:\work\yichang" arcpy.env.snapRaster = "dem.tif" arcpy.env.extent = "宜昌市界.shp" # 方式一:数据管理工具箱裁剪 arcpy.Clip_management( in_raster="dem.tif", rectangle="#", out_raster="dem_clip.tif", in_template_dataset="宜昌市界.shp", nodata_value="-9999", clipping_geometry="ClippingGeometry", ) # 方式二:Spatial Analyst 掩膜提取 outExtractByMask = ExtractByMask("dem.tif", "宜昌市界.shp") outExtractByMask.save("dem_mask.tif")

代码里Clip_management的clipping_geometry参数传ClippingGeometry,是按 shp 面边界裁剪而非外接矩形,这一步很多人漏掉,默认参数会裁出整个矩形外框。nodata_value要配合预检结果写,避免输出栅格把原 NoData 改成 0。ExtractByMask更简洁,但它要求 Spatial Analyst 授权,且输出会在面边界处保留一圈 NoData 像元。这两种方式的结果在 ArcMap 里肉眼几乎一样,差别在后续计算:掩膜提取的 NoData 会参与栅格计算器的条件判断,而 Clip 输出通常直接带背景值。做水文分析推荐 ExtractByMask,做地形切片导出推荐 Clip。

3.2 QGIS 里用面图层裁剪:gdalwarp 一条命令解决

QGIS 用户不需要打开两个工具箱,核心就是 GDAL 的gdalwarp。栅格菜单里的“裁剪栅格按掩膜图层”对话框最终也是在调它,只是界面把参数藏起来了。直接在终端跑的好处是参数透明、可以批量。

gdalwarp \ -cutline ./yichang_dem/宜昌市界.shp \ -crop_to_cutline \ -dstnodata -9999 \ -co COMPRESS=DEFLATE \ ./yichang_dem/dem.tif ./yichang_dem/dem_clip.tif

-cutline指定裁剪边界 shp,-crop_to_cutline是核心参数,让输出范围贴合面边界而不是外包矩形,少了它你会得到一张矩形底图加一个空洞。-dstnodata -9999把外部区域统一写成 -9999,与上一步无效值口径保持一致。-co COMPRESS=DEFLATE是压缩选项,30m DEM 裁到市域范围后文件不大,但 DEFLATE 能让后续读取更快。执行完用gdalinfo dem_clip.tif看一眼角坐标和 NoData 标记,确认没有丢失投影。QGIS 图形界面里如果发现裁剪结果边缘有白边,多半是没勾选“裁剪到裁切线”选项,中文界面里它叫“按裁切线裁剪”,不是“裁剪到图层范围”。

3.3 Python 批量裁剪:rasterio 的 mask 函数与多块边界合并

当你有多个区县 shp 想分别裁出 DEM,或者想把宜昌市级 shp 拆成乡镇后批量处理,ArcMap 一个个点工具太慢。用 rasterio 写个循环最省事。

import rasterio from rasterio.mask import mask from shapely.geometry import mapping import geopandas as gpd boundary = gpd.read_file("./yichang_dem/宜昌市界.shp") features = [mapping(boundary.geometry.unary_union)] with rasterio.open("./yichang_dem/dem.tif") as src: out_img, out_transform = mask( src, features, crop=True, nodata=-9999, ) out_meta = src.meta.copy() out_meta.update({ "driver": "GTiff", "height": out_img.shape[1], "width": out_img.shape[2], "transform": out_transform, "nodata": -9999, }) with rasterio.open("./yichang_dem/dem_clip.tif", "w", **out_meta) as dst: dst.write(out_img)

代码里boundary.geometry.unary_union先做几何合并,避免 shp 里存在多个面要素时掩膜只套用第一个面。crop=True表示按边界收缩输出范围。nodata=-9999让裁出来的背景区域统一成无效值,这样后面填洼时不会把边界外的平地误当成真实地形。注意输出投影来自源栅格,如果 shp 与 dem 投影不一致,mask 会抛异常或裁出错误区域。这种批量方式配合 glob 遍历多个 tif 和多个 shp,一晚上能把一整个地市的数据洗一遍。

4. 裁剪后的 DEM 才真正可用:填洼、坡度坡向与等高线三个派生方向

裁剪完成不代表分析能直接开始。原始 DEM 里包含真实地形中的洼地、平地噪声和采集误差,直接做汇水分析会出现断头河和伪洼地。所以裁剪后的下一步是把 DEM 变成“可被水文和地形工具信任”的底图,常见做法是依次做填洼、坡度坡向、等高线提取。这三个方向也是搜索热词里“dem文件”“从dem提取shp”最常见的落点。

4.1 填洼(Fill):为什么流域分析前必须填,Z limit 怎么设

填洼就是把 DEM 中低于周围像元的闭合凹陷填平到出水口高度。天然地形确实存在封闭洼地,比如喀斯特地貌里的漏斗,但在小流域分析里这些洼地会阻断水流路径,导致流向计算戛然而止。填洼工具不区分真实洼地和数据噪声,肉眼判断不了,只能靠参数控制。

import arcpy from arcpy.sa import * arcpy.env.workspace = r"D:\work\yichang" arcpy.env.snapRaster = "dem_clip.tif" fill_out = Fill("dem_clip.tif", z_limit=5) fill_out.save("dem_fill.tif")

z_limit是填洼深度的上限,单位与 DEM 高程单位一致。设 5 表示只填埋深度在 5 米以内的洼地,超过 5 米的真实漏斗和采石坑会被保留。如果不填这个参数,工具会把所有洼地全部填平,宜昌西部山区的溶蚀洼地会被彻底抹掉,后续提取的等高线会多出一圈假闭合圈。QGIS 里对应工具是 SAGA 的 Fill Sinks(wang & liu),参数 Threshold 同理。填洼完成后用栅格计算器做一次dem_fill - dem_clip,差值图里亮点集中的区域就是被填掉的主要洼地,顺手检查有没有填出异常大的水面。

4.2 坡度坡向:输出单位选度数还是百分比,影响很大

坡度工具输出单位有两种:度数(degree)和百分比(percent)。度数适合做坡度分级图,百分比适合做土壤侵蚀和工程设计。两者换算关系是百分比等于度数正切值乘以100,45 度对应 100%。做规划选址时百分比的“临界值”更容易被人接受,比如 25% 以下算缓坡,超过 60% 算陡坡。

import arcpy from arcpy.sa import * slope_out = Slope("dem_fill.tif", output_measurement="DEGREE") slope_out.save("yichang_slope.tif") aspect_out = Aspect("dem_fill.tif") aspect_out.save("yichang_aspect.tif") hillshade_out = Hillshade("dem_fill.tif", azimuth=315, altitude=45) hillshade_out.save("yichang_hillshade.tif")

output_measurement传DEGREE或PERCENT_RISE。坡度计算前必须确保 DEM 是投影坐标系,如果是经纬度坐标,ArcGIS 会按伪米制计算,结果偏小且不可信。Aspect输出是 0 到 360 度的坡向方位角,平地为 -1,用来做阴坡阳坡分析时要重分类,把 315-360 和 0-45 合并成北坡。Hillshade是山体阴影,虽然是渲染工具不是分析工具,但它能快速暴露 DEM 里残留的条带噪声,我经常用凌晨阳光角度先跑一张看数据质量。

4.3 从 DEM 提取 shp:等高线间距的选择与线平滑

标题热词里有不少人在找“arcgis 从 dem 提取 shp”,这里特指等高线。等高线是从 DEM 栅格里提取出的矢量线要素,输出为 shp,可以作为宜昌市级边界内的地形辅助线叠加到规划图上。

import arcpy arcpy.env.workspace = r"D:\work\yichang" arcpy.env.snapRaster = "dem_fill.tif" contour_out = arcpy.gp.Contour_sa( in_raster="dem_fill.tif", out_polyline_features="yichang_contour.shp", contour_interval=10, base_contour=0, )

contour_interval设 10 表示每 10 米生成一条等高线。宜昌市城区平缓地段高差几十米,10 米间距能看清地形骨架;西部山区陡峭,建议 20 米或 50 米,否则线太密压盖道路和房屋。base_contour设起始高程,常用 0 米起算。提取出的等高线带有锯齿,直接出图不好看,但不要急着平滑——平滑工具会把高程属性字段搞乱,而且矢量线一旦平滑会绕过真实地形特征。更合理的做法是把等高线 shp 转成 3D 线要素显示在 ArcScene 里,或者叠加到山体阴影上做半透明出图。如果源数据其实是 DSM(表面模型),即包含了树木和建筑高度的表面高程,直接提取等高线会出现大量虚假闭合圈,必须先做地面滤波或者换数据源。这里的密码在于明白 DEM 与 DSM 的区别:DEM 是裸地表,DSM 是地物顶面。

5. 宜昌 DEM 分析常见问题排查:五个最容易翻车的现场与处理办法

30m DEM 数据本身的坑不算多,真正让人反复重做的是坐标系、无效值和工具参数这三个黑匣子。以下五个现象按“现象 → 原因 → 解决”写,照着排查能省掉一整天返工。

5.1 裁剪结果全黑或全 0

现象:ArcMap 里裁剪后的 tif 一片黑,拉伸显示只有 0 和 255 两个值。

原因:shp 面要素和 DEM 栅格的投影坐标系不一致,导致掩膜范围根本没落在栅格上,所有像元都被判为 NoData;或者nodata_value参数写作 0,把有效高程也标记成空。

解决:打开两个图层属性,分别记录投影信息,用 Project 工具统一后再裁剪。若确认只有 NoData 问题,在 Clip_management 里把nodata_value设为 -9999,并在符号系统里单独给 NoData 设透明色。验证方法是用识别工具点几个像元,读出真实高程而不是 0。

5.2 裁剪范围整体偏移几百米,边界线切在山顶上

现象:裁剪结果边界大体正确,但锯齿边缘明显,边界线与 shp 边界差出两三个像元,部分山坡被切掉。

原因:栅格像元与 shp 矢量的空间位置没有严格对齐,GDAL 的 mask 默认按像元边界取整,ArcGIS 环境设置里没有指定 snapRaster。

解决:在 ArcToolbox 环境里把snapRaster指向源 DEM,让裁剪输出像元格网与源栅格完全重合;用 gdalwarp 时增加-tr参数明确输出分辨率,比如-tr 30 30,并让它与源栅格对齐。裁剪后把 shp 叠加到底图上,边界处像元脱落超过一个像元宽度就需要重算。

5.3 NoData 区域被当成 0 米参与填洼

现象:填洼后的水文分析在边界外出现巨大洼地,或者河网顺着矩形边缘走。

原因:裁剪输出没有正确设置 NoData,背景区域被写成 0 或 -9999,而 Fill 工具把它当作有效高程参与计算,边缘处形成一道虚假挡墙。

解决:填洼前用栅格计算器写Con(IsNull(dem_clip), dem_clip, dem_clip)确认 NoData 存在,然后用SetNull把所有非有效值统一。再做一次Fill,并用IsNull检查输出节点。更稳妥的办法是在裁剪工具里指定-dstnodata -9999,让背景值保持为空。

5.4 高程统计里出现负 100 米或一万米的异常值

现象:用识别工具点长江水面,高程显示为 -32768;点山顶显示为 10000 以上。

原因:数据生产阶段把无效高程填充成了极端哨兵值,常见 -32768、-9999、0,ArcMap 默认把这些值当有效数据参与拉伸显示,才会整片怪异颜色。

解决:用第二章的 python 统计脚本找出哨兵值,然后做条件重映射,把哨兵值改写成 NoData。栅格计算器写法是Con(dem == -32768, NoData(), dem)。处理完再检查一遍极值,确保有效高程范围落在该区域合理的几十米到两千多米区间。

5.5 shp 缺少 .prj 文件导致裁剪和填洼全程错位

现象:shp 能打开能显示,但和 DEM 叠不上,gdalwarp 报“transform failed”或坐标系 mismatch。

原因:zip 包里数据来源裁剪时不严谨,shp 文件没带投影定义。ArcMap 在有投影缺失时可能自动猜测成 WGS84 地理坐标,猜错就全盘错位。

解决:先看 shp 所在目录有没有 .prj,没有就按已知来源补坐标系。通常这种行政边界一版来自国土空间规划或基础测绘数据,坐标系不是 CGCS2000 就是 WGS84。用 Define Projection 工具手动指派,再 Project 到目标坐标系。注意补投影和重新投影是两步,必须先补齐再转换,顺序反了等于又把错误放大一次。

6. 把成果变成可交付的数据包:分块导出、文本互转与三维切片收尾技巧

裁剪和派生做完,通常会进入交付环节。给甲方或同事的成果至少要考虑三件事:大范围 DEM 要不要分块、shp 怎么转成其它交换格式、三维展示用哪种方案。这三个收尾技巧都是实际工作中高频出现的需求。

渔网分块处理大面积 DEM 时,常见做法是先用 Create Fishnet 生成覆盖全市的格网 shp,再按格网循环裁剪。30m DEM 在宜昌市范围不算大,但如果后续接 5 米或 10 米数据,分块会让读取和处理快很多。格网间距按 2000 乘 2000 像元切,每块约 60 万像元,ArcMap 加载和 python 处理都舒服。

shp 转 txt 的诉求多来自需要用文本交换数据的场景。ArcGIS 里可以用 Raster to ASCII 把 DEM 导出成 txt 栅格,但 shp 转 txt 通常指的是把矢量属性写成逗号或制表符分隔文本,用 geopandas 一行代码实现。

import geopandas as gpd gdf = gpd.read_file("./yichang_dem/宜昌市界.shp") gdf["wkt"] = gdf.geometry.to_wkt() gdf[["name", "wkt"]].to_csv("./yichang_dem/宜昌市界.txt", sep="\t", index=False)

to_wkt()把几何写出文本格式,再连带属性字段导出成 tab 分隔的 txt。反过来,别人发来带 WKT 字段的文本,用shapely.wkt.loads转成几何再写成 shp 文件就能恢复矢量。这套互转流程比直接用 FME 脚本简单得多,也绕开了 dbf 中文字段编码的老问题。

三维展示方面,除了 ArcScene 拉伸,近两年流行把 DEM 和边界 shp 一起转成三维瓦片,网页端直接加载。转换思路并不复杂:先把 DEM 转成带高程属性的 terrain 或 3D 模型,再切片成 3D Tiles。但 30m 数据做城市级三维会显得很粗糙,更合适的是做山体阴影叠加后的半透明起伏图,配合市级边界 shp 做范围线。

最后说个习惯:我每次把包含 shp 的 zip 交付出去之前,强制自己先删掉临时文件再压缩,解压后第一件事是看 .prj 和 .tfw 是否齐全。早年为图省事跳过预检,被甲方一句“你的裁切结果为什么整体往东飘了十几米”问得哑口无言。从那以后,凡是 DEM 加 shp 的活,先把坐标系和无效值这两关过了再谈出图。希望帮到你。

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

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

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

立即咨询