Help us understand the problem. What is going on with this article?

気象庁アメダス観測データのAPI「JJWD」をきちんと作り直した

長らく停止しておりましたが2020年9月、再始動しました。
https://jjwd.info/
image.png

JJWD とは / 作り直した動機

JSONized Japanse Weather Dataの略で、気象庁が公開しているCSV形式のアメダス観測値データを使いやすいJSON形式に変換して提供するAPIサービスです。

当初は2017年のアドベントカレンダーに間に合わせるために構築したAPIでした。

停止したにもかかわらず、記事にコンスタントにLGTMがついておりました。
需要はあるものを放置したくはないですし、データを必要としている人も多いと思ったのできちんと使える形で整備した次第です。

image.png

使い方

使用に際して

気象業務法に抵触しない範囲で使用してください。
観測された値を基に何らかの処理を行って独自の予測を行う場合、資格ならびに気象庁の許可が必要です(観測されたままの値や、発表された気象予報を第三者に伝達することは問題ありません)。

また、気象庁以外が「警報」「注意報」などを発表することは許可されていません。

観測所検索

GET https://jjwd.info/api/v2/stations/search?

URL に以下のパラメータを付与して検索できます。

  • stn_name_ja: 観測所名 日本語(部分一致)
  • stn_name_en: 観測所名 英語(部分一致)
  • pref_ja: 都府県振興局 日本語(部分一致)
  • pref_en: 都府県振興局 英語(部分一致)
  • address: 住所 日本語(部分一致)

たとえば
https://jjwd.info/api/v2/stations/search?pref_ja=東京&address=世田谷
では、都府県振興局に 東京 を含み、なおかつ住所に 世田谷 を含む観測所が取得できます。

北海道は振興局単位なのでご注意ください。

/api/v2/stations/search?pref_ja=東京&address=世田谷
{
    "about": "JJWD - JSONized Japanese Weather Data:  https://jjwd.info/",
    "datasource": "https://www.data.jma.go.jp/obd/stats/data/mdrr/",
    "author": "Original data are from Japan Meteorological Agency. API data are modified by jjwd.info .",
    "version": "v2.0",
    "stations": [
        {
            "stn_num": 44126,
            "pref_ja": "東京",
            "pref_en": "Tokyo",
            "stn_type": "RobotRain",
            "stn_name_ja": "世田谷",
            "stn_name_ja_kana": "セタガヤ",
            "stn_name_en": "SETAGAYA",
            "stn_multipoint": false,
            "target_main_ja": null,
            "target_sub_ja": null,
            "target_main_en": null,
            "target_sub_en": null,
            "stn_temp": false,
            "stn_daylight": true,
            "address": "世田谷区岡本",
            "address_sub": null,
            "lat": 35.6267,
            "lng": 139.62,
            "lat_sub": null,
            "lng_sub": null,
            "elevation": 35,
            "alt_anemometer": null,
            "alt_thermometer": null,
            "elevation_sub": null,
            "alt_anemometer_sub": null,
            "alt_thermometer_sub": null,
            "start_date_rain": "1974-11-01",
            "start_date_multi": null,
            "snow_stn_num": null,
            "updatedAt": "2020-09-11T19:00:32.933Z",
            "preall": {
                "year": 2020,
                "month": 9,
                "day": 12,
                "hour": 19,
                "minute": 50,
                "precip_1h_new_record_month": null,
                "precip_1h_new_record_in_decade": null,
                "precip_3h_new_record_month": null,
                "precip_3h_new_record_in_decade": null,
                "precip_6h_new_record_month": null,
                "precip_6h_new_record_in_decade": null,
                "precip_12h_new_record_month": null,
                "precip_12h_new_record_in_decade": null,
                "precip_24h_new_record_month": null,
                "precip_24h_new_record_in_decade": null,
                "precip_48h_new_record_month": null,
                "precip_48h_new_record_in_decade": null,
                "precip_72h_new_record_month": null,
                "precip_72h_new_record_in_decade": null,
                "precip_daily_new_record_month": null,
                "precip_daily_new_record_in_decade": null,
                "precip_1h": 0.5,
                "precip_1h_q": 8,
                "precip_1h_daily_max": 13,
                "precip_1h_daily_max_q": 5,
                "precip_3h": 0.5,
                "precip_3h_q": 8,
                "precip_3h_daily_max": 24,
                "precip_3h_daily_max_q": 4,
                "precip_6h": 1,
                "precip_6h_q": 8,
                "precip_6h_daily_max": 24,
                "precip_6h_daily_max_q": 4,
                "precip_12h": 5,
                "precip_12h_q": 8,
                "precip_12h_daily_max": 26.5,
                "precip_12h_daily_max_q": 4,
                "precip_24h": 29,
                "precip_24h_q": 8,
                "precip_24h_daily_max": 29,
                "precip_24h_daily_max_q": 4,
                "precip_48h": 29,
                "precip_48h_q": 8,
                "precip_48h_daily_max": 29,
                "precip_48h_daily_max_q": 4,
                "precip_72h": 29.5,
                "precip_72h_q": 8,
                "precip_72h_daily_max": 29.5,
                "precip_72h_daily_max_q": 4,
                "precip_daily": 28.5,
                "precip_daily_q": 4,
                "updatedAt": "2020-09-12T11:13:41.878Z"
            },
            "max_wind": null,
            "max_gust": null,
            "max_temp": null,
            "min_temp": null
        }
    ]
}

観測所番号を指定して取得

GET https://jjwd.info/api/v2/station/{stn_num}

観測所番号がわかっている場合はこちらを使用することができます。

/api/v2/station/46046
{
    "about": "JJWD - JSONized Japanese Weather Data:  https://jjwd.info/",
    "datasource": "https://www.data.jma.go.jp/obd/stats/data/mdrr/",
    "author": "Original data are from Japan Meteorological Agency. API data are modified by jjwd.info .",
    "version": "v2.0",
    "station": {
        "stn_num": 46046,
        "pref_ja": "神奈川",
        "pref_en": "Kanagawa",
        "stn_type": "RobotRain",
        "stn_name_ja": "相模原中央",
        "stn_name_ja_kana": "サガミハラチュウオウ",
        "stn_name_en": "SAGAMIHARACHUO",
        "stn_multipoint": false,
        "target_main_ja": null,
        "target_sub_ja": null,
        "target_main_en": null,
        "target_sub_en": null,
        "stn_temp": false,
        "stn_daylight": true,
        "address": "相模原市中央区中央",
        "address_sub": null,
        "lat": 35.5717,
        "lng": 139.37,
        "lat_sub": null,
        "lng_sub": null,
        "elevation": 149,
        "alt_anemometer": null,
        "alt_thermometer": null,
        "elevation_sub": null,
        "alt_anemometer_sub": null,
        "alt_thermometer_sub": null,
        "start_date_rain": "1975-05-16",
        "start_date_multi": "1975-05-16",
        "snow_stn_num": null,
        "createdAt": "2020-08-29T15:44:50.463Z",
        "updatedAt": "2020-09-11T19:00:32.937Z",
        "preall": {
            "year": 2020,
            "month": 9,
            "day": 12,
            "hour": 19,
            "minute": 50,
            "precip_1h_new_record_month": null,
            "precip_1h_new_record_in_decade": null,
            "precip_3h_new_record_month": null,
            "precip_3h_new_record_in_decade": null,
            "precip_6h_new_record_month": null,
            "precip_6h_new_record_in_decade": null,
            "precip_12h_new_record_month": null,
            "precip_12h_new_record_in_decade": null,
            "precip_24h_new_record_month": null,
            "precip_24h_new_record_in_decade": null,
            "precip_48h_new_record_month": null,
            "precip_48h_new_record_in_decade": null,
            "precip_72h_new_record_month": null,
            "precip_72h_new_record_in_decade": null,
            "precip_daily_new_record_month": null,
            "precip_daily_new_record_in_decade": null,
            "precip_1h": 0,
            "precip_1h_q": 8,
            "precip_1h_daily_max": 10,
            "precip_1h_daily_max_q": 5,
            "precip_3h": 0.5,
            "precip_3h_q": 8,
            "precip_3h_daily_max": 10.5,
            "precip_3h_daily_max_q": 4,
            "precip_6h": 2,
            "precip_6h_q": 8,
            "precip_6h_daily_max": 13.5,
            "precip_6h_daily_max_q": 4,
            "precip_12h": 5,
            "precip_12h_q": 8,
            "precip_12h_daily_max": 16,
            "precip_12h_daily_max_q": 4,
            "precip_24h": 18.5,
            "precip_24h_q": 8,
            "precip_24h_daily_max": 18.5,
            "precip_24h_daily_max_q": 4,
            "precip_48h": 18.5,
            "precip_48h_q": 8,
            "precip_48h_daily_max": 18.5,
            "precip_48h_daily_max_q": 4,
            "precip_72h": 19,
            "precip_72h_q": 8,
            "precip_72h_daily_max": 19,
            "precip_72h_daily_max_q": 4,
            "precip_daily": 17.5,
            "precip_daily_q": 4,
            "updatedAt": "2020-09-12T11:13:41.884Z"
        },
        "max_wind": null,
        "max_gust": null,
        "max_temp": null,
        "min_temp": null
    }
}

各パラメータの意味など、詳細はドキュメントをご参照ください
https://jjwd.info/doc-ja.html

データソース

基地局データ

気象庁の地域気象観測所一覧 [ZIP圧縮形式]から取得しています。
ZIP形式の中にCSVが格納されているのですが、開発中に提供されているファイルの名前が変わってしまい、取得できなくなるエラーが発生しました(気象庁さん、配布するファイルの名称は固定してほしい……)。

観測値データ

「最新の気象データ」CSVダウンロードから取得しています。データのつらさは相変わらずです。その辺は前回と同じようになんとかしました。

実装

Node.js + Express.js + Sequelize の無難な構成です。

ざっくりとしたディレクトリ構成
/
| - /schedule
| - | - fetch_amedas_stations.js
| - | - fetch_csv_files.js
| - /models
| - /public
| - | - /css
| - | - /js
| - app.js
| - package.json

データベース

Heroku Postgresを使用しています。件数的にはHobby devに収まるので無料の枠内で運用しています。

アクセス数が増えたら Connection Limit などを緩和するために Standard 0 などにアップグレードする必要がありそうです(Hobby Basicは行数の制限緩和のみなので)。

データの更新

Heroku Scheduler でNodeのスクリプトを走らせてUpsertしています。
Heroku Scheduler を使うと、Herokuにデプロイした repository 上にあるスクリプトを定時に走らせる事が可能です。DBのモデルやnpmパッケージも本体と共用できるので便利です。

観測値データの更新は 10 分おきに取得しています。
基地局データの更新は 1 日おきに取得しています。ただし、生の CSV データが配布されている観測値と異なり、基地局データは元データが zip で圧縮されているので /tmp にデータをダウンロードした上で展開する処理を挟んでいます(Heroku では /tmp/log 以外にファイルを書き込めないので要注意です)。

静的ページ(ドキュメント)

シンプルに app.use(express.static(__dirname + '/public')); で配信しています。

デザインには Tailwind.css を使用しています。
便利なCSSフレームワークですが、何もしないとファイルサイズが巨大なので、デプロイ前にpurgeやminifyしましょう。

また、軽くCSSアニメーションを入れたかったのでanimistaを使用しています。

デプロイ

Heroku を使用しています。
独自ドメインを使用したかったので、いまのところ月額7ドルの Hobby Dyno を使用しています。

独自ドメイン

公式ドキュメントを参考に、バリュードメインで以下のように CNAME で設定しています。

ドメイン設定
cname @ ****************.herokudns.com.
ryo-a
交通・モビリティ、地理、文字、UI、l10n/i18nが好きです。Node.js i18n WG / Electron i18n
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした