【GAS】気象庁XMLから地震情報を取得してGoogle Chatに自動通知する方法

## はじめに

本記事では、Google Apps Script (GAS) を活用し、**地震発生時に Google Chat へ自動で安否確認メッセージを送信するシステム** を構築する方法を紹介します。  
このシステムは、気象庁の地震速報を自動取得し、**震度5弱以上**の地震が発生した場合に、Google Chat のスペースにメッセージを送信する仕組みになっています。
GAS を使った業務自動化に興味のある方は、ぜひ参考にしてください。

:::note warn
本記事で紹介するシステムは、Google Workspace の有料版を利用していることを前提としています。無料の Google アカウントでは一部の機能が制限される可能性があるため、ご注意ください。
:::


### この記事でわかる・できること

- 気象庁の XML データを取得し、地震情報を抽出する方法
- Google Apps Script を使って Google Chat に自動通知を送る方法


### 動作環境・使用するツールや言語

- **OS**: 任意 (Windows / macOS / Linux)
- **ツール**: Google Apps Script (GAS), Google Chat Webhook
- **言語**: Google Apps Script


## 地震情報の取得方法
気象庁が公開している**気象庁防災情報XML**を利用し、地震の検知および情報取得を行います。

https://xml.kishou.go.jp/xmlpull.html

高頻度フィールドの地震火山XMLから、タイトルが「震源・震度に関する情報」のデータを取得し、その中の最大震度によって通知を出すか否かを判定しています。

## ソースコード

```javascript
function sendEQAlert() {
 // constants
 // ネームスペース
 const eqvolxm_uri = 'https://www.data.jma.go.jp/developer/xml/feed/eqvol.xml';
 const w3org05_uri = 'http://www.w3.org/2005/Atom';
 const seismol_uri = 'http://xml.kishou.go.jp/jmaxml1/body/seismology1/';
 const informa_uri = 'http://xml.kishou.go.jp/jmaxml1/informationBasis1/';

 const eqlevel_num = 5; // 通知下限震度
 const diffmin_int = 600000; // 600000ms：10分

 const earthqu_str = '震源・震度に関する情報';

 // 地震火山 XML 関連
 let eqvolxm_conText, eqvolxm_document, eqvolxm_element;
 // 詳細情報 XML 関連
 let vxse53_conText, vxse53_document, vxse53_element;
 // ネームスペース
 let w3org05_ns, seismol_ns, informa_ns;
 // 配列・配列要素
 let entryfd_array = [], entrynd_array = [], entrynd_elem;
 // 文字・数字
 let trigger_date, title_str, urlid_str, eqlevel_str, eqlevel_conv;
 let eqinfo_str, hypoce_str;
 // XML 取得
 eqvolxm_conText = UrlFetchApp.fetch(eqvolxm_uri).getContentText();
 eqvolxm_document = XmlService.parse(eqvolxm_conText);
 eqvolxm_element = eqvolxm_document.getRootElement();
 // entry ノードをすべて entrydf_array 配列に push する
 entrynd_array = eqvolxm_element.getDescendants();
 for(var i in entrynd_array) {
   entrynd_elem = entrynd_array[i].asElement();
   if ( entrynd_elem != null && entrynd_elem.getName() == 'entry') {
       entryfd_array.push(entrynd_elem);
   }
 }
 // xmlns 定義
 w3org05_ns = XmlService.getNamespace(w3org05_uri);
 seismol_ns = XmlService.getNamespace(seismol_uri);
 informa_ns = XmlService.getNamespace(informa_uri);
 
 // トリガー発動時刻取得(ミリ秒)
 trigger_date = new Date().getTime();
 // entry の一つ一つの要素を true になるまで見る
 entryfd_array.some(function(fdElement, j) {
   
   // title と updated を取得
   title_str = fdElement.getChild('title', w3org05_ns).getText();
   updat_date = (new Date(fdElement.getChild('updated',w3org05_ns).getText())).getTime();

    // タイトルが「震源・震度に関する情報」のものを取得する
   if(earthqu_str == title_str){
    if(trigger_date - diffmin_int <= updat_date){
 
       // 詳細情報を取得
       urlid_str = fdElement.getChild('id', w3org05_ns).getText(); 
       vxse53_conText = UrlFetchApp.fetch(urlid_str).getContentText();
       vxse53_document = XmlService.parse(vxse53_conText);
       vxse53_element = vxse53_document.getRootElement();
       // 最大震度取得
       eqlevel_str = vxse53_element.getChild('Body',seismol_ns).getChild('Intensity',seismol_ns)
                   .getChild('Observation',seismol_ns).getChild('MaxInt',seismol_ns).getText();

       //取得した震度を変換する処理
       switch(eqlevel_str){
         case '5-': 
           eqlevel_conv = 5.1; //震度5弱
           break;
         case '5+':
           eqlevel_conv = 5.2; //震度5強
           break;
         case '6-':
           eqlevel_conv = 6.1; //震度6弱
           break;
         case '6+':
           eqlevel_conv = 6.2; //震度6強
           break;
         default:
           eqlevel_conv = eqlevel_str;
       }

       if(eqlevel_conv >= eqlevel_num){
         // 震度が規定値以上なら情報取得
         eqinfo_str = vxse53_element.getChild('Head',informa_ns).getChild('Headline',informa_ns)
                     .getChild('Text',informa_ns).getText(); 
         hypoce_str = vxse53_element.getChild('Body',seismol_ns).getChild('Earthquake',seismol_ns)
                     .getChild('Hypocenter',seismol_ns).getChild('Area',seismol_ns)
                     .getChild('Name',seismol_ns).getText(); 
         
         //チャット送信処理
         try{
           chatSend();
         }catch(e){
           Logger.log(e.message);
         }
         
       }else{
         Logger.log("震度が " + eqlevel_str + " のため処理せず");
       }
     }else{
       // true を返すことで some から抜ける
       return true;
     }
   }

   function chatSend() {
     const webhookUrl = "WebhookのURL";
     const googleformUrl = "GoogleformのURL"; 

     //通知するメッセージ
     const mailMessage ="【" + eqinfo_str + "】\r\n" + 
      "最高震度: " + eqlevel_str + "\r\n" +
      "震源地: " + hypoce_str + "\r\n\r\n" +
      "以下の回答フォームから、安否状況を報告してください。（報告期限：1時間以内）\r\n" + googleformUrl;
    
     const data = {
     'text': mailMessage
     };
     const options = {
       'method': 'post',
       'headers' : {
         'Content-Type': 'application/json; charset=UTF-8'
       },
       'payload': JSON.stringify(data)
     };
     // Webhook URL に POST
     const response = UrlFetchApp.fetch(webhookUrl, options);
     // レスポンスをログ出力
     Logger.log(response.getContentText());
   }
 });
}
```
**WebhookのURL、GoogleフォームのURL**を変更すればコード自体は完成します。

Webhook URLの取得方法については、こちらの記事などを参考にして下さい。

https://blog.css-net.co.jp/entry/gas/chat-webhook#2-2-1-Webhook-URL-%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%81%A8%E7%A2%BA%E8%AA%8D

<br>処理の流れとしては、まず、タイトルが「震源・震度に関する情報」かつ直近10分以内に更新されたものをXMLから取得しています。
そして、XMLから取得した最大震度の情報をもとに、最大震度が規定値より大きいかどうかを判定します。
判定の結果、震度5弱以上ならChatでメッセージを送信する、という流れです。


## GAS のトリガー設定
トリガーを以下のように設定することで、10分間隔でコードを実行するようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3866388/75321171-817d-48e1-a0de-61b630171c92.png)


## 通知メッセージ例
Chatに送信されるメッセージは以下のようになります。
（震度5弱は「5-」、5強は「5+」のように表示されます。）

![jisin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3866388/b547d8e0-5f77-4f2b-8a40-b619f35964f3.png)




## おわりに・まとめ
本記事では、Google Apps Script を用いて気象庁の地震速報を取得し、Google Chat に自動通知するシステムの作り方を紹介しました。
このシステムを活用することで、迅速な情報共有と安否確認を行うことが可能になります。ぜひ、業務の効率化や防災対策に役立ててみてください！

## 参考資料


ソースコードの参考にした記事:

https://note.com/hrfr84/n/n7b7ee971bada