2023年の天気図をGIFアニメーションで振り返ってみる

# 概要
2023年も残りわずかということで、今年の天気図をGIFアニメーションで動画化して振り返ってみました。

# データの準備
気象庁のサイトでは毎日3時間ごと（日本時間0時除く）の日本周辺域の実況天気図を公開しています。

https://www.jma.go.jp/bosai/weather_map/

これらの天気図を使いたいところなのですが、画像のURLパスを見てみると…

```
https://www.jma.go.jp/bosai/weather_map/data/png/20231128020830_0_Z__C_010000_20231128000000_MET_CHT_JCIspas_JCP600x581_JRcolor_Tjmahp_image.png
```
となっていました。
最後のスラッシュの後に、日時の情報があるのでこれらを変えれば画像にアクセスできるはずです。

しかし、よくよく見てみるとURLの形式は以下のようになっています。

```
https://www.jma.go.jp/bosai/weather_map/data/png/  #ここは共通
20231128020830   # 画像をアップした日時(UTC:YYYYMMDDhhmmss)？
_0_Z__C_010000_  # ここも共通
20231128000000   # 天気図の日時(UTC:YYYYMMDDhhmmss)
_MET_CHT_JCIspas_JCP600x581_JRcolor_Tjmahp_image.png  # ここも共通
```

**画像のアップロード日時**らしきものがURLに入ってしまっているため、これだと難しいですね…[^1]。
[^1]:全探索すれば不可能ではないです。

そこで、[**100年天気図データベース**](http://agora.ex.nii.ac.jp/digital-typhoon/weather-chart/)という気象庁の天気図をアーカイブしているサイトを使います。

http://agora.ex.nii.ac.jp/digital-typhoon/weather-chart/

例えば2023年11月28日9時の天気図画像のURLは
```
http://agora.ex.nii.ac.jp/digital-typhoon/weather-chart/wxchart/202311/SPAS_COLOR_202311280000.png
```
となっており、`/202311/`や`202311280000`の部分を書き換えれば他の日時の天気図へもアクセス可能です。

データ取得はPythonで行いました。コード例は以下です。
日本時間の2023年1月1日3時～11月27日21時までの天気図画像を`/data/`ディレクトリ内に保存します。

```python:./data/collect-data.py
import os
import urllib.request
import urllib.error
import urllib.parse
import time
import datetime

#### 期間を指定 ####
# 開始日時(UTC)
start = datetime.datetime(2022, 12, 31, 18, 0, 0)
# 終了日時(UTC)
end = datetime.datetime(2023, 11, 27, 12, 0, 0)
####################


savedir = './'  # データ保存先ディレクトリ
if not os.path.exists(savedir):
    os.makedirs(savedir)

d = start
while d <= end:
    if d.hour in [0, 3, 6, 9, 12, 18, 21]:
        # データ取得先URL
        url = f'http://agora.ex.nii.ac.jp/digital-typhoon/weather-chart/wxchart/{d.year}{d.month:02d}/SPAS_COLOR_{d.strftime("%Y%m%d%H%M")}.png'

        # ファイル名
        basename = os.path.basename(url)
        savename = os.path.join(savedir, basename)

        # ファイルが存在しない場合のみダウンロード
        if not os.path.exists(savename):
            try:
                urllib.request.urlretrieve(url, savename)
                time.sleep(1)
                print('downloaded:', basename)
            except urllib.error.URLError as e:
                print(url)
                print(e)
        else:
            print('file exists:', basename)

    d += datetime.timedelta(hours=3)
```


# GIFアニメーションの作成
Anthropicが開発したAIチャット「Claude」に、GIFの作り方を聞いたら一発で動きました。

https://claude.ai/chats

Claudeの回答コードで`.save()`の引数を若干いじったものを、下に載せておきます。

```python:generate-gif.py
from PIL import Image
import glob

frames = []
imgs = glob.glob("./data/*.png")
for i in imgs:
    new_frame = Image.open(i)
    frames.append(new_frame)

frames[0].save('animation.gif', format='GIF', append_images=frames[1:], save_all=True, duration=50, loop=0, optimize=True)
```

そして、出来上がったGIFアニメーションがこちらです。
※全部やると163MBとなり、Qiitaにアップロードできなかったので、下ではShort版を見せています。

![animation-short.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1900599/907e399d-b7c0-7e59-24b1-8b4acaa90c41.gif)

`save()`の引数を簡単に説明しておくと、

- `save_all`：`True`にしておかないとアニメーションにならない
- `duration`：1フレームあたりの表示時間（単位：ミリ秒）
- `loop`：`0`にすると無限ループ、`1`以上にするとその指定回数で再生停止
- `optimize`：`True`にするとGIF画像の最適化[^2]が行われる

# ソースコード
今回使ったコードなどはGitHubに上げています。
本記事のは`02-weathermap`に入っています。

https://github.com/matsuda-tkm/qiita-calender-2023/



[^2]:**Claudeによると**、GIFのフレーム間で共通しているピクセルは1回しか定義しない、ピクセルのパターンを辞書登録して圧縮する、などしているらしい（事実確認はしていない）。