第 3 回 東京公共交通オープンデータチャレンジの授賞式が 2020 年 1 月 30 日に開催され、審査員特別賞をいただきました。
この記事では、今回応募した 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 も色々な情報が取得できるので面白いですよ。