본문 바로가기
수치모델 관련/COAWST

Python API 기반 ERA5 데이터 다운로드 하기(@CDS) (1/2)

by 광원다이노스 2025. 1. 2.

1. 개요

ERA5는 유럽중기예보센터(ECMWF)에서 제공하는 전 지구 대기 재분석 자료이다. 이 데이터는 Climate Data Store (CDS)를 통해 제공되며, CDS API를 이용해 손쉽게 다운로드할 수 있다. 본 포스팅에서는 Python cdsapi을 통하여 WRF 지역 모델링을 위한 ERA5 데이터를 다운로드하는 방법을 소개한다.

 

2. 준비사항

2.1 필요한 라이브러리

import cdsapi
import xarray as xr
from datetime import datetime, timedelta
import os, glob, importlib

 

2.2 CDS API 설정

 

3. Python 코드 설명

3.1 다운로드 영역 설정

전 지구 데이터인 ERA5를 통으로 다운로드할 수도 있지만, 용량이 상당히 크므로 지리적 범위에 대한 subset을 잘라내서 다운로드 하는 방법을 권장한다. 아래 두가지 방식이 활용 가능하다.

 

방법 1: 지역 모델 도메인 활용

# 모델 도메인 파일에서 격자 정보 읽기
dst_grd = xr.open_dataset(self.fname_roms)

# 경계에서 10도 여유 공간 확보
sponge = 10  # in degrees
lonW = dst_grd.lon_rho.min().values - sponge
lonE = dst_grd.lon_rho.max().values + sponge
latS = dst_grd.lat_rho.min().values - sponge
latN = dst_grd.lat_rho.max().values + sponge

# CDS API 형식으로 영역 정의 [북위, 서경, 남위, 동경]
sub_region = [ latN, lonW, latS, lonE ]

 

방법 2: 직접 위경도 입력

도메인 파일이 없는 경우, 원하는 영역의 위경도 값을 직접 입력할 수 있다.

# 예시: 한반도 영역
sub_region = [ 43, 124, 32, 132 ]  # [북위, 서경, 남위, 동경]

주의사항:

  • 위경도 값은 실수형으로 입력
  • 위도: -90 ~ 90, 경도: -180 ~ 180
  • 순서는 반드시 [북위, 서경, 남위, 동경] 준수

 

3.2 데이터 변수 설정

단일 고도 변수

single_variables = [
    '10m_u_component_of_wind', '10m_v_component_of_wind', '2m_dewpoint_temperature',
    '2m_temperature', 'forecast_albedo', 'forecast_logarithm_of_surface_roughness_for_heat',
    'high_cloud_cover', 'land_sea_mask', 'low_cloud_cover',
    'mean_sea_level_pressure', 'medium_cloud_cover', 'sea_ice_cover',
    'sea_surface_temperature', 'skin_temperature', 'snow_albedo',
    'snow_depth', 'snowfall', 'soil_temperature_level_1',
    'soil_temperature_level_2', 'soil_temperature_level_3', 'soil_temperature_level_4',
    'soil_type', 'surface_pressure', 'total_cloud_cover',
    'total_column_water', 'total_column_water_vapour', 'total_precipitation',
    'volumetric_soil_water_layer_1', 'volumetric_soil_water_layer_2', 'volumetric_soil_water_layer_3',
    'volumetric_soil_water_layer_4',
]

 

기압 고도 변수

pressure_variables = [
    'geopotential', 'relative_humidity', 'specific_humidity',
    'temperature', 'u_component_of_wind', 'v_component_of_wind',
]

pressure_levels = [
    '1',   '2',   '3',   '5',   '7',   '10',  '20',  '30',
    '50',  '70',  '100', '125', '150', '175', '200', '225',
    '250', '300', '350', '400', '450', '500', '550', '600',
    '650', '700', '750', '775', '800', '825', '850', '875',
    '900', '925', '950', '975', '1000',
]

 

3.3 전체 코드

import cdsapi
import xarray as xr
from datetime import datetime, timedelta
import os, glob, importlib

def get_ERA5_data_from_CDS(self):
    start = self.start_date
    end = self.end_date

    start_str = start.strftime('%Y-%m-%d')
    end_str = end.strftime('%Y-%m-%d')

    datestring = f"{start_str}/{end_str}"
    print(datestring)

    # 영역 설정
    sub_region = self.sub_region

    c = cdsapi.Client()

    target_2d = f'ERA5/era5_hourly_single_subset_{self.case_name}.grib'
    target_3d = f'ERA5/era5_hourly_pressure_subset_{self.case_name}.grib'

    single_variables = [
        '10m_u_component_of_wind', '10m_v_component_of_wind', 
        '2m_dewpoint_temperature', '2m_temperature', 'forecast_albedo', 
        'forecast_logarithm_of_surface_roughness_for_heat',
        'high_cloud_cover', 'land_sea_mask', 'low_cloud_cover', 
        'mean_sea_level_pressure', 'medium_cloud_cover', 'sea_ice_cover',
        'sea_surface_temperature', 'skin_temperature', 'snow_albedo', 'snow_depth', 
        'snowfall', 'soil_temperature_level_1', 'soil_temperature_level_2', 
        'soil_temperature_level_3', 'soil_temperature_level_4', 'soil_type', 
        'surface_pressure', 'total_cloud_cover', 'total_column_water', 
        'total_column_water_vapour', 'total_precipitation',
        'volumetric_soil_water_layer_1', 'volumetric_soil_water_layer_2', 
        'volumetric_soil_water_layer_3', 'volumetric_soil_water_layer_4',
    ]

    pressure_variables = [
        'geopotential', 'relative_humidity', 'specific_humidity',
        'temperature', 'u_component_of_wind', 'v_component_of_wind',
    ]

    pressure_levels = [
        '1',   '2',   '3',   '5',   '7',   '10',  '20',  '30',
        '50',  '70',  '100', '125', '150', '175', '200', '225',
        '250', '300', '350', '400', '450', '500', '550', '600',
        '650', '700', '750', '775', '800', '825', '850', '875',
        '900', '925', '950', '975', '1000',
    ]

    time_ranges = [
        "00:00", "01:00", "02:00", "03:00", "04:00", "05:00",
        "06:00", "07:00", "08:00", "09:00", "10:00", "11:00",
        "12:00", "13:00", "14:00", "15:00", "16:00", "17:00",
        "18:00", "19:00", "20:00", "21:00", "22:00", "23:00"
    ]

    # single level
    dataset = "reanalysis-era5-single-levels"

    request = {
        "product_type": ["reanalysis"],
        "variable": single_variables,
        "date": datestring,
        "area": sub_region,
        "time": time_ranges,
        "data_format": "grib",
        "download_format": "unarchived"
    }

    if not os.path.isfile(target_2d):
        c.retrieve(dataset, request, target_2d)

    # pressure level
    dataset = "reanalysis-era5-pressure-levels"

    request = {
        "product_type": ["reanalysis"],
        "variable": pressure_variables,
        'pressure_level': pressure_levels,
        "date": datestring,
        "area": sub_region,
        "time": time_ranges,
        "data_format": "grib",
        "download_format": "unarchived"
    }

    if not os.path.isfile(target_3d):
        c.retrieve(dataset, request, target_3d)

 

4. 실행 방법

4.1 기본적인 데이터 다운로드

가장 간단한 사용 예시

class case_info:
    def __init__(self):
        # 데이터 다운로드 기간 설정
        self.start_date = datetime(2023, 8, 1)
        self.end_date = datetime(2023, 8, 31)

        # 관심 영역 설정 [북위, 서경, 남위, 동경]
        self.sub_region = [43, 124, 32, 132]

        # 저장할 파일명 설정
        self.case_name = "korea_202308"

if __name__ == "__main__":
    my = case_info()
    get_ERA5_data_from_CDS(my)

 

4.2 여러 기간의 데이터 다운로드

특정 사례들에 대한 데이터를 순차적으로 다운로드 하는 예시

class case_info:
    def __init__(self, start, end, name):
        self.start_date = start
        self.end_date = end
        self.case_name = name
        self.sub_region = [43, 124, 32, 132]  # 한반도 영역

if __name__ == "__main__":
    # 다운로드할 사례들 정의
    cases = [
        {
            'start': datetime(2023, 7, 1),
            'end': datetime(2023, 7, 31),
            'name': 'korea_202307'
        },
        {
            'start': datetime(2023, 8, 1),
            'end': datetime(2023, 8, 31),
            'name': 'korea_202308'
        }
    ]

    # 각 사례별로 데이터 다운로드
    for case in cases:
        my = case_info(case['start'], case['end'], case['name'])
        get_ERA5_data_from_CDS(my)

 

4.3 실행 결과

아래와 같이 ERA5 폴더에 grib 파일이 생성된다. ERA5 폴더가 없으면 미리 만들자.

  • 단일 고도 자료: ERA5/era5_hourly_single_subset_{case_name}.grib
  • 기압 고도 자료: ERA5/era5_hourly_pressure_subset_{case_name}.grib

 

5. 주의사항

  • 저장 공간: 넓은 지역에 대해 추출된 ERA5 데이터는 용량이 크기 때문에, 충분한 저장 공간을 확보해야 한다.
  • 다운로드 시간: 데이터 크기에 따라 다운로드에 상당한 시간이 소요될 수 있고, CDS 서버 상태에 따라 그 속도가 변할 수 있다.
  • 동시 다운로드 제한: CDS는 사용자당 동시 다운로드 요청 수를 제한하고 있어서 다운로드는 순차적으로 처리된다.

 

6. 참고자료

이상으로 Python CDS API를 이용한 ERA5 데이터 다운로드 방법을 알아보았다. 누구나 이 코드를 활용하여 원하는 기간과 영역의 ERA5 데이터를 쉽게 다운로드할 수 있기를 바란다. 추후 이 포스팅에서 생성한 2개의 grib 형식 파일을 WRF 모델의 입력자료로 활용하는 다음 포스팅을 게시할 계획이다.

 

연관 포스팅:

2025.01.03 - [기타 소소한 포스팅] - WRF 모델을 위한 ERA5 데이터 전 처리하기(2/2)