天候・気温予測サービス "OpenWeather" のAPIを活用する

「Python2年生 スクレイピングのしくみ」という本に**OpenWeather**という天候・気温予測サービスの使い方が紹介されていたので紹介します。

豪雨災害・台風災害の対策に役立てたり、植え付け・収穫時期の調整といった農業に役立てることで労働生産性を改善できるサービスなのではないかと期待しています。

https://openweathermap.org/

# APIを活用する

API（**アプリケーションプログラミングインタフェース、 Application Programming Interface**）とはソフトウェアがお互いにやり取りするためのシステムやフレームワークである、と大雑把に理解しています。例えばコンセントの材質や誘電率などを知らなくても家電をコンセントにつなげばいつでも様々な家電を使うことができます。電気インフラとコンセント・家電はこのようなシステムの関係になっているのですが、ソフトウェア開発では**APIが電機システムにおけるコンセントの役割**をします。

# 今（当日）の天候を取得する
OpenWeatherに登録すると無償で1分60回APIを呼び出せるようになります（APIの有効化には数時間ほどかかります）。 例えば都市名ごとの気象情報のjsonファイルをAPIから取得するには
> https://api.openweathermap.org/data/2.5/weather?q={city_name}&appid={API_key} 

このリンクの`{city_name}を都市名`に、`{API_key}を取得したAPIキー`にPython経由でrequests.getでアクセスしてください。またデフォルトがだと気温が華氏で取得されます。摂氏を基準としたい場合はクエリに `units=metric` を含めます。 

```
import requests
import json
from pprint import pprint

url = "https://api.openweathermap.org/data/2.5/weather?q={city_name}&units=metric&appid={API_key}"
# xxxxxには取得したAPIキーを入れる
url = url.format(city_name = "Tokyo", API_key = "xxxxx")

jsondata = requests.get(url).json()
pprint(jsondata)
```

```
{'base': 'stations',
 'clouds': {'all': 75},
 'cod': 200,
 'coord': {'lat': 35.6895, 'lon': 139.6917},
 'dt': 1687917481,
 'id': 1850144,
 'main': {'feels_like': 35.33,
          'humidity': 66,
          'pressure': 1010,
          'temp': 30.54,
          'temp_max': 32.55,
          'temp_min': 27.75},
 'name': 'Tokyo',
 'sys': {'country': 'JP',
         'id': 2001249,
         'sunrise': 1687894045,
         'sunset': 1687946462,
         'type': 2},
 'timezone': 32400,
 'visibility': 8000,
 'weather': [{'description': 'broken clouds',
              'icon': '04d',
              'id': 803,
              'main': 'Clouds'}],
 'wind': {'deg': 180, 'speed': 5.14}}
```
取得したデータには、このようなキーと値が含まれています。
```
print("天気：",jsondata["weather"][0]["main"])
print("天気詳細：",jsondata["weather"][0]["description"])

print("都市名：",jsondata["name"])
print("気温：",jsondata["main"]["temp"])
print("体感気温：",jsondata["main"]["feels_like"])
print("最低気温：",jsondata["main"]["temp_min"])
print("最高気温：",jsondata["main"]["temp_max"])
print("気圧：",jsondata["main"]["pressure"])
print("湿度：",jsondata["main"]["humidity"])

print("風速：",jsondata["wind"]["speed"])
print("風の方角：",jsondata["wind"]["deg"])
```
```
天気： Clouds
天気詳細： broken clouds
都市名： Tokyo
気温： 29.84
体感気温： 34.42
最低気温： 27.75
最高気温： 31
気圧： 1010
湿度： 69
風速： 4.63
風の方角： 170
```

# 郵便番号のある座標の天候

```
url = "https://api.openweathermap.org/data/2.5/weather?zip={zip_place}&units=metric&appid={API_key}"
# xxxxx
url = url.format(zip_place = "110-0006,JP", API_key = "xxxxx")

jsondata = requests.get(url).json()
pprint(jsondata)

print("天気：",jsondata["weather"][0]["main"])
print("天気詳細：",jsondata["weather"][0]["description"])

print("都市名：",jsondata["name"])
print("気温：",jsondata["main"]["temp"])
print("体感気温：",jsondata["main"]["feels_like"])
print("最低気温：",jsondata["main"]["temp_min"])
print("最高気温：",jsondata["main"]["temp_max"])
print("気圧：",jsondata["main"]["pressure"])
print("湿度：",jsondata["main"]["humidity"])

print("風速：",jsondata["wind"]["speed"])
print("風の方角：",jsondata["wind"]["deg"])
```

```
{'base': 'stations',
 'clouds': {'all': 75},
 'cod': 200,
 'coord': {'lat': 35.7022, 'lon': 139.7749},
 'dt': 1687919279,
 'id': 0,
 'main': {'feels_like': 36.4,
          'humidity': 64,
          'pressure': 1010,
          'temp': 31.22,
          'temp_max': 32.29,
          'temp_min': 28.97},
 'name': 'Akihabara',
 'sys': {'country': 'JP',
         'id': 268395,
         'sunrise': 1687894022,
         'sunset': 1687946445,
         'type': 2},
 'timezone': 32400,
 'visibility': 10000,
 'weather': [{'description': 'broken clouds',
              'icon': '04d',
              'id': 803,
              'main': 'Clouds'}],
 'wind': {'deg': 170, 'speed': 5.14}}
天気： Clouds
天気詳細： broken clouds
都市名： Akihabara
気温： 31.22
体感気温： 36.4
最低気温： 28.97
最高気温： 32.29
気圧： 1010
湿度： 64
風速： 5.14
風の方角： 170
```

# JSONデータの書き出し
取得したJSONデータを書きだす・読みめるようにしておくとデータの管理に便利です。
## 書き出し
```
from pathlib import Path
#JSONデータの書き出し

# JSONはバイナリモードでファイルに書き込みする。直接オブジェクトをファイルに出力することはできない
with open("20230628_weather_akihabara.json", "wb") as outfile:
    json_str = json.dumps(jsondata)
    json_bin = json_str.encode("utf-8")
    outfile.write(json_bin)

"""
with open("20230628_weather_akihabara.json", "wb") as outfile:
    outfile.write(jsondata)
"""
```
## 読み込み
```
#JSONデータの読み込み
with open("20230628_weather_akihabara.json", "r") as readfile:
    jsondata = json.loads(readfile.read())
    pprint(jsondata)
```

# 5日間、3時間ごとの天候を予測する
OpenWeatherでは天候の予測情報も取得できます。当日の天候にはクエリに`weather`、予測情報の取得には`forecast`を用います。58なので**40回分の予測データ**が入手できます。データは"list"下の階層に格納されています。
```
import requests
import json
from pprint import pprint

url = "https://api.openweathermap.org/data/2.5/forecast?q={city_name}&units=metric&appid={API_key}"
# xxxxxには取得したAPIキーを入れる
url = url.format(city_name = "Tokyo", API_key = "xxxxx")

jsondata = requests.get(url).json()
pprint(jsondata)
```

```
{'city': {'coord': {'lat': 35.6895, 'lon': 139.6917},
          'country': 'JP',
          'id': 1850144,
          'name': 'Tokyo',
          'population': 12445327,
          'sunrise': 1687894045,
          'sunset': 1687946462,
          'timezone': 32400},
 'cnt': 40,
 'cod': '200',
 'list': [{'clouds': {'all': 75},
           'dt': 1687921200,
           'dt_txt': '2023-06-28 03:00:00',
           'main': {'feels_like': 36.3,
                    'grnd_level': 1005,
                    'humidity': 65,
                    'pressure': 1010,
                    'sea_level': 1010,
                    'temp': 31.06,
                    'temp_kf': 1.27,
                    'temp_max': 31.06,
                    'temp_min': 29.79},
           'pop': 0.03,
           'sys': {'pod': 'd'},
           'visibility': 10000,
           'weather': [{'description': 'broken clouds',
                        'icon': '04d',
                        'id': 803,
                        'main': 'Clouds'}],
           'wind': {'deg': 168, 'gust': 4.5, 'speed': 4.82}},
(以下40日分)
```
`"list"` `"dt"dt_text"`にはタイムスタンプと時刻が含まれていますが、これはUTC(協定世界時)となっているのでJST（日本標準時）に変換します。
変換には`datetime`のタイムスタンプを変換するための機能を使います。

```
#UTCをJSTに変換するためのタイムスタンプの変換を定義する
from datetime import datetime, timedelta, timezone
timestamp = 1687921200

tz = timezone(timedelta(), "UTC")
utc = datetime.fromtimestamp(timestamp, tz)
print(utc)

tz = timezone(timedelta(hours=+9), "JST")
jst = datetime.fromtimestamp(timestamp, tz)
print(jst)
```

```
2023-06-28 03:00:00+00:00
2023-06-28 12:00:00+09:00
```
forecastのjsonデータすべてをJSTのタイムスタンプに変換するには、このように記述します。

```
import requests
import json
from pprint import pprint
from datetime import datetime, timedelta, timezone

#forecastデータ
# xxxxxには取得したAPIキーを入れる
url = "https://api.openweathermap.org/data/2.5/forecast?q={city_name}&units=metric&appid={API_key}"
url = url.format(city_name = "Tokyo", API_key = "xxxxx")
jsondata = requests.get(url).json()
pprint(jsondata)

#タイムスタンプを変換
tz = timezone(timedelta(hours=+9), "JST")
for dat in jsondata["list"]:
    jst = str(datetime.fromtimestamp(dat["dt"], tz))[:-9]
    print("UST={ust}, JST={jst}".format(ust=dat["dt_txt"], jst=jst))
```

```
{'city': {'coord': {'lat': 35.6895, 'lon': 139.6917},
          'country': 'JP',
          'id': 1850144,
          'name': 'Tokyo',
          'population': 12445327,
          'sunrise': 1687894045,
          'sunset': 1687946462,
          'timezone': 32400},
 'cnt': 40,
 'cod': '200',
 'list': [{'clouds': {'all': 83},
           'dt': 1687932000,
           'dt_txt': '2023-06-28 06:00:00',
           'main': {'feels_like': 36.22,
                    'grnd_level': 1003,
                    'humidity': 62,
                    'pressure': 1008,
                    'sea_level': 1008,
                    'temp': 31.38,
                    'temp_kf': 1.03,
                    'temp_max': 31.38,
                    'temp_min': 30.35},
           'pop': 0.08,
           'sys': {'pod': 'd'},
           'visibility': 10000,
           'weather': [{'description': 'broken clouds',
                        'icon': '04d',
                        'id': 803,
                        'main': 'Clouds'}],
           'wind': {'deg': 166, 'gust': 4.82, 'speed': 4.47}}
(以下40日分)
```
# 5日間の気温を折れ線グラフに
5日先の秋葉原の気温を折れ線グラフにし表示します。

```
import requests
import json
from pprint import pprint
from datetime import datetime, timedelta, timezone

import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

#forecastデータ
url = "https://api.openweathermap.org/data/2.5/forecast?q={city_name}&units=metric&appid={API_key}"
# xxxxxには取得したAPIキーを入れる
url = url.format(city_name = "Tokyo", API_key = "xxxxx")
jsondata = requests.get(url).json()

#データフレーム
df = pd.DataFrame(columns=["気温","体感温度"])

#タイムスタンプを変換
tz = timezone(timedelta(hours=+9), "JST")
for dat in jsondata["list"]:
    jst = str(datetime.fromtimestamp(dat["dt"], tz))[:-9]
    #print("UST={ust}, JST={jst}".format(ust=dat["dt_txt"], jst=jst))
    
    temp = dat["main"]["temp"]
    feels_like = dat["main"]["feels_like"]
    df.loc[jst] = [temp,feels_like]
    #df.loc[jst] = temp

df.plot(figsize=(15,8))
plt.ylim(-10, 40)
plt.grid()
plt.show()
```
![download.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337578/e015d9aa-66b1-2c05-5f1a-5cc3f99bc42b.png)
予測によると、秋葉原のこれから先5日間の気温は大きく上昇することはないようです。これに加え天候・風速なども合わせてデータとすると非常に有用な予測となります。

有償プランで30日の天候気温などが予測できれば災害対策や農業の労働生産性を改善になる素晴らしいシステムが実現できるのではないかと考えました。