GPSマルチユニットのウェブアプリ作ってみた!

はじめに

2021年7月29日(木)〜30日(金)までトレノケート株式会社 山下様とIoT.kyoto シニアエバンジェリスト 辻、マーケ担当 水口で御在所岳ワーケーションツアーを行いました。ワーケーション当日の様子についてはこちら[1] [2][3]をご覧ください。

いつもと違う環境でワーケーションを行うと言うことで、GPSマルチユニット SORACOM Editionをもっていこう、そしてどうせならオリジナルのアプリ画面を作ろうと言う話になりました。

そこでこの6月からIoT.kyotoに参画した新入社員2名で、マルチユニット用の可視化ウェブアプリ(下図)を作成しました。
簡単にではありますが、ご紹介させていただきます。

実際のアプリ画面はこちらです ⇨ https://gozaisho.handson.iot.kyoto/

今回のアプリのアーキテクチャ

使用したサービス

frontend
Vue.js
Vuetify
Chart.js
vue-chart.js
Leaflet
axios
vue-axios

backend
AWS Lambda
  boto3
AWS DynamoDB
  DynamoDB Streams
AWS Amplify

このアプリの特徴的な機能

  • 標高計算機能

国土地理院が提供しているAPIを利用して高度情報をメートル単位で算出しています。画面上では数値でも見れますし、電波強度とともにグラフ化もしているので相関関係が容易に見て取れます。

  • 不快指数計算機能

温度と湿度のデータに基づき不快指数を算出し、それと連携していおたんの表情が5段階で変化します。5段階とはいっても3が一番心地よい状態で、1に近づけば寒く、5に近づけば暑く感じているといった仕様です。

第1段階第2段階第3段階第4段階第5段階
  • GPS軌跡表示機能

GPSの位置情報から過去2時間の間にどのようなルートを通ったかが描画されており、常にマップ上で確認することができます。位置情報の取得が1分毎なので車など移動速度の速い乗り物だと多少のブレはありますが、下の画像を見ても一定の正確さで軌跡が表示できていることがわかります。また現在地にはいおたんのピンが表示され、常にやさしい気持ちで画面を見ることができます。

レスポンシブデザインにも対応しており、ただ画面幅に合わせて要素を下に回していくのではなく、 PCで表示した際とモバイルで表示した際の画面の構成が異なったものになるようデザインしました。

苦戦した箇所

  • データ連携

今回、Vue.jsで数値やグラフへのデータ連携を行う際、親コンポーネント(以下、親)内でvue-axiosを利用して取得したデータを子コンポーネント(以下、子)へ渡して処理を行う必要がありました。しかし、Vue.jsの全てのコンポーネントインスタンスは、各々が隔離されたスコープを持つため、子のテンプレートで親のデータを直接参照できないという問題がありました。

少し調べたところVue.jsにはそういった時用のコンポーネントオプションであるpropsがあることがわかりました。propsを利用して値を渡すには子側でpropsオプションを設定した後、親側のテンプレートタグにデータをバインディングする必要があります。そうすることで親データが更新されるたびに動的にそのデータが子にも渡るようになります。これで親から子へのデータ連携が可能になりました。

しかし、今度は親のデータを渡すタイミングと子が処理を行うタイミングに意図しないズレが生じてしまうといった問題が発生しました。これにはVueインスタンスのライフサイクルフックを見直すことで対応することができました。問題が発生した時点では親でのvue-axiosでデータを取得する外部APIを呼び出すタイミングと子でのデータの処理のタイミングがどちらもMountedで行われていましたが、外部APIのタイミングをCreatedに変更することでズレを解消することができました。

   

  • DynamoDB Streamsの設定(受け取りデータの確認)

今回、高度情報を取得するために国土地理院のサーバサイドで経緯度から標高を求めるプログラムを利用していますが、開発の初期段階では、フロントからの呼び出しがあるたびに外部APIを呼び出す仕様にしていました。この仕様だと、APIの呼び出し先に過度な負荷をかける上に、画面読み込みにも時間がかかってしまいます。この問題を解決するためにDynamoDB Streams(以下、ストリーム)を使用することにしました。

DynamoDB Streams の変更データキャプチャによると、ストリームはDynamoDBへの項目の追加・変更・削除をリアルタイムで記録してくれるため、その記録情報をトリガーとして何らかのアクションを起こすことができます。

今回はDynamoDBへの項目追加をトリガーとして次のようなLambdaを実行することにしました。

新たに作成したLambda :
外部APIを呼び出して高度を取得。取得した高度をDynamoDBに追加された項目の属性elevationに書き込む。

つまり、GPSマルチユニットで取得したデータがDynamoDBに書き込まれたタイミングでLambdaが実行され、属性elevationに高度が書き込まれるようになります。(下図)

このようにストリームを利用することで、外部APIを実行する処理とフロントへデータを返す処理を完全に切り離して実行することが可能となりました。その結果、外部APIへの負荷軽減と画面読み込み時間の短縮を同時に実現することができました。

なお、実際の手順としては、作成したLambdaのトリガーにストリームを追加したり、ストリームに対してデータの読み込み等ができるようにLambdaに権限付与を行っています。詳しくはリファレンスをご参照ください。

感想

今回、自分たちで一からアプリを作成することも、フロントエンドとバックエンドに分かれて作業することも初めての経験だったので、全てが手探りでのスタートでした。フロントもバックも前途多難でした。

フロントはそもそもVue.jsに触れ始めて日が浅かったので、

  • そもそもVue CLIのプロジェクトってなに?
  • axiosってどうやってAPIキー埋め込みするの?
  • 外部APIでとってきたデータはどうやって格納するの?
  • Vuetifyで表現できない細かいデザインはどうすればいい?

バックはDynamoDB Streamsはもちろんですが、Lambdaすらまともに触ったことがなかったので

  • そもそもDecimal型のデータってなに?
  • DynamoDBにどうやってクエリ書くの?条件づけは?
  • ストリームから来たデータはLambdaからどうやって参照するの?
  • INSERT以外のイベントも来るけどどうする?

などわからないことや知らないことばかりで、一つずつ調べて実際にトライしますが、大体最初はうまくいかず、エラーが返ってきます。エラーの原因を調べて再挑戦しますが、今度は別のエラーがでたりとまさに試行錯誤の連続でした。

しかしながら、最終的には諸先輩方のご教示もあり、無事期日までに完成させ、当初の目的であるワーケーションツアーにて利用していただくことができました。今回の経験で、自分たちで一から作成したものを利用していただけることの喜びを知り、成長することができました。今後の業務でもこの経験を生かし、新しいことに挑戦していきたいです。