基于ERA5与Atlite的全国风光出力因子计算:30公里网格逐小时序列
简介基于ERA5历史气象再分析数据与Atlite库构建的中国2020年全域风电与光伏发电出力因子时间序列计算模型资源包面向新能源发电预测、电力系统规划与碳中和政策评估等研究场景适合能源领域研究人员、电网调度人员及可再生能源方向学生使用。模型覆盖全国范围实现约30公里网格精度的小时级风电与光伏标准化出力计算能够为电力系统灵活调度和极端天气应对提供数据支撑。资源共26个文件主体为17个MATLAB脚本与2个Python脚本分别负责数据加载、气象处理、风光出力计算、储能配置及结果绘图等环节另含2个csv配置数据、环境依赖yaml、说明文档及附赠资料压缩包仅1.82MB结构清晰便于二次开发。已有64人学习下载该资源模型方法论完整有助于快速掌握从ERA5气象数据到标准化出力因子的全链路建模思路。1. 用 ERA5 和 Atlite 复算全国风光出力因子把 30 公里网格的小时级时间序列当作可再生资源评估的地基做全国性的新能源资源评估时最缺的不是装机统计而是一套干净、连续、口径统一的逐小时出力时间序列。这份资源做的就是这件事基于 ERA5 历史气象再分析数据用 Atlite 库把中国全域切成约 30 公里精度的网格输出 2020 年全年逐小时的风电、光伏标准化出力因子——也就是容量因子单位装机的实际发电水平。适合三种人做省级消纳评估的规划工程师、给电力市场或电网节点做时序分析的研究生、以及想拿小时级风光序列去训练 LSTM 等时间序列预测模型但不想自己从气象数据起步的人。它的核心价值不是某个算法有多新而是全国统一气象源、统一转换模型、统一网格口径省掉你到处拼数据、对不齐坐标系的功夫。2. 为什么是 ERA5 Atlite从再分析数据到出力因子的四层转换逻辑2.1 ERA5 的时空特性0.25° 网格、1 小时步长与可用变量的取舍ERA5 是 ECMWF 的第五代全球再分析产品空间分辨率大约 0.25°×0.25°在中纬度地区对应地面上的格距大约是 28 km 乘 31 km这就是标题里说的“约 30 公里网格精度”的来历。时间上提供逐小时数据正好满足风电、光伏出力因子的小时级时间序列需求。2020 年是数据质量比较稳的一年也是目前很多省级规划还在用的基准年份选它做全口径序列比较合适。实际操作中需要关注的不是分辨率高不高而是变量跟发电物理过程的对应关系。风力发电看近地面风场Atlite 主要用到 100 m 高度的风分量光伏发电看地表太阳辐射核心是水平面总辐射SSRD和直接辐射、散射辐射的拆分。顺带还要温度、地表气压用于空气密度修正和光伏温度系数修正。下面这张表是我拿到一份 ERA5 数据后第一件事就要核对的内容变量物理含义在出力因子模型里的用途u100 / v100100 m 高度纬向/经向风速分量合成风速输入风机功率曲线u10 / v1010 m 高度风速分量备选风速源或用于检验外推结果t2m2 m 温度光伏组件温度系数修正sp地表气压空气密度修正影响风功率输出ssrd向下短波辐射通量光伏水平面总辐射 GHIfdir向下直接辐射通量直射分量用于倾斜面辐射转换有一个常见误区ERA5 的 ssrd 是累计量表示的是上一个输出时刻到当前时刻的累计辐射量直接拿来当瞬时辐射通量算数值会大得离谱。Atlite 内部会把时间步长换算成瓦每平方米但如果你自己用 xarray 读原始 nc 文件拼模型这里就埋着一个很大的坑。2.2 Atlite 干了什么从 Cutout 到出力因子的四层转换管线Atlite 是丹麦一个可再生能源研究组开源的库它的定位不是给你一堆气象数据而是把气象数据直接转成“能发电多少”的工程结果。它内部有一套完整的转换管线我把它拆成四层理解第一层是数据层Atlite 用 Cutout 对象统一管理空间范围、时间范围和气象变量。你给它一个经纬度范围和时间起点它按需从 CDS 拉取 ERA5 数据然后缓存成本地 netCDF。第二层是插值层当你计算某个具体风电场或光伏电站时Atlite 在 Cutout 的网格上做双线性插值把格点气象值插到目标点上。第三层是物理转换层风用功率曲线映射风速光伏用辐射模型把 GHI 拆成倾斜面上的分量再考虑温度系数。第四层是聚合层把单点结果合并成区域序列输出标准化出力。用代码表达这四层管线大致长这样import atlite import xarray as xr # 第一层定义 Cutout范围覆盖中国全域 cutout atlite.Cutout( namechina_2020, moduleera5, xs[72, 136], # 经度范围从东经72度到136度 ys[17, 54], # 纬度范围从北纬17度到54度 time2020-01-01, # 数据起始时间 dt1h, # 时间步长1小时 ) # 第二层准备风与太阳辐射相关的 ERA5 变量 cutout.prepare(features[wind, solar]) # 第三、四层计算每格点的标准化出力 wind_cf cutout.wind( turbineNREL-5MW, # 用内置功率曲线 smoothTrue, # 平滑功率曲线避免风速-功率突变 add_wind_speedTrue, # 额外保存合成风速便于排查 ) solar_cf cutout.solar( capacity1.0, # 归一化容量使输出直接是容量因子 surface_tilt30, # 光伏组件倾角 surface_azimuth180, # 朝向正南 )这里xs和ys是 Cutout 的边界坐标Atlite 会按 ERA5 原生 0.25° 分辨率生成网格不需要手动重采样重采样反而会引入不必要的平滑误差。dt1h对应标题里的小时级时间序列time写起始日Atlite 在计算具体区域时按需扩展时间范围。smoothTrue这个参数值得解释一句。真实风机的功率曲线在额定风速附近有陡变如果直接用原始曲线风速小误差会被放大成出力的大跳变。平滑曲线相当于对原始曲线做卷积滤波让序列更接近真实场站出力对后续做时间序列分析和 LSTM 预测更友好。我一般会保持开启除非你要复现某个特定机型在低风速段的确切切入行为。2.3 为什么不用 MERRA-2 或者气象站插值国内做这类计算的人常在这几个数据源之间选MERRA-2、气象站观测插值、ERA5。MERRA-2 空间分辨率约 0.5°×0.625°比 ERA5 粗糙风速场在复杂地形区域的代表性偏差更大气象站插值的优点是贴近真实测点但站点分布不均西部和青藏高原几乎没有像样的测风塔插值出来的空间场很难用于“全域网格化”分析。ERA5 在中纬度的综合表现是三个选项里最稳的而且 Atlite 对它的变量做了适配下载、读取、转换都是现成的路径省掉大量自己啃 GRIB 格式的时间。3. 复现前的准备工作边界设定、文件结构与 zip 包检查3.1 30 公里网格的边界设定中国全域的经纬度范围怎么取拿到资源后第一步不是跑代码而是先确认 Cutout 的空间边界。标题写了“中国全域”但全域怎么定义直接决定格点总数和计算耗时。按 ERA5 原生 0.25° 网格计算东经 72 度到 136 度大约是 257 个格点北纬 17 度到 54 度大约是 149 个格点网格总数接近 38000 个。每个格点有全年 8760 小时的数据风电和光伏各一套容量因子序列这个数据量对内存和时间都是实打实的压力。我一般建议把边界收在目标分析区域内而不是笼统地“全中国”。如果你只需要省级结果先在代码里把 Cutout 的经纬度范围收窄再叠加省份边界做掩膜这样内存占用和数据读写量都会明显下降。判断面积的方法很直接输出wind_cf.shape看维度是不是(time, lat, lon)如果 lat 和 lon 都上百就要考虑按大区拆开跑。3.2 zip 包检查与目录规划先别急着解压看一眼加密位资源是以 zip 压缩包形式分发的很多人一上来直接双击解压解到一半报“文件损坏”就慌。实际上相当一部分 zip 包打不开不是损坏而是压缩时用了伪加密标记。伪加密的包用 Windows 自带解压工具会提示输入密码但你用 7-Zip 或命令行工具查看时加密位和真实加密文件并不一致。遇到这种问题先执行下面的命令看文件头信息# 列出 zip 包内容不急于解压 unzip -l China_2020_ERA5_Atlite.zip # 查看每个条目的压缩方式与加密标志 zipinfo -v China_2020_ERA5_Atlite.zip | head -60 # 如果怀疑伪加密用 7z 直接测试能否读取 7z l China_2020_ERA5_Atlite.zipunzip -l能看到包内文件清单哪些是 nc 数据文件、哪些是脚本、哪些是说明文档一目了然。zipinfo -v输出里重点看每一条的“file security status”如果显示encrypted但整包又没有真实密码说明极可能是伪加密。7-Zip 对这类容错做得比系统自带工具好优先用它做解压和测试。解压之后我习惯按下面的目录结构组织避免后续所有脚本里的相对路径都乱掉mkdir -p china_era5/{scripts,data/raw,data/processed,output/netcdf,output/csv,logs}scripts放转换和计算脚本data/raw放从 zip 解出来的原始 Cutout 缓存data/processed放中间变量output/netcdf和output/csv放最终结果。这样做的直接好处是当你想把某一年的结果重新算一遍时不用去翻十几个散落的文件夹。3.3 从 ERA5 原始数据重算时的 CDS API 配置如果地资源只给了结果序列而你想自己重新下载 ERA5 原始数据那么需要配置 CDS API。这里有一个易错点CDS 的下载链接在 2023 年后切换了新端点旧代码里https://cds.climate.copernicus.eu/api/v2的地址已经不能直接用需要改到新的 API 网关。配置文件的写法是import cdsapi # 新版 CDS API 使用 .cdsapirc 中保存的 url/key # url 应为 https://cds.climate.copernicus.eu/api # key 格式为 {uid}:{api_key} client cdsapi.Client() client.retrieve( reanalysis-era5-single-levels, { product_type: reanalysis, variable: [ 100m_u_component_of_wind, 100m_v_component_of_wind, 10m_u_component_of_wind, 10m_v_component_of_wind, 2m_temperature, surface_pressure, surface_solar_radiation_downwards, total_sky_direct_solar_radiation_at_surface, ], year: 2020, month: [f{m:02d} for m in range(1, 13)], day: [f{d:02d} for d in range(1, 32)], time: [f{h:02d}:00 for h in range(24)], area: [54, 72, 17, 136], # [北纬, 西经, 南纬, 东经] format: netcdf, }, era5_2020_china.nc, )area参数的顺序是北大西洋公约组织式的坑先北纬封顶、再西经边界、再南纬、再东经写反了会得到一块完全不对的数据区域。变量清单里100 m 风是风电计算的主力10 m 风主要用来做交叉验证total_sky_direct_solar_radiation_at_surface是光伏直射分量的来源。逐月下载再合并比一次性提交全年任务更不容易被服务端拒绝。4. 计算风电与光伏标准化出力参数设置与分模块实现4.1 风电出力因子功率曲线、空气密度与平滑开关风电标准化出力的本质是把风速序列映射到 0 到 1 的容量因子区间。映射的核心是功率曲线这条曲线描述“风速多少时风机输出占额定容量的比例”。典型的大型陆上风机切入风速约 3 m/s额定风速约 10 到 12 m/s切出风速 25 m/s。低于切入风速出力为 0高于切出风速也强制为 0避免叶片超速保护场景被误算成满发。Atlite 内置了多种风机的功率曲线比如 NREL 5MW 这类被科研界用滥了的机型。用它做全国范围评估有一个好处结果不绑定某个特定厂家的商业机型别人复现你的结果时不需要猜功率曲线参数。如果资源包的脚本里允许自定义曲线通常的做法是读入一个两列的 CSV第一列是风速第二列是对应出力比import pandas as pd import numpy as np # 自定义功率曲线示例两列分别为风速(m/s)与出力系数 pc pd.read_csv(power_curve.csv) wind_speed pc[wind_speed].values power_coeff pc[power_coeff].values def apply_power_curve(v: np.ndarray, ws: np.ndarray, pc_val: np.ndarray) - np.ndarray: # np.interp 做线性插值低于最小风速按 0 处理 cf np.interp(v, ws, pc_val, left0.0, right0.0) # 风速超过切出风速时强制置零 cf[v ws[-1]] 0.0 return cf # 示例把 100m 风速序列转成容量因子 v100 wind_cf[wind_speed].values capacity_factor apply_power_curve(v100, wind_speed, power_coeff)np.interp的left和right参数很关键。很多人在风速低于曲线起点时默认填充 0这没错但对高于曲线终点的风速直接填right0.0是物理正确的因为风机已经切出如果填成最后一个点的值就会把超高风速算成满发这属于典型的物理性错误。真实的风功率密度还受空气密度影响高原地区空气稀薄同样风速下出力比平原低几个百分点。严谨的做法是用 ERA5 的地表气压和温度算出当地空气密度再对出力做线性修正# 空气密度修正因子rho / rho_0 # 其中 rho_0 是标准海平面密度 1.225 kg/m^3 pressure era5[sp].values # 单位 Pa temperature era5[t2m].values # 单位 K rho pressure / (287.05 * temperature) correction rho / 1.225 cf_corrected capacity_factor * correction修正幅度在中东部平原大约在 ±3% 以内但在青藏高原区域可以到 20% 以上。如果你拿到的资源脚本里没有做这一步强烈建议自己补上。4.2 光伏出力因子倾角、朝向与散射辐射分量光伏出力因子比风电复杂的地方在于地面气象站测的是水平面辐射而光伏组件是倾斜安装的必须把水平面总辐射拆成直接辐射和散射辐射再分别投影到倾斜面加上地面反射的贡献。Atlite 的solar()方法在内部完成了这一整套转换但结果对两个输入参数极其敏感组件倾角和朝向。# 光伏出力因子计算设定组件安装参数 solar_cf cutout.solar( capacity1.0, surface_tilt30, # 倾角 30 度华东华北常见优化值 surface_azimuth180, # 朝向正南北半球首选 albedo0.2, # 地表反照率裸地与草地混合取值 trackingFalse, # 固定支架不开启跟踪 temperature_modelPVWatts, # 组件温度模型 )surface_azimuth的定义是正南为 180 度正东为 90 度正西为 270 度。国内工商业屋顶常见朝向其实是南偏西 5 到 10 度因为下午电价更高或者午后云量更少这个参数对全年总出力的影响能达到 3% 到 5%。albedo如果设成 0.2双面组件或者高反照率环境雪地、水面下会明显低估背面增益。如果你要评估的是水面光伏或跟踪支架记得把albedo调高、tracking打开。4.3 输出时间序列的字段与归一化口径标准化出力的定义是“实际出力除以装机容量”所以无量纲数值在 0 到 1 之间偶尔因为辐射模型或温度修正出现极其微小的负值或略大于 1 的值需要做截断处理。最终输出建议同时保存 netCDF 和 CSV 两种格式前者保留网格空间信息后者方便直接进 pandas 做时间序列分析和机器学习。常见的输出字段至少应该包含time、lat、lon、wind_capacity_factor、solar_capacity_factor再加一个wind_speed用于排查。把网格数据聚合成省级序列时注意按经纬度匹配省份掩膜后再做面积加权平均直接对所有格点等权平均会在西部大面积无人区上扭曲全省的风光资源特征。5. 复现时最容易翻车的 5 个点现象、原因与解决5.1 现象风电出力因子在低风速时段异常偏高有次我拿到结果后画全年曲线发现很多格点的风电出力在夜间低风速时段居然有 0.4 以上的容量因子明显不对。排查后发现问题出在功率曲线输入的风速高度不一致——脚本里读的是 ERA5 的 10 m 风速但功率曲线对应的是轮毂高度风速。10 m 风速受地表摩擦影响比 100 m 低 20% 到 40%把低风速映射到为 100 m 设计的功率曲线上自然整体偏高。解决方法是严格使用u100/v100合成风速作为输入如果资源里只有 10 m 风至少要按粗糙度做对数风廓线外推而不是直接套功率曲线。5.2 现象光伏出力因子白天整体偏低且午后曲线不对称另一个高频问题是光伏出力白天峰值只有 0.5 左右而不是接近 1。原因通常是 ERA5 的 ssrd 辐射通量在 Atlite 内部被视为“瞬时值”而某些版本在读取时没有正确除以累计时长导致水平面总辐射偏低。判断方法很直接把 6 月晴空日的峰值辐射跟当地理论晴空辐射对比如果太阳高度角最高的中午辐射值连 700 W/m² 都不到那一定是单位换算问题。解决方法是升级 Atlite 版本或者在读取 ssrd 后手动除以 3600 秒再传入辐射模型。午后曲线不对称则大概率是albedo设置过高或组件朝向偏西逐小时对比实测辐射数据就能定位。5.3 现象不同月份拼接处出现数据断裂用逐月下载的 ERA5 数据合并成全年的 Cutout 时1 月 31 日 23 时和 2 月 1 日 0 时之间偶尔会出现一个巨大的出力跳变曲线在月初直接断崖。排查后通常是两个原因一是concat时没有按时间坐标去重两个月份文件重叠了一个时次二是不同月份下载时area参数不一致导致网格错位。解决的笨办法是合并后执行一次严格的时间对齐import xarray as xr ds xr.open_mfdataset(era5_2020_*.nc, combineby_coords) # 去除重复时间戳确保全年 8760 个小时次 ds ds.sortby(time) _, unique_idx np.unique(ds[time], return_indexTrue) ds ds.isel(timeunique_idx)open_mfdataset默认combineby_coords会沿着时间维度合并但不会主动去重。np.unique配合return_index是过滤重叠时间的稳妥做法。合并后检查len(ds[time])是否严格等于 87842020 年是闰年或 8760不相等就说明中间还有时次缺失。5.4 现象内存占用过高导致 Cutout 构建失败一次算全国 38000 个格点、全年 8760 个小时次的风电和光伏各一套序列加上中间变量内存轻松冲破 16 GB。很多人遇到MemoryError以为是无解的问题其实关键在 Cutout 构建时没有用 dask 分块。Atlite 底层依赖 xarray只要你准备数据和计算时保持chunks参数不为空它就能自动按块流式计算# 打开 Cutout 缓存时显式指定 chunks按时间维分块 ds xr.open_dataset(china_cutout.nc, chunks{time: 720, lat: 40, lon: 40})chunks{time: 720}表示每次只加载 720 个小时次进内存相当于 30 天的数据其余部分按需读取。实际操作中加上分块后16 GB 内存跑全国数据是可行的但不要同时打开风电和光伏两组中间变量算完一个立即del释放。5.5 现象zip 内的 netCDF 文件无法打开提示文件头错误解压后如果用xr.open_dataset()打开 nc 文件报not a valid NetCDF file不要急着怀疑数据损坏。先看文件前缀# 识别文件真实格式 file era5_2020_china.nc # 正常输出应包含 NetCDF 字样如果不是多半是压缩格式被改过扩展名常见情况是 zip 包内文件被压缩软件改动过扩展名或者伪加密导致解压出来的是一个不完整的数据流。用file命令能快速看真实格式。如果提示是gzip compressed data那么直接把扩展名改成.gz解压一次再用如果提示data且无特征那就是文件损坏重新解压并优先用 7-Zip 的“测试”功能确认完整性。6. 拿到结果后的验证与降尺度把 30 公里网格数据用出业务价值6.1 先画几条曲线用物理直觉给数据把关计算完成后先别急着做统计或进模型先随机挑几个格点画全年逐小时容量因子曲线。风电的曲线应该是平滑波动的夜间不一定比白天高但春冬总体强于夏秋——全国大多数区域近地面风场就是这种气候模态。光伏曲线一定是白天为 0、中午单峰、夜间恒为 0 的形状。如果光伏曲线在凌晨出现非零值或者风电曲线出现密集的 0 和 1 跳变直接回第 5 章排查。import matplotlib.pyplot as plt # 抽查东经 115、北纬 35 附近格点的风电与光伏容量因子 sel wind_cf.sel(lat35, lon115, methodnearest) sel_solar solar_cf.sel(lat35, lon115, methodnearest) fig, ax plt.subplots(2, 1, figsize(12, 6), sharexTrue) ax[0].plot(sel[time], sel, linewidth0.5) ax[1].plot(sel_solar[time], sel_solar, linewidth0.5, colororange) plt.show()抽查点要选在气象特征差异大的地方比如一个华北平原格点、一个青藏高原格点。这一步的目的不是算精度而是排除单位错误和物理性 bug——这两类问题画图一眼就能看出来。6.2 三个落地去向对标实测、乘装机、喂给时间序列模型验证过后标准化出力最实用的三个去向分别是乘以某地区实际装机容量得到理论出力曲线用于跟电网调度记录做相关性分析直接作为省级风电、光伏的基准序列与统调出力做对比评估消纳空间或者经过差分和归一化后喂给 LSTM 这类时间序列预测模型。很多拿这份资源去训练模型的人会问“要不要先把容量因子平滑一下”我的建议是保留原始小时级波动因为尖峰和骤降恰恰是电网调度最关心的变化特征模型应该学着预测它们而不是被预处理消掉。从那以后我每次换一个年份或者换一个地理区域都会强制走一遍“画曲线、查变量单位、验证边界”这个流程再往下做量化分析。希望帮到你。本文还有配套的精品资源点击获取