第04章 - 栅格数据处理基础
第04章:栅格数据处理基础
4.1 栅格数据概述
4.1.1 什么是栅格数据
栅格数据是由规则排列的像元(像素)组成的数据结构,每个像元存储一个或多个数值。在地理空间领域,栅格数据广泛用于表示连续的地理现象。
┌─────────────────────────────────────────────────────────────┐
│ 栅格数据结构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───┬───┬───┬───┬───┬───┬───┬───┐ │
│ │ 45│ 48│ 52│ 55│ 58│ 60│ 62│ 65│ ← 行 0 │
│ ├───┼───┼───┼───┼───┼───┼───┼───┤ │
│ │ 42│ 46│ 50│ 53│ 56│ 59│ 61│ 63│ ← 行 1 │
│ ├───┼───┼───┼───┼───┼───┼───┼───┤ │
│ │ 40│ 44│ 48│ 51│ 54│ 57│ 60│ 62│ ← 行 2 │
│ ├───┼───┼───┼───┼───┼───┼───┼───┤ │
│ │ 38│ 42│ 46│ 49│ 52│ 55│ 58│ 60│ ← 行 3 │
│ └───┴───┴───┴───┴───┴───┴───┴───┘ │
│ ↑ │
│ 列0 列1 列2 列3 列4 列5 列6 列7 │
│ │
│ 每个格子 = 一个像元(Pixel/Cell) │
│ 格子中的数值 = 像元值(可以是高程、温度、反射率等) │
│ │
└─────────────────────────────────────────────────────────────┘
4.1.2 常见栅格数据类型
| 数据类型 | 描述 | 示例 |
|---|---|---|
| 卫星影像 | 遥感传感器获取的图像 | Landsat、Sentinel、MODIS |
| 数字高程模型 | 地表高程数据 | SRTM、ASTER GDEM、ALOS |
| 正射影像 | 经过正射校正的航空/卫星影像 | 无人机影像、航拍图 |
| 土地覆盖 | 分类后的土地利用数据 | GlobeLand30、FROM-GLC |
| 气象数据 | 气温、降水、风速等 | ERA5、GPM |
| 专题图 | 各种专题分析结果 | NDVI、坡度、坡向 |
4.1.3 常见栅格格式
| 格式 | 扩展名 | 特点 |
|---|---|---|
| GeoTIFF | .tif, .tiff | 行业标准,广泛支持 |
| Cloud Optimized GeoTIFF | .tif | 云优化,支持范围读取 |
| JPEG 2000 | .jp2 | 高压缩率,有损/无损 |
| HDF5 | .h5, .hdf5 | 科学数据格式,多维数组 |
| NetCDF | .nc | 气象海洋数据标准格式 |
| IMG | .img | ERDAS IMAGINE 格式 |
| ECW | .ecw | 高压缩率商业格式 |
4.2 读取栅格数据
4.2.1 打开数据集
from osgeo import gdal
import numpy as np
# 启用异常处理
gdal.UseExceptions()
# 打开数据集(只读)
ds = gdal.Open('example.tif')
# 打开数据集(读写)
ds = gdal.Open('example.tif', gdal.GA_Update)
# 检查是否成功打开
if ds is None:
print("无法打开文件")
else:
print(f"成功打开: {ds.GetDescription()}")
# 获取基本信息
print(f"宽度: {ds.RasterXSize} 像素")
print(f"高度: {ds.RasterYSize} 像素")
print(f"波段数: {ds.RasterCount}")
print(f"驱动: {ds.GetDriver().ShortName}")
# 关闭数据集(重要!)
ds = None
4.2.2 使用上下文管理器
推荐使用上下文管理器来自动管理资源:
from osgeo import gdal
from contextlib import contextmanager
@contextmanager
def open_raster(filepath, mode=gdal.GA_ReadOnly):
"""栅格数据集上下文管理器"""
ds = gdal.Open(filepath, mode)
if ds is None:
raise IOError(f"无法打开文件: {filepath}")
try:
yield ds
finally:
ds = None
# 使用示例
with open_raster('example.tif') as ds:
print(f"宽度: {ds.RasterXSize}")
print(f"高度: {ds.RasterYSize}")
# 自动关闭
4.2.3 获取数据集信息
from osgeo import gdal, osr
def get_raster_info(filepath):
"""获取栅格数据详细信息"""
ds = gdal.Open(filepath)
info = {
'filepath': filepath,
'driver': ds.GetDriver().ShortName,
'width': ds.RasterXSize,
'height': ds.RasterYSize,
'bands': ds.RasterCount,
}
# 地理变换
gt = ds.GetGeoTransform()
if gt:
info['geotransform'] = {
'origin_x': gt[0],
'origin_y': gt[3],
'pixel_width': gt[1],
'pixel_height': gt[5],
'rotation_x': gt[2],
'rotation_y': gt[4],
}
# 计算边界
info['bounds'] = {
'min_x': gt[0],
'max_x': gt[0] + ds.RasterXSize * gt[1],
'min_y': gt[3] + ds.RasterYSize * gt[5],
'max_y': gt[3],
}
# 投影信息
proj = ds.GetProjection()
if proj:
srs = osr.SpatialReference()
srs.ImportFromWkt(proj)
info['projection'] = {
'name': srs.GetName(),
'epsg': srs.GetAuthorityCode(None),
'is_geographic': srs.IsGeographic(),
'is_projected': srs.IsProjected(),
}
# 波段信息
info['band_info'] = []
for i in range(1, ds.RasterCount + 1):
band = ds.GetRasterBand(i)
band_info = {
'band_number': i,
'data_type': gdal.GetDataTypeName(band.DataType),
'nodata': band.GetNoDataValue(),
'block_size': band.GetBlockSize(),
'color_interp': gdal.GetColorInterpretationName(
band.GetColorInterpretation()
),
}
# 统计信息(如果可用)
stats = band.GetStatistics(False, False)
if stats[0] != stats[1]: # 检查是否有效
band_info['statistics'] = {
'min': stats[0],
'max': stats[1],
'mean': stats[2],
'std': stats[3],
}
info['band_info'].append(band_info)
ds = None
return info
# 使用示例
info = get_raster_info('example.tif')
import json
print(json.dumps(info, indent=2, ensure_ascii=False))
4.2.4 读取像素数据
from osgeo import gdal
import numpy as np
ds = gdal.Open('example.tif')
# 方法1:读取整个波段为数组
band = ds.GetRasterBand(1)
data = band.ReadAsArray()
print(f"数据形状: {data.shape}")
print(f"数据类型: {data.dtype}")
# 方法2:读取所有波段
all_bands = ds.ReadAsArray()
print(f"所有波段形状: {all_bands.shape}") # (bands, height, width)
# 方法3:读取指定区域
x_off, y_off = 100, 200 # 起始位置
x_size, y_size = 50, 50 # 读取大小
subset = band.ReadAsArray(x_off, y_off, x_size, y_size)
print(f"子集形状: {subset.shape}")
# 方法4:读取并重采样
target_width = 500
target_height = 500
resampled = band.ReadAsArray(
buf_xsize=target_width,
buf_ysize=target_height
)
print(f"重采样后形状: {resampled.shape}")
# 方法5:使用 ReadRaster 读取原始字节
raw_data = band.ReadRaster(0, 0, 100, 100)
arr = np.frombuffer(raw_data, dtype=np.uint8).reshape(100, 100)
ds = None
4.2.5 处理无效值
from osgeo import gdal
import numpy as np
ds = gdal.Open('example.tif')
band = ds.GetRasterBand(1)
# 获取无效值
nodata = band.GetNoDataValue()
print(f"无效值: {nodata}")
# 读取数据
data = band.ReadAsArray()
# 方法1:使用布尔掩码
if nodata is not None:
valid_mask = data != nodata
valid_data = data[valid_mask]
print(f"有效像元数: {valid_data.size}")
print(f"有效值范围: {valid_data.min()} - {valid_data.max()}")
# 方法2:使用 NumPy 掩码数组
masked_data = np.ma.masked_equal(data, nodata)
print(f"有效像元平均值: {masked_data.mean()}")
# 方法3:替换无效值
data_filled = np.where(data == nodata, np.nan, data)
print(f"均值(忽略NaN): {np.nanmean(data_filled)}")
ds = None
4.3 创建栅格数据
4.3.1 使用 Create 方法
from osgeo import gdal, osr
import numpy as np
def create_raster(filepath, width, height, bands=1,
dtype=gdal.GDT_Float32, driver='GTiff'):
"""创建新的栅格数据集"""
# 获取驱动
driver_obj = gdal.GetDriverByName(driver)
# 创建选项
options = ['COMPRESS=LZW', 'TILED=YES']
# 创建数据集
ds = driver_obj.Create(filepath, width, height, bands, dtype, options)
return ds
# 创建简单栅格
ds = create_raster('output.tif', 1000, 1000)
# 设置地理变换
# 假设覆盖北京区域
origin_x = 116.0
origin_y = 40.5
pixel_size = 0.001 # 约111米
geotransform = (origin_x, pixel_size, 0, origin_y, 0, -pixel_size)
ds.SetGeoTransform(geotransform)
# 设置投影(WGS84)
srs = osr.SpatialReference()
srs.ImportFromEPSG(4326)
ds.SetProjection(srs.ExportToWkt())
# 写入数据
band = ds.GetRasterBand(1)
data = np.random.rand(1000, 1000).astype(np.float32)
band.WriteArray(data)
# 设置无效值
band.SetNoDataValue(-9999)
# 计算统计信息
band.ComputeStatistics(False)
# 刷新并关闭
ds.FlushCache()
ds = None
4.3.2 使用 CreateCopy 方法
from osgeo import gdal
def copy_raster(src_path, dst_path, driver='GTiff', options=None):
"""复制栅格数据集"""
src_ds = gdal.Open(src_path)
driver_obj = gdal.GetDriverByName(driver)
if options is None:
options = ['COMPRESS=LZW', 'TILED=YES']
dst_ds = driver_obj.CreateCopy(dst_path, src_ds, 0, options)
src_ds = None
dst_ds = None
# 简单复制
copy_raster('input.tif', 'output.tif')
# 转换格式(GeoTIFF 转 JPEG)
copy_raster('input.tif', 'output.jpg', driver='JPEG', options=['QUALITY=90'])
# 复制并压缩
copy_raster('input.tif', 'compressed.tif',
options=['COMPRESS=DEFLATE', 'PREDICTOR=2', 'ZLEVEL=9'])
4.3.3 创建多波段彩色影像
from osgeo import gdal, osr
import numpy as np
def create_rgb_raster(filepath, width, height,
red_data, green_data, blue_data):
"""创建RGB彩色栅格"""
driver = gdal.GetDriverByName('GTiff')
ds = driver.Create(
filepath, width, height, 3, gdal.GDT_Byte,
['COMPRESS=LZW', 'PHOTOMETRIC=RGB']
)
# 写入各波段
for i, (data, interp) in enumerate([
(red_data, gdal.GCI_RedBand),
(green_data, gdal.GCI_GreenBand),
(blue_data, gdal.GCI_BlueBand)
], 1):
band = ds.GetRasterBand(i)
band.WriteArray(data)
band.SetColorInterpretation(interp)
return ds
# 创建测试数据
width, height = 500, 500
red = np.random.randint(0, 256, (height, width), dtype=np.uint8)
green = np.random.randint(0, 256, (height, width), dtype=np.uint8)
blue = np.random.randint(0, 256, (height, width), dtype=np.uint8)
ds = create_rgb_raster('rgb_image.tif', width, height, red, green, blue)
# 设置地理信息
ds.SetGeoTransform((116.0, 0.001, 0, 40.0, 0, -0.001))
srs = osr.SpatialReference()
srs.ImportFromEPSG(4326)
ds.SetProjection(srs.ExportToWkt())
ds.FlushCache()
ds = None
4.3.4 创建金字塔
from osgeo import gdal
def build_overviews(filepath, levels=None, resampling='AVERAGE'):
"""创建影像金字塔"""
ds = gdal.Open(filepath, gdal.GA_Update)
if levels is None:
# 自动计算金字塔级别
max_dim = max(ds.RasterXSize, ds.RasterYSize)
levels = []
level = 2
while max_dim // level >= 256:
levels.append(level)
level *= 2
print(f"创建金字塔级别: {levels}")
# 创建金字塔
ds.BuildOverviews(resampling, levels)
ds = None
# 使用示例
build_overviews('large_image.tif', levels=[2, 4, 8, 16, 32])
# 不同的重采样方法
# NEAREST: 最近邻(分类数据)
# AVERAGE: 平均值(连续数据)
# BILINEAR: 双线性
# CUBIC: 三次卷积
# CUBICSPLINE: 三次样条
# LANCZOS: Lanczos
# MODE: 众数(分类数据)
4.4 栅格数据处理
4.4.1 裁剪栅格
from osgeo import gdal
def clip_raster_by_extent(src_path, dst_path,
min_x, min_y, max_x, max_y):
"""按范围裁剪栅格"""
ds = gdal.Warp(
dst_path,
src_path,
outputBounds=(min_x, min_y, max_x, max_y),
format='GTiff',
creationOptions=['COMPRESS=LZW']
)
ds = None
def clip_raster_by_shapefile(src_path, dst_path, shapefile_path):
"""按矢量边界裁剪栅格"""
ds = gdal.Warp(
dst_path,
src_path,
cutlineDSName=shapefile_path,
cropToCutline=True,
dstNodata=-9999,
format='GTiff',
creationOptions=['COMPRESS=LZW']
)
ds = None
# 按范围裁剪(北京中心区域)
clip_raster_by_extent(
'input.tif', 'clipped.tif',
116.3, 39.8, 116.5, 40.0
)
# 按边界裁剪
clip_raster_by_shapefile(
'input.tif', 'clipped_by_boundary.tif',
'boundary.shp'
)
4.4.2 重投影
from osgeo import gdal
def reproject_raster(src_path, dst_path,
dst_crs='EPSG:3857',
resampling='bilinear'):
"""重投影栅格"""
# 重采样方法映射
resample_methods = {
'nearest': gdal.GRA_NearestNeighbour,
'bilinear': gdal.GRA_Bilinear,
'cubic': gdal.GRA_Cubic,
'cubicspline': gdal.GRA_CubicSpline,
'lanczos': gdal.GRA_Lanczos,
'average': gdal.GRA_Average,
'mode': gdal.GRA_Mode,
}
ds = gdal.Warp(
dst_path,
src_path,
dstSRS=dst_crs,
resampleAlg=resample_methods.get(resampling, gdal.GRA_Bilinear),
format='GTiff',
creationOptions=['COMPRESS=LZW', 'TILED=YES']
)
ds = None
# WGS84 转 Web Mercator
reproject_raster('wgs84.tif', 'webmercator.tif', 'EPSG:3857')
# WGS84 转 CGCS2000
reproject_raster('wgs84.tif', 'cgcs2000.tif', 'EPSG:4490')
# 转换到 UTM 投影
reproject_raster('wgs84.tif', 'utm50n.tif', 'EPSG:32650')
4.4.3 重采样
from osgeo import gdal
def resample_raster(src_path, dst_path,
target_width=None, target_height=None,
target_resolution=None, resampling='bilinear'):
"""重采样栅格"""
src_ds = gdal.Open(src_path)
# 计算目标尺寸
if target_resolution:
gt = src_ds.GetGeoTransform()
extent_x = src_ds.RasterXSize * abs(gt[1])
extent_y = src_ds.RasterYSize * abs(gt[5])
target_width = int(extent_x / target_resolution)
target_height = int(extent_y / target_resolution)
resample_methods = {
'nearest': gdal.GRA_NearestNeighbour,
'bilinear': gdal.GRA_Bilinear,
'cubic': gdal.GRA_Cubic,
'average': gdal.GRA_Average,
}
ds = gdal.Warp(
dst_path,
src_ds,
width=target_width,
height=target_height,
resampleAlg=resample_methods.get(resampling, gdal.GRA_Bilinear),
format='GTiff',
creationOptions=['COMPRESS=LZW']
)
src_ds = None
ds = None
# 按尺寸重采样
resample_raster('input.tif', 'resampled.tif',
target_width=500, target_height=500)
# 按分辨率重采样(0.01度)
resample_raster('input.tif', 'resampled_res.tif',
target_resolution=0.01)
4.4.4 镶嵌
from osgeo import gdal
def mosaic_rasters(src_files, dst_path, nodata=None):
"""镶嵌多个栅格"""
vrt_options = gdal.BuildVRTOptions(
resampleAlg='nearest',
srcNodata=nodata,
VRTNodata=nodata
)
# 创建 VRT
vrt = gdal.BuildVRT('', src_files, options=vrt_options)
# 转换为 GeoTIFF
ds = gdal.Translate(
dst_path, vrt,
format='GTiff',
creationOptions=['COMPRESS=LZW', 'TILED=YES', 'BIGTIFF=IF_SAFER']
)
vrt = None
ds = None
# 镶嵌多个 DEM 文件
dem_files = ['dem_n35e116.tif', 'dem_n35e117.tif',
'dem_n36e116.tif', 'dem_n36e117.tif']
mosaic_rasters(dem_files, 'mosaic.tif', nodata=-9999)
4.4.5 波段计算
from osgeo import gdal
import numpy as np
def calculate_ndvi(red_path, nir_path, output_path):
"""计算 NDVI"""
red_ds = gdal.Open(red_path)
nir_ds = gdal.Open(nir_path)
red = red_ds.GetRasterBand(1).ReadAsArray().astype(np.float32)
nir = nir_ds.GetRasterBand(1).ReadAsArray().astype(np.float32)
# 计算 NDVI: (NIR - Red) / (NIR + Red)
# 避免除零
denominator = nir + red
ndvi = np.where(denominator > 0, (nir - red) / denominator, 0)
# 创建输出
driver = gdal.GetDriverByName('GTiff')
out_ds = driver.Create(
output_path,
red_ds.RasterXSize,
red_ds.RasterYSize,
1, gdal.GDT_Float32,
['COMPRESS=LZW']
)
out_ds.SetGeoTransform(red_ds.GetGeoTransform())
out_ds.SetProjection(red_ds.GetProjection())
out_band = out_ds.GetRasterBand(1)
out_band.WriteArray(ndvi)
out_band.SetNoDataValue(-9999)
out_band.ComputeStatistics(False)
red_ds = None
nir_ds = None
out_ds = None
# 使用 gdal_calc.py 进行波段计算
def band_calc_with_gdal(expression, output_path, **input_files):
"""使用 gdal_calc 进行波段计算"""
import subprocess
cmd = ['gdal_calc.py', '-A', input_files.get('A', ''),
'--outfile', output_path, '--calc', expression,
'--NoDataValue=-9999']
for key, value in input_files.items():
if key != 'A':
cmd.extend([f'-{key}', value])
subprocess.run(cmd)
# 示例:计算 NDVI
# band_calc_with_gdal(
# '(B-A)/(B+A)', 'ndvi.tif',
# A='red_band.tif', B='nir_band.tif'
# )
4.5 栅格分析
4.5.1 统计分析
from osgeo import gdal
import numpy as np
def raster_statistics(filepath):
"""计算栅格统计信息"""
ds = gdal.Open(filepath)
results = []
for i in range(1, ds.RasterCount + 1):
band = ds.GetRasterBand(i)
# 强制计算统计
stats = band.ComputeStatistics(False)
# 获取直方图
hist = band.GetHistogram()
band_stats = {
'band': i,
'min': stats[0],
'max': stats[1],
'mean': stats[2],
'std': stats[3],
'nodata': band.GetNoDataValue(),
}
# 更详细的统计(使用 NumPy)
data = band.ReadAsArray()
nodata = band.GetNoDataValue()
if nodata is not None:
valid_data = data[data != nodata]
else:
valid_data = data.flatten()
if len(valid_data) > 0:
band_stats.update({
'median': float(np.median(valid_data)),
'sum': float(np.sum(valid_data)),
'count': len(valid_data),
'percentile_25': float(np.percentile(valid_data, 25)),
'percentile_75': float(np.percentile(valid_data, 75)),
})
results.append(band_stats)
ds = None
return results
# 使用示例
stats = raster_statistics('dem.tif')
for s in stats:
print(f"波段 {s['band']}:")
print(f" 最小值: {s['min']:.2f}")
print(f" 最大值: {s['max']:.2f}")
print(f" 平均值: {s['mean']:.2f}")
print(f" 标准差: {s['std']:.2f}")
4.5.2 分区统计
from osgeo import gdal, ogr
import numpy as np
def zonal_statistics(raster_path, vector_path, stats=['mean', 'sum', 'count']):
"""计算分区统计"""
raster_ds = gdal.Open(raster_path)
band = raster_ds.GetRasterBand(1)
gt = raster_ds.GetGeoTransform()
nodata = band.GetNoDataValue()
vector_ds = ogr.Open(vector_path)
layer = vector_ds.GetLayer()
results = []
for feature in layer:
fid = feature.GetFID()
geom = feature.GetGeometryRef()
# 获取几何范围
env = geom.GetEnvelope() # (minX, maxX, minY, maxY)
# 转换为像素坐标
x_off = int((env[0] - gt[0]) / gt[1])
y_off = int((env[3] - gt[3]) / gt[5])
x_size = int((env[1] - env[0]) / gt[1]) + 1
y_size = int((env[2] - env[3]) / gt[5]) + 1
# 确保在影像范围内
x_off = max(0, x_off)
y_off = max(0, y_off)
x_size = min(x_size, raster_ds.RasterXSize - x_off)
y_size = min(y_size, raster_ds.RasterYSize - y_off)
if x_size <= 0 or y_size <= 0:
continue
# 读取数据
data = band.ReadAsArray(x_off, y_off, x_size, y_size)
# 掩码处理
if nodata is not None:
valid_data = data[data != nodata]
else:
valid_data = data.flatten()
# 计算统计
feature_stats = {'fid': fid}
if len(valid_data) > 0:
if 'mean' in stats:
feature_stats['mean'] = float(np.mean(valid_data))
if 'sum' in stats:
feature_stats['sum'] = float(np.sum(valid_data))
if 'count' in stats:
feature_stats['count'] = len(valid_data)
if 'min' in stats:
feature_stats['min'] = float(np.min(valid_data))
if 'max' in stats:
feature_stats['max'] = float(np.max(valid_data))
if 'std' in stats:
feature_stats['std'] = float(np.std(valid_data))
results.append(feature_stats)
layer.ResetReading()
raster_ds = None
vector_ds = None
return results
# 使用示例
# stats = zonal_statistics('dem.tif', 'zones.shp')
4.5.3 地形分析
from osgeo import gdal
import numpy as np
def calculate_slope(dem_path, output_path):
"""计算坡度"""
ds = gdal.DEMProcessing(
output_path,
dem_path,
'slope',
format='GTiff',
creationOptions=['COMPRESS=LZW']
)
ds = None
def calculate_aspect(dem_path, output_path):
"""计算坡向"""
ds = gdal.DEMProcessing(
output_path,
dem_path,
'aspect',
format='GTiff',
creationOptions=['COMPRESS=LZW']
)
ds = None
def calculate_hillshade(dem_path, output_path,
azimuth=315, altitude=45):
"""计算山体阴影"""
ds = gdal.DEMProcessing(
output_path,
dem_path,
'hillshade',
azimuth=azimuth,
altitude=altitude,
format='GTiff',
creationOptions=['COMPRESS=LZW']
)
ds = None
def calculate_tpi(dem_path, output_path):
"""计算地形位置指数"""
ds = gdal.DEMProcessing(
output_path,
dem_path,
'TPI',
format='GTiff',
creationOptions=['COMPRESS=LZW']
)
ds = None
def calculate_tri(dem_path, output_path):
"""计算地形粗糙度指数"""
ds = gdal.DEMProcessing(
output_path,
dem_path,
'TRI',
format='GTiff',
creationOptions=['COMPRESS=LZW']
)
ds = None
def calculate_roughness(dem_path, output_path):
"""计算地形粗糙度"""
ds = gdal.DEMProcessing(
output_path,
dem_path,
'roughness',
format='GTiff',
creationOptions=['COMPRESS=LZW']
)
ds = None
# 使用示例
calculate_slope('dem.tif', 'slope.tif')
calculate_aspect('dem.tif', 'aspect.tif')
calculate_hillshade('dem.tif', 'hillshade.tif')
4.5.4 等值线生成
from osgeo import gdal, ogr
def generate_contours(dem_path, output_path, interval=100, base=0):
"""生成等值线"""
dem_ds = gdal.Open(dem_path)
band = dem_ds.GetRasterBand(1)
# 创建输出
driver = ogr.GetDriverByName('ESRI Shapefile')
dst_ds = driver.CreateDataSource(output_path)
dst_layer = dst_ds.CreateLayer('contour', geom_type=ogr.wkbLineString)
# 添加属性字段
field_defn = ogr.FieldDefn('elevation', ogr.OFTReal)
dst_layer.CreateField(field_defn)
# 生成等值线
gdal.ContourGenerate(
band, # 输入波段
interval, # 等值线间隔
base, # 基础高程
[], # 固定高程列表(空)
0, # 使用无效值
band.GetNoDataValue() or 0, # 无效值
dst_layer, # 输出图层
-1, # ID 字段索引(-1 不创建)
0 # 高程字段索引
)
dem_ds = None
dst_ds = None
# 使用示例
generate_contours('dem.tif', 'contours.shp', interval=50)
4.6 栅格格式转换
4.6.1 使用 Translate
from osgeo import gdal
def convert_raster(src_path, dst_path, format='GTiff', options=None):
"""转换栅格格式"""
if options is None:
options = []
translate_options = gdal.TranslateOptions(
format=format,
creationOptions=options
)
ds = gdal.Translate(dst_path, src_path, options=translate_options)
ds = None
# GeoTIFF 转 JPEG
convert_raster('input.tif', 'output.jpg', 'JPEG', ['QUALITY=90'])
# GeoTIFF 转 PNG
convert_raster('input.tif', 'output.png', 'PNG')
# GeoTIFF 转 Cloud Optimized GeoTIFF
convert_raster('input.tif', 'output_cog.tif', 'COG',
['COMPRESS=LZW', 'OVERVIEW_RESAMPLING=AVERAGE'])
# 转换并更改数据类型
def convert_with_type_change(src_path, dst_path, output_type=gdal.GDT_Byte):
"""转换并更改数据类型"""
options = gdal.TranslateOptions(
format='GTiff',
outputType=output_type,
scaleParams=[[]], # 自动缩放
creationOptions=['COMPRESS=LZW']
)
ds = gdal.Translate(dst_path, src_path, options=options)
ds = None
# Float32 转 Byte(自动缩放到0-255)
convert_with_type_change('float_dem.tif', 'byte_dem.tif', gdal.GDT_Byte)
4.6.2 批量转换
from osgeo import gdal
import os
from pathlib import Path
def batch_convert(src_dir, dst_dir, src_format='*.tif',
dst_format='GTiff', dst_ext='.tif', options=None):
"""批量转换栅格格式"""
Path(dst_dir).mkdir(parents=True, exist_ok=True)
if options is None:
options = ['COMPRESS=LZW']
translate_options = gdal.TranslateOptions(
format=dst_format,
creationOptions=options
)
src_path = Path(src_dir)
converted = 0
for src_file in src_path.glob(src_format):
dst_file = Path(dst_dir) / (src_file.stem + dst_ext)
print(f"转换: {src_file} -> {dst_file}")
ds = gdal.Translate(str(dst_file), str(src_file),
options=translate_options)
ds = None
converted += 1
print(f"共转换 {converted} 个文件")
# 批量转换示例
batch_convert('input_images', 'output_images',
src_format='*.img', dst_format='GTiff')
4.7 高级创建选项
4.7.1 GeoTIFF 创建选项
from osgeo import gdal
# 完整的 GeoTIFF 创建选项
geotiff_options = [
# 压缩选项
'COMPRESS=LZW', # 压缩方法: NONE, LZW, JPEG, DEFLATE, PACKBITS, ZSTD, WEBP
'PREDICTOR=2', # 预测器: 1(无), 2(水平差分), 3(浮点)
'ZLEVEL=6', # DEFLATE/ZSTD 压缩级别: 1-9
# 分块选项
'TILED=YES', # 使用分块存储
'BLOCKXSIZE=256', # 块宽度
'BLOCKYSIZE=256', # 块高度
# 大文件选项
'BIGTIFF=IF_SAFER', # BigTIFF: YES, NO, IF_NEEDED, IF_SAFER
# 颜色选项
'PHOTOMETRIC=RGB', # 色彩解释: MINISBLACK, RGB, YCBCR, PALETTE
# 其他选项
'PROFILE=GeoTIFF', # 配置文件: GeoTIFF, BASELINE, GDALGeoTIFF
'INTERLEAVE=PIXEL', # 交织方式: BAND, PIXEL
'ALPHA=YES', # 添加 Alpha 通道
'NBITS=8', # 位深度(某些数据类型)
]
# Cloud Optimized GeoTIFF 选项
cog_options = [
'COMPRESS=LZW',
'TILED=YES',
'BLOCKSIZE=512',
'COPY_SRC_OVERVIEWS=YES',
'OVERVIEWS=AUTO',
]
# 创建高压缩率 GeoTIFF
def create_compressed_tiff(src_path, dst_path):
"""创建高压缩率 GeoTIFF"""
options = gdal.TranslateOptions(
format='GTiff',
creationOptions=[
'COMPRESS=DEFLATE',
'PREDICTOR=2',
'ZLEVEL=9',
'TILED=YES',
'BLOCKXSIZE=512',
'BLOCKYSIZE=512',
]
)
ds = gdal.Translate(dst_path, src_path, options=options)
ds = None
4.7.2 其他格式选项
from osgeo import gdal
# JPEG 选项
jpeg_options = [
'QUALITY=85', # 质量: 0-100
'WORLDFILE=YES', # 创建 world 文件
]
# PNG 选项
png_options = [
'ZLEVEL=9', # 压缩级别: 1-9
'WORLDFILE=YES',
]
# JPEG 2000 选项
jp2_options = [
'QUALITY=25', # 质量因子
'REVERSIBLE=NO', # 是否无损
'NBITS=8', # 位深度
]
# GeoPackage 选项
gpkg_options = [
'TILE_FORMAT=PNG', # 瓦片格式: PNG, PNG8, JPEG, WEBP
'QUALITY=75', # JPEG 质量
'BLOCKSIZE=256', # 瓦片大小
]
4.8 本章小结
本章详细介绍了 GDAL 栅格数据处理的基础知识:
- 栅格数据概述:数据结构、常见类型和格式
- 读取栅格数据:
- 打开数据集和获取信息
- 读取像素数据(整体/分块/子集)
- 处理无效值
- 创建栅格数据:
- Create 和 CreateCopy 方法
- 多波段影像创建
- 金字塔创建
- 栅格处理:
- 裁剪、重投影、重采样
- 镶嵌、波段计算
- 栅格分析:
- 统计分析、分区统计
- 地形分析(坡度、坡向、山体阴影)
- 等值线生成
- 格式转换:
- Translate 方法
- 批量转换
- 高级选项:
- GeoTIFF 创建选项
- 其他格式选项
掌握这些基础操作是进行更复杂栅格数据处理的前提。
4.9 思考与练习
- 解释栅格数据的 GeoTransform 参数的作用。
- 如何高效读取一个 10GB 的 GeoTIFF 文件?
- 比较 Create 和 CreateCopy 两种创建数据集方法的区别。
- 编写代码计算两个 DEM 文件的高程差。
- 如何将一个 Float32 类型的 DEM 转换为 8 位彩色图?
- 实现一个函数,将多景卫星影像镶嵌为一幅完整影像。
- 使用 GDAL 计算指定区域的 NDVI 并统计其分布。

浙公网安备 33010602011771号