10
1

More than 3 years have passed since last update.

LINE ボットと LIFF で 第 3 回 東京公共交通オープンデータチャレンジで審査員特別賞をもらった話

Posted at

第 3 回 東京公共交通オープンデータチャレンジの授賞式が 2020 年 1 月 30 日に開催され、審査員特別賞をいただきました。

第 3 回 東京公共交通オープンデータチャレンジ 結果発表

この記事では、今回応募した imacoco ~I'm here~ について技術的な説明と課題について説明します。

機能

LINE ボットと LIFF を使ったアプリで、ワンタップで以下のことが分かります。

  • 山手線に乗っている場合、どの駅を出て、どの駅へ向かっているか
  • それ以外の場合は自分の場所を地図で表示

ポイントは「ワンタップ」で出来ることであり、わざわざ位置情報を送る必要はありません。(あくまで LINE ボットにたどり着いてからワンタップ)

実装期間

相方がアイデアをまとめてくれたおかげで、実装は 2 時間くらいでした。尚、簡単に実装したかったため TypeScript を使わないという無謀をしています。

なぜ LINE ボットにしたか

いつも通りの理由ですが、一応書いておきます。

  • アプリストアに公開する必要がない
  • モバイルで見る場合ブラウザより見やすい
  • ユーザープロファイルが取れのでパーソナライズしやすい
  • LIFF によりブラウザで出来ることはほぼできる
  • LINE が好き

では技術的な話をしていきましょう。

位置と進行方向の取得

今回は以下の方法で現在の位置と進行方向、および乗車中の電車情報を取得しました。

  • 1 秒の間隔で 2 回位置情報を取得
async function getLocation() {
    // Get two locations in 2 seconds
    if (navigator.geolocation) {
        m.innerHTML = language == "ja-JP" ?
            "現在位置を確認中..." :
            "Checking your location....";
        coords1 = coords2 = l.innerHTML = null;
        navigator.geolocation.getCurrentPosition((position) => { coords1 = position.coords; });
        await sleep(1000);
        navigator.geolocation.getCurrentPosition((position) => { coords2 = position.coords; });
        ...
}
  • それぞれの位置情報から、山の手線の最寄りの駅と、駅の位置情報から進行方向を確認
async function getStations(coords1, coords2) {
    // send location to server
    const coord = {
        latitude: coords1.latitude,
        longitude: coords1.longitude
    };

    const options = {
        method: 'POST',
        body: JSON.stringify(coord),
        headers: {
            'Content-Type': 'application/json'
        }
    }

    let stations = JSON.parse(await (await fetch('/get-stations', options)).json());
    if (stations.length < 2) {
        return { fromStation: null, toStation: null };
    }
    let fromStation;// = stations.sort((a, b) => (a.distance > b.distance ? 1 : -1))[0];
    let toStation;
    stations.some((station) => {
        station.distance2 = calcDistance({ longitude: coords2.longitude, latitude: coords2.latitude }, { longitude: station.long, latitude: station.lat }) - station.distance;
    });

    stations.some((station) => {
        if (stations[0].distance2 > 0 && station.distance2 < 0) {
            toStation = station;
            fromStation = stations[stations.indexOf(station) - 1];
            return true;
        }
        else if (stations[0].distance2 < 0 && station.distance2 > 0) {
            fromStation = station;
            toStation = stations[stations.indexOf(station) - 1];
            return true;
        }       
    });
    return { fromStation: fromStation, toStation: toStation };
}
  • 同じ場所を走行している電車情報を取得
クライアント側
async function getTrain(stations) {

    if (stations.fromStation == null || stations.toStation == null) {
        return {};
    }
    const options = {
        method: 'POST',
        body: JSON.stringify(stations),
        headers: {
            'Content-Type': 'application/json'
        }
    }

    let train = JSON.parse(await (await fetch('/get-train', options)).json());
    return train;
}
サーバー側
app.post('/get-train', async function (req, res) {
    var fromStation = req.body.fromStation;
    let toStation = req.body.toStation;

    var response = await request.get(`${apiurl}/odpt:Train?acl:consumerKey=${key}&odpt:railway=${railway}&odpt:fromStation=${fromStation["sameAs"]}&odpt:toStation=${toStation["sameAs"]}`);
    let train = JSON.parse(response)[0];
    if (train === undefined) {
        res.json("{}");
    }
    else {
        let result = {
            trainNumber: train["odpt:trainNumber"],
            direction: train["odpt:railDirection"],
            delay: train["odpt:delay"]
        }
        res.json(JSON.stringify(result));
    }
});

結構アナログにデータ取得と計算しているので、もっと格好いい方法知りたいです。

LIFF の役目

LIFF を使ったメインの目的は位置情報を取得することです。位置情報をチャットで送ることはできますが、ワンタップでは実現できないことと、2 つの位置情報が必要であったこと、および位置情報の送り方以外と知られていない問題があったため LIFF が最適解となりました。

  • LIFF 起動
  • 位置情報を取得してもろもろ計算
  • メッセージを送付して LIFF を自動で閉じる
if (liff.isInClient()) {
  liff.sendMessages([{
    "type": "location",
    "title": message,
    "latitude": coords2.latitude,
    "longitude": coords2.longitude
  }]);
  liff.closeWindow();
}

結果ユーザーは LIFF を目にしますが、操作はしません。メッセージが出て消えるだけです。動作はビデオをご覧ください。

  • ユーザーの使用言語の特定

OS の言語設定に依存しますが、日本語と英語対応を行いました。Messaging API でユーザープロファイルが簡単に取得できるのは助かります。

LINE ボットの役目

JavaScript だけであれば Web ページでも同じですが、LINE を使った理由以下の通りです。

  • 地図の機能が手軽に使える
    • チャット画面に簡単な地図表示
    • タップすると本格的な地図アプリが起動する
  • 情報を他の人とも簡単に共有しやすい
  • ボット側から情報を送ることも容易
  • グルチャの場合、誰かが「いまどこ」と発言すると、ボタンが送られてきてワンタップで知らせることができる

リッチメニューの役目

今回は「ワンタップ」ですべてを終わらせることをテーマにしていたため、ワンタップで LIFF が起動して、自動的にすべてが終了する仕組みとして以下の機能を使いました。

  • リッチメニューから直接 LIFF を起動
  • ボタンテンプレートのクリックで LIFF を起動

いずれも LINE の UX の機能に助けられています。

課題

開発してテストしている中で以下の課題がありますが、まだ解決策はありません。

  • GPS の精度が iOS の場合悪い/遅い
  • LINE ボットを開くのが面倒
  • 東京は電車多すぎて山手線に限定してしまった。GPS 精度とタイムラグの限界
  • 現在位置を送ると乗っている電車を返す API が公開されていないため、計算が大変

今回は山手線で試しましたが、逆に都内を少し離れる電車で、駅の間隔長いものやバスの方が効果が高い気がします。

まとめ

LINE ボットだけでは実現できないことも、LIFF を組み合わせると結構色々なことが解決します。LIFF は v2 も出ましたし今後ミニアプリもあるので、目が離せない存在になっていきそうです。

尚、第4回の東京公共交通オープンデータチャレンジが現在募集中です。是非アイデアを考えて参加してみてください。API も色々な情報が取得できるので面白いですよ。

東京公共交通オープンデータチャレンジ

10
1
0

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
10
1