用bestiapop将气象数据栅格化并生成作物模型标准输入文件 📅 发布时间:2026/9/8 2:10:54 👁 浏览次数: 简介BestiaPop是一个面向作物模型用户与农业科研人员的Python包专注于自动下载SILO或NASA POWER网格化气候数据并将其转换为APSIM、DSSAT等模型可识别的MET、WTH等自定义格式有效弥合原始气候数据与作物模型输入之间的格式鸿沟。资源包共46个文件以Python源码、Jupyter示例笔记本、RST/Markdown文档、CSV样例数据及环境配置等为主压缩包约8.2MB目录包含核心模块、示例、文档与样例数据便于快速上手。已有582人下载学习适合需批量处理点尺度或格点气候数据、构建标准化模型输入文件的农业信息技术人员。通过该包使用者可掌握从数据自动下载、清洗转换到格式输出的完整流程理解SILO与NASA POWER接口的调用方式并将相关模块直接集成至自身的作物建模工作流中。 做区域作物模型模拟的同行应该都有过这种经历模型要的是逐日、逐格点的气象输入序列手头拿到的却是几十个离散站点的观测数据或者一张粗分辨率的气候格点场。我最早处理这类数据时全靠GIS软件里做空间插值再写脚本做格式转换最后手工拼成模型要求的天气文件一个流程跑下来腰酸背痛不说中间只要坐标系或者日期序列错了一处后面全得重来。后来接触到bestiapop这个Python包才发现栅格化气候数据生成、以及如何把处理结果喂给作物模型这件事本身完全可以自动化而且能省掉大量重复劳动。这篇文章就是我用bestiapop把站点气象数据和粗网格再分析数据处理成作物模型标准输入的一次完整复盘从环境安装、配置思路讲到实际运行结果再附上一堆文档里不会明说、但实测经常踩中的坑。1. 作物模型缺的不是数据而是能直接被吃进去的数据1.1 作物模型对气象输入的格式要求有多死板以最常用的DSSAT和APSIM为例作物模型运行一个格点或一个田块通常需要一组逐日气象数据日最低温、日最高温、降水量、太阳辐射部分模型还要求风速、相对湿度或露点温度。这些变量不是动态读入就行而是有严格单位、严格列序、严格时间标记的固定文件格式。DSSAT的天气文件要求站点经纬度、海拔、多年平均气温等头信息然后按年份日序组织逐日记录辐射单位是MJ/m²/d降水单位是mm/d日期里还区分到儒略日。APSIM的met文件类似但文件头用[weather.met.weather]这种节区变量名必须是tmin、tmax、rain、radn而且缺测值要用一个约定的特殊值标记。这意味着你手里的原始数据哪怕变量齐全也要经过单位换算、格式重排、缺失值替换、日期对齐这一整套转换。最容易被忽略的是日期对齐站点数据缺了几天、再分析数据的日历是360天还是365天、闰年怎么处理这些细节都会让模拟结果出现莫名其妙的断点。1.2 原始气候数据最常见的四种形态实际项目里我接触到的原始气候数据基本跑不出下面四种离散站点观测气象站的逐日/逐时CSV空间上不连续一个区域可能只有几十个站点。优点是观测真实缺点是格点模型没法直接用而且站点分布往往不均匀城区密、山区稀。格点再分析产品如ERA5、AgERA5、GLDAS这类本身已经是规则网格时间连续空间覆盖完整但分辨率往往在5公里到30公里不等直接作为区域作物模型的输入会偏粗尤其在地形复杂的区域降水和温度的空间细节丢失严重。全球气候模式输出历史模拟或未来情景的GCM数据网格更粗通常在100公里量级通常需要先做统计降尺度或者偏差校正才能给出相对可信的站点/格点逐日序列。区域气候模式降尺度结果分辨率介于GCM和观测之间大概10-50公里但时间序列里常带有系统偏差依然不能直接当观测用。不管是哪种来源进入作物模型之前都需要把它们统一到覆盖研究区、分辨率合适、逐日连续、变量单位标准的栅格化数据集上。1.3 为什么不能跳过栅格化直接跑模型有人会问DSSAT可以按站点跑为什么非要做成栅格原因是区域作物模拟的目标往往是空间连续的产量制图、灾害评估或气候变化影响分析。如果只在站点位置跑模拟站点的代表性、空间抽样的偏差会被直接带入结果产出的不是一张面上的图而是几十个孤立点。另一个问题是作物模型里很多参数本身带有空间属性比如土壤网格、品种分布、播种期如果气象数据停留在站点形态很难和这些空间图层做逐格点匹配。栅格化真正解决的是把气象数据和模型需要的其他空间输入放在同一个框架下对齐。手工做这套流程的问题在于环节太多插值、裁剪、时间聚合、单位换算、格式拼装任何一个环节换了数据源就要重新调参而且每一步都可能引入不一致。bestiapop这类工具的价值就是把这套流程固定成可重复、可配置、可验证的流水线。2. bestiapop的设计逻辑与处理管线为什么它不是又一个插值脚本2.1 定位面向作物模型的端到端前处理工具bestiapop并不是要发明新的插值算法也没有自己做气候模拟它做的事很朴素把原始气象数据 - 作物模型可用栅格输入这条链路里所有重复性的处理步骤用Python捆绑成一个可配置的工具。它的核心价值在于语义化也就是说它知道温度可能要分最高温和最低温降水要按天累加辐射的单位是MJ/m²/d而不是像普通脚本那样只知道这一列是数值。有了这层语义理解它才能自动做变量映射、单位换算和格式转换。我在对比CDOClimate Data Operators和直接用xarrayrioxarray手工拼流程时发现CDO确实长于NetCDF的批量运算但对离散站点的空间插值、以及最后一步生成DSSAT/APSIM格式文件基本帮不上忙还是要自己写胶水代码。xarray生态灵活但插值、裁剪、重投影、格式转换这些环节要自己组装代码一多数据处理逻辑和业务逻辑混在一起后面维护很痛苦。bestiapop把这层封装住以后配置项和数据处理逻辑分开了换研究区、换数据源通常只需要改配置文件不用动代码。2.2 核心处理管线拆解从一次实际运行来看bestiapop的处理管线大致可以拆成六步读取原始数据支持站点表格式数据CSV、Excel按站点经度、纬度、时间、变量组织和网格化的NetCDF数据。定义目标网格需要给出研究区的经纬度范围或参考栅格文件以及目标分辨率这是整个栅格化的基准。空间处理站点数据用插值方法IDW、克里金、最近邻等生成目标网格上的逐日字段格点数据则做重采样或降尺度到目标分辨率。时间处理把小时数据按模型需要聚合成日值校验闰年、缺测日保证输出的日期序列是连续完整的。变量标准化把原始变量重命名、换算单位必要时用日照时数估算太阳辐射或者对降水做偏差校正。输出生成标准栅格文件GeoTIFF或NetCDF并且可以直接输出DSSAT天气文件、APSIM的met文件等模型输入格式。这个流程本身不复杂但每一步都有不少需要判断的规则。比如降水插值就不能用温度插值同一套参数日照时数转辐射又涉及纬度、日序、天文辐射计算。bestiapop把这些规则内置了使用者不需要重新实现这是它最实用的地方。2.3 配置驱动而不是代码驱动实际使用下来最好评的设计是配置驱动。下面是一个简化版的配置文件结构示意project: name: demo_region start_date: 2000-01-01 end_date: 2020-12-31 target_grid: crs: EPSG:4326 xmin: 115.0 ymin: 35.0 xmax: 122.0 ymax: 42.0 resolution: 0.01 # 约1km variables: tmax: source: tmax method: idw unit: degC tmin: source: tmin method: idw unit: degC prcp: source: precip method: nearest # 降水不推荐IDW具体原因后面讲 unit: mm/d srad: source: sunshine_hours method: angstrom unit: MJ/m2/d output: format: [geotiff, dssat] dssat_station_prefix: ST这种设计的优势非常明显换一个研究区不用动主程序只需要改目标网格范围和站点文件路径换一种插值方法也只是改一个关键词。这意味着同一套代码可以在多个项目里反复用也方便团队里非编程背景的人参与配置。3. 环境准备与安装最容易翻车的三个地方3.1 Python版本与依赖库的选择bestiapop的底层依赖包含rasterio、xarray、pandas、numpy、scipy这些地理空间和数据处理库其中rasterio、GDAL、fiona这几个库是装环境时最容易出问题的。根据我踩过的坑Python版本建议选3.9到3.11不要一上来就追最新版。我在Python 3.12上装rasterio时预编译轮子经常跟不上最后只能退回3.10才顺利装完。如果你的机器上已经存在系统级GIS软件如QGIS自带的Python不建议直接混用最好单独建一个虚拟环境否则很容易出现GDAL版本ABI冲突。3.2 以终端实操为例的安装过程在Linux或者macOS上最简单的安装方式直接走pippython -m venv crop_env source crop_env/bin/activate pip install --upgrade pip pip install bestiapopWindows用户我的建议是优先用conda建环境因为rasterio、GDAL在Windows上通过pip安装经常要自己找编译好的whl文件比较折腾conda create -n crop_env python3.10 -y conda activate crop_env conda install -c conda-forge gdal rasterio geopandas -y pip install bestiapop安装完成后验证是否正常python -c import bestiapop; print(bestiapop.__version__)如果这行能顺利输出版本号基本说明环境没问题了。3.3 安装过程中最常见的三种报错及对应处理我帮同事处理过几台机器报错主要集中在三处GDAL版本冲突报错信息通常类似RuntimeError: GDAL_DATA not found或Couldnt find proj.db。这不是bestiapop的问题而是系统里存在多个GDAL版本环境变量指向混乱。解决办法是重新设置GDAL_DATA和PROJ_LIB环境变量或者在conda环境里用conda install -c conda-forge gdal统一版本不要混装。rasterio导入即崩溃往往是和fiona或shapely的ABI版本不一致。我的处理习惯是一起重装一组匹配版本pip install -U rasterio fiona shapely pyproj让它们从同一套二进制轮子解析依赖冲突就少了。pip编译源码卡住多半是pip在尝试从源码构建rasterio而不是使用预编译轮子。建议先升级打包相关依赖pip install wheel setuptools cython或者直接改用conda安装rasterio部分再装bestiapop本身即可。注意如果你是在服务器上离线安装最好提前把依赖的whl文件打包下载好。rasterio、GDAL这类库对系统库版本敏感离线环境里直接pip install很容易遇到依赖不一致的连环坑。4. 一次完整实测把80个站点变成一套1公里分辨率的逐日栅格气候数据4.1 输入数据的形态与边界条件我这次测试用的是华北某区域的数据集80个气象站点时间跨度20年逐日的最低气温、最高气温、降水量、日照时数存成一个站点表CSV。文件结构大概是station_id, lon, lat, date, tmin, tmax, prcp, sunshine。研究区边界的做法是用shapefile圈定我需要把最终栅格控制在边界范围内。原始数据里有一些已知的问题个别站点在2003年夏季缺测了半个月部分站点的降水记录存在连续零值这可能是仪器故障或记录异常另外日照时数的单位是小时而非分钟。这些细节如果不处理后面生成的栅格在模型里会出现数据空洞甚至让模拟直接中断。4.2 目标网格与配置文件设计目标网格我设置为WGS84经纬度坐标系下约1公里分辨率也就是0.01度范围覆盖研究区边界外扩0.1度避免边缘插值产生空心。配置里我特别把降水的插值方法设成了nearest而不是和温度一样用idw。原因后面会细讲简单说就是降水空间连续性差IDW插值会把局部强降水抹平还会在站点周围产生靶心状伪影。温度的idw全称为反距离加权插值距离衰减系数我用的是2.5如果地形起伏较大可以考虑在后续升级里加入海拔协变量但这次测试先不做。日照时数转太阳辐射bestiapop用的是Angstrom-Prescott公式需要额外给出经验系数a、b。如果手头没有本地标定值直接用默认的0.25和0.50也是行业里常用的经验值对大部分区域的月平均辐射精度基本够用。更讲究一点的做法是用附近有实际辐射观测的站去标定这两个系数我在这次测试里用了默认值实测下来月总量和附近站点实测辐射偏差在8%以内。4.3 运行代码与过程管理配置文件准备好以后主体运行代码很简单import bestiapop # 方式一直接用配置文件运行 bestiapop.run(config_fileconfig_demo.yaml) # 方式二在Python脚本中逐步调用便于调试 from bestiapop import ClimateDataPipeline pipeline ClimateDataPipeline(config_demo.yaml) pipeline.load_weather_data(stations.csv) pipeline.set_target_grid_from_bbox( crsEPSG:4326, xmin115.0, ymin35.0, xmax122.0, ymax42.0, resolution0.01 ) pipeline.interpolate() pipeline.aggregate_daily() pipeline.standardize_units() pipeline.generate_outputs()整个流程跑了大约40分钟插值是主要耗时环节。输出目录里自动生成了两个子目录geotiff/下是逐日每个变量的GeoTIFF文件dssat/下是按照目标网格划分的DSSAT天气文件。我选的输出模式是先把栅格全部生成好再由模型逐格点读取。4.4 输出数据验证的三板斧跑完不代表能用我只认三重验证空间完整性检查用Python检查所有输出GeoTIFF的像素范围、NoData占比。正常情况NoData占比应该和边界外面积一致如果边界内部突然出现大块NoData那多半是某个站点的坐标偏移或插值半径设置太小。单格点时间序列对比随机取一个格点和它最近的站点实测序列做对比。温度插值的RMSE一般在1-2摄氏度以内降水因为用的是最近邻几乎和邻近站点一致这符合预期。统计量对比把20年月平均温度和降水的栅格值与研究区历史气候平均值对比如果出现整体偏高或偏低超过10%要先怀疑单位或偏差校正环节是否出错。我当时第一次跑出来发现整体辐射值偏高排查原因后才意识到日照时数的小时/分钟单位没对上换算系数差了6成。这套验证做完以后生成的数据才敢往下游模型里送。5. 踩坑清单栅格化气候数据时容易忽略的五个隐藏问题5.1 降水千万别和温度走同一种插值方案这是整个实践里代价最大的一条坑。第一次跑的时候我把所有变量都设置成IDW插值结果降水栅格看起来正常但输入模型后模拟的旱灾事件全部失真。根本原因是降水的空间分布不是光滑场邻近站点可能一个下了暴雨一个滴雨未下IDW会把这种强梯度抹成一片中等强度降水导致面平均降水被系统性拉低极端日也被平滑成普通雨天。对降水的处理我建议按目的分两种做区域面平均分析可以直接用泰森多边形或者最近邻做逐日格点驱动优先使用气象卫星融合降水产品比如MSWEP、CHIRPS这类而不是自己插值。宽度超过千米的目标网格对降水做IDW基本都会遇到这个问题这是物理性质决定的不是调参就能解决的。5.2 缺测数据的处理策略要分变量对待气温缺测可以用临近站点插补也可以用栅格附近的时空邻域填补但降水缺测最好不要盲目填充。我当时遇到2003年一个站点连续缺测半个月如果直接把空白格点用邻近站平均值填上那半个月的模拟结果就带上了人为制造的平稳降水这在作物模型里会直接改变土壤水分演算过程。我的做法是两条腿走路温度类变量用时空插补并打上标记降水类变量如果单日缺测用周围站点的平均值但要写入数据日志如果连续缺测超过5天直接把该格点标记为无效或者从当次模拟窗口里剔除不强行填数。5.3 时间基准是UTC还是当地时间影响比想象中大再分析数据和站点观测的时间基准经常不一致。ERA5默认是UTC而气象站的日值通常按当地时间24小时统计有些只记录到当天北京时间20时。如果直接混用日降水量会被切到错误的日期上。作物模型对日期标记极其敏感出一天错后面整条模拟序列全废。我处理的这批数据在夏季会出现午后强降水如果把UTC当日值当成当地日值相当于把当天的降水往前或往后挪了几个小时叠加到日尺度上日最大降水量和降水频率都会偏差。我的做法是在数据读取阶段统一声明时间基准转成当地标准时后再做日聚合并且在输出前随机抽10天人工核对原始站点的24小时累积值和栅格值的对应关系。5.4 坐标系混用导致的距离失真站点坐标是WGS84经纬度目标网格如果直接用经纬度网格算距离在高纬度地区会严重失真。比如在华北经度方向每一度的实际距离随着纬度和海拔变化不大但在东北或更高纬度一度经度的距离比一度纬度短很多。如果还对站点距离做无差别计算插值权重就错了。我后来在配置里统一把插值使用的坐标系转成适合当地的分带投影或等距投影插值完成后再转回WGS84输出。虽然多了一步转换但插值精度提升非常明显。尤其是用克里金或者IDW时距离计算必须保证各向同性的物理意义。5.5 性能优化时间与内存的平衡20年、每天输出多层栅格数据量是很可观的。我第一次直接全量跑内存峰值接近20GB服务器直接卡死。后来用了两个办法解决一是按时间分块先处理一年的数据验证当前年输出没问题后再批量推进这样内存消耗被压到可控范围。二是对插值区域做掩膜只对研究区边界内的格点计算边界外一律留作NoData既省时间又减小了文件体积。如果你的站点数量特别大比如超过500个建议把插值半径设置成动态值而不是固定搜索半径这样既能保证空间覆盖又不至于离站点太远的格点去插值出不可信的值。从我个人这几次实操来看用bestiapop做栅格化气候数据的价值不只是省时间更在于把原来五花八门的处理流程收敛成一套可复现、可验证的标准化步骤。数据换了一茬、站点重新布了一轮、模型从DSSAT换到APSIM配置文件调整一下基本就能接着用。最后再分享两个小习惯每次跑完先保留一份配置文件副本以日期命名方便回溯第一次接触这个包或者换数据集时别急着全量跑先用一年的子集试运行一遍核对所有输出格式和统计量再放开全量。这套工作流跑顺以后你就能把更多的精力放到作物模型的参数标定和结果分析上而不是浪费在数据格式又对不上的反复折腾里。本文还有配套的精品资源点击获取