【LINE BOT】位置情報と目的地の距離を発信

# 背景
友人の誕生日で暗号を解いて、LINE BOTで位置情報を送信すると目的地とあってるか？的なことをやりたかったので実装してみました！

# 実装したもの
https://lin.ee/4geh8NJXF

##失敗した時
![S__7266328.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514870/3f6deaaa-e6ed-7112-ada2-b108c4443ec8.jpeg)


## 正解した時
![S__7266324.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/514870/23a86ce3-0218-2222-f803-d65f2aeef381.jpeg)


# 実装
Line Botの標準機能だと、位置情報から値を取得して・・
みたいなのができなかったので、下記の記事を参考にHerokuにデプロイしました。

[LINEのBot開発 超入門（前編） ゼロから応答ができるまで](https://qiita.com/nkjm/items/38808bbc97d6927837cd)

※この記事の通りに進めたら超簡単にできたのでオススメです。

ちなみに記事内で出てくる、チャンネルID,チャンネルシークレット,チャンネルアクセストークンの場所はこの画面の「チャンネル基本設定」「Messaging API設定」にあります！(少し探したのここくらい)

実際のソースコードはこちら

```index.js
// 省略
// -----------------------------------------------------------------------------
// ルーター設定
server.post('/bot/webhook', line.middleware(line_config), (req, res, next) => {
    // 先行してLINE側にステータスコード200でレスポンスする。
    res.sendStatus(200);

    // 距離を算出する関数
    function distance(lat1, lng1, lat2, lng2) {
      lat1 *= Math.PI / 180;
      lng1 *= Math.PI / 180;
      lat2 *= Math.PI / 180;
      lng2 *= Math.PI / 180;
      return 6371 * Math.acos(Math.cos(lat1) * Math.cos(lat2) * Math.cos(lng2 - lng1) + Math.sin(lat1) * Math.sin(lat2));
    }

    // 一つ目の目的置
    let goal1Lat = 35.57891;
    let goal1Lng = 139.7052;

    // 二つ目の目的置
    let goal2Lat = 35.57724;
    let goal2Lng = 139.7038;

    // 三つ目の目的地
    let goal3Lat = 35.58125;
    let goal3Lng = 139.70265;

    // すべてのイベント処理のプロミスを格納する配列。
    let events_processed = [];

    // イベントオブジェクトを順次処理。
    req.body.events.forEach((event) => {
        if (event.type == "message" && event.message.type == "location") {
            // 一つ目の目的地
            if (distance(goal1Lat, goal1Lng, event.message.latitude, event.message.longitude) <= 0.05) {
                events_processed.push(bot.replyMessage(event.replyToken, [
                {
                    type: "text",
                    text: "正解だ。次の暗号を解いて移動しろ、ヒントはご飯だ"
                },
                {
                    type: "image",
                    originalContentUrl: '画像のURL',
                    previewImageUrl: '画像のURL'
                }
                ]));
            } else if (distance(goal2Lat, goal2Lng, event.message.latitude, event.message.longitude) <= 0.05) {
                events_processed.push(bot.replyMessage(event.replyToken, 
                [
                {
                    type: "text",
                    text: "正解だ。次の暗号を解いて移動しろ"
                },
                {
                    type: "image",
                    originalContentUrl: '画像のURL',
                    previewImageUrl: '画像のURL'
                }
                ]));
            } else if (distance(goal3Lat, goal3Lng, event.message.latitude, event.message.longitude) <= 0.05) {
                events_processed.push(bot.replyMessage(event.replyToken, 
                [{
                    type: "text",
                    text: "正解だ。次の暗号を解いて移動しろ"
                },
                {
                    type: "text",
                    text: "暗号のパスワードはXXXXだ。よくやったな・・。"
                }
                ]
                ));
            } else {
                events_processed.push(bot.replyMessage(event.replyToken, [
                {
                    type: "text",
                    text: "残念だな。正確に暗号を読み解け、そして正確に移動して答えてみろ"
                },
                {
                    type: "text",
                    text: `次の目的地まで： ${Math.floor(distance(goal1Lat, goal1Lng, event.message.latitude, event.message.longitude) * 1000)} mだ`
                },
                ]));
            }
        }
    });

    // すべてのイベント処理が終了したら何個のイベントが処理されたか出力。
    Promise.all(events_processed).then(
        (response) => {
            console.log(`${response.length} event(s) processed.`);
        }
    );
});
```

# 実装の詳細

まず距離を算出する関数についてです。

```index.js
    // 距離を算出する関数
    function distance(lat1, lng1, lat2, lng2) {
      lat1 *= Math.PI / 180;
      lng1 *= Math.PI / 180;
      lat2 *= Math.PI / 180;
      lng2 *= Math.PI / 180;
      return 6371 * Math.acos(Math.cos(lat1) * Math.cos(lat2) * Math.cos(lng2 - lng1) + Math.sin(lat1) * Math.sin(lat2));
    }

    // 一つ目の目的置
    let goal1Lat = 35.57891;
    let goal1Lng = 139.7052;

    // 二つ目の目的置
    let goal2Lat = 35.57724;
    let goal2Lng = 139.7038;

    // 三つ目の目的地
    let goal3Lat = 35.58125;
    let goal3Lng = 139.70265;
```

こちらの関数は2つの地点の緯度・経度を引数に渡すことで、
Kmの単位で距離を返します。
下記の記事から丸々使わせていただいてます。(ありがとうございます・・。)

[緯度経度から2地点間の距離 (km) を計算する JavaScript](https://qiita.com/kawanet/items/a2e111b17b8eb5ac859a)


次に位置情報を受け取り、判定して結果を送信する部分です。

```index.js
    // イベントオブジェクトを順次処理。
    req.body.events.forEach((event) => {
        if (event.type == "message" && event.message.type == "location") {
            // 一つ目の目的地
            if (distance(goal1Lat, goal1Lng, event.message.latitude, event.message.longitude) <= 0.05) {
                events_processed.push(bot.replyMessage(event.replyToken, [
                {
                    type: "text",
                    text: "正解だ。次の暗号を解いて移動しろ、ヒントはご飯だ"
                },
                {
                    type: "image",
                    originalContentUrl: '画像のURL',
                    previewImageUrl: '画像のURL'
                }
                ]));
            }
// 目的地を複数設定しているので省略
              else {
                events_processed.push(bot.replyMessage(event.replyToken, [
                {
                    type: "text",
                    text: "残念だな。正確に暗号を読み解け、そして正確に移動して答えてみろ"
                },
                {
                    type: "text",
                    text: `次の目的地まで： ${Math.floor(distance(goal1Lat, goal1Lng, event.message.latitude, event.message.longitude) * 1000)} mだ`
                },
                ]));
            }
        }
    });
```

まずLine Botに対してメッセージが送信されると
`event`に送信された情報が入ります。
位置情報の場合は
`event.type = 'message'`
`event.message.type == 'location'`
という情報が入るので、if文で位置情報の送信時のみコードを実行するようにしています。

送信されたデータにどのような値が入っているかは
Messaging APIの公式のリファレンスがかなりわかりやすかったです！
※こちらを見れば画像だったりや、ビデオが送られた時など、いろんなトリガーで作れると思います！
[Messaging APIリファレンス](https://developers.line.biz/ja/reference/messaging-api/)

送信された位置情報の緯度・経度については
`event.message.latitude`
`event.message.longitude`
に入っているので、最初に宣言した距離を算出する関数に渡しています。

今回は以下のように条件をつけることで、50m以内という風にしています。
`if (distance(goal1Lat, goal1Lng, event.message.latitude, event.message.longitude) <= 0.05)`


そして50m以内であれば、正解のメッセージ、50m以上であれば不正解のメッセージと目的地までの距離を返すようにしています！
今回は少しめんどくさかったので、常に一番最初の目的地との距離を返してますw
※それっぽく見えればいいかなということで・・。


# 詰まった点

メッセージを受信した時に、返信のメッセージを複数に分けて送信したかったのですが、
うまくいかず、少してこずりました。
しかし、すでにやってくれていた先人がいました！(ありがとうございますっ！)

[最近知ったMessaging APIの複数メッセージ送信機能](https://qiita.com/ufoo68/items/ea923025e7989ff5af3d)


# やってみて

友達の誕生日とかにうまく使えるので、よくLINE BOTを作るのですが、
Herokuとかにdeployしなくても十分いい感じに使えるので感動してます。。
次はクーポンの機能とか、リッチメッセージとからへんをうまく使って遊んでみたいと思います。


# 参照先まとめ

・LINE BOTをHerokuにdeployしてメッセージ受信するまで
[LINEのBot開発 超入門（前編） ゼロから応答ができるまで](https://qiita.com/nkjm/items/38808bbc97d6927837cd)

・2点間の距離を求める
[緯度経度から2地点間の距離 (km) を計算する JavaScript](https://qiita.com/kawanet/items/a2e111b17b8eb5ac859a)

・メッセージの複数送信
[最近知ったMessaging APIの複数メッセージ送信機能](https://qiita.com/ufoo68/items/ea923025e7989ff5af3d)