GDAL 入门指南
GDAL(Geospatial Data Abstraction Library,地理空间数据抽象库)是处理栅格地理空间数据的开源"瑞士军刀"。它可以读写数百种格式(GeoTIFF、JPEG、PNG、NetCDF、HDF5、ECW、COG 等),支持重投影、波段运算、裁剪、格式转换、金字塔构建等操作,是遥感与 GIS 领域的基石工具。
1. 安装
Python 环境
bash
pip install gdal # 某些平台可能失败
pip install gdal==$(python -c "from osgeo import gdal; print(gdal.__version__)")更推荐的方式:使用 conda,会自动装好 C++ 运行时依赖。
bash
conda install -c conda-forge gdal验证安装:
python
from osgeo import gdal, osr
print(gdal.__version__) # 例如 3.9.2命令行工具
安装 GDAL 后还附带一组常用 CLI 工具:
| 工具 | 用途 |
|---|---|
gdalinfo | 查看数据集元信息(尺寸、CRS、波段数等) |
gdal_translate | 格式转换、简单参数调整 |
gdalwarp | 重投影 / 重采样 |
gdalbuildvrt | 生成虚拟数据集(VRT) |
gdaladdo | 构建概览(overviews/金字塔) |
gdaldem | DEM 高程图派生(坡度、山体阴影等) |
gdal_contour | 从栅格提取等值线 |
nearblack | 去除黑边 |
gdal_grid | 散点转栅格 |
2. 核心概念
- Dataset:一个打开的栅格文件对象,所有操作的入口。
- Band:单个波段(如 RGB 中的 R 波段)。通过
ds.GetRasterBand(i)获取。 - Block:磁盘上的存储块,
ReadBlock/WriteBlock按块 I/O,适合大文件。 - Overview:降采样金字塔,加速大范围浏览显示。
- NoData:无效值标记,通过
GetNoDataValue()读取。 - GeoTransform:仿射变换矩阵,把像素坐标映射到地理坐标:
x_geo = gt[0] + x_pix * gt[1] + y_pix * gt[2] y_geo = gt[3] + x_pix * gt[4] + y_pix * gt[5] - SRS / CRS:空间参考系(坐标系),由
osr.SpatialReference表示,常见 WKT / EPSG 编码 / PROJ 字符串互转。
3. 最小示例:读写 GeoTIFF
3.1 读取
python
from osgeo import gdal
ds = gdal.Open("dem.tif")
if ds is None:
raise FileNotFoundError("打不开 dem.tif")
print("尺寸:", ds.RasterXSize, "x", ds.RasterYSize)
print("波段数:", ds.RasterCount)
print("数据类型:", gdal.GetDataTypeName(ds.GetRasterBand(1).DataType))
print("GeoTransform:", ds.GetGeoTransform())
print("投影(WKT):", ds.GetProjection())
band = ds.GetRasterBand(1)
data = band.ReadAsArray() # NumPy 数组 (y, x)
nodata = band.GetNoDataValue()
stats = band.ComputeStatistics(False) # (min, max, mean, std)
print("统计:", stats)3.2 写入
python
import numpy as np
from osgeo import gdal, osr
arr = np.random.randint(0, 255, size=(256, 256), dtype=np.uint8)
driver = gdal.GetDriverByName("GTiff")
out = driver.Create("out.tif", 256, 256, 1, gdal.GDT_Byte)
out.SetGeoTransform((440720, 30, 0, 3751320, 0, -30)) # UTM 31N 示例
srs = osr.SpatialReference()
srs.ImportFromEPSG(32631)
out.SetProjection(srs.ExportToWkt())
out.GetRasterBand(1).WriteArray(arr)
out.FlushCache()
del out4. 常用操作
4.1 重投影(gdalwarp)
bash
gdalwarp -t_srs EPSG:4326 -r bilinear in_utm.tif out_wgs84.tifPython:
python
from osgeo import gdal
src = gdal.Open("in.tif")
dst = gdal.GetDriverByName("GTiff").CreateCopy("out.tif", src, copyOnly=True)
# 用 WarpOptions 做真正的重采样
warp = gdal.AutoCreateWarpedVRT(src, src.GetGeoTransform(), src.GetProjection(),
None, "EPSG:4326", gdal.GRPC_BILINEAR)
gdal.GetDriverByName("GTiff").CreateCopy("out_wgs84.tif", warp, copyOnly=True)4.2 裁剪子区域
python
sub = ds.GetRasterBand(1).ReadAsArray(xoff=100, yoff=100, xsize=500, ysize=500)或整幅 ROI:
bash
gdal_translate -projwin 440720 3751320 441220 3750820 in.tif crop.tif4.3 格式转换
bash
gdal_translate -of JPEG -co "QUALITY=90" in.tif out.jpg
gdal_translate -ot Float32 -scale 0 255 0 1 in.tif normalized.tif4.4 拼接 / VRT
bash
gdalbuildvrt mosaic.vrt scene1.tif scene2.tif scene3.tif
gdalwarp -t_srs EPSG:32631 -r near mosaic.vrt mosaic.tif4.5 构建金字塔(Overviews)
bash
gdaladdo -r average in.tif 2 4 8 16 32Python:
python
ds = gdal.Open("in.tif", gdal.GA_Update)
for b in range(1, ds.RasterCount + 1):
ds.BuildOverviews('AVERAGE', [2, 4, 8, 16])4.6 多波段合成(RGB → 单文件)
python
r = gdal.Open("red.tif").GetRasterBand(1).ReadAsArray()
g = gdal.Open("green.tif").GetRasterBand(1).ReadAsArray()
b = gdal.Open("blue.tif").GetRasterBand(1).ReadAsArray()
rgb = np.stack([r, g, b], axis=0) # (3, h, w)
driver = gdal.GetDriverByName("GTiff")
out = driver.Create("rgb.tif", rgb.shape[1], rgb.shape[2], 3, gdal.GDT_UInt16)
for i, band_arr in enumerate(rgb, start=1):
out.GetRasterBand(i).WriteArray(band_arr)
out.SetGeoTransform(gdal.Open("red.tif").GetGeoTransform())
out.SetProjection(gdal.Open("red.tif").GetProjection())
out.FlushCache()4.7 内存 Dataset(无需落盘)
python
mem_drv = gdal.GetDriverByName("MEM")
mem_ds = mem_drv.Create("", width, height, bands, gdal.GDT_Float32)
mem_ds.GetRasterBand(1).WriteArray(arr)
# 可直接作为其它函数的输入5. 坐标系统(OSR)
python
from osgeo import osr
srs = osr.SpatialReference()
srs.ImportFromEPSG(4326) # WGS84 经纬度
print(srs.ExportToWkt())
print(srs.ExportToProj4()) # +proj=longlat +datum=WGS84 ...
# 任意两种 SRS 互转
srs2 = osr.SpatialReference()
srs2.ImportFromEPSG(32631) # UTM 31N
transformer = osr.CoordinateTransformation(srs, srs2)
lon, lat = 120.0, 30.0
x, y, _ = transformer.TransformPoint(lon, lat)
print(x, y) # UTM 坐标GDAL 3.x 起内部使用 OGR 的 CRS 对象,
ImportFromEPSG已能正确处理多数情况;老代码里的SetWellKnownGeogCS("WGS84")仍可用但推荐 EPSG 方式。
6. 性能技巧
- 分块读取:大文件避免一次性
ReadAsArray(),改用ReadBlock(blockX, blockY)或RasterIO带窗口读取。 RASTERIO_FLAGS:gdal.GRF_ReadOnly、gdal.GRF_RastNoData等标志位控制行为。- 驱动选项(Creation Options):写 GeoTIFF 时常用
-co COMPRESS=LZW -co TILED=YES -co BLOCKSIZE=256。 - 缓存:
gdal.SetCacheUnlimited()可放开内存缓存限制(注意机器内存)。 - 多线程:
gdalwarp自带-wo NUM_THREADS=8;Python 侧可配合concurrent.futures对分块并行。 - Virtual Raster (VRT):不落地即可组合多个文件,I/O 按需读取。
7. 调试与排错
gdalinfo -stats file.tif:快速确认文件能否被识别、是否有 NoData。GDAL_DEBUGON=1:环境变量打印详细日志。CPL_CURL_VERBOSE=1:调试网络数据源(HTTP/S3/WMS)。- 报错
No such file or directory多半是驱动未注册,先gdal.AllRegister()。 - Windows 上 DLL 找不到:确保用 conda 安装的 gdal,或将
bin目录加入 PATH。
8. 进一步学习
- 官方文档:https://gdal.org/ (API 参考、教程、CLI 手册)
- 官方 Python 教程:https://gdal.org/en/latest/programs/gdal_utils.html
- 配套矢量库 OGR(现为 GDAL 的一部分):处理 Shapefile / GeoJSON / GPKG
- 高级用法常结合
rasterio(Pythonic 封装)或xarray(多维科学数据)
本指南基于 GDAL 3.x API 编写。不同版本间个别函数签名可能有差异,请以你安装的版本对应文档为准。
