見出し画像

ERA5の気象データを可視化する①~折れ線グラフ~

先日、屋外の環境を測るために以下のようなものを作成しました。

測定データされたがどのくらい妥当なのかを確認するために、比較対象として「ERA5」という衛星データを取得してみました。


ERA5

ERA5は、ヨーロッパ中期予報センター(ECMWF)によって開発された気象データセットです。ERA5は、大気、地表、海洋に関する広範な気象情報を提供するものです。

ERA5は、高解像度で空間的にも時間的にも詳細な気象データを提供し、大気圏の状態、風、気温、湿度、降水量など、さまざまな情報を含んでいます。

時間毎データを取得する

以下から、各種気象データを取得します。

1940 年から現在までの単一レベルの ERA5 時間別データ

ダウンロードに時間がかかります。

1940 年から現在までの圧力レベルに関する ERA5 時間別データ

垂直方向の圧力レベルごとにデータが格納されています。地表付近のデータを取得する場合は「1000hPa」を選択します。こちらもダウンロードに時間がかかります。

各データを取得するための変数の値は以下が参考になります。

https://confluence.ecmwf.int/display/CKB/ERA5%3A+data+documentation

CDS APIの使用方法

ERA5関連のデータをダウンロードする際、事前にCDS APIの設定を行っておく必要があります。以下のページを参考に設定しておいてください。

データを取得するPythonコード

  • データ抽出期間の日付とデータ抽出の地名を入力する。

  • 時間毎の気温、相対湿度、気圧、風速、降水量、全天日射量を取得する。

  • 取得した気象データはそれぞれCSVファイルとして出力する。

今回は、2023年1月1〜10月31日の自宅付近の環境データを一時間おきに取得してみます(下記コードでは、データ取得地点を「東京都」に変更してあります)。

import cdsapi
import xarray as xr
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import csv
import os
from datetime import date, datetime, timedelta
from geopy.geocoders import Nominatim

################################################################################
# 設定値
################################################################################
# データ抽出期間
dt1 = datetime(2023, 10, 1, 0, 0, 0)
dt2 = datetime(2023, 10, 7, 0, 0, 0)

# データ抽出地点
address = '東京都'

################################################################################
# データ抽出期間を補正する関数
################################################################################
def correct_dt(shortname, dt1, dt2):
    # ディレクトリを作成する
    if not os.path.exists('./csv_'+str(shortname)):
        os.makedirs('./csv_'+str(shortname))
        
    path = './csv_'+str(shortname)
    filelist = []
    files = os.listdir(path)
    
    for filename in files:
        if os.path.isfile(os.path.join(path, filename)):
            filelist.append(filename)
    
    # データ取得開始日時
    if len(filelist) == 0:
        start = 'ERA5_'+str(format(dt1.year, '04'))+'-'+str(format(dt1.month, '02'))+'-'+str(format(dt1.day, '02'))+'T'+str(format(dt1.hour, '02'))+'_00_00_'+str(shortname)+'.csv'
    else:
        start = max(filelist)
    str_d1 = str(start[5:9])+str(start[10:12])+str(start[13:15])+str(start[16:18])
    
    # データ取得終了日時
    if datetime.now() - timedelta(7) > dt2:
        end = dt2
    else:
        end = datetime.now() - timedelta(7)
    str_d2 = end.strftime('%Y%m%d%H')
    
    # データ抽出期間を設定
    dt1 = datetime(int(str_d1[:4]), int(str_d1[4:6]), int(str_d1[6:8]), int(str_d1[8:]), 0, 0)
    dt2 = datetime(int(str_d2[:4]), int(str_d2[4:6]), int(str_d2[6:8]), int(str_d2[8:]), 0, 0)

    return (dt1, dt2)

################################################################################
# 地名から緯度、経度を取得する関数
################################################################################
def get_lat_lon(address):
    geolocator = Nominatim(user_agent="geo_locator")
    location = geolocator.geocode(address)
    if location:
        return (location.latitude, location.longitude)
    else:
        return None

################################################################################
# CSVファイルを生成する関数
################################################################################
def gen_csv(shortname, time, value):
    with open('./csv_'+str(shortname)+'/ERA5_'+str(time[:10])+'T'+str(time[11:13])+'_00_00_'+str(shortname)+'.csv', mode='w', newline='') as file:
        writer = csv.writer(file)
        writer.writerow([str(time), str(value)])

################################################################################
# 「ERA5 hourly data on single levels from 1940 to present」からデータを抽出する関数
# https://cds.climate.copernicus.eu/cdsapp#!/dataset/reanalysis-era5-single-levels
################################################################################
def reanalysis_era5_single_levels(name, shortname, lon, lat, dt1, dt2):
    # データ抽出期間を補正
    period = correct_dt(shortname, dt1, dt2)
    dt1 = period[0]
    dt2 = period[1]
    print(f'{shortname}: {dt1} - {dt2} ({lon}, {lat})')
    
    dt = dt1

    time = []
    value = []
    while dt <= dt2:
        print(dt)
        
        c.retrieve(
            'reanalysis-era5-single-levels',
            {
                'product_type': 'reanalysis',
                'variable': str(name),
                'year': str(dt.year),
                'month': str(dt.month),
                'day': str(dt.day),
                'time': str(dt.time()),
                'format': 'netcdf'
            },
            'download.nc')

        if lon < 0:
            lon = 360 + lon
    
        data_array = xr.open_dataset('download.nc')
        data = data_array.sel(longitude=lon, latitude=lat, time=dt, method='nearest')
        time.append(dt)
        value.append(data[str(shortname)].values)
        
        gen_csv(str(shortname), str(dt), data[str(shortname)].values)
        
        dt += delta

################################################################################
# 「ERA5 hourly data on pressure levels from 1940 to present」からデータを抽出する関数
# https://cds.climate.copernicus.eu/cdsapp#!/dataset/reanalysis-era5-pressure-levels
################################################################################
def reanalysis_era5_pressure_levels(name, shortname, lon, lat, lev, dt1, dt2):
    # データ抽出期間を補正
    period = correct_dt(shortname, dt1, dt2)
    dt1 = period[0]
    dt2 = period[1]
    print(f'{shortname}: {dt1} - {dt2} ({lon}, {lat})')
    
    dt = dt1

    time = []
    value = []
    while dt <= dt2:
        print(dt)
        
        c.retrieve(
            'reanalysis-era5-pressure-levels',
            {
                'product_type': 'reanalysis',
                'variable': str(name),
                'year': str(dt.year),
                'month': str(dt.month),
                'day': str(dt.day),
                'time': str(dt.time()),
                'level': int(lev),
                'format': 'netcdf'
            },
            'download.nc')

        if lon < 0:
            lon = 360 + lon
    
        data_array = xr.open_dataset('download.nc')
        data = data_array.sel(longitude=lon, latitude=lat, time=dt, method='nearest')
        time.append(dt)
        value.append(data[str(shortname)].values)
        
        gen_csv(str(shortname), str(dt), data[str(shortname)].values)
        
        dt += delta

################################################################################
# 変数
################################################################################
# 緯度、経度
coordinates = get_lat_lon(address)
lat = coordinates[0]
lon = coordinates[1]

# 圧力レベル
lev = 1000

# 時間間隔
delta = timedelta(hours=1)

# CDS API
c = cdsapi.Client()

################################################################################
# 各データをダウンロードしてCSVファイルを生成
################################################################################
#-------------------------------------------------------------------------------
# ERA5 hourly data on pressure levels from 1940 to present
#-------------------------------------------------------------------------------
# 気温 (K)
reanalysis_era5_pressure_levels('temperature', 't', lon, lat, lev, dt1, dt2)

# 相対湿度 (%RH)
reanalysis_era5_pressure_levels('relative_humidity', 'r', lon, lat, lev, dt1, dt2)

#-------------------------------------------------------------------------------
# ERA5 hourly data on single levels from 1940 to present
#-------------------------------------------------------------------------------
# 気圧 (Pa)
reanalysis_era5_single_levels('surface_pressure', 'sp', lon, lat, dt1, dt2)

# 風速 (m/s)
reanalysis_era5_single_levels('10m_u_component_of_wind', 'u10', lon, lat, dt1, dt2)
reanalysis_era5_single_levels('10m_v_component_of_wind', 'v10', lon, lat, dt1, dt2)

# 降水量 (m/h)
reanalysis_era5_single_levels('convective_precipitation', 'cp', lon, lat, dt1, dt2)

# 全天日射量 (J/m2)
reanalysis_era5_single_levels('surface_solar_radiation_downwards', 'ssrd', lon, lat, dt1, dt2)

取得したデータを可視化するPythonコード

  • 各データごとに取得したCSVファイルを結合する。

  • 各データごとに結合したCSVファイルから、可視化したい物理量ごとのデータフレームを作成する。

  • 各データフレームを時系列にプロットしたグラフをPNGファイルとして保存する。

import os
import pandas as pd
import csv
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import numpy as np
import math

################################################################################
# CSVファイルを結合する関数
################################################################################
def combine_csv(shortname):
    # 生成されたCSVファイルを名前昇順で並び替える
    path = './csv_'+str(shortname)
    csv_files = [f for f in os.listdir(path) if f.endswith('.csv')]
    csv_files.sort()
    
    # データを結合したものをCSVファイルとして出力する
    result = []
    for file in csv_files[:]:
        df = pd.read_csv(os.path.join(path, file))
        result.append([df.columns[0], df.columns[1]])
    with open(str(shortname)+'.csv', mode='w', newline='') as file:
        writer = csv.writer(file)
        writer.writerows(result)

################################################################################
# 気温 (degC)
################################################################################
# 元データの「短い名前」
shortname = 't'

# CSVファイルを結合する
combine_csv(shortname)

# 結合したCSVファイルを読み込む
df = pd.read_csv(str(shortname)+'.csv', names=['datetime', 'value'])

df['datetime'] = pd.to_datetime(df['datetime'])
df['value'] = df['value'].astype(float)

datetime = df['datetime']
value = df['value']

# データを編集する
value_adj = []
for i in range(len(datetime)):
    value_adj.append(value[i] - 273.15)
df_temperature = pd.concat([datetime,  pd.Series(value_adj)], axis=1)
df_temperature.columns = ['datetime', 'temperature']

# グラフを生成する
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(datetime, value_adj)
plt.ylim(-50, 50)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax.xaxis.set_major_locator(mdates.DayLocator())
plt.xticks(rotation=30)
plt.ylabel('Temperature (degC)')
plt.savefig('temperature.png')
plt.show()

################################################################################
# 相対湿度 (%RH)
################################################################################
# 元データの「短い名前」
shortname = 'r'

# CSVファイルを結合する
combine_csv(shortname)

# 結合したCSVファイルを読み込む
df = pd.read_csv(str(shortname)+'.csv', names=['datetime', 'value'])

df['datetime'] = pd.to_datetime(df['datetime'])
df['value'] = df['value'].astype(float)

datetime = df['datetime']
value = df['value']

# データを編集する
value_adj = []
for i in range(len(datetime)):
    value_adj.append(value[i])
df_relative_humidity = pd.concat([datetime,  pd.Series(value_adj)], axis=1)
df_relative_humidity.columns = ['datetime', 'relative_humidity']

# グラフを生成する
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(datetime, value_adj)
plt.ylim(0, 100)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax.xaxis.set_major_locator(mdates.DayLocator())
plt.xticks(rotation=30)
plt.ylabel('Relative Humidity (%RH)')
plt.savefig('relative_humidity.png')
plt.show()

################################################################################
# 気圧 (hPa)
################################################################################
# 元データの「短い名前」
shortname = 'sp'

# CSVファイルを結合する
combine_csv(shortname)

# 結合したCSVファイルを読み込む
df = pd.read_csv(str(shortname)+'.csv', names=['datetime', 'value'])

df['datetime'] = pd.to_datetime(df['datetime'])
df['value'] = df['value'].astype(float)

datetime = df['datetime']
value = df['value']

# データを編集する
value_adj = []
for i in range(len(datetime)):
    value_adj.append(value[i] / 100)
df_pressure = pd.concat([datetime,  pd.Series(value_adj)], axis=1)
df_pressure.columns = ['datetime', 'pressure']

# グラフを生成する
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(datetime, value_adj)
plt.ylim(900, 1100)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax.xaxis.set_major_locator(mdates.DayLocator())
plt.xticks(rotation=30)
plt.ylabel('Pressure (hPa)')
plt.savefig('pressure.png')
plt.show()

################################################################################
# 風速 (m/s)
################################################################################
# 元データの「短い名前」
shortname1 = 'u10'
shortname2 = 'v10'

# CSVファイルを結合する
combine_csv(shortname1)
combine_csv(shortname2)

# 結合したCSVファイルを読み込む
df1 = pd.read_csv(str(shortname1)+'.csv', names=['datetime', 'value1'])
df2 = pd.read_csv(str(shortname2)+'.csv', names=['datetime', 'value2'])
df = pd.merge(df1, df2, on='datetime')

df['datetime'] = pd.to_datetime(df['datetime'])
df['value1'] = df['value1'].astype(float)
df['value2'] = df['value2'].astype(float)

datetime = df['datetime']
value1 = df['value1']
value2 = df['value2']

# データを編集する
value_adj = []
for i in range(len(datetime)):
    value_adj.append((value1[i]**2 + value1[i]**2)**0.5)
df_wind_speed = pd.concat([datetime,  pd.Series(value_adj)], axis=1)
df_wind_speed.columns = ['datetime', 'wind_speed']

# グラフを生成する
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(datetime, value_adj)
plt.ylim(0, 30)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax.xaxis.set_major_locator(mdates.DayLocator())
plt.xticks(rotation=30)
plt.ylabel('Wind Speed (m/s)')
plt.savefig('wind_speed.png')
plt.show()

################################################################################
# 降水量 (mm/h)
################################################################################
# 元データの「短い名前」
shortname = 'cp'

# CSVファイルを結合する
combine_csv(shortname)

# 結合したCSVファイルを読み込む
df = pd.read_csv(str(shortname)+'.csv', names=['datetime', 'value'])

df['datetime'] = pd.to_datetime(df['datetime'])
df['value'] = df['value'].astype(float)

datetime = df['datetime']
value = df['value']

# データを編集する
value_adj = []
for i in range(len(datetime)):
    value_adj.append(value[i] * 1000)
df_precipitation = pd.concat([datetime,  pd.Series(value_adj)], axis=1)
df_precipitation.columns = ['datetime', 'precipitation']

# グラフを生成する
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(datetime, value_adj)
plt.ylim(0, 30)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax.xaxis.set_major_locator(mdates.DayLocator())
plt.xticks(rotation=30)
plt.ylabel('Precipitation (mm/h)')
plt.savefig('precipitation.png')
plt.show()

################################################################################
# 全天日射量 (W/m2)
################################################################################
# 元データの「短い名前」
shortname = 'ssrd'

# CSVファイルを結合する
combine_csv(shortname)

# 結合したCSVファイルを読み込む
df = pd.read_csv(str(shortname)+'.csv', names=['datetime', 'value'])

df['datetime'] = pd.to_datetime(df['datetime'])
df['value'] = df['value'].astype(float)

datetime = df['datetime']
value = df['value']

# データを編集する
value_adj = []
for i in range(len(datetime)):
    value_adj.append(value[i]/3600)
df_ghi = pd.concat([datetime,  pd.Series(value_adj)], axis=1)
df_ghi.columns = ['datetime', 'ghi']

# グラフを生成する
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(datetime, value_adj)
plt.ylim(0, 1000)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax.xaxis.set_major_locator(mdates.DayLocator())
plt.xticks(rotation=30)
plt.ylabel('GHI (W/m2)')
plt.savefig('ghi.png')
plt.show()

################################################################################
# 一括表示
################################################################################
# データフレームを統合する
df = df_temperature
#df = pd.merge(df, df_relative_humidity, on='datetime')
df = pd.merge(df, df_pressure, on='datetime')
df = pd.merge(df, df_wind_speed, on='datetime')
df = pd.merge(df, df_precipitation, on='datetime')
df = pd.merge(df, df_ghi, on='datetime')

# グラフを生成する
fig = plt.figure(figsize=(12, 6))
ax1 = fig.subplots()
ax2 = ax1.twinx()
ax1.plot(df['datetime'], df['temperature'], color='r', label='Tmperature (degC)', linewidth=0.5)
ax1.plot(df['datetime'], df['relative_humidity'], color='y', label='Relative Humidity (%RH)', linewidth=0.5)
ax2.plot(df['datetime'], df['pressure'], color='g', label='Pressure (hPa)', linewidth=0.5)
ax1.plot(df['datetime'], df['wind_speed'], color='c', label='Wind Speed (m/s)', linewidth=0.5)
ax1.plot(df['datetime'], df['precipitation'], color='b', label='Precipitation (mm/h)', linewidth=0.5)
ax2.plot(df['datetime'], df['ghi'], color='m', label='GHI (W/m2)', linewidth=0.5)
ax1.set_ylim(0, 100)
ax2.set_ylim(0, 1200)
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
#ax1.xaxis.set_major_locator(mdates.DayLocator())
#ax1.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=mdates.SU))
ax1.xaxis.set_major_locator(mdates.MonthLocator())
fig.autofmt_xdate(rotation=30)
ax1.set_ylabel('Tmperature, Relative Humidity, Wind Speed, Precipitation')
ax2.set_ylabel('Pressure, GHI')
lines_1, labels_1 = ax1.get_legend_handles_labels()
lines_2, labels_2 = ax2.get_legend_handles_labels()
lines = lines_1 + lines_2
labels = labels_1 + labels_2
ax1.legend(lines, labels, loc='upper center', bbox_to_anchor=(0.5, 1.15), ncol=3)
plt.savefig('total.png')
plt.show()

生成されたグラフ

以下のようなグラフが得られました。

気温 (degC)

相対湿度 (%RH)


気圧 (hPa)

風速 (m/s)

降水量 (mm/h)

全天日射量 (W/m2)

一括表示

まとめ

とにかくダウンロードに時間がかかるデメリットはあるものの、日時と地名を指定するだけで、市販のセンサでは測定の大変な、風速、降水量、日射量についても取得することができました。これらのデータから、体感温度や日射時間など、様々な指標に加工することもできそうですね。

他にもいろいろなデータを取得することができますので、よろしければ試してみてください。

この記事が気に入ったらサポートをしてみませんか?