Python GDAL实战:ASTER GDEM V003全国DEM高程数据处理(1123幅分幅合并→34省裁切,30m精度)
摘要 :本文详细记录了基于 Python(rasterio + GDAL)对 ASTER GDEM V003 原始数据进行全国34省级行政区DEM裁切的完整工程实践。涵盖数据下载、分幅合并(mosaic)、矢量裁切(mask)、质量检验全流程,附完整可复现代码。数据覆盖N18°N53°、E73°E134°,成果34个GeoTIFF文件,总计10.61GB,124.8亿有效像素。
关键词:DEM高程数据、ASTER GDEM、Python rasterio、GDAL、遥感数据处理、GeoTIFF、空间数据裁切、数字高程模型
标签 :
GIS遥感空间数据库数据处理地理信息系统

成果免费获取方式
数据由WX号 YouGIS顽石 整理分享,完全免费。两种获取方式任选其一:
方式一:关键词(推荐)
关注 YouGIS顽石 ,发送:DEM-省份简称 获取省份成果数据,DEM-分幅号 获取分幅数据
DEM-{省份简称} → 获取省份裁切成果数据,如 DEM-河南
DEM-{分幅号} → 获取原始分幅数据,如 DEM-N30E14
方式二:YouGIS 数据助手
| 平台 | 入口 |
|---|---|
| 小程序 | 搜索「YouGIS数据助手」 |
| PC 端 | https://yougis.com.cn/res/home |
支持在线查看数据元信息(分辨率、坐标系、覆盖范围),适合批量检索多个数据集。
一、数据背景与选型
1.1 ASTER GDEM V003 vs V002 vs SRTM
| 对比项 | SRTM C-band | ASTER GDEM V002 | ASTER GDEM V003 |
|---|---|---|---|
| 分辨率 | 30m | 30m | 30m |
| 纬度覆盖 | N60°~S56° | N83°~S83° | N83°~S83° |
| 垂直精度(RMSE) | ~16m | 17m | 8.5m |
| 数据源年份 | 2000年2月 | 2000-2010 | 2000-2018 |
| 空洞密度 | 少 | 较多 | 大幅减少 |
| 中国高纬度覆盖 | 差(漠河N53°已覆盖,但更北缺失) | 好 | 好 |
选型结论:全国DEM处理选 ASTER GDEM V003,兼顾覆盖范围(含高纬度)和精度。SRTM适合低纬度区域专项研究。
1.2 数据获取来源
| 来源 | 网址 | 特点 |
|---|---|---|
| NASA EarthData | https://earthdata.nasa.gov | 原始来源,需注册 |
| 地理空间数据云 | https://www.gscloud.cn | 国内镜像,下载速度快 |
| ASTER GDEM官网 | https://asterweb.jpl.nasa.gov/gdem.asp | 产品说明文档 |
二、原始数据技术规格
2.1 核心参数
text
数据产品:ASTER GDEM V003
发布机构:NASA & METI
发布时间:2019年8月
分幅规则:1°×1° 经纬度网格
单幅像素:3601 × 3601
单幅大小:~41 MB
空间分辨率:0.000278°(赤道约30m)
数据类型:signed 16-bit integer (int16)
空间参考:WGS84 / EPSG:4326
NoData值:-9999
文件格式:GeoTIFF
命名规则:ASTGTMV003_N{纬度}E{经度}_dem.tif
2.2 全国覆盖统计

text
分幅总数:1,123幅
覆盖范围:N18°~N53°, E73°~E134°
总数据量:~49 GB
单幅示例:ASTGTMV003_N39E116_dem.tif → 北京
ASTGTMV003_N30E114_dem.tif → 武汉
2.3 分幅筛选------根据省份bbox确定所需图幅
python
import numpy as np
def get_tile_range(min_lat, max_lat, min_lon, max_lon):
"""根据经纬度范围获取需要的ASTER GDEM分幅编号"""
lats = range(int(np.floor(min_lat)), int(np.ceil(max_lat)) + 1)
lons = range(int(np.floor(min_lon)), int(np.ceil(max_lon)) + 1)
tiles = [f"N{lat}E{lon}" for lat in lats for lon in lons]
return tiles
# 示例:河南省范围约 N31°~N36°, E110°~E117°
tiles = get_tile_range(31, 36, 110, 117)
print(f"河南需 {len(tiles)} 幅: {tiles}")
# 输出: 河南需 42 幅: ['N31E110', 'N31E111', ..., 'N36E117']
三、处理流程与核心代码
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 数据下载 │ -> │ 分幅合并 │ -> │ 省界裁切 │ -> │ 质量检验 │
│ 1123幅 │ │ 按省镶嵌 │ │ 矢量裁切 │ │ 统计+核查 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
~49 GB 临时大文件 34个TIF _summary.json
3.1 分幅合并(Mosaic)
python
import rasterio
from rasterio.merge import merge
from rasterio.transform import array_bounds
def mosaic_tiles(tile_paths, output_path):
"""合并多个DEM分幅为单个TIFF(LZW压缩)"""
src_files = [rasterio.open(p) for p in tile_paths]
mosaic, transform = merge(src_files)
profile = src_files[0].profile.copy()
profile.update({
'height': mosaic.shape[1],
'width': mosaic.shape[2],
'transform': transform,
'compress': 'lzw',
'tiled': True, # 启用分块写入,提升读取性能
'blockxsize': 256,
'blockysize': 256,
})
with rasterio.open(output_path, 'w', **profile) as dst:
dst.write(mosaic)
for src in src_files:
src.close()
print(f"[OK] 合并完成: {output_path} ({mosaic.shape[2]}×{mosaic.shape[1]})")
3.2 省界裁切(Mask)
python
import rasterio
from rasterio.mask import mask
import json
def clip_to_province(input_tif, geojson_path, output_tif, nodata=-9999):
"""用省级行政区划矢量(GeoJSON)裁切栅格"""
with open(geojson_path) as f:
geojson = json.load(f)
# 处理 FeatureCollection / Feature / Geometry 等不同结构
if geojson['type'] == 'FeatureCollection':
shapes = [feat['geometry'] for feat in geojson['features']]
elif geojson['type'] == 'Feature':
shapes = [geojson['geometry']]
else:
shapes = [geojson]
with rasterio.open(input_tif) as src:
out_image, out_transform = mask(
src, shapes, crop=True, nodata=nodata, all_touched=True
)
profile = src.profile.copy()
profile.update({
'height': out_image.shape[1],
'width': out_image.shape[2],
'transform': out_transform,
'nodata': nodata,
'compress': 'lzw',
'tiled': True,
})
with rasterio.open(output_tif, 'w', **profile) as dst:
dst.write(out_image)
print(f"[OK] 裁切完成: {output_tif}")
3.3 质量检验
python
import rasterio
import numpy as np
from pathlib import Path
import json
def validate_dem(tif_path):
"""验证DEM数据质量,返回统计字典"""
with rasterio.open(str(tif_path)) as src:
arr = src.read(1)
valid = arr[arr != src.nodata]
return {
'code': Path(tif_path).stem,
'width': src.width,
'height': src.height,
'crs': str(src.crs),
'dtype': str(src.dtypes[0]),
'total_pixels': int(arr.size),
'valid_pixels': int(valid.size),
'valid_ratio': round(valid.size / arr.size * 100, 2),
'elev_min': int(valid.min()),
'elev_max': int(valid.max()),
'elev_mean': round(float(valid.mean()), 1),
'size_mb': Path(tif_path).stat().st_size // (1024 * 1024),
}
# 批量检验
results = {}
for tif in sorted(Path('a_output').glob('*.tif')):
stats = validate_dem(tif)
results[stats['code']] = stats
print(f"{stats['code']}: {stats['valid_pixels']:>12,} px | "
f"{stats['elev_min']:>5}~{stats['elev_max']:<5}m | "
f"{stats['size_mb']}MB")
# 导出汇总JSON
with open('_summary.json', 'w') as f:
json.dump(results, f, ensure_ascii=False, indent=2)
四、成果数据总览

4.1 整体统计
| 指标 | 数值 |
|---|---|
| 成果文件数 | 34个 |
| 总数据量 | 10,868 MB (10.61 GB) |
| 总有效像素 | 12,476,309,771(约124.8亿) |
| 有效像素占比 | 43.42% |
| 高程范围 | -275m ~ 8802m |
| 数据类型 | int16 |
| 坐标系 | WGS84 (EPSG:4326) |
| NoData值 | -9999 |
| 压缩方式 | LZW |
4.2 34省完整清单
| 编码 | 省份 | 图幅数 | 像素尺寸 | 有效像素 | 高程范围 | 大小(MB) |
|---|---|---|---|---|---|---|
| 110000 | 北京 | 6 | 4008×2865 | 9,258,626 | 3~2296m | 19 |
| 120000 | 天津 | 4 | 3605×3491 | 8,632,021 | -5~1097m | 7 |
| 130000 | 河北 | 39 | 10845×9470 | 65,857,002 | 0~2818m | 199 |
| 140000 | 山西 | 27 | 9010×7195 | 44,388,292 | 148~3055m | 195 |
| 150000 | 内蒙古 | 464 | 28848×19948 | 280,552,490 | 91~2840m | 1230 |
| 210000 | 辽宁 | 31 | 10846×9001 | 62,045,913 | -275~1337m | 162 |
| 220000 | 吉林 | 43 | 12642×8930 | 65,444,891 | 0~2630m | 214 |
| 230000 | 黑龙江 | 84 | 18054×13394 | 145,335,435 | 0~1671m | 519 |
| 310000 | 上海 | 5 | 3962×2889 | 6,837,004 | -10~352m | 4 |
| 320000 | 江苏 | 30 | 9014×6900 | 38,332,587 | -32~685m | 61 |
| 330000 | 浙江 | 25 | 7937×7226 | 34,425,836 | 0~1920m | 118 |
| 340000 | 安徽 | 27 | 9010×7019 | 42,437,091 | -2~1869m | 122 |
| 350000 | 福建 | 23 | 8292×8292 | 40,449,760 | 0~2160m | 150 |
| 360000 | 江西 | 29 | 9369×7958 | 47,502,717 | -1~2145m | 177 |
| 370000 | 山东 | 40 | 10849×7407 | 45,702,055 | -11~1532m | 134 |
| 410000 | 河南 | 42 | 10489×8529 | 50,054,549 | 12~2411m | 156 |
| 420000 | 湖北 | 40 | 12646×9014 | 62,375,665 | -6~3097m | 201 |
| 430000 | 湖南 | 35 | 10489×9728 | 56,001,688 | 19~2097m | 235 |
| 440000 | 广东 | 32 | 10493×9366 | 55,021,568 | 0~1901m | 185 |
| 450000 | 广西 | 48 | 12646×10090 | 65,193,980 | -16~2109m | 271 |
| 460000 | 海南 | 30 | 9370×6300 | 23,508,420 | 0~1839m | 32 |
| 500000 | 重庆 | 21 | 7585×6845 | 36,514,212 | 26~2776m | 106 |
| 510000 | 四川 | 70 | 16258×14022 | 125,472,821 | 169~7523m | 658 |
| 520000 | 贵州 | 30 | 9369×8650 | 54,395,708 | 247~2896m | 223 |
| 530000 | 云南 | 60 | 14479×11586 | 103,015,277 | 76~6710m | 506 |
| 540000 | 西藏 | 151 | 28848×21612 | 260,749,872 | 122~8802m | 1476 |
| 610000 | 陕西 | 40 | 11711×9013 | 65,016,912 | 166~3762m | 266 |
| 620000 | 甘肃 | 187 | 20704×16057 | 172,464,233 | 615~5786m | 507 |
| 630000 | 青海 | 97 | 19853×16625 | 205,670,309 | 1682~6751m | 801 |
| 640000 | 宁夏 | 12 | 6127×5412 | 22,346,251 | 1090~3554m | 56 |
| 650000 | 新疆 | 224 | 34260×23418 | 357,088,649 | -154~8199m | 1833 |
| 710000 | 台湾 | 30 | 6482×7938 | 19,908,780 | 0~3886m | 44 |
| 810000 | 香港 | 2 | 1622×1801 | 1,582,840 | 0~958m | 1 |
| 820000 | 澳门 | 1 | 1082×1441 | 762,668 | -2~171m | 0 |
五、工程注意事项
5.1 大文件内存管理
新疆成果文件 650000.tif 像素尺寸达 34260×23418(8亿+像素),直接全量读取会触发内存溢出。
python
# ❌ 错误:全量读取大文件
with rasterio.open('650000.tif') as src:
arr = src.read(1) # 1.6GB内存!
# ✅ 正确:分块读取(windowed read)
with rasterio.open('650000.tif') as src:
for window in src.block_windows(1):
block = src.read(1, window=window[1])
# 逐块处理...
5.2 坐标系一致性检查
裁切前务必确认矢量与栅格坐标系一致,否则裁切结果为空或错位。
python
import rasterio
import geopandas as gpd
with rasterio.open('merged.tif') as src:
raster_crs = src.crs.to_epsg()
gdf = gpd.read_file('province_boundary.geojson')
vector_crs = gdf.crs.to_epsg()
if raster_crs != vector_crs:
print(f"[WARNING] 坐标系不一致! 栅格: EPSG:{raster_crs}, 矢量: EPSG:{vector_crs}")
gdf = gdf.to_crs(raster_crs) # 统一到栅格坐标系
5.3 噪声像素过滤
650000.tif(新疆)存在 104 个极端异常像素(-32287~32008m),使用前过滤:
python
import rasterio
import numpy as np
with rasterio.open('650000.tif') as src:
profile = src.profile.copy()
arr = src.read(1).astype(np.float32)
# 过滤物理不可能的高程值
arr[(arr < -1000) | (arr > 9000)] = -9999
with rasterio.open('650000_filtered.tif', 'w', **profile) as dst:
dst.write(arr.astype(np.int16), 1)
5.4 投影转换注意事项
数据为经纬度坐标(EPSG:4326),进行以下分析前需投影:
| 分析类型 | 推荐投影 | 原因 |
|---|---|---|
| 面积/体积计算 | Albers等积投影 | 保持面积不变 |
| 距离/坡度分析 | UTM投影 | 局部区域变形小 |
| 流域提取 | UTM投影 | 水流方向计算需平面坐标 |
python
# 使用GDAL进行投影转换(命令行)
# gdalwarp -t_srs EPSG:32649 input.tif output_utm49n.tif
# Python方式
import subprocess
subprocess.run([
'gdalwarp', '-t_srs', 'EPSG:32649',
'-r', 'bilinear', '-of', 'GTiff',
'-co', 'COMPRESS=LZW',
'input.tif', 'output_utm49n.tif'
])
六、数据读取与可视化
python
import rasterio
import numpy as np
import matplotlib.pyplot as plt
with rasterio.open('410000.tif') as src:
dem = src.read(1)
dem = np.where(dem == -9999, np.nan, dem).astype(np.float32)
fig, ax = plt.subplots(figsize=(10, 8))
im = ax.imshow(dem, cmap='terrain')
plt.colorbar(im, label='高程 (m)', shrink=0.8)
ax.set_title('河南省 DEM (ASTER GDEM V003)', fontsize=14)
ax.set_xlabel('列号')
ax.set_ylabel('行号')
plt.tight_layout()
plt.savefig('henan_dem.png', dpi=150)
plt.show()
七、总结
| 阶段 | 内容 | 规模 |
|---|---|---|
| 原始数据 | ASTER GDEM V003 | 1123幅,~49GB |
| 处理 | 下载→合并→裁切→检验 | rasterio + GDAL |
| 成果 | int16, EPSG:4326, LZW压缩 | 34省TIF,10.61GB |
处理流程全代码开源、可复现,所有34个成果文件技术规格统一。
参考文献
- NASA/METI. ASTER GDEM Version 3. (2019). https://asterweb.jpl.nasa.gov/gdem.asp
- NASA EarthData Search. https://earthdata.nasa.gov
- 地理空间数据云. https://www.gscloud.cn
- rasterio Documentation. https://rasterio.readthedocs.io
- GDAL Documentation. https://gdal.org
如果觉得有用,点赞收藏关注一键三连,有问题评论区交流 ✌️