简介:一套基于ERA5历史气象再分析数据与Atlite开源库的中国2020年全域高时空分辨率风电与光伏发电出力因子时间序列计算模型,覆盖全国约30公里网格精度的小时级标准化出力。模型面向电力系统规划、新能源发电预测、项目选址和碳排放交易等场景,可将风速、辐射等气象要素高效转换为风光出力因子,为电网调度与碳中和评估提供量化依据。资源包共26个文件,以17个Matlab脚本为核心,另含Python脚本、CSV数据、YAML环境配置及Markdown/Word说明文档,压缩包约1.82MB,结构清晰。目前已有65人学习下载,适合新能源功率预测、能源系统仿真和政策分析研究者使用,并附赠说明文档,可直接运行验证,也便于二次开发不同区域风光时序与储能配置分析。
1. 拿到 ERA5 和 Atlite,先搞懂这条时间序列到底在解决什么
做新能源消纳、电网规划或者风光资源评估的从业者,大概率都经历过这种尴尬:想算某地某年逐小时的风电、光伏出力,打开气象站数据一看,要么站点稀疏,要么时间断裂,要么只有日均值。中国那么大,靠实测站点拼不出一张全国同时刻的风光出力图。这就是 ERA5 历史气象再分析数据与 Atlite 库结合的价值所在——用再分析气象场驱动物理模型,把全国约 30 公里网格上的小时级风电与光伏标准化出力因子算出来,而且不受站点限制、时间完全连续。这篇笔记就围绕标题里的 2020 年全国模型,把数据准备、Atlite 建模、参数设置和典型坑位一次讲透,读完你可以自己复现一套,不只停留在下载别人算好的结果。
这套方案最合适的读者是三类人:做省级或大区级新能源消纳分析的电力工程师,需要把风光出力曲线喂进生产模拟程序的人,以及做资源评估但不想买商业数据库的初创团队。它不解决微观选址那种百米级精度问题,但做宏观趋势、year-round 出力特性、多区域对比,绰绰有余。
2. ERA5 与 Atlite 的选型逻辑:为什么这对组合算全国出力最稳
2.1 为什么选 ERA5 而不选实测气象站或其它再分析数据
实测气象站数据的问题很简单:中国气象站虽多,但分布极不均匀,东部密集西部稀疏,而且很多站不发布完整的逐小时风速、辐射记录。你要做全国 30 公里网格的连续时间序列,实测数据根本撑不起来。ERA5 是全球再分析产品,把观测数据同化进数值模式,输出规则网格,水平分辨率 0.25 度,正好对应约 30 公里。它提供 1940 年至今的逐小时数据,2020 年整年自然包含在内,而且变量齐全,风速、风向、温度、气压、短波辐射、长波辐射都有。风功率密度和光伏板输出功率模拟需要的物理量,ERA5 全能给出。
其它再分析产品也不是不能用,但各有取舍。有的产品时间分辨率到小时但空间分辨率偏粗,做省级边界会吃亏;有的空间分辨率更高但数据滞后严重,2020 年的数据可能要等很久。ERA5 在空间分辨率、时间连续性、变量完整度和数据获取便利性之间,平衡得最好。我自己的经验是:凡是做全国尺度的风光出力时间序列,先跑 ERA5,跑不通再考虑替代品。
2.2 Atlite 库在出力因子计算里的角色
Atlite 是一个基于 Python 的可再生能源潜力评估库,核心价值在于把"气象数据"转成"电力出力",中间的风机功率曲线、光伏板倾角、温度影响系数、空气密度修正这些物理过程,它全部封装好了。你不用自己写空气密度修正公式,不用查每种风机的功率曲线,Atlite 内置了一组主流机型曲线,也支持你自定义 wind turbine 的 hub_height、diameter 等参数。
Atlite 处理风光的路径大致分两步:第一步是创建 cutout,本质是把 ERA5 原始数据裁剪到你关注的区域和时间范围,重新组织成 Atlite 内部的数据结构;第二步是构造风力发电场和光伏电站的布局,然后调用 convert_power 方法,把气象场转换成发电出力。这一步的物理内核包括:对风速做轮毂高度换算、考虑空气密度对功率曲线的影响、对辐射做倾角投射、算光伏板温度造成的效率折减。公式不少,但 Atlite 把它们变成了一个配置项。
替代方案是自己写物理公式,比如用风功率密度公式加 IEC 功率曲线逐格计算。不是说不行,但你要处理边界、处理单位、处理时区,一套下来光调 bug 就能耗掉两周。Atlite 在这条路上走得比较稳,社区也有人在持续维护,遇到问题基本能找到讨论记录。对于"2020 年全国逐小时出力因子"这种大规模计算任务,用 Atlite 能省掉大量底层物理细节的调试时间。
2.3 30 公里网格与小时级时间尺度的匹配关系
30 公里网格意味着什么?它不是一个任意选的数,而是 ERA5 本身的固有分辨率。你下载的 ERA5 数据就是 0.25 度经纬度网格,在中国境内大约东西方向 25 到 28 公里、南北方向 30 公里左右,所以标题说"约 30 公里"是对 ERA5 原生网格的直接引用,而不是你后期重采样得出的结果。这里要特别注意:不要自己去做降尺度,30 公里网格的出力因子直接来自 ERA5 原生网格,任何进一步的重采样都会引入虚假的空间平滑效应。标题里的精度描述是数据源自带的,不是模型额外提供的。
小时级时间分辨率同样来自 ERA5 的逐小时输出。2020 年是闰年,全年 8784 个小时,全国网格约 300 行乘以 340 列,算下来有大约 10 万个格点。每个格点要输出 8784 个时次的风电出力因子和光伏出力因子,数据规模不小,但对一台 32 核 128GB 内存的服务器来说完全在可接受范围内。关键是要选对数据存取格式和并行策略,否则会卡在 IO 上。后面章节细讲。
3. 从 CDS 拉取 2020 年全国气象场:最小可行流程与存储规划
3.1 用 Python 请求 ERA5 全年逐小时数据的最小脚本
下载 ERA5 最标准的路径是官方的气候数据存储服务,需要注册账号、拿到 API key,然后通过 Python 的 cdsapi 库发起请求。这里给一个我常用的最小脚本,按"2020 年全年、中国范围、逐小时、两米风速与地表辐射"配置,单个请求不会太大。
import cdsapi client = cdsapi.Client(url="https://cds.climate.copernicus.eu/api", key="你的个人key,格式为UID:APIKEY") client.retrieve( "reanalysis-era5-single-levels", { "product_type": "reanalysis", "variable": [ "10m_u_component_of_wind", "10m_v_component_of_wind", "100m_u_component_of_wind", "100m_v_component_of_wind", "surface_solar_radiation_downwards", "surface_thermal_radiation_downwards", "2m_temperature", ], "year": "2020", "month": ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"], "day": [f"{d:02d}" for d in range(1, 32)], "time": [f"{h:02d}:00" for h in range(0, 24)], "area": [55, 70, 15, 140], # 北纬55到15,东经70到140 "data_format": "netcdf", "download_format": "unarchived", }, "era5_china_2020.nc", )这段脚本的要点:variable 列表里前十米和一百米的风分量都要,因为 Atlite 计算轮毂高度风速时默认从十米风廓线外推,但如果有百米的实测层,外推会更准,你可以在后面建模时二选一。短波辐射字段 "surface_solar_radiation_downwards" 是光伏模拟的核心输入,它累加到小时值,单位是 J/m²,Atlite 内部会换算成辐照度 W/m²。2m 温度用于光伏板温度模型和空气密度修正,不能省。area 参数的顺序是北纬、西经、南纬、东经,也就是 North、West、South、East,这个顺序容易写反,写反之后下载下来的范围会超出预期,而且程序不报错。
3.2 全国网格数据量估算与存储策略
2020 年全年全国范围、7 个变量的逐小时 NetCDF 文件有多大?这里给一个经验估算:单变量逐小时全球数据量大约在 80 到 120GB 每年,中国区域裁剪后大约缩小到十分之一左右。7 个变量全下载的话,预计在 60 到 100GB 之间。听起来不小,但 NetCDF 内部有压缩,实际占用通常更低。我一般会在下载完成后先用 ncdump 或者 xarray 打开数据集查看维度规模,确认网格数和时次数正确,再进入预处理。
存储规划上有一个容易被忽视的点:不要把原始下载文件和后续处理文件放在同一块磁盘上。Atlite 创建 cutout 时会把数据按时间切片重组,写临时文件和中间结果,如果磁盘空间只剩二三十 GB,很容易在跑到一半时撑爆。我通常给这个任务留出原始数据一份、cutout 一份、最终输出一份,共三份空间,每份预留 100GB 比较稳妥。如果磁盘紧张,可以每完成一个月的转换就删除对应的原始月度子集。
3.3 把原始 NetCDF 整理成 Atlite 认识的 cutout
ERA5 原始下载文件是一个巨大的 NetCDF,Atlite 不能直接吃,需要用 atlite.Cutout 类来创建 cutout 对象。创建过程中,Atlite 从原始数据里抽取你指定区域和时间的字段,并按内部规范重组。这个步骤看起来只是调用一个构造函数,但底层做了大量时间索引、空间对齐、单位换算的工作,出了问题也最隐蔽。
import atlite cutout = atlite.Cutout( name="china-2020", module="era5", xs=slice(70, 140), # 经度范围,对应东经70到140 ys=slice(15, 55), # 纬度范围,对应北纬15到55 time="2020-01-01", time_idx_start="2020-01-01", time_idx_stop="2021-01-01", ) cutout.prepare()这里要注意两点:第一,module 参数必须明确指定为 era5,Atlite 还有很多其它数据源,比如再分析数据或地形数据,不同数据源的变量映射逻辑完全不同。第二,prepare() 内部会去读取原始文件,如果你的原始 NetCDF 路径和 cutout 定义的 region 不一致,Atlite 会静默裁剪,但如果路径写错,prepare 会直接抛 KeyError,这时要检查变量名是否是 Atlite 期望的 "wind" 或 "temperature" 之类。prepare 成功之后,Atlite 会生成一个独立的 cutout 目录,后续所有建模计算都基于这个目录,不再依赖原始 NetCDF。
4. 调用 Atlite 构建风电场与光伏电站:全国出力因子的核心计算
4.1 布局定义:全国网格上的资源评估与离散化
Atlite 的建模单位不是"一个风电场",而是一个布局,由若干坐标点组成。你可以理解为:每个坐标点代表一个假想的风机或光伏板安装位置。既然是全国 30 公里网格的资源评估,最直接的做法就是把之前 cutout 覆盖到的所有网格点都作为布局点,每个点代表该网格内一个标准化的风力发电机组或光伏电站。这种做法在资源评估上叫全域布点,得到的出力因子直接反映该网格的资源禀赋。
import numpy as np import xarray as xr import atlite cutout = atlite.Cutout("china-2020", module="era5") lon = np.arange(70.125, 139.875, 0.25) # 约340个经度格点 lat = np.arange(54.875, 15.125, -0.25) # 约160个纬度格点 layout = xr.Dataset( {"wind": (("y", "x"), np.ones((len(lat), len(lon)))), "pv": (("y", "x"), np.ones((len(lat), len(lon))))}, coords={"y": lat, "x": lon}, )布局变量的数值不是装机容量,而是权重。全国资源评估场景里所有格点权重取 1,即每个格点代表一个单位容量的标准化机组。如果你只想看某个省,就把该省份边界外的 grid point 权重置为 0,这样 Atlite 算出力时会自动跳过这些点。权重设置的妙处在于,你可以把全国模型和区域模型用同一套代码跑,区别只在于 layout 里的权重分布。
4.2 风电气象变量到功率曲线的换算逻辑
风电场出力计算的核心函数是 convert_power,但真正决定结果质量的,是传给它的 turbine 参数。Atlite 内置了若干常见机型,如果你不指定,它有一个默认配置,但那个默认配置对应的轮毂高度可能和你的假设不一致。用 Vestas V164 这类大型风机和用 2MW 级风机算出来的全国容量因子差异不小,尤其在内陆低风速区。我通常不直接使用默认参数,而是显式指定轮毂高度和功率曲线。
turbine = atlite.WindTurbine( name="Generic-3MW", hub_height=100, # 轮毂高度,单位米 power_curve=[(0.0, 0.0), (2.0, 0.0), (4.0, 0.15), (6.0, 0.4), (8.0, 0.7), (10.0, 0.9), (12.0, 1.0), (25.0, 1.0), (28.0, 0.0)], # 风速m/s对应出力百分比 diameter=110, # 风轮直径,单位米 ) wind_plant = atlite.wind_plant( cutout=cutout, layout=layout["wind"], turbine=turbine, )这段配置的含义先讲清楚:轮毂高度决定风速外推的距离,Atlite 会把 10 米或 100 米的风速按风廓线幂律外推到 100 米高度。功率曲线是风速到出力百分比的映射,休止风速 25 米每秒,切出风速 28 米每秒,这组参数更接近陆地大功率机组的典型水平。diameter 影响单机扫风面积,进而参与风功率密度计算。三个参数互相影响,不能只调一个就下结论。
4.3 光伏面板模型:倾角、方位角与地表辐射的联动
光伏出力的物理过程和风电完全不同,Atlite 里对应的是 solar_panel_plant 逻辑,核心参数是面板朝向、倾角、峰值功率对应的面积,以及温度系数。国内做资源评估时,默认南向、倾角 30 度左右是最常见的设置,但这只适用于固定支架。如果你打算模拟跟踪支架,倾角和方位角都会随时间变化,Atlite 也支持,但计算量会明显提高。
pv_plant = atlite.solar_panel_plant( cutout=cutout, layout=layout["pv"], panel=atlite.SolarPanel( orientation={"slope": 30.0, "azimuth": 180.0}, # 倾角30度,正南 capacity=1.0, # 单位容量,1kWp area=8.0, # 对应面积,平方米 temperature_coefficient=-0.004, # 温度系数,每摄氏度 ), )这里 capacity 设成 1.0 是为了得到归一化出力,也就是标题说的标准化出力因子。area 8 平方米对应一块常见 300W 级别光伏板,但因为你设的是 1kWp 容量,area 在这里主要影响面板温度和散热计算,对标准化的影响很小。temperature_coefficient 是负值,表示温度升高出力下降,这是光伏模拟里最容易漏掉的一项。很多新手直接拿辐射乘效率,算出来的夏季出力明显偏高,就是因为没考虑高温折减。
4.4 批量计算 8784 小时出力因子并输出标准时间序列
方案的核心产出是"每个格点、每个小时的风电和光伏出力因子"。Atlite 在完成上面的 layout 和 plant 定义后,调用 convert_power 并设定 to_plant 参数,把网格数据聚合到布局点。这里有一个输出层面的选择:如果你要的是全国总量曲线,可以按面积或权重加权求和;如果你要的是每个格点的标准化出力因子,就必须关闭空间聚合,直接保存每个网格的序列。
wind_cf = cutout.wind(plant=wind_plant, layout=layout["wind"]) pv_cf = cutout.pv(plant=pv_plant, layout=layout["pv"]) import pandas as pd df = pd.DataFrame( { "wind_cf_flat": wind_cf.values.reshape(-1), # 展开成一维序列 "pv_cf_flat": pv_cf.values.reshape(-1), } ) df.to_parquet("china_2020_cf_factor.parquet")wind_cf 和 pv_cf 都是 xarray DataArray,坐标是时间和格点编号。直接存 NetCDF 也行,但后续做统计分析和可视化时,Parquet 格式读起来更快。注意这里有一个关键提示:cutout.wind 返回的是每个格点每个小时的容量因子,值域范围在 0 到 1 之间,如果出现大于 1 的值,通常是辐射单位换算出了问题,要检查变量单位。小时级序列一定要保留 UTC 时间索引,因为 ERA5 的时间体系是 UTC。如果你要和中国本地时间对齐,不要直接改索引,而是另存一列,否则会和其它数据源合并时错位。
5. 从数据下载到建模完成,最容易翻车的五个坑位
5.1 坑一:CDS 接口下载任务排队时间远超预期
现象:提交下载请求后,进度条长时间停在 0%,甚至两三个小时没有任何反应,很多人以为程序卡死直接终止了。
原因:气候数据存储服务的任务队列是异步的,高峰期排队几个小时很正常,尤其是一次性提交全年多变量的请求。
解决:把大请求拆成 12 个月的小请求,每个月单独提交。虽然总数据量不变,但每个子任务在队列里的权重更低,总体等待时间往往缩短一半以上。另外,下载脚本要做好断点续传设计,失败后从断点继续而不是从头重新下载。
5.2 坑二:NetCDF 时间轴是 UTC,直接转本地时间导致光照曲线整体偏移
现象:算出来的光伏出力因子曲线,在每天正午前后出现尖峰,但尖峰时刻比实际正午提前或延后 8 个小时,看起来像数据错乱。
原因:ERA5 时间戳统一用 UTC,中国在东八区,光伏出力的辐射峰值应该出现在本地时间正午。如果把 UTC 时间轴直接当作本地时间画图,会看到出力峰值出现在 UTC 凌晨 4 点左右,视觉上极度违和。
解决:分析绘图时统一使用北京时间,但内部计算保持 UTC 不变。具体做法是保存时间戳时保持 UTC,画图时把索引先 shift 8 小时再画,或者把本地时间另存成一个维度,不要原地修改原时间索引。这个坑在下游接入调度系统时特别容易踩,因为电网的数据几乎全部用北京时间。
5.3 坑三:地表辐射单位换算错误导致光伏出力因子超过 1
现象:光伏出力因子序列里不定期出现大于 1 的异常值,尤其是在晴朗夏季,高值区全在午前时段。
原因:ERA5 的短波辐射字段单位是 J/m²,代表的是时间段累计值,要转成 W/m² 需要除以时间步长秒数,也就是 3600 秒。如果 Altite 版本没有做自动换算,或者你自定义面板时传入了错误单位的辐射数据,就会导致模拟出的辐照度虚高,出力因子超出物理上限。
解决:数据预处理阶段先用 xarray 统一检查变量单位,凡是带有 "J m**-2" 的变量,统一除以 3600 转成 "W m**-2"。Atlite 不同版本对单位自动换算的支持程度不一致,以你安装版本的官方文档为准。稳妥做法是在 prepare 之前显式做一次单位转换,不要依赖库的默认行为。
5.4 坑四:风电机组功率曲线参数混用,导致低风速区出力被系统性压低
现象:全国平均风电容量因子算出来不到 0.1,明显低于业内经验值,尤其是在内蒙古、甘肃这类风资源区,数值低得不合常理。
原因:盲目使用了 Atlite 默认风机配置。默认机型的切入风速偏高,在低风速区大量时次出力为零;或者轮毂高度偏低,没有体现出高空风速的优势。
解决:显式定义 WindTurbine,把切入风速降到 3 米每秒左右,轮毂高度设到 100 米或更高,休止风速保持 25 米每秒。这三个参数对国内风电资源评估结果影响最大,建议先做一个敏感性测试:同一组数据分别用 80 米、100 米、120 米轮毂高度跑一遍,看全国总量因子变化,确定一个对外解释合理的配置。
5.5 坑五:内存不足导致 convert_power 跑到一半进程崩溃
现象:convert_power 执行到几个月时,内存占用持续攀升,最终进程被系统 OOM 杀掉,前面算好的部分全部丢失。
原因:Atlite 在计算过程中会把多个时间步的气象场同时读入内存做向量化计算,网格数越多、时间范围越长,峰值内存越高。全国 0.25 度网格长时间序列计算,峰值内存很容易突破 64GB。
解决:不要试图一次把全国跑完,按季度或者按月循环,每个子时间段算完立即写盘,然后释放内存。另一种办法是把 layout 按区域分块,比如按东北、华北、西北分 6 块,每块单独计算,最后合并结果。分块策略几乎不影响结果精度,但对内存开销的改善立竿见影。
6. 用容量因子这把尺子,快速验证模型结果到底靠不靠谱
模型算完不是终点,验证才是让结果具备说服力的关键一步。我习惯做的第一件事是统计全国面积加权的年容量因子,风电大约落在 0.12 到 0.25 之间,光伏落在 0.13 到 0.20 之间。这组数值来自行业公开统计数据的经验区间,如果算出来偏离太远,基本可以断定前面某个环节出了问题。注意这是宏观区间验证,不是精度验证,主要用来筛掉系统性错误。
第二件事是抽点对比。选几个网格点,把模拟出力曲线和当地公开的风电场、光伏电站实际运行数据做逐小时相关性分析。相关系数在 0.7 以上就算不错,因为 30 公里网格本身不可能复现单机尺度的湍流波动。相关性太低时优先检查风机参数和面板倾角是否合理,而不是怀疑数据源。还有一种做法是做月均出力曲线对比,看模拟结果的季节性波动和电网实际消纳统计是否一致,这个对宏观资源评估很实用。
第三件事是空间分布合理性检查。把全年平均出力因子画成全国地图,风力资源高值区应该分布在高原、西北和沿海地带,光伏高值区应该分布在青藏高原和西北地区,如果看到异常的高值条带或者块状突变,通常是边界裁剪或权重设置出了问题。这一步虽然简单,却能抓住变量单位错、经纬度顺序反这类隐藏很深的 bug。
我自己的教训是:第一次跑完全国 2020 年序列,发现西北光伏出力因子高到离谱,排查了半天,最后发现是 layout 权重里经纬度坐标写反了,网格点全部错位。从那以后,我每次建模之前都会先画一张全年平均的分布图,扫一眼形态再继续往下走。这套流程走下来,基本能和标题里的方案对齐,希望帮到你。
本文还有配套的精品资源,点击获取