Python实战:全球植被生产力BEPS模型数据(1981-2019)的读取、转换与可视化分析

张开发
2026/4/15 10:39:19 15 分钟阅读

分享文章

Python实战:全球植被生产力BEPS模型数据(1981-2019)的读取、转换与可视化分析
1. 认识BEPS模型数据全球植被生产力数据是研究生态系统碳循环的重要基础。居为民教授团队发布的1981-2019年全球逐日GPP/NEP/NPP数据集采用BEPSBoreal Ecosystem Productivity Simulator模型生成这个模型考虑了植被参数、气象条件和大气CO2浓度等多重因素。对于生态学和地理信息科学领域的研究者来说这套数据简直就是宝藏。我第一次接触这套数据时发现它有几个显著特点首先是时间跨度长覆盖了近40年的连续观测其次是空间分辨率精细达到0.072727°×0.072727°最后是数据格式特殊采用.img二进制格式存储。这些特点让数据很有价值但也给处理带来了挑战。数据下载后你会看到文件名类似GPP_2019_001.img这样的结构其中2019代表年份001代表一年中的第几天。这种命名方式虽然规范但当你需要处理几十年的数据时手动操作就变得不现实了。这时候Python就派上大用场了。2. 环境准备与数据读取2.1 安装必要的Python库处理这类空间数据我们需要几个核心工具GDAL地理空间数据转换的瑞士军刀NumPy处理大型数组的利器Matplotlib数据可视化的标配安装这些库很简单用pip就能搞定pip install gdal numpy matplotlib不过要注意GDAL在某些系统上安装可能会遇到问题。我在Windows上就踩过坑后来发现最简单的方法是去这个网站下载预编译的whl文件。如果你用Anaconda那更简单conda install -c conda-forge gdal2.2 理解数据存储结构原始数据采用.img格式这是一种二进制文件。打开文件头你会发现它没有包含地理参考信息这意味着我们需要手动指定一些参数。经过多次尝试我总结出这些关键参数行数2090列数4950左上角经纬度(89.23°, -180.0°)右下角经纬度(-62.77°, 180.0°)这些参数在后续的坐标转换中至关重要。我建议先处理单日数据测试流程确认无误后再批量处理。下面这段代码展示了如何读取单日数据import numpy as np def read_img_file(filepath): data np.fromfile(filepath, dtypenp.int16) return data.reshape((2090, 4950))3. 数据格式转换实战3.1 从IMG到GeoTIFF的转换原始.img格式虽然节省空间但在实际分析中很不方便。GeoTIFF是更通用的选择它能把数据值和空间参考信息打包在一起。下面这个函数是我经过多次调试优化后的版本from osgeo import gdal, osr import os def convert_to_geotiff(input_path, output_path): try: # 读取数据 data np.fromfile(input_path, dtypenp.int16) data data.reshape((2090, 4950)) # 创建输出文件 driver gdal.GetDriverByName(GTiff) ds driver.Create(output_path, 4950, 2090, 1, gdal.GDT_Int16) # 设置地理参考 geotransform (-180.0, 0.072727, 0, 89.23, 0, -0.072727) ds.SetGeoTransform(geotransform) # 设置WGS84投影 srs osr.SpatialReference() srs.ImportFromEPSG(4326) ds.SetProjection(srs.ExportToWkt()) # 写入数据 ds.GetRasterBand(1).WriteArray(data) ds.FlushCache() return True except Exception as e: print(f转换失败: {str(e)}) return False finally: ds None3.2 批量处理技巧处理几十年的逐日数据手动操作不现实。我开发了一个批量处理脚本可以自动遍历目录结构import glob def batch_convert(input_dir, output_dir): os.makedirs(output_dir, exist_okTrue) img_files glob.glob(os.path.join(input_dir, *.img)) for img_file in img_files: filename os.path.basename(img_file) tif_file os.path.join(output_dir, filename.replace(.img, .tif)) if not os.path.exists(tif_file): success convert_to_geotiff(img_file, tif_file) if success: print(f成功转换: {filename}) else: print(f转换失败: {filename})这个脚本会自动跳过已经转换过的文件避免重复工作。对于大型数据集我建议分年度处理或者使用并行计算加速。4. 数据质量检查与预处理4.1 异常值处理BEPS模型输出的GPP值范围通常在0到20 gC/m²/day之间。但在实际数据中你可能会遇到一些异常值def check_data_quality(data): valid_min, valid_max 0, 20 invalid_count np.sum((data valid_min) | (data valid_max)) if invalid_count 0: print(f警告: 发现{invalid_count}个异常值) data np.clip(data, valid_min, valid_max) return data4.2 缺失值处理原始数据使用特定值表示缺失如-9999。处理时我们需要先识别这些值def handle_missing_values(data, missing_value-9999): mask (data missing_value) if np.any(mask): print(f发现{np.sum(mask)}个缺失值) # 可以用邻近像元平均值填充 from scipy.ndimage import median_filter data[mask] median_filter(data, size3)[mask] return data5. 数据可视化技巧5.1 基础空间分布图使用Matplotlib绘制全球GPP分布import matplotlib.pyplot as plt from mpl_toolkits.basemap import Basemap def plot_global_gpp(data, titleGlobal GPP Distribution): plt.figure(figsize(15, 8)) m Basemap(projectioncyl, resolutionc, llcrnrlat-60, urcrnrlat90, llcrnrlon-180, urcrnrlon180) m.drawcoastlines() m.drawcountries() x np.linspace(-180, 180, 4950) y np.linspace(90, -60, 2090) xx, yy np.meshgrid(x, y) m.pcolormesh(xx, yy, data, cmapYlGn, shadingauto) plt.colorbar(labelGPP (gC/m²/day)) plt.title(title) plt.show()5.2 时间序列分析分析特定区域多年变化趋势def analyze_trend(data_dir, lat_range, lon_range): years range(1981, 2020) annual_gpp [] for year in years: yearly_data [] for day in range(1, 366): filepath os.path.join(data_dir, fGPP_{year}_{day:03d}.tif) if os.path.exists(filepath): dataset gdal.Open(filepath) data dataset.ReadAsArray() dataset None # 提取目标区域 lat_idx (np.linspace(90, -60, 2090) lat_range[0]) \ (np.linspace(90, -60, 2090) lat_range[1]) lon_idx (np.linspace(-180, 180, 4950) lon_range[0]) \ (np.linspace(-180, 180, 4950) lon_range[1]) region_data data[lat_idx, :][:, lon_idx] yearly_data.append(np.nanmean(region_data)) annual_gpp.append(np.nanmean(yearly_data)) plt.figure(figsize(12, 6)) plt.plot(years, annual_gpp, o-) plt.xlabel(Year) plt.ylabel(Mean Annual GPP (gC/m²/day)) plt.title(fGPP Trend {lat_range}-{lon_range}) plt.grid(True) plt.show()6. 高级分析与应用6.1 季节性变化分析植被生产力有明显的季节性特征。我们可以提取特定区域的季节循环def seasonal_cycle(data_dir, lat, lon): # 找到最近的像元 lat_idx np.argmin(np.abs(np.linspace(90, -60, 2090) - lat)) lon_idx np.argmin(np.abs(np.linspace(-180, 180, 4950) - lon)) # 收集多年同一天的数据 doy_data [[] for _ in range(365)] for year in range(1981, 2020): for day in range(1, 366): filepath os.path.join(data_dir, fGPP_{year}_{day:03d}.tif) if os.path.exists(filepath): dataset gdal.Open(filepath) data dataset.ReadAsArray() dataset None doy_data[day-1].append(data[lat_idx, lon_idx]) # 计算多年平均 seasonal_avg [np.mean(vals) for vals in doy_data] # 绘制季节曲线 plt.figure(figsize(12, 6)) plt.plot(range(1, 366), seasonal_avg) plt.xlabel(Day of Year) plt.ylabel(GPP (gC/m²/day)) plt.title(fSeasonal Cycle at {lat}°N, {lon}°E) plt.grid(True) plt.show()6.2 数据导出与共享分析完成后你可能需要将结果导出为其他格式def export_to_csv(data, output_file): import pandas as pd if isinstance(data, np.ndarray): data pd.DataFrame(data) data.to_csv(output_file, indexFalse) print(f数据已导出到 {output_file})对于空间数据还可以考虑导出为NetCDF格式便于与其他气候模型数据集成def export_to_netcdf(data, lats, lons, output_file): from netCDF4 import Dataset rootgrp Dataset(output_file, w, formatNETCDF4) # 创建维度 lat rootgrp.createDimension(lat, len(lats)) lon rootgrp.createDimension(lon, len(lons)) # 创建变量 latitudes rootgrp.createVariable(lat,f4,(lat,)) longitudes rootgrp.createVariable(lon,f4,(lon,)) gpp rootgrp.createVariable(gpp,i2,(lat,lon,)) # 写入数据 latitudes[:] lats longitudes[:] lons gpp[:,:] data rootgrp.close() print(fNetCDF文件已保存到 {output_file})处理BEPS模型数据时我最大的体会是细节决定成败。比如地理转换参数的设置、异常值的处理方式都会显著影响最终结果。建议在正式分析前先用小样本数据测试每个步骤确认无误后再扩展到整个数据集。另外这类大规模数据处理很耗内存可以考虑使用Dask等工具进行分块处理。

更多文章