PythonのFastAPIを用いた気象データ提供APIの実装

この記事では、Pythonで効率的かつ迅速なAPIを実装するための最先端フレームワークであるFastAPIを使って、気象データを提供するための方法についてご紹介します。

気象データを提供するためのRESTful APIを考える

FastAPIは、JSON形式のデータをクライアントに返すことができるため、Webアプリケーションの開発において非常に便利です。これによって、開発者はデータの整合性と安全性を確保しながら安心して開発を進めることができ、迅速かつ高品質なWebアプリケーションの実装を行うことができます。

気象データを迅速かつ正確に伝達する上でも、API連携が重要な役割を担います。また、API連携によるインターフェースは、システムの維持管理の側面でも、適切かつ柔軟な対応が可能となります。

FastAPIはPythonで実装されるため、気象データもPythonで処理できれば、実装の複雑さを回避することができそうです。

そこで、FastAPIをインストールしたPython環境に気象関連でおなじみのPygribとMetpyのモジュールを併用して、気象データを提供するAPIを作成していきたいとおもいます。

気象データを提供するAPIを作る

FastAPIの環境構築や基本的な使い方は、以下の記事で紹介していますので参考にしてください。

開発環境の構築

FastAPIがインストールされているPython環境に、PygribとMetpyのモジュールを追加していきます。ここでは、LinuxのPythonの仮想環境(venv)を使っています。ご自身の環境にあわせて構築してください。

(FastAPI) [reafnex@devsrv01 ~]$ pip3.12 install pygrib metpy

(FastAPI) [reafnex@devsrv01 ~]$ pip3.12 list
Package            Version
------------------ ------------
annotated-types    0.6.0
anyio              4.2.0
certifi            2024.2.2
charset-normalizer 3.3.2
click              8.1.7
contourpy          1.2.0
cycler             0.12.1
fastapi            0.109.1
fonttools          4.49.0
h11                0.14.0
idna               3.6
kiwisolver         1.4.5
matplotlib         3.8.3
MetPy              1.6.1
numpy              1.26.4
packaging          23.2
pandas             2.2.0
pillow             10.2.0
Pint               0.23
pip                23.2.1
platformdirs       4.2.0
pooch              1.8.1
pydantic           2.6.0
pydantic_core      2.16.1
pygrib             2.1.5
pyparsing          3.1.1
pyproj             3.6.1
python-dateutil    2.8.2
pytz               2024.1
requests           2.31.0
scipy              1.12.0
setuptools         69.1.1
six                1.16.0
sniffio            1.3.0
starlette          0.35.1
traitlets          5.14.1
typing_extensions  4.9.0
tzdata             2024.1
urllib3            2.2.1
uvicorn            0.27.0.post1
xarray             2024.2.0

暑さ指数(WBGT)を求めるクラスを作成する

今回は、暑さ指数(WBGT)を返すAPIを実装したいので、はじめにWBGTを計算するクラスを作成していきます。

WBGTを計算するクラスに関する詳しい内容は、以下の記事で紹介しています。

クラスの全体は、以下のとおりです。

import pygrib
import metpy.calc as mpcalc
from metpy.units import units
from datetime import timedelta
import numpy as np

class WBGTModule :
        def __init__(self, reflat, reflon, grib2) :

                # 抽出する地点の緯度経度と抽出範囲を設定する
                self.req_lat1 = reflat - 0.025
                self.req_lat2 = reflat + 0.025
                self.req_lon1 = reflon - 0.03125
                self.req_lon2 = reflon + 0.03125

                # GRIB2ファイル名を設定する
                self.grib2dat = grib2

                # UTCから日本時間に変換するための時刻差を設定する
                self.diff_time = timedelta(hours=9)

        def extractValidTime(self, physquantity) :
                validtime = physquantity.validDate + self.diff_time
                return validtime

        def extractPointData(self, physquantity) :
                pointdata = physquantity.data(
                        lat1 = self.req_lat1, lat2 = self.req_lat2,
                        lon1 = self.req_lon1, lon2 = self.req_lon2
                        )[0][0][0]
                return pointdata

        def calcWBGT(self) :
                # GPVファイル名を指定してオープンする
                gpv_file = pygrib.open(self.grib2dat)

                # 日射量、気温、相対湿度、風速ベクトルを取り出す。
                shortwave = gpv_file.select(name="Mean surface downward short-wave radiation flux")
                temp = gpv_file.select(name="Temperature")
                rhum = gpv_file.select(name="Relative humidity")
                pres = gpv_file.select(name="Surface pressure")
                u_comp = gpv_file.select(name="10 metre U wind component")
                v_comp = gpv_file.select(name="10 metre V wind component")

                # 予想時刻と抽出した要素データを格納するリストを初期化する
                valid_dates = []
                solar_list  = []
                temp_list   = []
                rh_list     = []
                dew_list    = []
                wspd_list   = []
                wetb_list   = []

                for sw, tk, rh, psfc, uwind, vwind in zip(shortwave, temp, rhum, pres, u_comp,v_comp) :
                        # 予想時刻をリストに抽出する
                        vt = self.extractValidTime(uwind)

                        # 日射量を抽出する
                        sw_pt = self.extractPointData(sw)

                        # 風速ベクトルを抽出してする
                        u_pt = self.extractPointData(uwind)
                        v_pt = self.extractPointData(vwind)
                        # 風速をMetpyで算出する
                        wspd = mpcalc.wind_speed(
                                u_pt * units('m/s'), v_pt * units('m/s')
                                )

                        # 湿球温度をMetpyで算出する
                        psfc_pt = self.extractPointData(psfc)
                        tc_pt   = self.extractPointData(tk) - 273.15
                        rh_pt   = self.extractPointData(rh)
                        dew_pt  = mpcalc.dewpoint_from_relative_humidity(
                                        tc_pt * units.degC,
                                        rh_pt * units.percent
                                        )
                        wetb_pt = mpcalc.wet_bulb_temperature(
                                        psfc_pt * units.Pa,
                                        tc_pt * units.degC,
                                        dew_pt.magnitude * units.degC
                                        )
                        # リスト変数に追加する
                        valid_dates.append(vt)
                        solar_list.append(sw_pt)
                        temp_list.append(tc_pt)
                        rh_list.append(rh_pt)
                        dew_list.append(dew_pt.magnitude)
                        wspd_list.append(wspd.magnitude)
                        wetb_list.append(wetb_pt.magnitude)

                # 黒球温度を計算する
                So = np.array(solar_list)
                U  = np.array(wspd_list)
                Ta = np.array(temp_list)
                Tg = ((So - 38.5) / ((So * 0.0217) + (U * 4.35) + 23.5)) + Ta

                # WBGTを計算する
                Tw = np.array(wetb_list)
                WBGT_out = (0.7 * Tw) + (0.2 * Tg) + (0.1 * Ta)

                return (valid_dates,
                        WBGT_out.tolist(),
                        temp_list,
                        dew_list,
                        rh_list
                        )

FastAPIのメインスクリプトを作成する

次に、先ほど作成したWBGTを計算するクラスをインポートしたAPIのメインスクリプトを作成します。

from fastapi import FastAPI
from fastapi.responses import JSONResponse
from typing import Optional
import pandas as pd
from WBGTModule import WBGTModule

## 気象庁MSMのGRIB2ファイル名を指定する
msm_gpv = 'Z__C_RJTD_20230720000000_MSM_GPV_Rjp_Lsurf_FH00-15_grib2.bin'

# FastAPIをインスタンス化する
app = FastAPI()

@app.get("/")

async def getWbgtApi(lat: Optional[float] = 35.00, lon: Optional[float] = 140.00):

        # WBGTを計算するクラスをインスタンス化する
        phys = WBGTModule(lat, lon, msm_gpv)

        # WBGTを計算するメソッドを実行して、予想時間、WBGT、気温、露点温度、湿度を取得する
        vt, wbgt, tc, dp, rh = phys.calcWBGT()

        # Pandasのデータフレームにリスト変数の値を入力する
        df = pd.DataFrame({
                        "Valid Date(JST)":vt,
                        "WBGT":wbgt,
                        "Temp degC":tc,
                        "Dewpoint degC":dp,
                        "Rel Humidity %":rh
                        })

        # PandasのデータフレームのデータをJSON形式に変換して返す。
        return JSONResponse(
                        content=df.to_json(
                                        orient='records',
                                        date_format='iso', force_ascii=False
                                        ),
                        media_type="application/json"
                        )

今回は、Pandasのデータフレームに格納したデータをJSON形式に変換してクライアントへ返すようにするため、JSONResponseモジュールをインポートしています。また、作成したWBGTModuleクラスもインポートします。

from fastapi import FastAPI
from fastapi.responses import JSONResponse
from typing import Optional
import pandas as pd
from WBGTModule import WBGTModule

はじめに、FastAPIをインスタンス化した後、@app.getデコレータでgetWbgtApi関数を「”/”」パスに対応させ、非同期でGETリクエストするように設定します。

Optionalで浮動小数型の型ヒントを設定し、リクエスト変数の指定がない場合は、緯度35.00度、経度140.00度をデフォルトとして代入するようにしています。

getWbgtApi関数の内部では、WBGTを計算するクラスをインスタンス化した後、WBGTを計算するcalcWBGTメソッドを実行して、予想時間、WBGT、気温、露点温度と相対湿度のリストを取得しています。

取得したリスト変数とキーをセットにしてPandasのデータフレームに入力しています。

# FastAPIをインスタンス化する
app = FastAPI()

@app.get("/")
async def getWbgtApi(lat: Optional[float] = 35.00, lon: Optional[float] = 140.00):

        # WBGTを計算するクラスをインスタンス化する
        phys = WBGTModule(lat, lon, msm_gpv)

        # WBGTを計算するメソッドを実行して、予想時間、WBGT、気温、露点温度、湿度を取得する
        vt, wbgt, tc, dp, rh = phys.calcWBGT()

        # Pandasのデータフレームにリスト変数の値を入力する
        df = pd.DataFrame({
                        "Valid Date(JST)":vt,
                        "WBGT":wbgt,
                        "Temp degC":tc,
                        "Dewpoint degC":dp,
                        "Rel Humidity %":rh
                        })

最後に、Pandasのデータフレームに入力したデータをto_jsonメソッドでJSON形式に出力し、JSONResponseモジュールを介してクライアントへ戻り値を返しています。

        # PandasのデータフレームのデータをJSON形式に変換して返す。
        return JSONResponse(
                        content=df.to_json(
                                        orient='records',
                                        date_format='iso', force_ascii=False
                                        ),
                        media_type="application/json"
                        )

APIを実行してみる

作成したAPIのスクリプトは、uvicornで実行します。

uvicornは、PythonのASGIサーバーを実行するためのモジュールで、非同期なWeb APIを起動することができます。

引数には、FastAPIを記述したスクリプト名mainとFastAPIのインスタンス名appを「:」でつなげて指定します。

--host=0.0.0.0--port=8000を設定して、外部ネットワークからのリクエストを8000番ポートでListenさせるようにします。

--reloadを引数に指定することで、uvicornで実行されたPythonコードの変更を自動的に反映させるようしています。

(FastAPI) [reafnex@devsrv01 FastAPI]$ uvicorn main:app --host=0.0.0.0 --port=8000 --reload
INFO:     Will watch for changes in these directories: ['/home/reafnex/Python/FastAPI']
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [1377] using StatReload
INFO:     Started server process [1379]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

ブラウザからAPIにアクセスしてみましょう。クエリパラメータを設定しない場合は、デフォルトで緯度35.00度、東経140.00度の気象データが以下のようにJSON形式で表示されます。

クエリパラメータに緯度と経度を設定した場合は、任意地点の気象データをJSON形式で取得することができます。

サーバー側においても、GETメソッドが200で成功していることが確認できました。

(FastAPI) [reafnex@devsrv01 FastAPI]$ uvicorn main:app --host=0.0.0.0 --port=8000 --reload
INFO:     Will watch for changes in these directories: ['/home/reafnex/Python/FastAPI']
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [1377] using StatReload
INFO:     Started server process [1379]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     192.168.11.192:49439 - "GET / HTTP/1.1" 200 OK
INFO:     192.168.11.192:49439 - "GET /favicon.ico HTTP/1.1" 404 Not Found
INFO:     192.168.11.192:49570 - "GET /?lat=33.00&lon=135.00 HTTP/1.1" 200 OK

まとめ

いかがでしたでしょうか。

今回は、API開発の最先端フレームワークであるFastAPIを使って気象データを提供するAPIの実装方法について解説しました。

FastAPIなどのAPIフレームワークを利用することにより、Webアプリケーションの開発者はデータの整合性と安全性を確保しながら、効率的かつ高品質なアプリケーションの開発に専念することができます。

FastAPIを利用することで、システム間において標準的なhttpプロトコルを使用したAPI連携が容易に実装でき、これがシステムの維持管理においても適切かつ柔軟な対応が可能となる大きなメリットとなります。

また、APIを介してクライアントが保有する様々なデバイスにも効率的に気象データを提供できるようになれば、エッジコンピューティングを活用した気象ビジネスソリューションの急速な発展にも寄与し、SDGsの目標13「気候変動に具体的な対策を」の達成に貢献できるかもしれません。

参考になれば幸いです。

システムのお悩みについてご相談ください

本サイトの掲載内容に関するお問い合わせは、こちらから承ります。
SOHOのシステム運用管理に関するお悩みごとについて、なんでもお気兼ねなくご相談ください。
現役システムエンジニアのスタッフが、ボランティアでご相談にご対応させていただきます。