ASTERIA Warpで地震情報をSlackに通知する：気象庁APIバージョン

一昨日、「[ASTERIA Warpで地震情報をSlackに通知する][Pre]」という記事を公開した途端に気象庁のサイトがリニューアルされてあっという間に賞味期限を迎えてしまいました。
これがスクレイピングの痛いところで、サイトのリニューアルによって簡単に壊れてしまいます。もともとウェブサイトは人が読むことを前提としていて、それは致し方ないこと。なので、ある程度仕様の保証されているAPIを使いたいところなのであります。

で、なんとこの度のリニューアルで、非公式・非公開ではありますが、APIらしきものができたらしい。なんで、がっくりしていたのですが、昨日仕事が落ち着いた夕方くらいに1時間ほどかけて、そのAPIを使ってSlack通知を復活させることに成功しました。この短時間で新しい仕様にマッチさせることができるあたりノーコードたるASTERIA Warpのいいところですね。（自画自賛）

一応、「政府標準利用規約」ってのに則っていれば使って良さそうな雰囲気はある。ただし、仕様自体は非公開なので、安定性とか仕様変更されない保障はないところは気をつけておきましょう。スクレイピングと同様ですね。

* [「政府標準利用規約（第 2.0 版）」](https://cio.go.jp/sites/default/files/uploads/documents/opendata_nijiriyou_betten1.pdf)
* [「政府標準利用規約（第 2.0 版）」の解説
](https://www.kantei.go.jp/jp/singi/it2/densi/kettei/gl2_betten_1_kaisetu.pdf)
* [政府標準利用規約：Wikipedia](https://ja.wikipedia.org/wiki/%E6%94%BF%E5%BA%9C%E6%A8%99%E6%BA%96%E5%88%A9%E7%94%A8%E8%A6%8F%E7%B4%84)

というわけで、新しいAPIを使ったやり方で前の記事をところどころ修正しつつもう一度書いていきます。


# 経緯

先日発生した地震で、社内で安否確認するために自動的にSlackに通知されるといいよねー的な話があり、とりあえず実現可能そうか試してみた。

要件的には、「地震が起きたらSlackに投稿して、安否確認のレスポンスを促す」ということ。もちろん、本当に被害が大きいときにSlackが大丈夫かとか、メールの方がキューイングされるので安心ではないか等々議論の余地はあるが、ひとまず、「地震が起きたらなにかする」を自動化できるかが自分的には興味があったのでやってみたわけです。

# プラットフォームの選択
コードで書けばできるのは分かってるわけですが、社内なので、まずはノーコードのプラットフォームとして **[ASTERIA Warp][Warp]** か **[Gravio](https://www.gravio.com/jp)** が候補に上がる。
Gravioだと基本的にエッジに置くことになり、これまた災害時のものとしてはイマイチなので却下。なので、今回は クラウドにも置けて比較的稼働が保証できるであろう **[ASTERIA Warp][Warp]** でやってみた。 

# 地震情報をどうやって取得するか
地震情報といえば **緊急地震速報**。なわけですが、安否確認を促す前提であれば、即時性よりも正確さが肝になる。誤爆も多いこと、通知を受けること自体が簡単でないことを考えると却下。

気象庁のサイトはどうか？
~~「[地震情報（震源・震度に関する情報）][JMAEarthquake]」というページにいい感じのデータがまとまっている。震度やマグニチュード、場所も含めて、だいたい3～5分で情報が発表されているようだ。これくらいのタイムラグなら問題なし。時刻が全角だったり色々難はありますが、Warpを使えばなんとかなりそう。~~ (2021.2.24のリニューアルでサイトがごそっと変わりました)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/d423ad30-4b08-76c4-c072-6b9d443aead9.png)
機械的にやるなら「[気象庁防災情報XMLフォーマット形式電文の公開（PULL型）](http://xml.kishou.go.jp/xmlpull.html)」というのもあるようなのだが、こちらもいまいち使い勝手がよくなさそう。Webhookとかそういうのあればいいのになぁ。。。平井大臣、頼みます。

# が、気象庁のサイトリニューアル

で、「[ASTERIA Warpで地震情報をSlackに通知する][Pre]」でやってみたわけですが、直後にサイトリニューアルにより程なく撃沈。こんなきれいなサイトになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/eb4102b8-2061-d3d5-224f-e8b2fd83b6d8.png)

Twitterの噂でAPIができたと聞いたのでいろいろ情報を探っていると、やっぱりJSON形式で情報を取得していることが判明。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/94a81896-6d81-e3ff-f407-4a9e20903255.png)
こいつを使わない手はない。というわけでこの情報を利用することに決定。

# 使用環境

使った環境は以下の通り。Warpの最新版でございます。

* ASTERIA Warp 2012.0991
 
# フローを作成する
概要はこんな感じ。

* 定期的に~~気象庁のサイトをスクレイピングして情報を取得する~~ → 気象庁のAPIで情報を取得する
* ある震度以上のときのみSlackに投稿する
* Slack投稿には、時刻、場所、マグニチュード、最大震度、~~詳細ページへのURL~~の情報を含む
* 二重投稿を防ぐために最後に投稿した地震の発生時刻を管理する

フローの全体感はこんな感じになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/1d738ea0-196b-d4b0-2545-0528e245bf15.png)

以下、ポイントを説明していきます。

## APIから地震情報を取得する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/09d5153f-3c0f-b2b2-d05f-0fa19b39d180.png)

前回よりコンポーネントは増えてますが、やることは単純になってます。

まずはRESTコンポーネントで気象庁のAPIを呼び出します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/d1c5e103-0b9c-d892-836f-9694ca07ce3a.png)

これをJSONDecodeコンポーネントを使って情報を抽出します。JSONDecodeは、JSONの構造をそのままXML形式にマッピングします。JSONもXMLも構造化されてるところは同じですが、若干違うのでパラメータを注意しながら設定します。

APIの結果はこのようなJSON構造になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/94098cb6-0917-1e77-8aa9-8b0e6e29d042.png)

パラメータ名は正直ドキュメントなしでは分かりづらいですが、値を見てだいたい想像つけます。
構造としては、地震の情報がJSONObjectになっていて、それが配列になっているようです。なので、次のようにパラメータを設定します。まず、**JSON種別**は**配列**を選択。これはルートがJSON配列になっているからですね。ルート要素名はとりあえずなんでもいいですが、`root`にしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/9d840a3b-4661-35b9-05c8-f3464c5f93e0.png)

抽出のポイントとなるのはストリームの定義です。
このような感じで書いておきます。必要そうなのは、

|キー|説明|
|:--|:--|
|rdt|レポート時刻|
|anm|震央|
|maxi|最大震度|
|mag|マグニチュード|
|at|発生時刻|
|ttl|情報種別|
|eid|地震ID？たぶん地震毎にユニークなIDがふられている|
|ser|シリアル番号？eid毎の情報の番号？|

`rdt`と`at`は日付なのでDateTime型、`mag`は小数なのでDecimal型にし、あとはString型で定義。いくつかのわかりやすいものには表示名をつけておきます。いくつか他にも使いそうなキーをフィールドとして定義しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/554c96f8-cf2a-7516-36e3-2daa22e4e871.png)

次のMapperで、値を使いやすいようにXMLからRecordに変換します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/97706e4d-581c-730c-1d08-6e1a33de4329.png)

レコードのフィールド定義は、「入力ストリーム定義を出力にコピーする」を使うと簡単です。右クリックで出てきます。これでコピーするとXMLストリームとしてコピーされるので、Recordに変更するといい感じでフィールドだけ残ります。コピーされたら、使いやすいように順序を変えておくといいと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/a8328b89-e15d-8801-ad0c-ac9ac9de9204.png)

あと、前回もそうでしたが、震度は「+」「-」がついているのでそれを外して条件判断しやすくしておきます。**最大震度数値**を出力フィールドに追加し、**震度**から最初の1文字だけをLEFT関数で引っ張ってきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/b0d61ae7-aab4-ae01-aa9b-4eaecd687078.png)

あとは、JSON配列を扱っているのでJSONDecodeからの出力がループしています。
これをソートできるように一旦ループを終了し、発生時刻の昇順にソートしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/3e622d4f-62f3-3ad6-674b-5c74e07269ff.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/fe63dea4-066f-0ff8-d229-350407e2ef0a.png)

## 投稿条件を指定する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/71e08307-3de9-3b57-4803-6f1029921532.png)

今回は安否確認が必要なので最大震度4以上としておきます。最大震度が4以上ならSlack投稿へ、そうでなければ無視します。
また、多重投稿を防ぐため、最後に投稿した地震発生日時よりも新しいときだけになるように条件を追加しておきます。
さらにいろいろ見ていると、震度情報以外にも速報とかいろいろはいっているので、タイトル(`ttl`)が`震源・震度情報`
、`ser`が`1`のときだけを拾ってくるようにします。`ser`は`0`だと情報がまだ不完全、`2`以降は大きな地震のときにさらに詳細に震度情報が入ってきたりするのですが、安否確認だけなら`1`のときの情報だけで十分だからです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/20a40ccc-4dc5-a3e9-c528-785479880814.png)

最終的な条件式はこんな感じですね。前回より少し雑多になりました。

```:BranchStart条件式
record.発生時刻 > $exvar.Earthquake.LastProcessed && record.最大震度数値 >= $exvar.Earthquake.MinSeismicIntensity && record.タイトル = "震源・震度情報" && record.ser = 1
```

最終投稿の地震発生日時を保存するには、外部変数のアプリケーション変数を利用します。こうすることで、Warpを終了したりしても情報が残っていて便利です。条件式で参照するには `$exvar.`のプレフィックスで参照できます。
さらに今回、テスト用に最大震度を変えやすいようにここに定数 **MinSeismicIntensity**を追加して利用しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/a9e6169b-569f-e5c4-8f18-f49707bc92d4.png)


## Slackに投稿

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/b96945d3-5574-5c47-f1f5-eecf8dbe0835.png)

メッセージを作成します。要件の通り、時刻、場所、マグニチュード、最大震度を含めます。今回詳細ページがないので、そこはやめました。それ以外はほぼ同じです。ついでに今回は安否確認を促すメッセージも追加しておきました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/ea1c2b21-8e76-35b9-4f7c-859f1ec453cb.png)



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/0f8dec05-2e1e-ddfa-463d-5361d48bec5e.png)


## 最終投稿日を保存する
投稿したら投稿した地震の発生日時をアプリケーション変数に保存しておきます。これはメッセージ作成のマッパーで同時に実行しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/6b49fa12-203c-b270-5171-ffc7eab3f5d1.png)


# 実行
震度4以上の地震が拾われてSlackに投稿されました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/beeb6110-ac25-ad75-5f7c-eee961339950.png)

# スケジュールトリガーを設定する
気象庁のページは分単位で時刻管理されているようなので、1分毎にフローが実行されるようにスケジュールトリガーを作成しておきます。これより間隔を延ばすと即時性が薄くなりますし、短くしてもあまり意味がないのでこれくらいで。気象庁のサイトにあまり負荷を書けるのも良くないので程々に。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41603/41cb908b-c5a9-b27c-0f5d-b7cb9d480b95.png)

# 結果
震度4以上だとなかなか発生しないので、今回も震度3にして放置しておきたいと思います。さてさて。

# 今後の課題
とりあえず大きな地震が発生したらSlackに投稿することはできました。
あとは、メッセージをうまく調整すれば簡易な安否確認プラットフォームができますね。
課題としてはいかのようなことがまだ残っていますので、また考えていければなぁと思ってます。

* 安否確認をどうするか。リアクション？ボット？
* 全国の地震を全部拾うのか？震央は文字列ですが表示されてるので、なんとか位置を拾うことはできるかもしれないが、出張等で社員がどこにいるかは特定できないので日本全国拾っておくべきなのか。事業所があるところだけにしたほうがいいのか？ → API中には震央の緯度経度、都道府県なども取得できるので、いろいろ調整はできそうです。


# 参考

* [ASTERIA Warpで地震情報をSlackに通知する(元記事)][Pre]
* [気象庁の地震情報サイト][JMAEarthquake]
* [ASTERIA Warp][Warp]
* [JSONDecodeコンポーネント][JSONDecode]


[Pre]:https://qiita.com/zrock/items/a84f66a032a7dad8d0f4
[JMAEarthquake]:https://www.jma.go.jp/bosai/map.html#10/37.693/140.982/&elem=int&contents=earthquake_map
[Warp]:https://www.asteria.com/jp/warp/
[JSONDecode]:https://help.asteria.com/documentation/warp/ja/2012/flow/designer/reference/component/JSONDecode.html