批量多线程下载ERA5数据

前言

由于ERA5官网内容的更新,现在网上能找到的很多的ERA5下载的脚本都是适用不了的,因此根据官网最新的接口更新一版

CDSAPI设置

直接参考最新版CDSAPI配置教程 点击蓝色字直接跳转

完整代码

import os
import cdsapi
from calendar import monthrange
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import List, Dict, Union, Generator

class ERA5Downloader:
    def __init__(self, config: Dict):
        self.config = config
        self.max_workers = min(4, os.cpu_count() or 1)  # 最大并行数限制为4
        self.executor = ThreadPoolExecutor(max_workers=self.max_workers)

    def build_request(
        self,
        year: Union[int, List[int]],
        month: Union[int, List[int]],
        day: Union[int, List[int]],
        time: Union[str, List[str]],
        variables: List[str]
    ) -> Dict:
        """构建请求参数字典"""
        request = {
            "product_type": self.config.get("product_type", "reanalysis"),
            "variable": variables,
            "year": str(year) if isinstance(year, int) else [str(y) for y in year],
            "month": f"{month:02d}" if isinstance(month, int) else [f"{m:02d}" for m in month],
            "format": self.config.get("format", "netcdf"),
        }

        # 处理层级参数
        if "pressure-levels" in self.config["dataset"]:
            request["level"] = self.config.get("level", ["1000"])

        # 处理时间参数
        if self.config["temporal_resolution"] == "monthly":
            request["day"] = "01"
            request["time"] = "00:00"
        else:
            request["day"] = str(day) if isinstance(day, int) else [f"{d:02d}" for d in day]
            request["time"] = time if isinstance(time, list) else [time]

        return request

    def generate_dates(self) -> Generator:
        """生成日期范围"""
        start_year = self.config["start_year"]
        end_year = self.config["end_year"]
        
        for year in range(start_year, end_year + 1):
            for month in self.config["months"]:
                if self.config["temporal_resolution"] == "daily":
                    _, num_days = monthrange(year, month)
                    for day in range(1, num_days + 1):
                        yield year, month, day
                else:  # monthly
                    yield year, month, 1

    def generate_filename(self, year: int, month: int, day: int = None) -> str:
        """生成标准化文件名"""
        base = f"{self.config['dataset']}_{year}"
        if self.config["temporal_resolution"] == "daily":
            return f"{base}_{month:02d}_{day:02d}.nc"
        return f"{base}_{month:02d}.nc"

    def download_task(self, request: Dict, filepath: str) -> None:
        """单个下载任务"""
        client = cdsapi.Client()
        try:
            client.retrieve(self.config["dataset"], request, filepath)
            print(f"成功下载: {filepath}")
        except Exception as e:
            print(f"下载失败: {filepath} - {str(e)}")

    def parallel_download(self) -> None:
        """并行下载控制器"""
        futures = []
        os.makedirs(self.config["output_dir"], exist_ok=True)

        for year, month, day in self.generate_dates():
            time_params = (
                [f"{h:02d}:00" for h in range(24)]
                if self.config["temporal_resolution"] == "hourly"
                else "00:00"
            )

            request = self.build_request(
                year=year,
                month=month,
                day=day,
                time=time_params,
                variables=self.config["variables"]
            )

            filename = self.generate_filename(year, month, day)
            output_path = os.path.join(self.config["output_dir"], filename)

            futures.append(
                self.executor.submit(
                    self.download_task,
                    request,
                    output_path
                )
            )

        # 监控进度
        for future in as_completed(futures):
            try:
                future.result()
            except Exception as e:
                print(f"任务异常: {str(e)}")

    def __del__(self):
        self.executor.shutdown(wait=True)

if __name__ == "__main__":
    # 配置示例(根据需求修改)
    CONFIG = {
        "dataset": "reanalysis-era5-single-levels",
        "variables": ["10m_u_component_of_wind", "10m_v_component_of_wind"],
        "start_year": 2022,
        "end_year": 2022,
        "months": [1, 2, 3,4,5,4,7,8,9,10,11, 12],  # 指定需要下载的月份
        "temporal_resolution": "hourly",  # hourly/daily/monthly
        "output_dir": r"D:\work\优快云/",
        "product_type": "reanalysis",
        "format": "netcdf",
        # 压力层数据集需要添加以下参数:
        # "level": ["1000", "850"],
    }

    downloader = ERA5Downloader(CONFIG)
    downloader.parallel_download()

代码解析

"dataset"可以从你下载的官网上直接获取
ERA5 hourly data on single levels from 1940 to present为例在这里插入图片描述
我们可以先在这个网页上进行初步的选择,然后在页面的最下面找到API request在这里面就能看到具体的dataset信息
在这里插入图片描述
"variables":如果大家不知道你下载的变量对应的Variables那么也可以从API Request中直接看到

接下来就是start_year、end_year、months、temporal_resolution、output_dir、format这几个参数:
其中product_typeformat一般情况下不需要修改;
start_yearend_yearmonths就是我们需要的数据的起止年份以及月份信息;
temporal_resolution稍微有一点点绕,这个参数是根据选择的数据库有关,比如本次选择的数据库是ERA5 hourly data on single levels from 1940 to present那么这个参数就是hourly,如果你选择的数据库是ERA5 monthly averaged data on single levels from 1940 to present那么这个参数就是monthly

 "start_year": 2022,
 "end_year": 2022,
 "months": [1, 2, 3,4,5,4,7,8,9,10,11, 12],  # 指定需要下载的月份
 "temporal_resolution": "hourly",  # hourly/daily/monthly
 "output_dir": r"D:\work\优快云/",
 "product_type": "reanalysis",
 "format": "netcdf",
<think>好的,我现在需要帮助用户处理ERA5-LAND的NetCDF4数据,结合中国城市行政边界来计算逐年城市平均气温。首先,我得理清用户的需求和可能的步骤。 用户的目标是处理全球逐月的NetCDF数据,提取中国各城市的年平均气温。ERA5-Land的数据是月度的,空间分辨率约9公里。要按城市行政边界来统计,可能需要用到地理空间处理库,比如geopandas和rasterio或者xarray。但NetCDF的处理通常用xarray,而地理边界可能需要用shapefile,比如城市边界的矢量数据。 首先,我需要确认用户的数据结构。ERA5-Land的NetCDF文件包含时间、纬度、经度,以及温度变量(比如2米气温,变量名可能是't2m')。用户需要读取这些数据,并按城市区域进行裁剪和平均。 接下来,步骤可能包括: 1. 加载NetCDF数据,使用xarray。 2. 加载中国城市的行政边界矢量数据,比如shp文件,用geopandas读取。 3. 对每个城市,逐月提取该区域内的气温数据,然后按年求平均。 4. 最后整合所有城市的逐年平均气温。 需要注意的问题包括:坐标系的一致性,比如ERA5数据通常是WGS84经纬度,而城市边界数据也需要是相同的坐标系。另外,处理时间维度,可能需要将月度数据按年份分组求平均。 还有,处理大文件时内存可能不够,需要分块处理或者逐月处理。比如,逐月读取数据,裁剪到各城市区域,计算月平均,再逐年汇总。 可能的代码流程: - 使用xarray打开NetCDF文件,可能用dask来处理大数据。 - 用geopandas读取城市边界的shp文件,遍历每个城市多边形。 - 对每个时间步(每月),将气温数据裁剪到每个城市区域,计算该区域的平均值。 - 将各月的平均值按年份分组,计算每年的平均气温。 - 保存结果为表格,比如CSV,每行对应城市和年份,列是平均气温。 另外,需要注意单位转换,ERA5的温度单位可能是开尔文,需要转换为摄氏度。 可能遇到的问题: - 矢量数据和栅格数据的空间对齐,比如如何将多边形应用到网格数据上。可以用rioxarray或者区域掩膜的方法。 - 处理大量城市时的效率问题,可能需要优化循环结构。 - 确保时间维度的正确解析,比如将时间变量转换为datetime格式,方便按年分组。 总结步骤: 1. 加载并预处理ERA5-Land数据:读取温度变量,转换单位,处理时间坐标。 2. 加载城市边界数据:确保坐标系一致,可能需要转换到WGS84。 3. 对每个城市和时间点,提取区域内的气温数据并计算平均值。 4. 按年份汇总月数据,得到逐年平均。 5. 输出结果。 现在需要把这些步骤转化为具体的Python代码,并确保库的安装和使用正确。比如使用xarray、geopandas、rioxarray、numpy等库。可能需要安装一些包,比如conda install -c conda-forge rioxarray。 另外,数据裁剪部分可能需要使用clip或mask方法,例如使用rioxarray将栅格数据裁剪到矢量区域。但ERA5数据可能没有投影信息,需要先设置CRS,或者确保矢量数据与栅格的坐标系一致。 举个例子,假设城市边界的shp文件是WGS84坐标,ERA5数据也是经纬度,那么可以直接进行空间操作。对于每个城市的多边形,创建一个掩膜,然后用这个掩膜提取对应的温度数据,计算该区域的平均值。 对于时间处理,用户需要逐月处理,然后将同一年的12个月的数据平均,得到年平均值。或者,如果用户只需要每年的平均,可以将所有月份按年分组,然后取平均。但需要注意数据的完整性,比如每年是否都有12个月的数据。 代码的大致结构可能如下: import xarray as xr import geopandas as gpd import rioxarray from shapely.geometry import mapping # 读取ERA5数据 ds = xr.open_dataset('era5_land_data.nc') t2m = ds['t2m'] # 假设温度变量名是t2m,单位是开尔文 t2m = t2m - 273.15 # 转换为摄氏度 # 读取城市边界 cities = gpd.read_file('china_cities.shp') # 确保ERA5数据有正确的空间坐标信息 t2m.rio.set_spatial_dims(x_dim='longitude', y_dim='latitude', inplace=True) t2m.rio.write_crs("EPSG:4326", inplace=True) # 逐城市处理 results = [] for idx, city in cities.iterrows(): geometry = city.geometry # 裁剪数据到城市区域 clipped = t2m.rio.clip([geometry], cities.crs) # 按时间计算年平均 yearly_avg = clipped.groupby('time.year').mean(dim='time') # 保存结果 for year in yearly_avg.year.values: avg_temp = yearly_avg.sel(year=year).values results.append({ 'city_name': city['name'], # 假设shp文件有城市名字段 'year': year, 'avg_temp': avg_temp }) # 转换为DataFrame并保存 import pandas as pd df = pd.DataFrame(results) df.to_csv('city_yearly_temperature.csv', index=False) 不过,上述代码可能需要调整,特别是关于rio.clip的使用是否正确,以及如何处理多维数据。另外,逐城市处理可能会比较慢,特别是当城市数量多时。可能需要优化,比如一次性处理所有城市,或者并行处理。 另一个方法是使用xarray的groupby功能,结合空间掩膜。例如,为每个城市生成一个掩膜,然后应用到整个数据集上,计算每个时间步的平均。 但生成掩膜可能需要先将矢量多边形转换为与ERA5网格对应的二维布尔数组。这可以通过rasterize方法实现,比如使用rasterio.features.rasterize。 例如: from rasterio import features import numpy as np # 假设已经有一个城市的多边形 city_geom = city.geometry # 创建与ERA5数据对应的坐标网格 lon = ds.longitude.values lat = ds.latitude.values x, y = np.meshgrid(lon, lat) coords = np.vstack((x.flatten(), y.flatten())).T # 将城市多边形转换为掩膜 mask = features.geometry_mask([city_geom], (len(lat), len(lon)), transform=some_transform, invert=True) 这里可能需要知道数据的transform,即从坐标到像素的转换,这可以通过xarray的rio模块获取。例如,使用rioxarray设置transform。 这可能比较复杂,需要更多的步骤。或许使用regionmask库会更方便,它可以处理多边形和xarray数据集的掩膜生成。 例如,使用regionmask创建每个城市的掩膜: import regionmask # 将城市转换为regionmask的Regions对象 regions = regionmask.from_geopandas(cities, names='name') # 生成掩膜,每个城市对应一个数字ID mask = regions.mask(t2m.isel(time=0), lon_name='longitude', lat_name='latitude') # 然后,对每个区域(城市)进行分组,计算平均 # 但regionmask可能需要每个区域单独处理,或者使用groupby 不过,可能需要更多的配置,比如确保坐标名称正确,以及处理可能的投影问题。 总的来说,用户的问题涉及多个步骤,可能需要结合多个库,并且需要注意数据处理的效率和正确性。代码示例可能需要简化,或者分步骤说明,确保用户能够逐步操作。</think>以下是使用Python处理ERA5-LAND数据并计算逐年城市平均气温的步骤详解: --- ### 1. 数据准备 - **ERA5-Land数据**:确保NetCDF文件包含以下变量: - 时间(`time`):逐月时间戳 - 经纬度(`longitude`, `latitude`) - 2米气温(`t2m`,单位为K) - **中国城市边界数据**:需为矢量格式(如Shapefile),包含城市名称字段(如`city_name`)[^1]。 --- ### 2. 核心代码实现 ```python import xarray as xr import geopandas as gpd import pandas as pd import numpy as np from shapely.geometry import mapping # 读取ERA5-Land数据 ds = xr.open_dataset("ERA5_land_monthly_t2m.nc") t2m = ds["t2m"] - 273.15 # 转换为摄氏度 # 读取城市边界数据 cities = gpd.read_file("china_cities.shp").to_crs("EPSG:4326") # 确保坐标系为WGS84 # 按城市逐年计算平均气温 results = [] for _, city in cities.iterrows(): # 裁剪数据到城市范围 clipped = t2m.rio.clip([city.geometry], cities.crs) # 按年分组计算均值 yearly_avg = clipped.groupby("time.year").mean(dim=["time", "latitude", "longitude"]) # 记录结果 for year in yearly_avg.year.values: results.append({ "city": city["city_name"], "year": int(year), "avg_temp": float(yearly_avg.sel(year=year)) }) # 保存为CSV pd.DataFrame(results).to_csv("city_yearly_temperature.csv", index=False) ``` --- ### 3. 关键步骤说明 1. **数据读取与预处理**: - 使用`xarray`加载NetCDF文件,提取温度变量并转换为摄氏度。 - 使用`geopandas`加载城市矢量数据,确保坐标系与ERA5一致(WGS84)。 2. **空间裁剪**: - 通过`rioxarray`的`clip`方法,将气温数据裁剪至每个城市的行政边界范围。 3. **时间维度聚合**: - 用`groupby("time.year")`对月度数据按年分组,计算年平均气温。 4. **结果输出**: - 将结果保存为CSV文件,包含城市名称、年份和平均气温三列。 --- ### 4. 注意事项 - **内存优化**:若数据量过大,使用`xr.open_mfdataset`或分块处理(`chunks={'time': 12}`)。 - **缺失值处理**:通过`dropna()`或插值方法处理无效值。 - **单位验证**:确认ERA5温度变量单位是否为K(默认需转换)。 ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心没有菜

你若晌饭便是义父!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值