Python で OpenWeather API の気象データを取得してみた

OpenWeather は様々な気象データの API を提供しており、一部の機能は無料で使用できる。

アプリ開発チュートリアルでもよく見かける定番の API だ。

今回は、データを取得して表示する Python スクリプトと共に紹介してみたい。

https://openweathermap.org/

以下の流れで進めていく。
1. OpenWeather にサインアップして API キーを入手する
2. API を呼び出してデータを取得する
    a. 事前準備
    b. Current weather data
    c. 5 day weather forecast

<br>

# 1. OpenWeather にサインアップして API キーを入手する
まずは、OpenWeather にサインアップする。

1. [サインアップページ](https://home.openweathermap.org/users/sign_up) にアクセスする <br><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3816889/c2af9fa6-031f-de3c-0680-12b282c25cdb.png" width="360">
2. フォームを入力し、**Create Account** をクリックする
3. ホーム画面が開かれる（サインアップに使用したメールアドレスに確認メールが届く。メール を開き、**Verify your email** をクリックする）
![20241221_1_openweather_mypage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3816889/4b3c9ef4-7309-1ed8-effb-8927b5629433.png)
4. ホーム画面の **API keys** をクリックする
5. **Default** という名前の API キーを確認する
![20241221_2_oenweather_apikeys.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3816889/4952bdac-e446-283e-ab28-df1c5b679141.png)


今回使用するのは、このデフォルトで用意されている API キーだ。
<br>

# 2. API を呼び出してデータを取得する

## 事前準備
### 環境のセットアップ
環境について説明しておく。

Python バージョン `3.13.1` を使用しており、`venv` で作成した仮想環境内で作業する。
```bash
python -m venv .venv
source .venv/bin/activate
```

API リクエストに `requests`、結果を整形して表示するために `pandas` を利用する。
```bash
pip install requests pandas
```

また、先ほど確認した OpenWeather の API キーを環境変数に入れておく
```bash
export API_KEY=<your-api-key>
```

\
今回用いるスクリプトの流れは次の通りだ。
1. ユーザー入力として「地名」を受け取る
2. 地名を緯度経度に変換する
3. 気象 API を呼び出す
4. 結果を確認する
<br>

### ヘルパーモジュールを作成する
気象データの取得に使用するヘルパーモジュールを作成しておく。
#### Geocoding API
まずは [Geocoding API](https://openweathermap.org/api/geocoding-api) を呼び出す処理だ。これも OpenWeather API の一つだ。

これを使用して、ユーザーから受け取った地名を緯度経度に変換する。後で出てくる気象データ用の API で、どの地点の天気を取得するかを指定するために使用する。

[Built-in geo-coding](https://openweathermap.org/current#builtin) を使用する方が簡単だが廃止されているため、今回は正規の方法を採用した。

\
Geocoding API を用いて関数のコードスニペットを以下に示す。

```python
def get_coordinates_by_city_name(city_name):
    """Converting a city name to geographical coordinates."""
    url = f"{BASE_URL}/{GEO_URL}?q={city_name}&appid={API_KEY}&limit=1"
    response = requests.get(url, timeout=1)
    data = response.json()

    if response.status_code != 200 or not data:
        raise Exception(f"City not found: {data if not data else data["message"]}")
    else:
        return data[0]["lat"], data[0]["lon"]
```

#### 日付フォーマット
後で表示用に使うので。以下のような感じ。
```python
def format_datetime(dt_value, format_string):
    """Convert a UNIX timestamp to a formatted date-time string."""
    return datetime.fromtimestamp(
        dt_value, tz=ZoneInfo("Asia/Tokyo")
    ).strftime(format_string)
```

\
上記をヘルパーにまとめたコード。
```python:open_weather_helper.py
"""OpenWeather API helper"""

import os
from datetime import datetime
from zoneinfo import ZoneInfo
import requests

API_KEY = os.environ.get("API_KEY")
BASE_URL = "http://api.openweathermap.org"
GEO_URL = "/geo/1.0/direct"


def get_coordinates_by_city_name(city_name):
    """Converting a city name to geographical coordinates."""
    url = f"{BASE_URL}/{GEO_URL}?q={city_name}&appid={API_KEY}&limit=1"
    response = requests.get(url, timeout=1)
    data = response.json()

    if response.status_code != 200 or not data:
        raise Exception(f"City not found: {data if not data else data["message"]}")
    else:
        return data[0]["lat"], data[0]["lon"]


def format_datetime(dt_value, format_string):
    """Convert a UNIX timestamp to a formatted date-time string."""
    return datetime.fromtimestamp(
        dt_value, tz=ZoneInfo("Asia/Tokyo")
    ).strftime(format_string)


if __name__ == "__main__":
    city_name_input = input("Enter city name: ")

    lat, lon = get_coordinates_by_city_name(city_name_input)

    print(f"\n(Latitude, Longitude) = ({lat}, {lon})")
```

実行してみる。

```bash
python geo_coding_sample.py
```
```
Enter city name: tokyo

(Latitude, Longitude) = (35.6828387, 139.7594549)
```

<br>

準備が終わったところで、OpenWeather API を呼び出してみる。

<br>

## Current weather data
[Current weather data](https://openweathermap.org/current) を使用すると、現在の天気や気温を取得できる。

ここでは、以下のコードで
* 天気
* 気温
* 湿度

を表示してみた。

```python:fetch_current_weather_by_city.py
"""Featch current weather by city."""

import requests
from pandas import DataFrame
from open_weather_helper import (
    API_KEY,
    BASE_URL,
    get_coordinates_by_city_name,
    format_datetime,
)

WEATHER_URL = "/data/2.5/weather"


if __name__ == "__main__":
    city_name = input("Enter city name: ")

    lat, lon = get_coordinates_by_city_name(city_name)

    url = f"{BASE_URL}/{WEATHER_URL}?lat={lat}&lon={lon}&appid={API_KEY}&units=metric"

    response = requests.get(url, timeout=1)
    data = response.json()

    if response.status_code == 200:
        print("\n==============================================")
        print(f"Current weather data in {city_name}")
        print("==============================================", end="\n\n")

        display_data = [
            ["Date/Time (JST)", format_datetime(data["dt"], "%Y-%m-%d %H:%M")],
            ["Weather", data['weather'][0]['description']],
            ["Temperature", f"{data['main']['temp']}°C"],
            ["Humidity", f"{data['main']['humidity']}%"],
        ]

        print(
            DataFrame(display_data).to_string(index=False, header=False)
        )
    else:
        print(f"City not found: {data['message']}")
```

実行結果

```bash
python fetch_current_weather_by_city.py
```

```
Enter city name: tokyo

==============================================
Current weather data in tokyo
==============================================

Date/Time (JST) 2024-12-21 23:17
        Weather        clear sky
    Temperature          10.46°C
       Humidity              65%
```
<br>
<br>

## 5 day weather forecast
[5 day weather forecast](https://openweathermap.org/forecast5) を使うと、直近５日間の３時間ごとの天気を取得できる。

データをいくつ取得するかは `cnt` パラメータで指定できる。今回は２４時間分取得する（`cnt=9`）。

```python:fetch_forecast_by_city.py
"""Featch current weather by city."""

import requests
from pandas import DataFrame
from open_weather_helper import (
    API_KEY,
    BASE_URL,
    get_coordinates_by_city_name,
    format_datetime,
)

FORECAST_URL = "/data/2.5/forecast"


if __name__ == "__main__":
    city_name = input("Enter city name: ")

    lat, lon = get_coordinates_by_city_name(city_name)

    endpoint = f"{BASE_URL}/{FORECAST_URL}"
    url = f"{endpoint}?lat={lat}&lon={lon}&appid={API_KEY}&units=metric&cnt=9"

    response = requests.get(url, timeout=1)
    data = response.json()

    if response.status_code == 200:
        print("\n==============================================")
        print(f"24 hours forecast in {city_name}")
        print("==============================================", end="\n\n")

        display_data = []
        for e in data["list"]:
            display_data.append([
                format_datetime(e["dt"], "%Y-%m-%d %H:%M"),
                e['weather'][0]['description'],
                f"{e['main']['temp']}°C",
                f"{e['main']['humidity']}%",
                f"{e['pop'] * 100}%"
            ])

        print(
            DataFrame(
                display_data,
                columns=[
                    "Date/Time (JST)",
                    "Weather",
                    "Temperature",
                    "Humidity",
                    "Precipitation"
                ]
            ).to_string(index=False)
        )
    else:
        print(f"City not found: {data['message']}")
```
実行結果
```bash
python fetch_forecast_by_city.py
```

```
Enter city name: tokyo

==============================================
24 hours forecast in tokyo
==============================================

 Date/Time (JST)    Weather Temperature Humidity Precipitation
2024-12-22 00:00  clear sky     10.24°C      69%            0%
2024-12-22 03:00  clear sky     10.01°C      59%            0%
2024-12-22 06:00  clear sky       8.8°C      49%            0%
2024-12-22 09:00  clear sky      8.43°C      35%            0%
2024-12-22 12:00  clear sky      9.17°C      29%            0%
2024-12-22 15:00 few clouds      9.11°C      21%            0%
2024-12-22 18:00  clear sky      6.41°C      28%            0%
2024-12-22 21:00  clear sky      5.49°C      31%            0%
2024-12-23 00:00  clear sky      5.03°C      32%            0%
```

<br>


# 以上だ

今回使用したもの以外にも、次のようなデータが取れる。

* 風向・風速
* 視程
* 日の出時刻
* 日の入り時刻

一度じっくり見てみるのも良いかもしれない。