子供たちのハイキングイベントでGPSマルチユニットSORACOM Editionを使って運営を楽にした話

# これは何？
子供たちが参加するハイキングイベントを [GPSマルチユニットSORACOM Edition](https://soracom.jp/products/kit/gps_multiunit/)  （以降、GPSマルチユニットとします）を使って、現在位置を把握したり、距離や速度の履歴を可視化して運営を楽にした個人の趣味プログラミングの紹介です。
GPSマルチユニットからクラウドサーバ（AWS）にGPSデータを送付し、そのGPSデータを可視化するWebアプリを作った内容を簡単に解説します。「おもしろ要素」や「奇をてらって」的な要素は一切ありませんが、作りあげる過程の苦悩的なことをメモにしたので、読み物としてはイマイチかもしれません･･･

<img width="600" alt="GPSマルチユニットの充電の様子" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39825/4afe60f1-ecab-0d5e-2201-9d541e0cd42a.jpeg">
↑は、GPSマルチユニットを充電している様子です。

# 背景
「よき社会人を育成する」ことを目指した青少年教育ボランティア団体である [ボーイスカウト](https://www.scout.or.jp/) では、夜を徹して子供たちがハイキングする恒例イベント（通称：オーバーナイトハイク）というのがあります。
子供たちだけで地図を見て歩くので、サポートする大人たちの想定しない道を進んでしまったりして [^1] 、迷子になるケースがあります。
今年はコロナがありキャンプなど密になりがちなイベントは敬遠されますが、ハイキングならソーシャルディスタンスを保って活動できるのでGoodです:blush:

「道を勘違いしたり」、「曲がる道を間違えたり」などなど夜歩く疲れも相まって「どうしてそうなった！:sweat:」と大人では考えにくいミスが起こり、「この子たちは、どこいったんだ！？:worried:」（当の本人たちは間違えたことに気付いていない）というトラブルが毎回発生します。

歩く子供たちにGPSを持たせておけば楽だ！と前々から考えていたのですが、個人で手軽に入手できるGPS発信デバイスがありませんでした [^2] 。そんな、ある日、SORACOMのページを見ていたら簡単に扱えそうなGPS発信デバイスとして「[GPSマルチユニット](https://soracom.jp/products/kit/gps_multiunit/)」が発売されており、これは使わない手はない！と飛びつきました。

# 目的
子供たちのハイキングイベント（オーバーナイトハイク）を楽に運営する:vulcan:

# 内容
## 現在値と軌跡の確認画面
Google Mapを利用して、「現在誰が、どこに居るか」「どんな軌跡なのか」を表示しています。
軌跡を表示することで、「子供たちが犯す道の間違いの傾向（前のグループに釣られて間違えたコースをいってしまう等）」、「各グループがどのように歩くかといった戦略の違い（とにかく細い道を駆使し最短距離を目指すグループ、間違えないように大通りを選択するグループ）」も見えて面白いです。

![移動軌跡.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39825/3900b2c2-e769-bbd1-b005-7445cf7e198c.png)

↑の画像の右図の3番のグループや6番のグループが何やら道を勘違いして、想定とは違うルートに行きかけていることがリアルタイムに分かります。特に3番のグループの場合は、想定ルートと大きくかけ離れているので、昔だったら「どこ行った！？」と車を出して総出で捜索されていたことでしょう。

## 速度や距離の記録
運営中には、子供たちのペース管理（疲れが見え始めてるとか、時速どれぐらいで歩いてるからどれぐらいで着く等の予測）が必要になります。
そのための元データとなるためのグラフです。

![speed_and_distance.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39825/e6a4ca55-bcc5-dcc9-cd07-aa72b5232562.png)


## チェックポイントの到着・出発記録
ハイキングでは、スタートからゴールの間に複数のチェックポイントが設定されます。このチェックポイントの到着・出発を記録します。
本来であれば、ジオフェンス（緯度・経度と半径などで定める仮想的な境界線）を構築しにGPSログに応じて、自動的に到着・出発を記録したいところですが、そこまでは開発している時間がありませんでしたので、手動で記録しています。

<img width="400" alt="CP履歴" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39825/f592a1d7-5e26-722d-d27c-d0128adad464.png"/>


# 技術的な話

## 全体構成

![全体構成図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39825/6583ceea-ea72-fdcb-eef7-f80b6ef14a5d.png)

何故この構成となったのかは、次節以降で解説しますが、AWSをベースとする構成をとっています。

- （GPSログ系）GPSマルチユニット → SORACOM Beam →　AWS API Gateway[^4] → AWS Lambda → DyanmoDB
- （管理系）管理用Webアプリ → AWS API Gateway → AWS Lambda → DynamoDB
 - 管理用WebアプリにAzureを使ってしまっている理由は後述します。

## 前提とした非機能要件的な話
全体構成を考える上で必要となりそうな非機能要件は下記のようにある程度洗い出しておきました。

- 性能面を決めるファクターである参加人数とか関わる人数とか
 - 子供たちのグループの数は、10グループ想定＝GPSマルチユニット10台。10台が1分間に1回ずつGPSデータの送付をする。（実際の参加は6グループまで減りましたが）
 - サポートする大人の数は、のべ15名程度。更新頻度的には、画面へはだいたい10秒に1回程度のアクセスか。
- 認証と認可周り
 - （画面系・認証）サポートする大人が見る画面は、1日限りしか使わないのにメール＆パスワードの登録は鬱陶しいので、簡単にログインできるようにしたい。（共通パスワードをかけるイメージ）
 - （API系／認証）リクエスト都度に正しい認証に基づくリクエストかどうかを検証する仕組みとする。

認証周りの仕組みについては、長くなるので、以降の記事で割愛します。

## 全体を構成する上での方針
今後継続的に使っていくことを考えて、GPSマルチユニットの購入費用は初期費用として仕方がないとしても、サーバ側の固定費はとにかく安く抑えたいと考えました。そこで、フロントエンドおよびバックエンドは次のような方針を立てました。

- フロントエンドは、管理用Webアプリで、固定費を抑えるために静的サイトで運用できるように、 Vue.js や ReactJs を使って開発（願望）。
 - 結果、間に合わず、Javaを使ったフレームワークに頼ることに･･･
- バックエンドは、固定費をとにかく抑えるため稼働しているだけで利用料を請求されるサービスは利用できないので、AWS API Gateway + AWS Lambda + DynamoDB を活用することにします。
 - 実装の仕組みは、[Serverless Framework](https://www.serverless.com/) を利用して、API Gateway + Lambda の管理を楽にしようと思います。
 - 実装言語はJavaしか使えないマンなので、Javaを選定 

## 各要素の実装で考えたこと
### バックエンド

バックエンドは、大きく次の2種類があり、最終的には API Gateway → AWS Lambda → DynamoDB の経路となっています。

- （GPSログ系）GPSマルチユニット → SORACOM Beam →　AWS API Gateway[^4] → AWS Lambda → DyanmoDB
- （管理系）管理用Webアプリ → AWS API Gateway → AWS Lambda → DynamoDB

#### （GPSログ系・管理系共通）AWS API Gateway → AWS Lambda → DyanmoDB
API Gateway は、 Serverless Frameworkを利用して、「管理画面用のAPI」と「GPSマルチユニットからのGPSログ受付のAPI」の大きく2種類作成しました。AWS Lambdaの実行環境に合わせて、Java11 を選択して、開発しています。

AWS Lambda の関数は、Serverless Framework が提供している [サンプルコード](https://github.com/serverless/examples/tree/master/aws-java-simple-http-endpoint) に従って、下記のようにハンドラーをJavaクラスと serverless.yml に定義していきます。

```java:SampleHandler.java
public class SampleHandler implements RequestHandler<Map<String, Object>, ApiGatewayResponse> {
    @Override
    public ApiGatewayResponse handleRequest(Map<String, Object> stringObjectMap, Context context) {
        // 処理を書く
    }
}
```

```yml:serverless.yml
service: sample-service

frameworkVersion: '1'

provider:
  name: aws
  runtime: java11
  stage: dev
  region: ap-northeast-1
  profile: sample-service-profile

package:
  artifact: target/sample-service.jar

functions:
  SampleHandler:
    handler: SampleHandler
    events:
      - http:
          path: /sample_handler
          method: post
```

AWS側のAPI Gatewayを作って、Lambdaを作って、API GatewayとLambdaを連携させる設定をして、といっためんどくさい設定は基本的に serverless framework にお任せで良いのですごく楽です。こちらは、 ```serverless deploy``` コマンドを叩くのみで良いのです。

serverless framework が適切に設定が実施できるよう（内部的にはAWS Lambdaの関数を作ったりしてくれるので）、
Identity and Access Management(IAM) での権限設定（権限追加）が必要です。最初から何を使うかよく分からなかったので、権限エラーが出る度に、トライ&エラーして権限を追加していく必要がありました。

「GPSマルチユニットからのGPSログ受付のAPI」を作ったまでは良かったのですが、「管理画面用のAPI」を必要な数分だけ作っていくと、API Gateway + Lambdaの数が増えてきてしまい、[イマイチな状況](https://www.serverless.com/blog/serverless-workaround-cloudformation-200-resource-limit) となりました。
そこで、汎用型のI/Fを用意して、内部的にAPIを呼び出すといったRPC(Remote Procedure Call)方式に切り替えました。gRPCのようにかっこよく実現したかったのですが、簡単に利用できそなものがない&時間が無くなってきたので、雑なオレオレRPC汎用呼び出しI/F(関数)を作って、内部的なロジックを呼び出す仕組みとしました。メソッドはすべてPOSTで、リクエスト＆レスポンスは、BodyにJSON形式で埋め込む形式としています。

![内部的な構造](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39825/94cd65d9-4d03-3554-66b9-44cda6d96113.png)

個人的にはRESTful APIは、リソースベースが意識されすぎて設計しにくいので、RPC+ファサードパターン的に入力と出力を決めてアクション（画面の初期表示やボタンの押下などのイベント的な意味）に見合った処理を定義できるほうがやり易いと感じています。API Gatewayの役割は、マイクロサービスのコンテキストでいうところの BFF(Backend For Frontend) 的な役割を込めています。さらに内部の処理は、Lambdaに分割し、さらなるマイクロサービス化を図ってもよかったのですが、設定ばかりが複雑になるのと規模感が知れてるのと起動時間がデメリットなので1つのjarに閉じ込めてしまいました。

Swaggerを使って設計すれば、Swagger Codegen を使ってコード生成が行われ、フロントエンドとバックエンドで共通のDTO(Data Transfer Object)を自動生成できて便利だなと思うのですが、今回はオレオレでやってしまったので、いちいち手作業で作っていく必要があり大変でした。

上記以外にもDI(Dependency Injection)の仕組みがあった方がコードの見通しが良くなるので入れてみたかったのですが、ハマリそうだったのと、Lambdaの起動が遅くなる懸念もあり、スキップしました。せっかくなので、Javaのマイクロサービスフレームワークである [MICRONAUT](https://micronaut.io/) とか使うと面白かったのかなぁと思ったりしています。


#### （GPSログ系）GPSマルチユニット → SORACOM Beam 部分
GPSマルチユニット用のSIMグループを作成し、GPSマルチユニットをグループ化します。
その後、SORACOM のダッシュボードから GPSマルチユニット用のSIMグループ の設定から SORACOM Beam 設定の設定に行き、 API Gateway エンドポイント(URL) の登録をしました。

特にハマることもなく、この設定だけでデータを送れたので、楽ちんでした。
![SIMグループの設定](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39825/c2925641-8187-a005-c936-54f97ff407a8.png)

### フロントエンド（Webアプリケーション）
本当は、Vue.js または ReactJs で作りたかったのですが、開催までの時間が無くなってきたので、使い慣れてる JavaのWebアプリケーションフレームワークの1つである Spring Boot と Webアプリケーションのインタフェースを構築するためのフレームワークである Java Server Faces(JSF) を利用した開発に切り替えました。このせいでフロントエンドをデプロイするサーバが必要になったので、急遽 Azure App Service [^3] を利用しています。

JSF は枯れた技術（人によってはオワコンと呼ばれます）ですが [AdminFaces](https://adminfaces.github.io/site/) や [PrimeFaces](https://www.primefaces.org/) といった素晴らしいUIコンポーネントがあり、Vue.js や ReactJs と同じようにコンポーネントを組み合わせるだけで、それなりのWebアプリができます。

<details><summary>JSFについてもっと詳しく･･･</summary><div>

POST/GET等のリクエストに応じたアクションベースではなく（Spring MVCのような）、画面表示時の動きはどうするか、ボタンが押下された場合にどう動くかのような振る舞いを定義するイベントベースで定義していきます（この点、Vue.jsやReactJSと同じような感じです）。振る舞いはすべてJavaで定義することができるため、npmのようなビルドシステムやパッケージ管理の配下に入ることも無く、Javascriptに触れることなく、画面の部分更新などSPA(Single Page Application)っぽいヌルッとした動きを持つそれなりのWebアプリが作ることができます。

一方で、JSFは、画面の構造をサーバ側に保持しておき状態変化に応じて常に同期（更新）していくステートフルな仕組みです。そのため、ボタン押下や画面更新の都度に、サーバ側との通信が発生することでVue.jsやReactJsに比べて動作はもっさりしますし、BtoCのようなスケーラブルなアプリケーションで利用するには工夫が必要（セッションを共有化するとかセッションに応じてアクセスするアプリケーションサーバを固定化する等）なフレームワークです。ある程度利用者が限定できるBtoBの業務システムなどに向いていると考えています。また、悪いことにドキュメントが少なく（特に日本語では）、とっつき易いフレームワークか、と言われれば否です。

</div></details>

JSF は、JBoss や GlasshFish などのJavaEEのアプリケーションサーバでしか動作しないイメージがあると思いますが、[JoinFaces](http://joinfaces.org/) と呼ばれる Spring Boot と JSF をつなげるライブラリによって、Spring Boot上でのJSF利用を実現しています。

このWebアプリケーションはあくまで表示部分のみを担当し、バックエンドロジックは、JavaのRESTクライアントライブラリを用いて AWS API Gateway を呼び出すことで実現しています。

### GPSマルチユニットについて
- 基本的には、SORACOMが提供しているマニュアルを読めば一通りの事は理解できました。設定とかも戸惑うことも特にありませんでした。
 - https://dev.soracom.io/jp/gps_multiunit/what-is-gps_multiunit/
- GPSマルチユニットは、基本的には稼働しっぱなしを想定しているのだろうと思いますが、電源オフ操作が難しく（中央のボタンを4秒押すとマニュアルにはある）、うまく電源が切れない･･･　ので、いつもバッテリー切れまで放置しています。
- GPSマルチユニットの起動直後は、GPSがうまく捕捉できずに、緯度・経度データ共に null となります。GPSを利用したい場合は、20～30分暖気運転（正しくGPSデータを捕捉するように電源を入れて放置）する必要があります。
 - 購入後に SORACOM Harverst で試運転している時に、どうやっても緯度・経度が null　になり、色々調べていて時間を溶かしていたらそのうち緯度・経度データが連携されてきたので、「GPSの衛星を捕捉するまでに時間がかかるんだなぁ」と理解しました。
- ~~GPSマルチユニットのデータ送信間隔をAPIで設定したいのですが、今のところ（2020年11月現在）そのI/Fが存在しない？ようで、SORACOMのダッシュボードにアクセスしてポチポチするしかないのが面倒です。~~
 - ~~実際送信間隔をほぼほぼ変えることはありませんが、できれば自作アプリ内で完結したいな、とは思います。~~
 - ~~台数が多いと1台ずつの設定も時間がかかるので、APIで叩きたくなります。~~
 - コメントいただきましたが、 https://dev.soracom.io/jp/docs/api/#!/Group/putConfigurationParameters から頑張ればできるようです！
- 最小データ送信間隔が1分なので、徒歩ならちょうどよい感じでデータを取得できます。しかし、車の場合は、1分間でそれなりの距離を進んでしまえるため（特に青信号が続く場合など）、間隔が空きすぎてスカスカな履歴データとなってしまいました。運転データとして取得するには使えないかもしれません。自転車だとどうでしょうか･･･
- GPSの精度については街中のハイキングと言うこともあってか、ズレを感じることはありませんでした。

# 費用

個人利用にしては、GPSマルチユニットの支出が重すぎた･･･ テンション上がりすぎて買ったが若干後悔:joy:。
そして、GPSマルチユニットの維持費の方がサーバ代より高いので、いろんなイベントで使ってもらって気持ち的にペイしていこうと思います！
（イベントに対して請求は0円で、個人の趣味です。今年は、特別定額給付金が出てるからちょっと無駄遣いしてもいいよね♪みたいなテンションで物を買ってしまってますが、軽く10万の出費を超えてしまってる気がします…:cry:）

## 初期費用

|項目|単価|数量|合計|
|:-----:|:-----:|:-----:|:-----:|
|GPSマルチユニットSORACOM Edition|11,000円|10台|110,000円(税抜)|
|SORACOM オンラインショップ送料 [^5] |1,000円|2回|2,000円|
|合計| | | 112,000円 |

## 運用費用

11月7～8日にイベントがあったので、その後、11月1日～11月10日時点での各サービスの費用を集計しました。

|項目|合計|
|:-----:|:-----:|
|SORACOMへのイベント開催二日間の支払い分|135円×2日分|
|Azure App Service(B1プラン)利用料|2.03円|
|AWS Lambda利用料|0円|
|AWS DynamoDB 利用料(11月1日～10日)|$0.33(≒34.64円)|
|AWS API Gateway 利用料(11月1日～10日)|$0.28(≒29.39円)|
|AWS Cloud Watch 利用料|0円(無料枠内?)|
|Google Map API利用料|0円(無料枠内)|
|合計|336.06円|

運営中にDynamoDBのReadキャパシティユニットをを引き上げた事が影響してか、有料になってしまいました。これは仕方ないかもしれません。
API Gateway は、イベント開始前からテストでデータを送ったりしていたのと、イベント終了後も稼働したまま放っておいたのもあってデータが2日間ぐらい送り続けられてしまい課金されてしまいました :crying_cat_face: 実際は無料の範囲内で収まるかもしれません。

Azure App Serviceを利用しているのですが、コストに2.03円しか表示されないのがイマイチ不可解です･･･　どさっと請求が来たら嫌だなぁとは思うもののダッシュボードから確認できるのは、2.03円のみでした。

冷静になって考えれば分かることですが（作るのが楽しくて冷静さを失ったわけですが笑）、SIMのステータスを「休止中」にしておいても、 1日10円×台数×日数分＝約3000円/月 の出費が確定して、かなり痛い･･･ :money_mouth: リモートワークになって飲み会が激減したので、飲み会1回分だと思って我慢しようと思います。

改善点として、GPSマルチデバイス　→　AWS API Gateway部分をSORACOM Funkを使えば、Lambdaを直接実行できるため、API Gatewayの実行回数が減り、もう少し節約できるかもです。

# 今後取り組みたいこと
- ジオフェンスを取り入れてチェックポイントの到着・出発の自動記録と出発・到着状況をサポートする大人のLINEグループへの通知（LINE BOT連携）をやってみたいなと思います。 
- 各チェックポイントやゴールへの到着予測時刻の算出。
- 参加した子供たちへ渡せる記録みたいなのを自動で出力してあげる仕組みなどあると嬉しいかもしれない！？と思っています。
 - どこでどう間違えたのか、どうすれば次回上手くいくのかといった成長に繋げるための仕組み
- 管理用Webアプリを Vue.js や ReactJs への移植。
- 速度のグラフ表示が詰まりすぎて見にくいので、表示間隔を落とすなど改善は必要だと思いました。

# プロジェクト規模感のメモ
個人プロジェクトの記録として、メモを残しておきます。

- 9月に思い立って業務終了後の時間（できて3時間）、土日（2～4時間ずつ）＋祝日をまるっと投入などで作り込みを行い、10月末には完成しました。
- 管理画面はイベントの登録・変更やGPSマルチユニットの紐付け(登録)や変更などを行える様になっており、先に紹介した画面を含めて20画面ぐらいあり。
- 総工数は、120時間（0.8人月）ぐらい。（1人日=8時間、1人月=20人日=160時間）  
- 規模感は、javaのコードだけで 10,000ステップ 程度 （うちテストコードが3,000行ぐらい）

# まとめと感想
- GPSマルチユニット + SORACOM Beam + AWS API Gateway + AWS Lambda + DynamoDB を利用して、位置情報監視システムが簡単に完成し、実際に役に立ちました。
  - GPSマルチユニットの維持費に比べたら、パブリッククラウドの利用料の安さ（というかタダ同然）は驚愕。サーバレスってすごい。
  - 年に数えるほどしか使わないサービスにおいては、サーバレス最強。（2回目）
- 子供たちにとって直接の恩恵を受けることはありませんが、サポートする大人たちから「すごく便利になった」、「迷ったり、他のグループに負けないようにデットヒートしてる光景など子供たちが努力している様子が手に取るように分かる（涙もの）」との声を頂きました。
  - かっこよく言えば、ボーイスカウトのようなボランティア活動であっても DX（Digital Transformation）が必要ですね！
  - GPSが無い時代は、サポートする大人たちが車で見回りを行い、子供たちがどこに居るのかを随時連絡し合っていました。その時に比べてだいぶ運営が楽になりました。
- GPSマルチユニットの数が10台～20台のオーダーであれば、現在値＋軌跡を画面表示可能ですが、100台オーダーになればGoogle Map自体への描画やデータのロードが重くなりますので、もっと別の表示の方法を選択すべきでしょう。
 - Google Map の軌跡表示を間引くこと、直近1時間だけ表示するなどデータを少なくする工夫は必要かもしれません。
- GPSマルチユニットからデータを受け付ける部分（SORACOM Beam → AWS API Gateway → AWS Lambda → DynamoDB 部分）は、1分間隔で10台が同時にデータ送付していても全然問題無さそうなので、このまま100台オーダーにスケールしても問題無い仕組みになっていると思います。
 - 仮にボトルネックが出るとしてもDynamoDBの書き込みキャパシティの問題なので、お金を出して書き込みキャパシティを引き上げて上げれば良いです。
- やっぱり趣味プログラミング楽しい！！:heart_eyes:

# 免責事項
- ボーイスカウトに関わりのある、ある大人の趣味的な取り組みであり、ボーイスカウト全体を代表する意見や取り組みではありません。

[^1]: ボーイスカウトでは、自ら地図を読み、歩くことを大切にしていることもあり、スマートフォン片手にGoogle Mapで調べながらいくことはしていません。
[^2]: 去年までは格安AndroidスマホにSORACOMのSIMを入れてAndroidアプリを用いた仕組みを作っていましたが、ハイキング開始時にAndroidアプリを立ち上げておいたり、電池の減りが早かったりしてバッテリーを追加装着するなど運用しにくい代物でした。GPS定期的に送信してくれるGPS類似アプリはあるものの、子供たちのスマホに入れたりするのは保護者の承諾など色々面倒なこともあり、運営側で準備する方が楽なこともあります。
[^3]: 本当は、無料で使えるアプリケーション運用サービスである [Heroku](https://jp.heroku.com/) を使おうと思ったのですが、すぐにメモリ500MB（無料版の上限）を超えてしまい、OutOfMemoryエラーが連発して、使い物にならないので、代替のサービスを探すことに･･･トホホ。AWS Elastic Beanstalkを使うのが筋かもしれませんが、デプロイを試みたらエラーとなり、諦めてSpring Bootを簡単にデプロイできそうな Azure App Serviceへ。
[^4]: SORACOM Funk を利用して AWS Lambdaと連携する方法もあるようですが、うまくデータ送付できなくてハマりそうだったので SORACOM Beamを使い、 API Gateway経由としました。
[^5]: テスト用に1台買って、残り9台を発注したので送料は2回分