LINE Botで位置情報を活用する

## はじめに
今回はLINEの位置情報を活用した飲食店検索botを作成したので,その内容をまとめてみました. LINE Botの画像付きのクイックリプライやカルーセルメッセージの実装も行っているので,それらも参考にしていただければと思います.

完成形はこのような形になります
![2022-02-12_12h03_32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/862467/ee9e30ab-d51f-ec12-2205-426cb53e5b58.png)
## 記事概要
* LINEbotをはじめるには
* GoogleMapsAPIの利用
* LINEMessagingAPIの機能
* 飲食店検索botの実装

:::note info 
<b>必要事項</b>
・ Pythonの基本操作
・ HTTP通信の基礎知識( webAPIの利用 )
・ WEBアプリケーションの基礎知識( Flaskの利用, Webhookの利用 )
:::
## LINEbotをはじめるには
#### LINE Developerの登録
![](https://apwg.org/wp-content/uploads/2019/01/LINE_Corporation_200x70.png)
LINEbotを作るには, まずLINE Developerに個人のアカウントを登録する必要があります. [こちら](https://developers.line.biz/ja/)からサイトに飛ぶことができます. 手順は以下を参考にしてください.

1. ログインボタンを押してConsoleに移動
1. LINEアカウントでログインを押す
1. メールアドレスとパスワードで認証
1. 承認番号がでるので自身の携帯で本人確認を行う(2段階認証)
1. LINE Developeに任意の名前と本人のメールアドレスを登録
1. 利用規約にチェックを入れてアカウント作成を押す

以上でLINE Developerの登録が完了します. 
#### プロバイダーの新規作成
Line Developer登録後, 自動的にLINE Developer Consoleの画面に遷移します. 
LINE Developer ConsoleではLINEが提供している様々なサービスを利用することができます. それらを利用するためにまずプロバイダー( 提供者 )を作成します.

2. 新規プロバイダーの作成をクリック
2. 任意のプロバイダー名を入力・作成

以上でプロバイダーの作成が完了します.
#### チャンネルの新規作成
チャンネルの作成にあたり, チャンネルの種類を選択する必要があります. 今回利用するのはMessaging APIです.![2022-02-12_12h28_17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/862467/28049f26-2890-0f9e-9067-0aa0af100d47.png)
選択後は, 

3. チャンネル説明を入力
3. 大業種( 業種の枠組み ), 小業種( 大業種内の枠組み )を順に入力
3. メールアドレスを入力
3. プライバシーポリシーURLとサービス利用規約URLは<b>無視</b>してよい
3. 最後に利用規約にチェックを入力

以上でチャンネルの作成が完了します. 
#### Botを作るために必要な情報と設定

LINEbotを作成する際には<b>チャンネルシークレット</b>と<b>チャンネルアクセストークン</b> また, <b>Webhookの設定</b>が必要になります. このいづれも先ほど作成したチャンネルの設定画面から取得及び変更することができます. 

* チャンネルシークレット : チャンネル基本設定から取得可能
* チャンネルアクセストークン( 長期 ) : Messaging API設定から取得可能
* Webhook設定 : Messaging APIから設定の変更可能

Webhookの設定に関して, Webhookを有効, Webhook URLにはLINE Platformから送信されるデータの受け取り先( 例 : 自身のサーバのURL + WEBアプリケーションのルーティング先 )を設定します. 
>システムの概観
![](https://developers.line.biz/assets/img/messaging-api-architecture.f40bffbb.png)
参照 : https://developers.line.biz/en/docs/messaging-api/overview/#how-messaging-api-works

:::note warn
<b>注意</b>
応答メッセージの設定をoffに設定しておきましょう
:::

以上でLINE Botの前準備が完了です.
## GoogleMapsAPIの利用
#### GoogleMapsAPIとは
GoogleCloudPlatformで提供されているWEB APIの一つです. GoogleMapsAPIは地図情報を用いたサービスを提供しており, 位置情報から付近の店舗情報, また店の評価や口コミ情報などを受け取ることができます. 今回はこの機能をLINE Botから利用できるようにしていきたいと思います.
#### GoogleCloudPlatformの登録
Googleから提供されているAPIを使うためには, まずGoogleCloudPlatformに登録する必要があります. サイトへは[こちら](https://console.cloud.google.com/)から飛べます. 記事作成時では90日間無料でサービスを利用することができ, またその後も200$/月のサービスが無料で利用できるようです. 

:::note warn
<b>注意</b>
登録にはクレジットカードの登録が必要になります
:::

#### プロジェクトの立ち上げ
GoogleCloudPlatformではプロジェクト単位でサービスを複数利用することができます. 新しいプロジェクトの作成は以下のように行います. 

4. プロジェクトの選択から新しいプロジェクトを押す
4. 任意のプロジェクト名と場所を入力後, 作成

#### 利用する機能の選択
ここから利用できる機能の選択を行っていきます. 「APIとサービス」を選択し, 「ライブラリ」からGoogleMapsと調べるとMaps API及びサービスが表示されます.
![2022-01-26_17h00_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/862467/fc58faab-7428-2d6e-89ad-a9706553a428.png)
今回はPlaces APIを有効にしていきます. 
![2022-01-26_17h00_44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/862467/da795ec0-9f9d-70bc-cb61-cd8688e68f01.png)

#### プロジェクトを外部から利用できるようにする
認証情報の設定画面からプロジェクト自体のAPIキーの作成を行います.
![2022-01-26_17h01_51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/862467/25afba01-2963-ee78-8b00-2427c28d3cb5.png)
これでPlaces APIを外部から利用できるようになりました. Places APIの詳細については[こちら](https://developers.google.com/maps/documentation/places/web-service?hl=ja)から確認できます.
## LINEMessageigAPIの機能
LINEMEssagingAPIの機能について, イベント・メッセージオブジェクト・クイックリプライ・カルーセルメッセージについて少し抑えておきます.
#### イベントとは
>LINE上での友だち追加やメッセージの送信といった操作のことです. 友達追加することをフォローイベント, メッセージの送信をメッセージイベント, 他にも送信取り消しイベントや動画視聴完了イベントなど様々な操作が想定されています. 開発者はその操作をWebhookイベントオブジェクトとして受け取り, それぞれ処理を記述することになるので覚えておきましょう. 詳細は[こちら](https://developers.line.biz/ja/reference/messaging-api/#webhook-event-objects)から確認できます.

#### メッセージオブジェクトとは
>LINEから送信されたメッセージのことです. メッセージオブジェクトにはいくつかの種類があり, 例えば画像メッセージ・音声メッセージ・動画メッセージ・位置情報メッセージなどがあります. 今回は位置情報メッセージを扱うので少し注意が必要です. 詳細は[こちら](https://developers.line.biz/ja/reference/messaging-api/#message-objects)から確認できます.

#### クイックリプライとは
>ユーザーの操作をあらかじめ制限する機能( 正しくはメッセージオブジェクト )の一つです. 逆に, ユーザーの操作をあらかじめ制限することでユーザーの利用障壁を低くする役割があります. 今回作るLINE Botでは以下のような見た目のものです.   (ドット画像 : https://hpgpixer.jp/image_icons/food.html ) 機能の詳細は[こちら](https://developers.line.biz/ja/reference/messaging-api/#message-objects)から確認できます.
![S__31596554.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/862467/4c63d037-1d51-e8ad-bdb3-a5d1402a5b8d.jpeg)

#### カルーセルメッセージとは

>ユーザーに複数の情報を送信するときに用いる機能( 正しくはメッセージオブジェクト )の一つです. 画像や関連するURLを合わせて送ることができ, 視覚的にも感覚的にも伝達効果が高い機能といえます. 今回作るLINE Botでは以下のような見た目のものです. 機能の詳細は[こちら](https://developers.line.biz/ja/reference/messaging-api/#template-messages)から確認できます.
![S__31596558.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/862467/f7192d66-60b8-a889-2da7-88f3fc298c13.jpeg)
>カルーセルとは回転台, 回転木馬, 回転コンベア, 回転棚などの意味を持つ英単語です.引用元 : https://e-words.jp/w/カルーセル.html

## 飲食店検索botの実装
Pythonのバージョン3.8.8を用いて実装していきます. 
LINEMessagingAPIをより手軽に利用するためline-bot-sdk-pythonライブラリをインストールします. line-bot-sdk-pythonの詳細は[こちら](https://github.com/line/line-bot-sdk-python)から確認できます.

```terminal:tarminal
$ pip install line-bot-sdk
```
つづいてwebアプリケーション開発モジュールのFlask, またHTTP通信ライブラリのrequestsとHTML解析をするBeautifulsoupをインストールします

```terminal:tarminal
$ pip install flask, beautifulsoup4, requests
```
pythonの環境が整ったので, 早速コードを書いていきます. まずは必要モジュールのインストール

```python:linebot.py
from flask import Flask, redirect, request, abort
# Webhookイベントを受け取るHandler
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
# 様々なメッセージオブジェクト
from linebot.models import (
    MessageEvent, TextMessage, LocationMessage, TextSendMessage, QuickReplyButton, QuickReply,  MessageAction, ImageMessage, LocationAction, TemplateSendMessage, CarouselTemplate, CarouselColumn, PostbackAction, CarouselTemplate, URIAction
)

# HTTP通信と解析を行うライブラリ
from bs4 import BeautifulSoup
import requests
import json
import time
```
基本設定を行います

```python:linebot.py
app = Flask(__name__)
port = "アプリケーションのポートを指定 : int"
line_bot_api = LineBotApi("アクセストークン")
handler = WebhookHandler("チャンネルシークレット")
places_api_key = "GoogleCloudPlatformのプロジェクトAPIキー"
places_api_url = "https://maps.googleapis.com/maps/api/place/nearbysearch/"
google_map_url = "https://www.google.com/maps/search/?api=1"

root_dir = "アプリケーションのディレクトリ"
sessions = {}
# 反応するメッセージをあらかじめ指定しておきます.
search_words = ["お腹すいた","おなかすいた","お腹空いた","店検索","ご飯たべたい","お店","ご飯"]
# クイックリプライで検索できる飲食店名
selectable_food = ["寿司屋","ラーメン屋","焼肉屋","居酒屋"]
# クイックリプライのドット画像場所
image_urls = [root_dir+"static/img/sushi.png",root_dir+"static/img/ramen.png",root_dir+"static/img/meat.png",root_dir+"static/img/bear.png"]
```

位置情報から周辺の飲食店の情報を入手する関数を作成します

```python:linebot.py
def find_place_by_geoinfo(latitude, longitude, keyword)->str:
    global places_api_url, places_api_key
    # 半径1km県内の店を取得します
    places_parameter = f"json?keyword={keyword}&types=food?language=ja&location={latitude},{longitude}&radius=1000&key={places_api_key}"
    places_api = places_api_url + places_parameter

    response = requests.get(places_api)
    soup = BeautifulSoup(response.content)
    data = json.loads(soup.text)
    return data
```

飲食店を表示するカーセルメッセージについて, 関数を作成しておきます

```python:linebot.py
def make_carousel(thumbnail_image_url,shop_name,like,map_url,user_ratings_total):
    column = CarouselColumn(
            thumbnail_image_url=thumbnail_image_url,
            title=shop_name,
            text=f"いいね👍 :{like}\n評価数 :{user_ratings_total}",
            actions=[URIAction(label='mapで表示',uri=map_url)]
            )
    return column
```

LINEから送信されたメッセージに対しての処理を記述していきます.

```python:linebot.py
@app.route("/callback", methods=['POST'])
def callback():
    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']
    # get request body as text
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)
    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        print("Invalid signature. Please check your channel access token/channel secret.")
        abort(400)
    return 'OK'

# Webhookイベントハンドラーでテキストメッセージを受け取った際の処理
@handler.add(MessageEvent, message=TextMessage)
def reply_message(event):
    global sessions, selectable_food
    # ユーザーidをもとにセッションを管理します
    if not event.source.user_id in sessions.keys():
        sessions[event.source.user_id] = {"flag":False,"food":None,"place":None}
   
    if event.message.text in search_words:
        sessions[event.source.user_id]["flag"]=True
        items = [
            QuickReplyButton(
                action=MessageAction(text=f"{food}",label=f"{food}"),image_url=f"{image_url}"
            ) for food,image_url in zip(selectable_food,image_urls)
        ]
        # クイックリプライオブジェクトを作成
        messages = TextSendMessage(text="どこか食べに行きますか?",quick_reply=QuickReply(items=items))
        line_bot_api.reply_message(event.reply_token, messages=messages)
        
    elif sessions[event.source.user_id]["flag"] and event.message.text in selectable_food:
        sessions[event.source.user_id]["food"] = event.message.text
        location = [QuickReplyButton(action=LocationAction(label="location"))]
        # クイックリプライオブジェクトを作成( 位置情報メッセージを指定 )
        messages = TextSendMessage(text="現在地教えてください!", quick_reply=QuickReply(items=location))
        line_bot_api.reply_message(event.reply_token, messages=messages)

    # 指定された文字列でない場合は"おなかすいたなぁ"と返します    
    else:
        help_message = "おなかすいたなぁ"
        line_bot_api.reply_message(event.reply_token, TextSendMessage(text=help_message))

# Webhookイベントハンドラーで位置情報メッセージを受け取った際の処理     
@handler.add(MessageEvent, message=LocationMessage)
def handle_location(event):
    global sessions, google_api_key, google_map_url
    # セッションで検索する要件( 飲食店の種類, 位置情報 )があるか確認
    if sessions[event.source.user_id]["flag"] and sessions[event.source.user_id]["food"]:
        latitude = event.message.latitude
        longitude = event.message.longitude
        # thumnailImage
        data = find_place_by_geoinfo(
            latitude=latitude,longitude=longitude,keyword=sessions[event.source.user_id]["food"]
        )
        columns = []
        # カルーセルメッセージは最大10件までしか表示できない点に注意
        for data in range(data["results"][:10]):
            try:
                photo_reference = data["photos"][0]["photo_reference"]
                image_url = get_photoURL(photo_reference)
                # shop_name and shop_rating
                shop_name = data["name"]
                like_num = data["rating"]
                place_id = data["place_id"]
                user_ratings_total = data['user_ratings_total']
                latitude = data['geometry']['location']["lat"]
                longitude = data['geometry']['location']["lng"]

                # 対象の飲食店におけるgooglemapのURLを作成
                map_url = google_map_url+f"&query={latitude}%2C{longitude}&quary_place_id={place_id}"
                # カルーセルメッセージオブジェクトを作成            
                carousel = make_carousel(
                    thumbnail_image_url=image_url, shop_name=shop_name, like=like_num, user_ratings_total=user_ratings_total, map_url=map_url
                )
                columns.append(carousel)
            except:
                continue
        # 対象の飲食店が1店舗もない場合の判別
        if len(columns)>= 1:
            message = TextSendMessage(text="近くにこんなお店があるみたい")
            line_bot_api.reply_message(event.reply_token, message)
            carousel_template_message = TemplateSendMessage(
                alt_text='Carousel template',
                template=CarouselTemplate(columns=columns)
            )
            time.sleep(0.4)
            line_bot_api.push_message(event.source.user_id, carousel_template_message)
        else:
            message = TextSendMessage(text="1km以内に候補が見つかりませんでした")
            line_bot_api.reply_message(event.reply_token, messages=mssage)

if __name__ == "__main__":
    app.run(port=port, debug=True)

```
これで作成完了です.

## おわりに
今回はLINEからの位置情報をもとに周辺の店を検索するBotを作成してきました. 現在地を用いたシステムは現実と結びついている感じがたのしいですね. また, LINEでは位置情報以外に画像や音声, 動画などを活用することができます. これらのデータを用いて最近流行りの機械学習を行えるBotの開発などもおもしろそうですね. 是非皆さんもいろんなアイデアをLINE Botを活用して実現させてみてください.

## 参考URL
https://developers.line.biz/ja/

https://developers.line.biz/en/docs/messaging-api/overview/#how-messaging-api-works

https://developers.line.biz/ja/reference/messaging-api/

https://github.com/line/line-bot-sdk-python

https://console.cloud.google.com/

https://developers.google.com/maps/documentation/places/web-service?hl=ja