从NC到GeoTIFF:Himawari-8卫星数据预处理实战指南

张开发
2026/4/18 14:02:41 15 分钟阅读

分享文章

从NC到GeoTIFF:Himawari-8卫星数据预处理实战指南
1. 认识Himawari-8卫星数据Himawari-8是日本气象厅发射的静止气象卫星它每10分钟就能对地球进行一次完整扫描生成包含16个波段的观测数据。这些数据以NetCDF.nc格式存储包含了从可见光到红外的多光谱信息。我第一次接触这些数据时发现它们就像是一个装满各种颜色糖果的罐子——每个波段都藏着不同的环境信息比如反射率数据能看植被亮温数据能测云顶高度。原始NC文件的结构有点复杂打开后你会看到这些关键部分反射率数据albedo_01到albedo_06亮温数据tbb_07到tbb_16太阳和卫星的几何角度数据SAA、SAZ等经纬度坐标信息这些数据最初都是未经校准的原始数值就像刚拍完的RAW格式照片需要经过一系列处理才能变成可用的科学数据。实测下来H8数据的空间分辨率在2km左右覆盖范围从东经80度到200度北纬60度到南纬60度特别适合监测东亚和西太平洋区域的气象变化。2. 搭建数据处理环境2.1 必备工具安装处理H8数据需要配置Python环境我推荐使用Miniconda来管理依赖。下面是我验证过的环境配置方案conda create -n h8 python3.8 conda install -c conda-forge gdal netCDF4 numpy pip install h5py这里有个坑要注意GDAL的版本必须和Python版本匹配。我有次装了不兼容的版本报错信息看得一头雾水最后发现是GDAL 3.4需要Python 3.9。建议直接用conda-forge的预编译版本省去编译麻烦。2.2 测试数据准备新手建议先用小样本数据练手。日本气象厅官网提供样例数据下载文件大概500MB左右。我习惯在项目目录这样组织文件结构/h8_data /raw # 存放原始NC文件 /temp # 处理中间结果 /output # 最终GeoTIFF3. 数据读取与定标处理3.1 读取NC文件核心代码读取H8数据我封装了个通用函数支持错误处理和自动关闭文件def read_h8_nc(filename, variable): try: with Dataset(filename) as nc: data nc.variables[variable][:] # 自动处理填充值 if hasattr(nc.variables[variable], _FillValue): data[data nc.variables[variable]._FillValue] np.nan return data except Exception as e: print(f读取{filename}出错{str(e)}) return None这个函数比简单用netCDF4.Dataset更健壮特别是处理大文件时with语句能确保文件正确关闭。我遇到过内存泄漏问题就是因为没及时关闭NC文件。3.2 反射率与亮温定标原始数据需要两步转换才有物理意义反射率定标公式ref_calibrated raw_ref * 0.0001这个系数是卫星厂商提供的把原始DN值转为0-1范围的反射率亮温定标公式bt_calibrated raw_bt * 0.01 273.15 # 转绝对温度(K)这里有个常见误区有人会忘记加273.15转换开尔文温度导致后续分析全错。我有次花了三天才找到这个bug。4. 地理坐标系统处理4.1 理解H8的GLL投影Himawari-8使用等经纬度投影Geographic Latitude/Longitude但它的经度范围是0-360°而非常见的-180-180°。这会导致在QGIS中直接显示时地图会从中间断开。我的解决办法是# 经度转换示例 lon np.where(lon 180, lon - 360, lon)4.2 设置GDAL地理转换参数创建GeoTIFF时这六个参数决定图像如何对应到地图上geotransform ( 80, # 左上角经度 0.02, # 经度分辨率(度/像素) 0, # 旋转参数(通常为0) 60, # 左上角纬度 0, # 旋转参数(通常为0) -0.02 # 纬度分辨率(南向为负) )注意纬度分辨率是负值因为H8数据是从北向南存储的。这个细节我在第一次处理时完全忽略了导致生成的图像上下颠倒。5. 生成GeoTIFF实战5.1 多波段写入技巧H8有16个波段手动一个个写太麻烦。我的优化方案是def create_geotiff(output_path, data_array, geotransform, epsg4326): driver gdal.GetDriverByName(GTiff) rows, cols, bands data_array.shape # 创建多波段文件 ds driver.Create(output_path, cols, rows, bands, gdal.GDT_Float32) ds.SetGeoTransform(geotransform) # 设置空间参考 srs osr.SpatialReference() srs.ImportFromEPSG(epsg) ds.SetProjection(srs.ExportToWkt()) # 批量写入波段 for i in range(bands): band ds.GetRasterBand(i1) band.WriteArray(data_array[:,:,i]) band.SetDescription(fBand_{i1}) # 设置波段名称 band.FlushCache() ds None # 确保文件写入磁盘5.2 性能优化建议处理全分辨率数据时内存可能爆掉。我总结了几种优化方案分块处理将大区域分成小tile处理chunk_size 1000 # 每块行数 for i in range(0, rows, chunk_size): chunk data[i:ichunk_size, :, :] # 处理当前块...使用内存映射对于超大文件data np.memmap(temp.bin, dtypefloat32, modew, shape(rows,cols,bands))压缩输出减少文件体积ds driver.Create(output_path, cols, rows, bands, gdal.GDT_Float32, options[COMPRESSDEFLATE, PREDICTOR2])6. 质量检查与常见问题6.1 数据验证方法生成GeoTIFF后我必做三项检查坐标验证在QGIS中叠加海岸线图层检查对齐情况值域检查反射率应在0-1之间亮温在180-340K左右波段对应用不同配色方案检查各波段是否正确6.2 踩坑记录问题1生成的TIFF在ArcGIS中显示为全黑原因没有正确设置NoData值解决添加band.SetNoDataValue(np.nan)问题2文件体积异常大原因使用了默认的无压缩存储解决创建时添加压缩选项COMPRESSLZW问题3经纬度偏移原因geotransform参数设置错误解决用gdalinfo检查实际地理范围7. 完整代码示例下面是我在实际项目中使用的增强版代码包含异常处理和日志记录import logging from pathlib import Path def process_h8_to_geotiff(input_nc, output_tif): 完整处理流程 # 配置日志 logging.basicConfig(filenameh8_processing.log, levellogging.INFO) logger logging.getLogger(__name__) try: logger.info(f开始处理 {input_nc}) # 读取所有波段 bands_data {} with Dataset(input_nc) as nc: for var in nc.variables: if var.startswith((albedo_, tbb_)): bands_data[var] nc.variables[var][:] # 定标处理 calibrated {} for name, data in bands_data.items(): if name.startswith(albedo): calibrated[name] data * 0.0001 else: calibrated[name] data * 0.01 273.15 # 创建三维数组 (height, width, bands) stack np.dstack([calibrated[falbedo_{i:02d}] for i in range(1,7)] [calibrated[ftbb_{i:02d}] for i in range(7,17)]) # 生成GeoTIFF create_geotiff(output_tif, stack, geotransform(80,0.02,0,60,0,-0.02)) logger.info(f成功生成 {output_tif}) return True except Exception as e: logger.error(f处理失败: {str(e)}, exc_infoTrue) return False if __name__ __main__: nc_file Path(data/NC_H08_20230101_0000.nc) tif_file Path(output/H8_20230101.tif) if not nc_file.exists(): print(输入文件不存在) else: success process_h8_to_geotiff(nc_file, tif_file) print(处理成功 if success else 处理失败请查看日志)这套代码加入了这些实用功能自动识别NC文件中的有效波段详细的运行日志记录更健壮的错误处理使用Path对象处理文件路径处理一个完整时次的数据约600MB在我的开发机上大约需要90秒主要耗时在磁盘IO环节。如果改用SSD存储和内存优化技巧时间可以缩短到50秒左右。

更多文章