Tech/ 快速下载逐日ERA5数据

Published:

ERA5数据通常在CDS平台进行下载,可以直接获取monthly和hourly两种数据,如果需要daily数据,需要从hourly数据再次计算。我之前参考这个链接,利用CDS平台的workflow功能直接下载daily数据,可以节省存储空间和时间。然而,近日(9月26日)CDS平台进行了一次迁移升级,升级版本和账号是小事,但关键在于workflow功能不再可用,这意味着必须先下载hourly数据再计算daily。此外,下载数据的排队时间也显著变长:尝试在新系统仅下载一个hour的全球单层变量,就排队了两个小时。

经过朋友的启发,想到一个邪道方法是从weatherbench下载ERA5数据。weatherbench是用来训练数据驱动天气预报模型的基准数据集,目前已经出到第二版,这是他们的文档。可以看到,他们的数据也是在CDS下载后整理的,时间范围是1959-2023,时间频次为6h,空间分辨率为0.25˚,提供13个气压层和37个气压层两种版本的数据。具体的变量可以点开文档中的 Data variables查看,共有62个变量。

数据可以通过 xarray库在线access,为了访问Google Cloud bucket,还需要额外安装两个库:gcsfsfsspec

配置好环境后,我们可以直接访问这个数据集,首先access到整个数据集:

import xarray as xr
ds = xr.open_zarr('gs://weatherbench2/datasets/era5/1959-2023_01_10-wb13-6h-1440x721_with_derived_variables.zarr')

这里实际上只下载了元数据的信息,没有下载任何数据,所以不会花费多少时间。如果访问某个具体的slice,例如:

ds_sel = ds.sel(level=500, time='2010-01-01').isel(time=0).geopotential.plot()

才会下载请求的数据。获取的 xr.DataArray对象可以直接用 to_netcdf等函数保存为文件。

了解基本原理后,我们就可以批量下载数据啦!提供完整的下载脚本以供参考,应该还可以用多线程优化一下。

(另注意降水下载的是mm/6hr,平均到daily后需要转换一下单位)

import os.path
import xarray as xr
import numpy as np
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta

ds = xr.open_zarr('gs://weatherbench2/datasets/era5/1959-2023_01_10-wb13-6h-1440x721_with_derived_variables.zarr')

years = np.arange(2000, 2023, 1)
months = np.arange(1, 13, 1)
  
wb_var_name = {
    "t2m"   : "2m_temperature",
    "msl"   : "mean_sea_level_pressure",
    "tp"    : "total_precipitation_6hr",
    "z"     : "geopotential",
    "u"     : "u_component_of_wind",
    "v"     : "v_component_of_wind",
    "q"     : "specific_humidity",
    "sp"    : "surface_pressure",
    "ts"    : "skin_temperature",
    "t"     : "temperature",
}
level_required = {
    "t2m"   : 0,
    "msl"   : 0,
    "tp"    : 0,
    "z"     : [200, 500, 700, 850],
    "u"     : [200, 500, 700, 850],
    "v"     : [500, 700, 850],
    "q"     : [500, 700, 850],
    "t"     : [500, 700, 850],
    "sp"    : 0,
    "ts"    : 0,
}

fdir = "/public/ldata/Data/Reanalysis/ERA5.global.0P25"

def download_daily_era5(year, month, var, level):
    if level is None:
        file_name = os.path.join(fdir, var, f"daily.{var}.{year}{month:02d}.nc")
    else:
        file_name = os.path.join(fdir, var, f"daily.{var}{level}.{year}{month:02d}.nc")
    os.makedirs(os.path.join(fdir, var), exist_ok=True)
  
    if os.path.isfile(file_name):
        print(f"{file_name} already downloaded! skip this request.")
        return
    print(f"retrieve year: {year}, month: {month}, var: {var}, level: {level}")
  
    month_start = datetime(year, month, 1)
    month_end = month_start + relativedelta(months=1) - timedelta(days=1)
  
    if level is None:
        ds_sub = ds[wb_var_name[var]].sel(time=slice(month_start, month_end))
        ds_sub.load()
        ds_daily = ds_sub.resample(time='D').mean()
    else:
        ds_sub = ds[wb_var_name[var]].sel(time=slice(month_start, month_end)).sel(level=level)
        ds_sub.load()
        ds_daily = ds_sub.resample(time='D').mean()
    ds_daily.to_netcdf(file_name)
    print(f"Saved to {file_name}")
    del ds_sub, ds_daily

if __name__ == '__main__':
    sfc_vars = ['t2m', 'msl', 'tp', 'sp']
    for var in sfc_vars:
        for year in years:
            for month in months:
                download_daily_era5(year, month, var, None)

    plev_vars = ['z', 'u', 'v', 'q', 't']
    for var in plev_vars:
        levels = level_required[var]
        for year in years:
            for month in months:
                for level in levels:
                    download_daily_era5(year, month, var, level)