简介:这份资源为新疆维吾尔自治区30m精度地形地貌栅格数据,面向地理信息、遥感、水文、生态及国土空间规划等方向的研究者与工程人员,用于区域地貌分类、地形起伏分析与建模制图。数据依据海拔划分为低海拔、中海拔、中高海拔、高海拔、极高海拔,依据起伏程度区分丘陵、小起伏、中起伏、大起伏、极大起伏,并涵盖丘陵、山脉、平原、沟壑等地形类型,以及海积、湖积、冲积、洪积、风积、冰碛、剥蚀等成因类型,坐标采用WGS84与Albers_Conic_Equal_Area,已按省整理为tif格式。压缩包约37.79MB,文件总数暂未提供,主要文件类型为tif栅格数据,可直接在ArcGIS、QGIS等平台加载使用。目前已有175人学习下载,适合需要快速获取新疆地貌基础数据、开展空间分析与专题制图的读者参考使用。
1. 新疆30m地形地貌数据:从拿到压缩包到跑通分析,中间隔着什么
如果你手头正好有一个「新疆维吾尔自治区地形地貌最新30m精度.rar」,大概率不是单纯想收藏一份数据,而是想把它用起来——做地形起伏度统计、坡度坡向提取、地貌类型分区,或者给水文、生态、选址类模型当底图。新疆地域辽阔,从阿尔泰山到昆仑山,从塔里木盆地到吐鲁番洼地,高差跨度极大,30m 分辨率意味着单省数据量就可能达到几十 GB 级别,直接双击打开往往不是正确姿势。这份数据通常以栅格 DEM 或地貌分类栅格的形式存在,可能附带投影信息、NoData 定义和分层打包结构。真正要解决的问题是:怎么解压不炸盘、怎么确认坐标系、怎么裁出研究区、怎么让 GIS 软件和 Python 都能稳定读取。适合做区域地理分析、遥感建模、工程选址和教学复现的从业者,也适合刚接触大栅格数据、想少走弯路的新手。下面按我实际处理这类数据的顺序,把每一步拆开讲。
2. 先搞清楚压缩包里装的是什么:30m 地形地貌数据的结构判断
2.1 从文件名和目录树反推数据组织方式
拿到一个 .rar 包,第一件事不是解压到桌面,而是先看目录树。Windows 下可以用 7-Zip 或 WinRAR 的「查看」功能,Linux/macOS 下用unrar l列出内容。常见结构有三种:第一种是单幅大栅格,比如xinjiang_dem_30m.tif,整省一张图;第二种是按图幅分块,比如N40E080.tif这种百万图幅命名;第三种是按地州或流域分文件夹,每个文件夹里再放若干 tif。判断依据很简单:看文件数量和单个文件大小。如果只有一个几 GB 的 tif,说明是整省拼接;如果有几百个几十 MB 的 tif,说明是分幅存储,后续必须做镶嵌或按需读取。
# Linux/macOS 下列出 rar 内容,不实际解压 unrar l "新疆维吾尔自治区地形地貌最新30m精度.rar" | head -50 # 只看文件数量和总大小 unrar l "新疆维吾尔自治区地形地貌最新30m精度.rar" | tail -5逻辑说明:unrar l只列目录,不写盘,适合先判断数据规模。参数上,head -50看前 50 行目录,tail -5看汇总行里的文件数和总大小。如果总大小超过你剩余磁盘空间的 2 倍,先别解压,考虑只解压研究区涉及的图幅。
2.2 用 gdalinfo 确认坐标系、分辨率和 NoData
解压出 tif 后,不要急着拖进 ArcGIS 或 QGIS。先用gdalinfo看一眼元数据,这是避免后续投影错乱最便宜的一步。重点看四个字段:Coordinate System、Pixel Size、NoData Value、Type。新疆常用投影有 WGS84 地理坐标系(EPSG:4326)和 CGCS2000 高斯克吕格投影(如 EPSG:4544 等 3 度带)。30m 分辨率在地理坐标系下表现为0.000277...度,在投影坐标系下才是真正的 30 米。如果 Pixel Size 是 0.000277 左右,说明是经纬度存储,做面积和距离计算前必须投影。
gdalinfo xinjiang_dem_30m.tif | grep -E "Coordinate System|Pixel Size|NoData|Type"逻辑说明:grep过滤关键行,避免被直方图信息刷屏。参数上,如果 NoData 是-9999或-32768,后续统计时要显式屏蔽;如果 Type 是Int16,说明是整数高程,做坡度计算前建议转Float32,否则梯度运算会丢精度。
2.3 判断是 DEM 还是地貌分类栅格
「地形地貌」四个字容易混。DEM 是连续高程,像素值代表海拔;地貌分类栅格是离散类别,像素值代表地貌类型编码,比如 1 代表山地、2 代表丘陵、3 代表平原。判断方法:看像素值范围。DEM 的范围通常从负几百米到七八千米;分类栅格的范围往往只有几十个整数值。也可以用gdalinfo -hist看直方图,连续分布的是 DEM,几个尖峰的是分类。这个判断直接决定后续分析路径:DEM 做坡度坡向、起伏度;分类栅格做面积统计和转移矩阵。
提示:如果压缩包里同时有 DEM 和地貌分类,先确认两者分辨率和范围是否一致,不一致时以 DEM 为基准做重采样对齐。
3. 把数据读进 Python:大栅格分块读取与内存控制
3.1 用 rasterio 做窗口读取而不是整幅加载
新疆 30m DEM 整幅加载到内存,按 Float32 算,面积约 166 万平方公里,像素数约 18 亿,单个波段就是 7 GB 以上,加上坡度计算中间变量,16 GB 内存的机器直接爆。正确做法是用rasterio的窗口读取,每次只读一个 block 或一个研究区窗口。下面是一个按窗口计算坡度的最小示例。
import rasterio from rasterio.windows import Window import numpy as np path = "xinjiang_dem_30m.tif" with rasterio.open(path) as src: # 只读左上角 2000x2000 的窗口做测试 window = Window(col_off=0, row_off=0, width=2000, height=2000) dem = src.read(1, window=window).astype("float32") transform = src.window_transform(window) nodata = src.nodata # 把 NoData 替换为 nan,避免参与梯度计算 if nodata is not None: dem[dem == nodata] = np.nan # 计算 x 和 y 方向梯度 dy, dx = np.gradient(dem) slope = np.degrees(np.arctan(np.sqrt(dx**2 + dy**2))) print("窗口内坡度范围:", np.nanmin(slope), np.nanmax(slope))逻辑说明:Window指定列偏移、行偏移、宽高,只把这一块读进内存。window_transform拿到该窗口的仿射变换,后续写结果时要用。np.gradient默认假设像素间距为 1,如果投影坐标系下像素是 30 米,需要把dx和dy分别除以 30,否则坡度值会偏大。参数上,窗口大小根据内存调整,8 GB 内存建议不超过 3000x3000。
3.2 分块写入结果并保留地理元数据
计算完一个窗口,不能只打印,要写回磁盘。用rasterio.open的w模式创建输出文件,复制源文件的 CRS、transform 和 dtype,然后按窗口写入。关键点是输出文件的 transform 要用窗口的 transform,而不是整幅的 transform,否则结果会错位。
with rasterio.open(path) as src: profile = src.profile.copy() profile.update(dtype="float32", nodata=np.nan, count=1) with rasterio.open("slope_test.tif", "w", **profile) as dst: window = Window(col_off=0, row_off=0, width=2000, height=2000) dem = src.read(1, window=window).astype("float32") if src.nodata is not None: dem[dem == src.nodata] = np.nan dy, dx = np.gradient(dem) slope = np.degrees(np.arctan(np.sqrt(dx**2 + dy**2))) dst.write(slope, 1, window=window)逻辑说明:profile继承源文件的驱动、CRS、transform 等,只改 dtype 和 nodata。dst.write的window参数确保写入位置正确。参数上,如果源文件是 Int16,输出坡度必须用 Float32,因为坡度有小数。NoData 设为 nan 后,后续用np.nanmean统计不会把无效值算进去。
3.3 用 dask 或 rioxarray 做惰性计算
如果不想手写窗口循环,可以用rioxarray打开文件,它基于 dask 做惰性分块。适合做全省统计而不是逐窗口调试。下面是一个用rioxarray计算平均高程的示例。
import rioxarray as rxr dem = rxr.open_rasterio("xinjiang_dem_30m.tif", chunks={"x": 2000, "y": 2000}) dem = dem.where(dem != dem.rio.nodata) mean_elev = dem.mean().compute() print("新疆平均高程:", float(mean_elev))逻辑说明:chunks指定分块大小,dask 会自动按块读取和计算。where屏蔽 NoData。compute()触发实际计算。参数上,chunks 太小会增加调度开销,太大吃内存,2000x2000 是常见折中。注意rioxarray对某些压缩格式支持不如rasterio直接,遇到读取报错时回退到rasterio窗口方式。
4. 投影、裁剪与重采样:让数据适配你的研究区
4.1 地理坐标系转投影坐标系:以 CGCS2000 3 度带为例
如果gdalinfo显示是 EPSG:4326,做坡度、面积、距离之前必须投影。新疆跨多个 3 度带,常见做法是按研究区中央经线选带号。比如乌鲁木齐附近约 87°E,对应 3 度带带号 29,EPSG 可能为 4544 或类似。用gdalwarp一行完成。
gdalwarp -t_srs EPSG:4544 -r bilinear -of GTiff \ xinjiang_dem_30m.tif xinjiang_dem_30m_proj.tif逻辑说明:-t_srs指定目标投影,-r bilinear是重采样方法,连续高程用 bilinear,分类栅格用 near。参数上,如果研究区跨带,不要强行用一个带号,考虑分带处理或使用 Albers 等面积投影。-of GTiff指定输出格式,大文件建议加-co COMPRESS=LZW压缩。
4.2 用研究区矢量裁剪:gdalwarp 的 cutline 用法
拿到投影后的数据,下一步是按行政区或流域裁剪。gdalwarp支持-cutline参数,直接传 shp 文件。注意 shp 的坐标系要和目标数据一致,否则裁出来是空的。
gdalwarp -cutline study_area.shp -crop_to_cutline \ -dstnodata -9999 -co COMPRESS=LZW \ xinjiang_dem_30m_proj.tif study_area_dem.tif逻辑说明:-cutline指定裁剪边界,-crop_to_cutline让输出范围紧贴边界,-dstnodata设置裁剪后空白区的值。参数上,如果 shp 是地理坐标系而栅格是投影坐标系,先ogr2ogr转 shp 或加-s_srs/-t_srs。裁剪后建议用gdalinfo -stats检查有效像素比例,比例过低说明坐标系不匹配。
4.3 重采样到统一分辨率:30m 到 90m 的取舍
有些模型要求 90m 或 1km 分辨率,直接重采样会丢细节。常见做法是先用 30m 计算坡度、起伏度等派生指标,再对派生指标重采样,而不是对 DEM 重采样后再算。因为 DEM 重采样会平滑地形,导致坡度偏小。如果必须重采样 DEM,用average而不是bilinear,后者在降采样时容易产生锯齿。
gdalwarp -tr 90 90 -r average \ xinjiang_dem_30m_proj.tif xinjiang_dem_90m.tif逻辑说明:-tr 90 90指定输出分辨率,-r average做均值聚合。参数上,-tr的单位跟随目标投影,投影坐标系下是米,地理坐标系下是度。重采样后要重新检查 NoData 是否被平均成有效值,必要时在重采样前把 NoData 设为 nan 并用-srcnodata声明。
注意:对分类栅格重采样必须用
near,用average会产生 1.5 这种无意义类别值。
5. 避坑与排查:处理新疆30m数据时最容易翻车的 5 个点
5.1 解压后文件名乱码,导致脚本读不到
现象:在 Windows 下解压后,文件名显示正常,但传到 Linux 服务器或用 Python 读取时报No such file or directory。原因:rar 包内文件名可能是 GBK 编码,Linux 默认 UTF-8,解压工具没有正确转码。解决:用unrar x -p指定密码(如果有),或在 Windows 下用 7-Zip 解压后手动重命名;更稳妥的是在 Linux 下用unrar的-sc参数尝试转换,或者直接用 Python 的rarfile库读取并指定编码。
5.2 坡度计算结果全是 0 或异常大
现象:用np.gradient算坡度,结果要么全是 0,要么出现 89 度这种极端值。原因:一是像素间距没有代入,np.gradient默认间距为 1,投影坐标系下 30m 像素会导致梯度被放大 30 倍;二是 NoData 没有屏蔽,-9999 参与梯度计算产生巨大差值。解决:把dx和dy分别除以像素宽度和高度,并在计算前把 NoData 替换为 nan。如果投影是地理坐标系,还要把度转米,纬度方向约 111km/度,经度方向乘以 cos(纬度)。
5.3 裁剪后数据范围对不上,裁出来是空白
现象:gdalwarp -cutline执行成功,但输出文件全是 NoData。原因:shp 和栅格坐标系不一致,或者 shp 的几何类型不是多边形(比如是线或点)。解决:先用ogrinfo -al -so study_area.shp看 shp 的 CRS 和几何类型,确保是多边形且 CRS 与栅格一致。不一致时用ogr2ogr -t_srs转换 shp。另外检查 shp 是否有无效几何,用ogr2ogr -makevalid修复。
5.4 内存溢出:整幅读取导致进程被 kill
现象:Python 脚本运行到src.read()时被系统 kill,或者报MemoryError。原因:整幅 30m 新疆 DEM 超过 7 GB,加上中间变量超过物理内存。解决:改用窗口读取或 dask 分块,设置chunks大小不超过内存的 1/4。如果必须整幅处理,考虑用gdal_translate先切分图幅,或者用rasterio的block_windows逐块处理。监控内存可以用htop或 Python 的psutil。
5.5 投影带号选错,面积统计偏差巨大
现象:投影后做面积统计,结果和官方公布的新疆面积差很多。原因:新疆跨 73°E 到 96°E,跨多个 3 度带,用一个带号投影会导致边缘区域变形大。解决:做全省面积统计时用 Albers 等面积投影(如 EPSG:4526 或自定义中央经线 85°E),而不是高斯克吕格。做局部研究时按研究区中央经线选带号。验证方法:投影后算一个已知县市的面积,和公开数据对比,偏差超过 1% 就要检查投影。
6. 进阶技巧:用地貌分类栅格做地形起伏度分区统计
6.1 从 DEM 计算起伏度并重分类
地形起伏度是窗口内最大高程减最小高程,常用 3x3 或 5x5 窗口。用scipy.ndimage的maximum_filter和minimum_filter可以快速实现。下面是一个窗口起伏度计算示例。
import rasterio import numpy as np from scipy.ndimage import maximum_filter, minimum_filter with rasterio.open("study_area_dem.tif") as src: dem = src.read(1).astype("float32") nodata = src.nodata profile = src.profile.copy() if nodata is not None: dem[dem == nodata] = np.nan # 用 5x5 窗口计算起伏度 max_dem = maximum_filter(dem, size=5) min_dem = minimum_filter(dem, size=5) relief = max_dem - min_dem profile.update(dtype="float32", nodata=np.nan) with rasterio.open("relief_5x5.tif", "w", **profile) as dst: dst.write(relief, 1)逻辑说明:maximum_filter和minimum_filter分别取窗口内最大和最小,相减得到起伏度。参数上,size=5对应 5x5 窗口,在 30m 分辨率下约 150m 范围。窗口越大,起伏度越平滑,但会掩盖小地形。NoData 区域在滤波后可能被邻域有效值填充,建议在滤波前用np.nan屏蔽,滤波后再把原 NoData 位置恢复为 nan。
6.2 按地貌类型分区统计起伏度
如果压缩包里附带地貌分类栅格,可以用rasterio读取分类,用numpy的bincount或pandas做分组统计。下面是一个按地貌类型统计平均起伏度的示例。
import rasterio import numpy as np with rasterio.open("relief_5x5.tif") as src: relief = src.read(1) with rasterio.open("geomorph_type.tif") as src: geom = src.read(1) mask = np.isfinite(relief) & (geom > 0) types = np.unique(geom[mask]) for t in types: vals = relief[mask & (geom == t)] print(f"地貌类型 {t}: 平均起伏度 {np.mean(vals):.2f} 米, 像元数 {len(vals)}")逻辑说明:mask同时屏蔽起伏度无效值和地貌类型无效值。np.unique拿到所有类型,循环统计。参数上,如果地貌分类编码有对照表,把t替换成类型名称。统计前确认两个栅格的 shape 和 transform 一致,不一致先用gdalwarp对齐。
6.3 用 zonal statistics 做更规范的分区统计
如果研究区是行政区矢量,用rasterstats做 zonal statistics 更规范。下面是一个按县统计平均高程的示例。
from rasterstats import zonal_stats stats = zonal_stats( "counties.shp", "xinjiang_dem_30m_proj.tif", stats=["mean", "min", "max", "std"], nodata=-9999, geojson_out=False ) for i, s in enumerate(stats[:5]): print(f"县 {i}: 平均高程 {s['mean']:.1f} 米")逻辑说明:zonal_stats自动处理矢量与栅格的对齐,stats指定要计算的指标,nodata声明无效值。参数上,矢量坐标系必须和栅格一致,否则结果为空。大矢量建议先按研究区裁剪栅格,减少计算量。rasterstats底层用rasterio和shapely,对大数据量可能较慢,可以分县循环。
6.4 我踩过的坑和现在的习惯
最早处理这类数据时,我习惯先解压到桌面再拖进 QGIS,结果 30m 新疆 DEM 让 QGIS 卡死好几次,后来改成先用gdalinfo看元数据,再用gdalwarp裁出研究区,最后才进 QGIS 出图。另一个血泪经验是投影带号,曾经用 3 度带 28 带做全省统计,面积比实际少了 8%,后来改用 Albers 才对齐。现在我的固定流程是:unrar l看结构 →gdalinfo看元数据 →gdalwarp投影和裁剪 → Python 窗口读取做分析 → 结果用rasterio写回并检查 NoData。这套流程不新鲜,但能避开九成以上的翻车。希望帮到你。
本文还有配套的精品资源,点击获取