3000円で作るGPSロガーシステム(M5Atom+GPSモジュール+クラウド完全無料枠)
2020年12月11日 金曜日
CONTENTS
【IIJ 2020 TECHアドベントカレンダー 12/13(日)の記事です】
はじめに
早いもので気が付けば2020年も終わりですね。
今年は自宅で過ごすことも多く、今までとは違う時間の使い方をした方も多くいたのでは?と思います。
こんな時こそ、童心に戻ってお金をかけずに大人の工作を始めてみませんか?
ここではAtomicGPSを使ってGPSロガーとそれを可視化するツールを作ってみたいと思います。
AtomicGPSとは
M5Stack、M5Atomシリーズご存じですか?
5cm四方の小箱(M5Atomは2.5cm四方)に、ディスプレイ、Wi-Fi、Bluetooth、スピーカー、ボタンなどが装備されており、拡張モジュールを追加することで様々なセンサー情報を取得できるマイコンです。
特筆するのはその値段で兄貴分のM5Stackシリーズが約2000円~、M5Atomシリーズが約1000円~となります。お財布にやさしいですね!
AtomicGPSはM5Stackシリーズの弟分にあたるM5Atom向けのGPSモジュールになります。
工作の方針
- とにかく安く!低コストで実現!
- 初期投資3000円(M5Atom Lite + GPSモジュール)のみで、クラウドは完全無料枠を使います
- 無料枠を超えたら課金されるクラウドサービスは対象外とします
作るもの
- GNSS位置情報をリアルタイムで地図上に表示します
- 過去訪れた場所をヒートマップで可視化します
こんな感じで可視化します
システム構成
利用機材とサービス
デバイス以外、無料サービスを前提に選定しています。
デバイス
- AtomicGPSを使います
MQTTブローカー
- 無料のMQTTブローカーサービス、shiftr.ioを使います
位置情報I/F
- Node.jsのデプロイに完全無料のOracle Cloud Free TierのIaaSを使います
データベース
- MongoDB AtlasのM0クラスタ(完全無料)を使用します
WEBブラウザ
- Google Chromeで動作確認を行います
解説
事前準備
M5Atom + GPSを入手しよう
- AtomicGPS
- Arduino IDEのインストール
無料サービスに登録しよう ※別のサービスでも可
- MQTTブローカー
- MongoDB
- Node.jsを実行できる環境
ソースコードを入手しよう
デバイス開発
- 格納先
- /m5atom-gps
ここでは以下の機能を実現します。
- GNSSから位置情報の取得
- Wi-FiでMQTTブローカーに位置情報をPublish
- SDカードから端末設定情報を取得
M5Stack/Atomの開発はC/C++をベースにしたArduino言語で行います。
開発、デプロイはArduino IDEを使用します。
Wi-FiのAPやパスワードなどの設定情報をSDカードから取得するようにしています。
MQTTのトピック名とメッセージはMQTTブローカー設定の項目を参照してください。
ソースコード、導入手順は以下を参照してください。
MQTTブローカー設定
ここでは以下の機能を実現します。
- デバイスから上がってきた位置情報をSubscriberへ配信
IoTではおなじみのみんな大好きMQTTです。ここでもGNSS位置情報をMQTTで送信します。
IoTデバイスの通信は、「AWS IoT コア」やGCPの「Cloud IoT Core」を使えばより簡単に大量デバイスの管理や通信ができますが、無料で実現したいのでshiftr.ioという無料MQTTブローカーサービスを使用します。
トピックは「m5atom/location」とします。
「m5atom」の部分をデバイスIDとしてデバイス毎に変更すれば複数端末にも対応出来るようにしています。
メッセージは、緯度、経度、高さ、GNSS受信時刻とします。
{ "lat":"35.159965", "lon":"136.907106", "alt":"3.10", "gpstime":"2020-11-20T00:00:37.907Z" }
位置情報I/F開発
- 格納先
- /insert-pubsub-mongo
- /vector-tile-mongo
ここでは以下の機能を実現します。
- insert-pubsub-mongo
- MQTTブローカーから位置情報を取得してデータベースに保持
- vector-tile-mongo
- データベースから指定領域のベクトルタイルを返却するAPI
ともにNode.jsで開発します。
Oracle Cloud Free TierのIaaS上で実行します。
完全無料で、1/8 OCPUと1GBメモリが2台、10MbpsのLBが使えます。
insert-pubsub-mongo (MQTTブローカーから位置情報を取得する機能)
MQTTブローカーで設定したトピックを受信しデータベースに格納します。
ソースコード、および導入手順は以下を参照してください。
vector-tile-mongo (ベクトルタイルを返却するAPI)
APIの入力パラメータにタイル座標を使用します。
タイル座標から領域を割り出し、GeoJSON形式のベクトルタイルを返却します。
ベクトルタイルを使えば情報量の多い地図データを広域から狭域まで扱えるようになります。
以下はタイル座標から矩形を求める計算です。
function tile2latlon(x, y, z){ const mod = (a, b) => { return a * b < 0 ? a % b + b : a % b } const x2lon = (x,z) => { const lon = mod((x / Math.pow(2, z) * 360 ), 360) - 180 return (lon != (-180) ? lon : 180) } const y2lat = (y,z) => { const n = Math.PI - 2 * Math.PI * y / Math.pow(2, z) return 180 / Math.PI * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))) } const minlon = x2lon(x, z) const maxlon = x2lon(parseInt(x,10)+1, z) const minlat = y2lat(parseInt(y,10)+1,z) const maxlat = y2lat(parseInt(y,10),z) return { min: {lat: minlat, lon: minlon}, max: {lat: maxlat, lon: maxlon} } }
タイル座標とは
Google Mapsの最大の功績は球面メルカトル図法(Webメルカトル図法)の発明でしょう。
南北86度程度以上を切る捨てることで正方形になることを利用し、4分割していくことでズームレベルとタイル座標で世界地図から詳細地図までをURLパスで表示可能としました。
この発明で今では当たり前なシームレスなWEB地図を実現したのです。
GeoJSONとは
JSONを用いたオープンな地理空間データフォーマットです。
地理空間データフォーマットは他にもWKT、KML、GML、Shapefileなどがありますが、GeoJSONはJavaScriptと相性が良いことからWEB地図ではよく使われる形式です。
ソースコード、導入手順は以下を参照してください。
データベース設定
データベースは空間インデックスが利用できるMongoDBを利用します。
空間インデックスが扱えるデータベースはOracleDB、SQLServer、PostGISなどが有名ですが、サーバ運用込みで無料で利用したいのでMongoDB Atlasを利用します。
MongoDBはDocumentDBと呼ばれるNoSQLの一つです。GeoJSONを扱うことができ、空間インデックスを張ることができます。
MongoDB AtlasはそのMongoDBのSaaSで小容量ながら完全無料で利用することができます。
DB、コレクション名
DB名は「devices」、コレクション名は「locations」としています。
ドキュメントはデバイスID、GeoJSON、GNSS受信日時、更新日時です。
{ _id: ObjectId(5fb6dd0c9018a66d7f27a3b7), deviceId: "m5atom", geometry: { type: "Point", coordinates: [ 136.883813, 35.171259, 2] }, gpstime: "2020-11-19T21:00:58.803Z", uploadtime: "2020-11-19T21:00:59.095Z" }
空間インデックスをgeometryに設定します。
devices.locations.createIndex( { geometry : "2dsphere" } )
フロントエンド開発
- 格納先
- /demo-viewer
ここでは以下の機能を実現します。
- 最新のデバイスの位置情報をリアルタイム表示
- 過去の位置情報をヒートマップで可視化
地図を容易に扱うことができるleaflet.jsを利用します。
leaflet.jsはオープンソースなJavaScriptのWeb地図ライブラリです。
Vueなどのフロントエンドフレームワークと組み合わせて作れば、非常に簡単でリッチなWeb地図が開発できます。
今回はシンプルに開発したいのでフレームワークを使わずにhtmlのみで実現します。
ベクトルタイル対応
leafletはラスタタイルは標準で対応していますが、ベクトルタイルには対応していませんので個別で実装が必要です。
標準で用意されているグリットレイヤを拡張して対応します。
タイルが呼び出されるイベントで該当タイルのベクトルタイルを取得してヒートマップを表示します。
ヒートマップではなく、GeoJSONレイヤを使えば地図として表示も可能です。
以下は実装例です。
L.gridLayer.GeoJson = function (opts) { return new L.GridLayer(opts) } L.gridLayer.GeoJson({ maxZoom: 22, minZoom: 10, maxNativeZoom: 16 }).on('tileload', function (event) { // タイルロードのタイミングでGeoJsonベクトルタイルを取得してヒートマップを生成 const url = "http://localhost:3000/api/geojson/{z}/{x}/{y}" fetch(L.Util.template(url, event.coords)) .then(res => res.json()) .then(geojson => { if (!geojson || geojson.features.length == 0) { return } const latlons = geojson.features.map((feature) => { return [feature.geometry.coordinates[1], feature.geometry.coordinates[0], 0.5] }) // ヒートマップの生成 event.tile.heat = L.heatLayer(latlons, { radius: 15, maxZoom: 18, minZoom: 10, blur: 15 }).addTo(heatLayer) }) }).on("tileunload", function (event) { // タイルがリリースされるタイミングで該当のヒートマップレイヤを削除 if (event.tile.heat && heatLayer) { heatLayer.removeLayer(event.tile.heat) } }).addTo(map)
MQTT対応
リアルタイム表示はMQTTでメッセージを受信します。
こちらも標準ではMQTTに対応していませんので、何らかのレイヤに拡張して実現します。
レイヤが作成されたイベントでマーカーレイヤの追加とMQTTの受信設定をします。そしてメッセージを受信する度にマーカーを更新します。
ソースコード、詳細は以下を参照してください。
デモ
名古屋周辺のサンプルデータ(※)を定期的に更新するデモです。
(※)テスト用に作成したデータです。実機で取得した値ではありません。
最後に
今回はGPSキットを使ってGNSS位置情報をWEB地図に表示するサンプルを作ってみました。
M5Stack/Atomは低価格ではありますが非常に拡張性に優れ、次々と拡張キットが追加されています。
また、最近では手軽で且つ無料でサービスを実現出来る環境も整っており、エンジニアにとってはおいしい時代になったと感じています。
コロナ禍の中、自宅にいる時間も増えています。
是非この時間を有意義につかって、安く楽しく大人の工作を楽しんでみましょう!