LoginSignup
36
32

More than 3 years have passed since last update.

緊急地震速報を強震モニタで受け取りJSONで扱うとめっちゃ速い

Last updated at Posted at 2020-05-13

概要

  • Chrome拡張機能「強震モニタ」からPythonのWebサーバーにJSONをPOST
  • 受け取ったJSONを料理してDiscordのWebhookやSlackなどへ投げる(POST)
  • 早い、安い、美味い
  • どれくらい速いかというと、macbookの強震モニタで速報を受け取った瞬間にはDiscordのチャンネルに出力されているくらい

前提事項

  • CentOS7、Python3で動作確認が取れています。
  • GoogleChromeの拡張機能「強震モニタExtension」を使用します。
  • 上述拡張機能から「地震情報を送信」でPythonのWebサーバーにJSONを飛ばします。

経緯

  • IFTTT経由でTwitterの地震速報アカウントのツイートを取得する仕組みを作りましたが、 Twitterのサーバー状態によってレスポンスタイムにばらつきが出てキレる。 最悪の場合、タイムリーな取得はできない。
  • 緊急地震速報を自分だけでなくDiscordのチャンネルに出力し、共有したい。
  • 地震モニタリングが趣味。
  • 2020年になってざわつき始めたのでデータ収集のため。

どうやって実現するか

データフローイメージ(文字列でごめんね)

強震モニタ地震情報Pushサーバー → CentOS7のGoogleChrome → CentOS7のPython Webサーバー → Discord/Slack/TwitterAPI

CentOS側

  • GUIを扱うのでrunlevel 5で起動
  • Google Chromeインストール
  • Chrome拡張機能の「強震モニタExtension」をインストール
  • 私はPythonでWebサーバー書いたのでPython3をインストール。Node.jsでもなんでも大丈夫だと思います。
  • 拡張機能内の設定で「地震情報を送信」に「http://localhost:8000」を指定

補足

  • Google Chromeのインストールに必要なrpmパッケージの依存関係が複雑(minimal構成でOS立ち上げたことも関係する)
  • 拡張機能から飛ぶデータはJSON形式。
  • 2020年5月13日現在のログから引用します。
{
'type': 'eew', # 緊急地震速報の場合、String型のeewという文字列
'time': '1589131429000', 
'report': '1', # 第一報の場合、String型の1という文字列。最終報はString型のfinalという文字列
'epicenter': '伊予灘', # 震源地
'depth': '60km', # 震源の深さ
'magnitude': 3.5, # 地震の規模を示すマグニチュード
'latitude': 33.8, # ここの2行はおそらく緯度経度的な
'longitude': 132.1, 
'intensity': '2',  # 予想震度
'index': 2
}

欲しい情報は大体String型とFloat型なのでそこだけ意識しておきます。
参考までに加速度検知のログも掲載しておきます。

{
'type': 'pga_alert', 
'time': '1589131441839', 
'max_pga': 0.637, 
'new': True, 
'estimated_intensity': 0, 
'region_list': ['愛媛']
}

Python側

Webサーバーとして動かします。

from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import parse_qs, urlparse
import sendToDiscord # 自前のコードです。この中でお料理してDiscordのWebhookにPOSTしています。


address = ('0.0.0.0', 8000)

class MyHTTPRequestHandler(BaseHTTPRequestHandler):

    def do_GET(self):

        parsed_path = urlparse(self.path)
        self.send_response(200)
        self.send_header('Content-Type', 'text/plain; charset=utf-8')
        self.end_headers()
        self.wfile.write(b'Hello from do_GET')

    def do_POST(self):

        parsed_path = urlparse(self.path)
        content_length = int(self.headers['content-length'])

        sendToDiscord.readJson('{}'.format((self.rfile.read(content_length).decode('utf-8'))))

        self.send_response(200)
        self.send_header('Content-Type', 'text/plain; charset=utf-8')
        self.end_headers()
        self.wfile.write(b'Hello from do_POST')

with HTTPServer(address, MyHTTPRequestHandler) as server:
    server.serve_forever()

頭の悪い書き方をしていますが、サンプルコードに1行加えただけです。
受け取ったJSONをそのまま関数に流し込んで処理させています。

2020/05/19 追記
@sirorabi516 さんへ
DiscordのWebhookに投げる側のコードの一部を貼り付けておきますね。

import json
import requests

webhookUrl = "DISCORD WEBHOOK URL"

'''
Discord Webhookにメッセージ送信する
'''
def sendDiscord(msg):

    # Create JSON
    payload = {
       "content" : "{0}".format(msg)
    }

    '''
    resの中身でステータスコードを確認してリトライ処理を組み込んだ方がよさそう
    '''
    # Send to Discord
    res = requests.post(webhookUrl, json.dumps(payload), headers={'Content-Type': 'application/json'})
    return


'''
Webサーバから受け取ったJSONを辞書に変換してメッセージを作成する
'''
def readJson(jsonData):
    jsonData = json.loads(jsonData)

    '''
    緊急地震速報かつ第一報かつ予想震度3以上なら処理させる
    '''

    # get first eew message
    if jsonData.get('type') == 'eew' and jsonData.get('report') == '1' and int(jsonData.get('intensity')) > 2:
        magnitude = float(jsonData.get('magnitude'))

        # get EQ Data
        epicenter = str(jsonData.get('epicenter'))
        depth = str(jsonData.get('depth'))
        intensity = str(jsonData.get('intensity'))

        '''
        discordの出力メッセージ作成部分。fixを指定すると全文黄色にできる。
        '''
        # add EQ Data
        msg = '''```fix

地 震 速 報 (第1報)

震  源  :  {0}
予想震度  :  {1}
規  模  :  M{2}
深  さ  :  {3}

```'''.format(epicenter, intensity, str(magnitude), depth)
        sendDiscord(msg)    
        return

    '''
    誤報の場合はpga_alert_cancelが送られてくる「らしい」のでそちらも検知するようにする
    '''
    # Alert Cancel
    if jsonData.get('type') == 'pga_alert_cancel':
        msg = '### Cancel Message ###'
        sendDiscord(msg)
        return

私はこんな感じで書きました。
最後の'pga_alert_cancel'は必須だと思ってます。
なぜなら、過去に東京の観測地点から「東京湾北部 震度7」という情報が第一報で飛んできて
それを受け取った私は「え?これは、東京が壊滅するレベルじゃないのか!」とパニックになったからなんですねえ。
結局それは落雷の影響で誤報だったというオチなのですが、情報を他所に流すということは変なパニックを起こさないように
誤報であったこともしっかり伝えてあげないといけません。
もちろん自分のためにも。

注意事項

  • do_GETを残しておかないとChrome拡張機能からJSONが飛んでこない。
  • Chrome拡張機能をデベロッパーモードで確認すると、ヘルスチェック的なことを情報送信先にしている。
  • その時に正常なレスポンスを返さないと情報を送ってもらえない。

最後に

今のところ、コストを掛けずに、かつ爆速で高度利用者向けの地震情報を扱えるのはこの方法が楽かなと思ってます。
JSONでデータを渡されるので自分の好きなようにメッセージに組み込み、
好きな場所に向けて情報を渡せるのでまだまだ他の使い方もできそうかな、という雰囲気もあります。

36
32
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
32