简介:宿州市30米分辨率DEM数字高程数据包,覆盖市行政区域并外延部分周边地带,以栅格高程文件记录地表海拔,适合GIS从业者、城乡规划与地质灾害研判人员用于地形分析、坡向计算、汇水模拟等场景。压缩包共12个文件,约33.64MB,核心为宿州市DEM高程TIFF栅格,配套tfw坐标定位与ovr金字塔加速显示;另含宿州市范围shp、shx、dbf、prj等矢量边界文件,便于数据裁剪、属性挂接与坐标系统一,xml元数据与sbn/sbx索引辅助文件管理与快速查询。目前已有380人浏览学习,属于可直接加载的GIS基础数据。获取后可结合矢量边界精准提取研究区,免去自行配准、矢量化的流程,同时保留周边地形以支持跨边界对比与流域连续性分析,是区域地理研究的实用高程底图。
1. 拿到宿州30米DEM数据包,先别急着解压
“安徽省宿州市DEM数字高程数据30m(含区域范围shp文件).zip”这份压缩包,文件名已经透露了两个关键信息:一是DEM栅格的分辨率是30米,二是附带了区域范围的shp边界。很多人拿到手后直接解压拖进GIS出图,结果要么边界和地形对不齐,要么裁剪后大片黑边,这不是数据有问题,而是忽略了坐标参考和无效值的确认。这份数据适合做宿州市域尺度的地形分析、坡度坡向提取、可视性和水文初步模拟,也适合用来练习栅格裁剪和区域统计。如果说30米精度有什么限制,那就是它能看到山川谷坡的大趋势,但看不清一两米的小沟坎。下面从解压后的文件识别讲起,顺手把裁剪、参数和常见坑一次说清。
2. 拆开压缩包:先搞清里面是栅格还是矢量,坐标系是什么
拿到zip后,先别急着双击拖到地图窗口。我的习惯是解压到单独目录,然后用GDAL系列工具先读一遍文件头。这样能避免很多后续坐标对不上的玄学问题。解压后你会看到两类东西:一类是.tif或.img结尾的DEM栅格,另一类是.shp结尾的边界矢量。先分清它们,再谈处理。
2.1 DEM文件的基本身份:分辨率、数据类型与NoData
DEM(数字高程模型)本质是一个单波段栅格,每个像元存一个高度值。30米分辨率意味着一个像元代表地面上30米乘30米的面积。宿州市的地形从淮北平原到丘陵过渡,30米分辨率的DEM在宏观尺度上足够用,但在局部沟谷分析时会有明显平滑效果,所以你拿它做不了精细的田间排水设计,只能做区域趋势判断。
常见这类30米数据是公开的卫星遥感DEM产品生成,也可能是从更高分辨率重采样来的,来源本身不影响你使用,但会影响高程值分布。文件格式多为GeoTIFF,少数是IMG或切片格式。你拿到手后,第一步就是在终端里执行:
gdalinfo -stats dem.tif如果实际文件名不是dem.tif,按实际替换即可。gdalinfo会输出类似下面的关键信息:
Size is 1200, 900 Pixel Size = (30.0, -30.0) Coordinate System is: PROJCRS["CGCS2000 / 3-degree Gauss-Kruger CM 117E", ...] NoData Value=-9999 Statistics: Minimum=-1, Maximum=42Size是栅格的宽和高像元数,1200 x 900意味着数据有约108万个像元,按30米分辨率算覆盖范围约972平方公里,这个量级对一个地级市来说可能只是部分区域,也可能是经过边界裁剪后的结果。Pixel Size里第一个数是水平分辨率,第二个数是垂直分辨率,都是30.0表示一个像元是30米乘30米。负号代表像元Y轴向下,这是栅格数据的正常表示,不用理会。
Coordinate System是坐标系统,这里用了CGCS2000高斯投影,这是国内常用坐标框架,某些数据包会直接给WGS84或UTM,也有一些是Albers等积投影,需要留意。CGCS2000与WGS84在地球形状上很接近,但投影到平面后数值差异可能在几十米到几百米,这就是为什么你不能随便混用。
NoData Value是无效值标记,DEM在范围之外的像元不会被赋予高程值,而是统一填一个占位值。常见的有-9999、-32768,也有用0的。这个值在做坡度计算、渲染和统计时必须排除,否则就会出现负无穷或海平面0米的假数据。
Statistics是gdalinfo配合-stats时读取到的栅格值统计,这里显示最小值为-1,最大值为42,说明该数据的高程单位是米,且区域内大部分是平原地带,高程很低。如果最小值为-32768,那很可能是把NoData混到统计里了,后面单独说。
2.2 shp区域范围文件:它是裁剪工具,也是坐标参考
压缩包里的shp文件不是普通背景图层,它是用来限定DEM范围的面要素。一个完整的Shapefile至少包含.shp、.shx、.dbf三个文件,有些还带有.prj投影文件。解压后千万不要只拷贝.shp,否则其他软件打不开,也不要手动改文件名,否则.shx和.dbf会失效。
shp的几何类型通常是Polygon,也就是面。它的外边界一般对应宿州市的行政或自然区域边界,内部可能有零散的洞或飞地,这些细节在裁剪时会直接影响结果。打开shp前,先用ogrinfo读一遍:
ogrinfo -al boundary.shp | head -n 30输出里会看到Geometry是Polygon,Extent给出范围边界,Layer SRS WKT给出了坐标系统。请你把它和之前gdalinfo里DEM的Coordinate System对比。如果两者都写着CGCS2000和相同的投影带,那么后续裁剪会很顺利;如果一个是经纬度WGS84,另一个是投影坐标,那必须先做投影转换,否则两个图层虽然数值上“看起来像”,实际位置会错开几十到几百米。
属性表里一般会有区域名称字段,比如NAME或OBJECTID。如果存在多个面要素,比如宿州市下辖的区县范围同时存在,你就可以根据需要只裁剪到某个县,也可以合并成市域整体。有些shp的面要素是单个大面,有些是碎斑块,这个要看属性表里每个要素的NAME和面积字段来判断。
2.3 用GDAL快速读取数据信息
除了命令行,我更喜欢在Python里用rasterio一次性把DEM的元数据读清楚,顺便检查NoData在栅格中的实际占比。下面的代码可以直接运行:
import rasterio with rasterio.open("dem.tif") as src: print("尺寸:", src.width, "x", src.height) print("分辨率:", src.res) print("坐标系:", src.crs) print("NoData:", src.nodata) print("读取范围:", src.bounds) band1 = src.read(1) print("值域:", band1.min(), band1.max())这里用rasterio打开栅格文件,src.width和src.height得到像元行列数,src.res返回水平和垂直分辨率,src.crs是坐标系统对象,src.nodata是文件里标记的无效值,src.bounds则是地理范围。band1是numpy二维数组,直接打印min和max,如果出现-9999,就说明统计里混入了无效值,需要用masked array重算:
import numpy as np valid = np.ma.masked_equal(band1, src.nodata) print("有效高程最小值:", valid.min()) print("有效高程最大值:", valid.max())masked_equal函数把等于nodata的像元屏蔽掉,后面的min和max就不会被无效值干扰。这一步虽然简单,但能帮你判断文件头里的NoData声明是不是真实有效。很多下载来的DEM在Header里写着NoData=-9999,但实际栅格中有不少0或-32768,这些“脏值”会一路带进后续处理。
读这些信息的意义在于,裁剪前你就已经知道数据底子是不是干净。如果发现值域异常或NoData占比过高,后面就要提前处理,而不是等出图时再猜。
3. 在QGIS里打开并验证数据:坐标、范围、值域一次看清
命令行读完信息后,再用QGIS做一次可视化核对。QGIS是开源桌面GIS,打开30米DEM这种数据没有压力。把DEM和shp按正确顺序拖入,能快速发现肉眼可见的对齐问题。
3.1 把DEM和shp拖进QGIS:图层顺序与渲染方式
解压目录里找到.tif和.shp,直接拖到QGIS的主窗口。如果没有拖拽成功,也可以从菜单“图层”→“添加图层”→“添加栅格图层”和“添加矢量图层”分别加载。加载后图层面板中会出现两个图层,顺序随加载先后而定。
我的习惯是让shp图层显示在DEM上方,方便看边界是否与地形吻合。DEM的默认渲染往往是灰色,需要切换成带渐变色的样式才能看出地形起伏。操作路径是:在图层面板中右键DEM图层,选择“属性”,在“符号学”或“样式”选项卡中把渲染类型从“单波段灰度”改成“单波段伪彩色”,选择一个从绿色到棕色的渐变色谱,然后点击“应用”。
此时地图窗口中,低海拔的平原会显示为绿色或蓝色,丘陵为黄色,较高的区域为棕色,整个宿州市的地形骨架一眼可见。同时把shp边界叠加在上面,如果边界和DEM的色调变化区域明显错位,那就说明坐标系或范围有问题,不要急着往下做。
3.2 查看坐标系与范围:从图层属性到地图窗口的核对
图层面板右键DEM图层,选择“图层属性”,切换到“信息”选项卡。这里会列出该栅格的宽高、像元大小、范围以及“CRS”坐标参考系统。同样右键shp图层查看“信息”中的CRS。要确保两个图层使用的CRS是同一个,或者至少属于同一地图投影体系。
如果发现两者不一样,不要直接用“图层”→“设置CRS”去改,因为这个操作只是重定义坐标,不改变底层几何数据。正确做法是把其中一个图层重投影为另一个图层的CRS。在QGIS中,最直接的方式是右键图层→“导出”→“另存为”,在保存对话框中指定“CRS”为目标坐标系。例如将shp从WGS84经纬度重投影到DEM使用的CGCS2000高斯投影,保存后得到一个新的shp文件,再加载进来进行比对。
范围核对也有技巧:在“图层属性”的信息中记录两个图层的范围值。DEM的x方向范围应该是宿州市经度跨度,宽度约从某个值到某个值,长度跨度也类似。如果shp的某个角落超出或完全脱离DEM范围,说明数据版本本身就不匹配。常见不是完整覆盖,而是shp略微大于DEM,这种不要紧,裁剪时会让DEM边缘出现少量无数据区。
3.3 用栅格计算器检查NoData与异常值
QGIS的栅格计算器用来做像素级运算。为了检测NoData的位置,可以新建一个表达式,将DEM中所有小于-1000的像元标记为1,其余为0。操作路径:“栅格”→“栅格计算器”,输入表达式:
"dem@1" < -1000这里的dem@1表示第一个波段的像元名。执行后生成一个二值栅格,值为1的区域就是那些异常低值。将这些区域叠加到shp边界上,如果它们零散出现在宿州市边缘或河流滩地,可能是真实低洼地;如果铺满整个区域,那就需要回头检查DEM原始数据是否已拼接并正确填了NoData。
更快的办法是在Python控制台或外部Python环境中直接统计异常值占比:
from osgeo import gdal import numpy as np ds = gdal.Open("dem.tif") band = ds.GetRasterBand(1) arr = band.ReadAsArray() nodata = band.GetNoDataValue() if nodata is not None: mask = (arr == nodata) print("NoData像元占比:", mask.sum() / arr.size) else: print("文件未声明NoData")这段代码用gdal打开栅格,读取波段数组。如果文件里声明了NoData,就统计等于该值的像元占比;如果没有声明,则打印提示。这样能判断裁剪和后续填洼时是否会有大量边缘无效区。
做完这三步,你已经对数据底细有了完整认知。接下来就可以放心裁剪了。
4. 用shp边界裁剪DEM:三步得到宿州市范围的干净地形
裁剪是拿到这份数据后最常做的操作。目标是把DEM从原始覆盖范围中抠出宿州市边界以内的像元,边界外全部设为NoData。下面给出两种做法,先讲QGIS图形化操作,再讲GDAL命令行。
4.1 裁剪前必须做的一件事:统一投影或使用相同坐标系
裁剪的数学本质是判断栅格像元是否落在矢量面内。如果两个数据的坐标系不同,判断会出错。所以第一步必须先保证shp和DEM的CRS一致。
如果shp与DEM的坐标系统不同,优先将shp重投影到DEM坐标系。原因是DEM是栅格,重投影会改变像元排列和分辨率,精度会损失;而矢量重投影只是重新计算顶点坐标,不损失几何精度。在QGIS中右键shp图层→“导出”→“另存为”,选择与DEM相同的CRS即可。保存时输出文件格式选择“ESRI Shapefile”,编码选择UTF-8,避免属性中文乱码。
如果两个坐标系的投影带不同,比如DEM是3度带中央经线117E,而shp是UTM 50N,即使都是CGCS2000/WGS84框架,数值偏差也会非常大,同样需要重投影。判断标准很简单:打开两个图层的信息面板,看“范围”里x、y的值是否在同一个数量级,例如都是三百公里级别的投影坐标,或都是117.1经度级别的经纬度。如果一个是经纬度,一个是米制投影,那就必须重投影,没有商量余地。
4.2 在QGIS中使用“裁剪栅格图层”工具:参数设置与结果检查
QGIS菜单路径是“栅格”→“工具”→第一个“裁剪栅格图层”(Clip raster by mask layer)。这个工具的界面非常直观:
- Input layer选择DEM
- Mask layer选择重投影后的shp
- Assign a specified nodata value to output bands:勾选并填-9999,或不勾选就继承源文件NoData
- 勾选“Crop the input layer to the extent of the mask layer”,这样输出栅格的范围就刚好是shp的范围
- 点击“运行”
这里的“Crop the input layer to the extent”非常关键。如果不勾选,结果栅格会保留DEM的原始矩形范围,只是把shp外部像元设为NoData,文件尺寸不会变小。勾选后,输出栅格将以shp的外接矩形为边界,边界外的像元被裁掉,文件更小、加载更快。
运行完成后,把输出的裁剪栅格加入地图,和shp边界叠在一起检查。正常情况是边界完全贴合,边界外没有颜色,边界内高程连续。如果发现边界内侧有一圈颜色异常或外侧有残留碎块,说明原始shp与DEM没有套合,需要回到上一节重新检查坐标系。另一个检查点是输出栅格的NoData值是否仍为-9999,如果变成了0或undefined,会影响后续坡度计算。
4.3 更可控的GDAL裁剪命令:gdalwarp与cutline
图形界面方便,但参数不够透明。在批处理或写脚本时,我一般使用GDAL的gdalwarp工具来完成同样的裁剪,因为它的剪裁逻辑更可控。执行下面的命令:
gdalwarp -cutline boundary.shp -crop_to_cutline -dstnodata -9999 dem.tif dem_clip.tif逐一说明参数:
-cutline boundary.shp:指定矢量裁剪边界,可以是面状shp,也可以是GeoJSON。-crop_to_cutline:让输出栅格的范围贴合cutline的外接矩形,相当于上面QGIS里的“Crop to extent”。如果不加这个参数,输出范围还是原DEM的范围。-dstnodata -9999:把输出栅格的NoData设为-9999,避免沿用源文件中可能错误的值。dem.tif是输入,dem_clip.tif是输出。文件名可以自己改。
gdalwarp内部会使用几何计算判断每一个像素中心是否落在裁剪面内。默认情况下,只有像素中心在面内才保留,边缘上的像素可能被舍弃或保留,取决于实际坐标。如果你希望边界上能和shp更贴合,可以增加一个特殊的选项:
gdalwarp -cutline boundary.shp -crop_to_cutline -dstnodata -9999 -wo CUTLINE_ALL_TOUCHED=TRUE dem.tif dem_clip.tif-wo CUTLINE_ALL_TOUCHED=TRUE表示只要像元与裁剪边界有任何接触就保留,这样的边界看起来更实,但也会多保留半格无效值。默认FALSE表示中心点必须落在面内。选择哪个取决于你的用途:做统计时建议FALSE,保留严格面积;做可视化时建议TRUE,避免边界出现锯齿空洞。
还有另一个常见的裁剪工具是gdal_translate配合-projwin,但它只能用矩形范围裁剪,不能按shp多边形精确裁剪,所以这里不用它。如果你手里有GeoJSON格式的边界,直接把-cutline后面后缀改成.geojson即可。
命令执行完后,用gdalinfo或Python检查输出栅格的范围和NoData,确认裁剪成功。
5. 避坑:30米DEM处理中常见的5个翻车现场
DEM处理参数看似简单,实际操作中踩坑概率极高。我把这些年见到最多的五个场景整理出来,每个都是“现象→原因→解决”,你照着排查能少走不少弯路。
5.1 现象:裁剪后边界有白边或黑边
刚裁剪完的DEM拉进QGIS,沿着shp边界总有一圈黑色或白色,非常刺眼。这一般是输出栅格的NoData值设得不对。如果源DEM的NoData是0,而裁剪工具把边界外写成-9999,那么渲染时-9999会被当作一个真实的高程值,显示为极黑或极白。解决方法是:在样式属性里,把NoData值设置为“透明”,并保证栅格的值域统计不包含NoData。最稳妥的办法是裁剪时统一指定dstnodata=-9999,并在渲染前用工具“映射NoData值”或“改变数据值”把非-9999的异常值全部改成-9999。
5.2 现象:DEM值域出现-32768或0,以为是高山或海平面
用QGIS的“信息”工具点击一个像元,看到高程-32768,明显不合理。这通常发生在没有检查NoData就做统计的场景。比如用栅格计算器计算坡度时,-32768参与运算,产生巨大的负值和假信息。解决方法是:在计算前先用“栅格重映射”或“NoData处理”将等于-32768的值设为无效。我一般会先做一步“将栅格中的指定值设为NoData”,把明显的异常值剔除后再进行后续分析,具体可以在QGIS中使用“重新计算NoData”工具,或者用GDAL的gdal_translate -a_nodata -9999重新写入NoData标记。
5.3 现象:shp和DEM明明在一个市,却怎么都对不齐
两个图层拖进去,边界与地形明显错开几百米,但描述的都是宿州市范围。原因九成是坐标系不匹配:shp可能是WGS84经纬度,DEM是CGCS2000高斯投影。QGIS的“图层CRS”提示是蓝色问号,就是典型的未正确定义。解决方法是:先确认两个图层各自的CRS,然后选择一个作为基准重投影另一个。不要用“设置CRS”强制改,那样只是改标签,坐标数值不变,错位还在。我习惯先输出一个重投影后的临时shp,和DEM叠加验证,确认对齐后再缓存。
5.4 现象:坡度/坡向结果一片噪声
30米DEM在平原地区的高程差很小,但原始数据中常有高频噪声,直接算坡度会出现大面积45度以上的假信息。原因是原始DEM未做平滑,或NoData边缘效应进入窗口计算。解决方法是:在计算坡度前,先对DEM做一个低通滤波,比如使用“焦点统计”取3x3中值,或者在QGIS的“地形分析”工具中选择“Smooth DEM”。计算过程中把输出NoData设为-9999,并避开无效值。对于宿州市这种兼有平原和丘陵的区域,坡度噪声很容易被误判为真实地形,建议将阈值可视化调一下再下结论。
5.5 现象:导出后图片发白,看不出地形起伏
明明DEM看起来很好,导出成图片或打印就一片惨白。原因是渲染时高程直方图受少量异常值影响,拉伸范围过大,比如从-9999到2000,而大部分像元在20-40米之间,反差就没了。解决方法是在图层样式的“直方图”中点击“计算直方图”,然后使用“累积计数截止”把两端各2%的像元截掉,让渲染范围集中在有效值的2%到98%区间。这样做后,平原上2米的高差也能通过颜色差看出来。
这五条是DEM处理中最常见的问题,其他的如属性编码乱码、文件名中文乱码也时有发生,但影响相对较小。记住,拿到任何DEM数据,第一件事永远是读文件头、看NoData、看CRS,这三件事能避开80%的坑。
6. 拿30米DEM还能做什么:从坡度坡向到等高线输出
裁剪好的宿州市DEM,不只是用来当背景底图。30米分辨率可以支撑很多区域尺度的分析。最常用的是坡度坡向提取:在QGIS的“栅格”→“地形分析”中有“坡度”和“坡向”工具,参数里最需要注意的是“Z factor”。如果DEM高程单位和水平坐标单位都是米,Z factor填1;如果高程单位是英尺或水平坐标是经纬度,就必须换算,否则结果会偏得离谱。比如经纬度坐标系下Z factor通常需要取111320,因为1度约对应111公里。
生成等高线也很方便:使用“栅格”→“提取”→“等高线”工具,设置等高线间距。宿州市平原地带建议10米,丘陵区域20-30米,间距太小会密密麻麻挤在一起。输出的是线状矢量,非常适合叠加遥感影像或土地利用图。我一般会在生成等高线前先做一次3x3中值平滑,这样等高线不会因噪声出现毛刺。
再进一步,可以用“填洼”工具(如“Fill Sinks”)处理后做流向和累积流量分析,用于洪水淹没模拟的初步判断。30米分辨率在县级尺度的汇水分析中足够用,但不要期望它识别排水沟。可视化上,可以做山体阴影(Hillshade),参数中太阳方位角默认315度、高度角45度,实际使用时可以把方位角调到清晨或傍晚光照角度,立体感更强。
我现在的习惯是:每次拿到DEM压缩包,先花三分钟跑一遍gdalinfo,再看一眼NoData和CRS,然后才决定接下来的流程。这个习惯帮我省下了无数次重来。希望这篇笔记能帮到你,让你在拿到宿州30米DEM时,从解压到出图都顺畅无阻。
本文还有配套的精品资源,点击获取