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

AWSで近くの避難場所を通知してくれるLinebotを作ってみた

はじめに

 本記事は「あなたを死なせないためのプログラミング」に感化され,自分もほぼ同じシステムをバックエンドとしてAWSを使って構築することを目的にしました.システムの詳細な内容は元記事も参考にしてください.

 また私も災害対策や避難誘導の一助になるようなシステムを作りたいと思い,「ドローンを活用した災害避難勧告システム」や「スマートスピーカーを利用した避難勧告システム」についての記事も公開を予定していますのでそれも合わせて見ていただければと思います.

対象読者

  • 実際に動くシステムを作ることを目的にしていて,作って学びたい方
  • LINE botの開発がしたい方
  • バックエンドとしてAWSを使いたいAWS†初学者†の方

製作物

友だち追加

最終的にはドローンシステムの構築を視野にいれていたのでアイコンがドローンなのは気にしないでください.位置情報を送ると周辺の避難場所を返します.(2019/11/13現在 東日本の避難場所だけに対応していますm_ _m)

避難場所データ

 全国の避難場所のデータをここから取ってきます.json形式でダウンロードしてください.
(https://www.geospatial.jp/ckan/dataset/hinanbasho/resource/5fe3d23c-03fb-49ee-8a65-4f7495a8ea40)
 その後,元記事などを参考にCSVファイルに変換しておきます.これをすることで後にPythonでPandasを使って処理しやすくなります.

バックエンド構成について

本LINEbotの簡単な構成図を以下に示します.
スクリーンショット 2019-11-13 0.18.48.png

AWSをほぼ初めて扱う方向けにまず大事なAWSでよく使われるサービスを挙げます.
Amazon API Gateway
外部サービスとAWSを連携する口になるもの.
ここではLINEのトークルームのアクションでwebhookされたリクエストデータを受ける
Amazon S3
データを蓄積してくれるストレージ.Google driveみたいなやつ.人間で言うと記憶.
ここには先程作った避難場所のCSVデータを入れておくことになります.
AWS lambda
リクエストデータを処理してくれるところ.サーバー.人間で言うと脳みそ.
lambdaの実行タイミングが設定でき,API Gatewayにリクエストが飛んだ時やS3にデータが追加された時などをトリガにできます.
ここではLINEから来た位置情報から近くの避難場所をLINEで表示できるように返すところ.

この3つだけ分かれば大丈夫です.

実装

 まずLINE botセットアップ,lambdaの構築,Gatewayの構築が必要になります.LINE developerアカウント, AWSのアカウントは事前に用意している前提です.
 以下の記事がとても参考になり,同じようにバックエンドにAWS,pythonを使っていますので,これでまず動くものを作ると良いと思います.
https://xp-cloud.jp/blog/2019/06/24/5560/

 この記事を参考に,ここではlambdaのコードを以下のように実装しました.

import json
import boto3
import urllib
import pyproj
import pandas as pd
import re
import os

# 2点間の距離計算
def calc_distance(ido1, kdo1, ido2, kdo2):
    g = pyproj.Geod(ellps='WGS84')
    result = g.inv(kdo1, ido1, kdo2, ido2)
    distance = result[2]
    return round(distance)

def lambda_handler(event, context):
    url = "https://api.line.me/v2/bot/message/reply"
    method = "POST"
    headers = {
        'Authorization': 'Bearer ' + os.environ['CHANNEL_ACCESS_TOKEN'],
        'Content-Type': 'application/json'
    }
    if event['events'][0]['message']['type'] == 'location':
        s3 = boto3.client('s3')
        bucket_name = 'japan-shelter-all'
        file_name = 'east.csv' 
        response = s3.get_object(Bucket=bucket_name, Key=file_name)
        df = pd.read_csv(response['Body'])

        # 現在位置付近の避難場所を特定対象にする
        ido_from = event['events'][0]['message']['latitude'] - 0.05
        ido_to = event['events'][0]['message']['latitude'] + 0.05
        kdo_from = event['events'][0]['message']['longitude'] - 0.05
        kdo_to = event['events'][0]['message']['longitude'] + 0.05
        df_near = df.query(
            f'ido > {ido_from} and ido < {ido_to} and kdo > {kdo_from} and kdo < {kdo_to}'
        )

        # 現在位置から一番近い避難場所を特定する
        min_distance = None
        min_row = None
        for index, row in df_near.iterrows():
            hinan_ido = row['ido']
            hinan_kdo = row['kdo']
            distance = calc_distance(
                event['events'][0]['message']['latitude'], event['events'][0]['message']['longitude'], hinan_ido, hinan_kdo
            )
            if min_distance is None or distance < min_distance:
                min_distance = distance
                min_row = row

        if min_distance != None:
            message = [
                {
                    "type": "text",
                    "text": f"{min_row['name']}に避難しましょう"
                },
                {
                    "type": "location",
                    "title": f"避難場所まで{min_distance}mです",
                    "address": f"{min_row['addr']}",
                    "latitude": f"{min_row['ido']}",
                    "longitude": f"{min_row['kdo']}"
                }
            ]
        else:
            message = [
                {
                    "type": "text",
                    "text": "近くに避難場所が見つかりません"
                }
            ]


        params = {
            "replyToken": event['events'][0]['replyToken'],
            "messages": message
        }
        request = urllib.request.Request(url, json.dumps(params).encode("utf-8"), method=method, headers=headers)
        with urllib.request.urlopen(request) as res:
            body = res.read()
    return 0

 このままlambdaを実行しようとしてもpandasやpyprojの外部ライブラリが読み込めないとエラーが出ます.この解決方法としてこれらの追加ライブラリをlayer形式でlambdaに取り込めるようにします.EC2(AWSの仮想マシン)でそれらのライブラリをインストールし、S3にアップロードした後にlayerに追加するようにします.(他の方法を合わせて見たい場合は【AWS・Lambda】Python外部ライブラリ読み込み方法を参考に)
 手順は以下のyoutubeの動画を参考にすると良いです.海外の方の動画ですが,とてもわかり易いです.pandasだけではなく追加でpyprojもインストールするのを忘れないようにしてください.https://www.youtube.com/watch?v=zrrH9nbSPhQ

スクリーンショット 2019-11-13 13.29.30.png

その後無事各種ライブラリがimportできてれば成功です.

まとめ

 LINEbot on AWS の簡単なハウツーを他の記事も参照しつつまとめました.自分自身AWSを普段触っているわけではないので,CSVファイルをDB管理した方が良かったよなどあったらぜひ教えて下さい.アプリ自体は疎通を確認して満足したので改良はしないつもりです.
 続編として「ドローンを活用した災害避難勧告システム」や「スマートスピーカーを利用した避難勧告システム」についても公開を予定していますので,ご覧ください
 この記事が参考になった,あるいは続編に期待という方は高評価おねがいします^ ^

Why do not you register as a user and use Qiita more conveniently?
  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
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