#前置き
秋葉原でM5stack用のGPSユニットを見つけたので買ってみました。緯度経度の情報はすぐにとれたので、カーナビのようにマップを表示してみました。
#概要
この装置はM5stack用のGPSユニットと国土交通省の「国土地理院」が提供する地理院タイルを使って自分のいる位置をマップ表示するものです。
地理院タイルの詳細については参考記事をご覧ください。
#環境
- OS:Windows10
- エディタ:Visual Studio Code
- IDE: PlatformIO (VSCodeの拡張機能)
- マイコン: M5stack GO
#必要なもの
- M5stack
- SDカード
- M5stack用のGPSユニット
#M5stackでGPSを使う
TinyGPS++というライブラリをダウンロードしてGPSの機能を使えるようにします。
#include <M5Stack.h>
#include <TinyGPS++.h>
HardwareSerial GPS_s(2);
TinyGPSPlus gps;
void setup() {
M5.begin();
GPS_s.begin(9600);
M5.Lcd.setTextSize(2);
}
void loop() {
while (!gps.location.isUpdated()) {
while (GPS_s.available() > 0) {
if (gps.encode(GPS_s.read())) {
break;
}
}
}
M5.Lcd.clear();
M5.Lcd.setCursor(0,0);
M5.Lcd.printf("lat: %f\nlng: %f\r\n", gps.location.lat(), gps.location.lng());
delay(1000);
}
とりあえず、緯度と経度を表示するだけのプログラムです。
#LovyanGFXを使う
LovyanGFXとは?
M5stackの高性能なグラフィックライブラリ。既存のグラフィックライブラリより多くの機能を備えている。
なぜLovyanGFXを使うのかというとM5stackライブラリのLcd関数だとマイナス座標の表示が出来ないからです。
ArduinoIDEやPlatformIOのライブラリマネージャーに登録されているので、インストールして使いましょう。
#座標の変換計算式
計算はGPSでとった緯度経度のデータを
緯度経度→ピクセル座標→タイル座標
の順番で変換していく必要があります。計算式についてはコチラの記事をご覧ください。
#マップ表示機能の下準備
最初にM5stackのSDカードに地図データを保存する必要があります。自分のいる地域の地図データをフリーソフトを使ってダウンロードします。
使うソフトはこれ。
このサイトでソフトをダウンロードしてください。ダウンロードしたファイルの中に説明書もあるので参照してください。
使い方については割愛します。
#マップ表示機能実装
//計算式 https://www.trail-note.net/tech/coordinate/
#include <M5Stack.h>
#include <TinyGPS++.h>
#define LGFX_AUTODETECT
#include "SD.h"
#include <LovyanGFX.hpp>
#define pi 3.141592653589793
static LGFX lcd;
HardwareSerial GPS_s(2);
TinyGPSPlus gps;
//ズーム倍率
int z = 17;
void setup()
{
//M5,LovyanGFX,GPSの初期化
M5.begin();
lcd.init();
GPS_s.begin(9600);
M5.Lcd.setTextSize(3);
M5.Lcd.setCursor(30,120);
M5.Lcd.println("Now loading...");
}
void loop()
{
//GPSの位置情報取得
while (!gps.location.isUpdated())
{
while (GPS_s.available() > 0)
{
if (gps.encode(GPS_s.read()))
{
break;
}
}
}
//Aボタンを押すとズーム倍率を上げる
M5.update();
if(M5.BtnA.wasPressed()){
if(z!=18){
z++;
}
}
//Bボタンを押すとズーム倍率を下げる
if(M5.BtnB.wasPressed()){
z--;
}
//変数の定義
double la = gps.location.lat();
double ln = gps.location.lng();
double L = 85.05112878;
//緯度経度→ピクセル座標の変換計算
double px = int(pow(2.0, z + 7.0) * ((ln / 180.0) + 1.0));
double py = int(pow(2.0, z + 7.0) * (-1 * atanh(sin(pi * la / 180.0)) + atanh(sin(pi * L / 180.0))) / pi);
//ピクセル座標→タイル座標の変換計算
int tx = px / 256;
int ty = py / 256;
//タイル画像の中の座標を計算
int x = int(px) % 256;
int y = int(py) % 256;
//画像9枚のファイルアドレスを用意
String filename[9];
filename[0] = String("/std/" + String(z) + "/" + String(tx - 1) + "/" + String(ty-1) + ".jpg");
filename[1] = String("/std/" + String(z) + "/" + String(tx) + "/" + String(ty-1) + ".jpg");
filename[2] = String("/std/" + String(z) + "/" + String(tx + 1) + "/" + String(ty-1) + ".jpg");
filename[3] = String("/std/" + String(z) + "/" + String(tx - 1) + "/" + String(ty) + ".jpg");
filename[4] = String("/std/" + String(z) + "/" + String(tx) + "/" + String(ty) + ".jpg");
filename[5] = String("/std/" + String(z) + "/" + String(tx + 1) + "/" + String(ty) + ".jpg");
filename[6] = String("/std/" + String(z) + "/" + String(tx - 1) + "/" + String(ty+1) + ".jpg");
filename[7] = String("/std/" + String(z) + "/" + String(tx) + "/" + String(ty+1) + ".jpg");
filename[8] = String("/std/" + String(z) + "/" + String(tx + 1) + "/" + String(ty+1) + ".jpg");
/*
画像9枚の並びはこのようになっている
[0][1][2]
[3][4][5]
[6][7][8]
*/
//Stringからchar配列に変換
for (int i = 0; i < 9; i++)
{
int str_len = filename[i].length() + 1;
char file[9][str_len];
filename[i].toCharArray(file[i], str_len);
}
//filename[4]を中心として画像を描画
int mainx = -1 * (x-160), mainy = -1 * (y-120);
lcd.drawJpgFile(SD, filename[4], mainx, mainy);
//他8枚の画像を描画
if (mainx > 0 && mainy > 0)
{
lcd.drawJpgFile(SD, filename[0], mainx - 256, mainy-256);
}
if (mainy > 0)
{
lcd.drawJpgFile(SD, filename[1], mainx , mainy-256);
}
if (mainx + 256 < 320 && mainy >0)
{
lcd.drawJpgFile(SD, filename[2], mainx + 256, mainy - 256);
}
if (mainx > 0)
{
lcd.drawJpgFile(SD, filename[3], mainx - 256, mainy);
}
if (mainx + 256 < 320)
{
lcd.drawJpgFile(SD, filename[5], mainx + 256, mainy);
}
if (mainx > 0 && mainy < -26)
{
lcd.drawJpgFile(SD, filename[6], mainx - 256, mainy + 256);
}
if (mainy < -26)
{
lcd.drawJpgFile(SD, filename[7], mainx, mainy + 256);
}
if (mainx + 256 < 320 && mainy < -26)
{
lcd.drawJpgFile(SD, filename[8], mainx + 256, mainy + 256);
}
//中心に印をつける
lcd.fillCircle(160, 120, 8, TFT_CYAN);
lcd.fillCircle(160, 120, 5, TFT_BLUE);
delay(1000);
}
マップ表示機能のプログラムです。そのまま使ったり、自分で機能を追加したりして使ってみてください。
#ArduinoIDEを使っている人へ
ArduinoIDEは使いやすいソフトですが、M5Stack.h や LovyanGFX.hpp をインクルードしているとコンパイルにかなり時間がかかります。(私は5分くらいかかりました...)
Visual Studio Code で使える PlatformIO を使いましょう。
#参考記事
https://maps.gsi.go.jp/development/siyou.html