M5Stackでカーナビっぽいものを作る！ 

#前置き
秋葉原で[M5stack用のGPSユニット](https://www.amazon.co.jp/%E3%82%B9%E3%82%A4%E3%83%83%E3%83%81%E3%82%B5%E3%82%A4%E3%82%A8%E3%83%B3%E3%82%B9-M5Stack%E7%94%A8GPS%E3%83%A6%E3%83%8B%E3%83%83%E3%83%88/dp/B07Z62RK1P/ref=asc_df_B07Z62RK1P/?tag=jpgo-22&linkCode=df0&hvadid=342405968082&hvpos=&hvnetw=g&hvrand=14962772570987643142&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009025&hvtargid=pla-895431988274&psc=1)を見つけたので買ってみました。緯度経度の情報はすぐにとれたので、カーナビのようにマップを表示してみました。

![IMG_20211219_014646.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2398061/d184237e-682e-187b-f34f-5ef58101c033.jpeg)

#概要
この装置は[M5stack用のGPSユニット](https://www.amazon.co.jp/%E3%82%B9%E3%82%A4%E3%83%83%E3%83%81%E3%82%B5%E3%82%A4%E3%82%A8%E3%83%B3%E3%82%B9-M5Stack%E7%94%A8GPS%E3%83%A6%E3%83%8B%E3%83%83%E3%83%88/dp/B07Z62RK1P/ref=asc_df_B07Z62RK1P/?tag=jpgo-22&linkCode=df0&hvadid=342405968082&hvpos=&hvnetw=g&hvrand=14962772570987643142&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009025&hvtargid=pla-895431988274&psc=1)と国土交通省の「国土地理院」が提供する[地理院タイル](https://maps.gsi.go.jp/development/siyou.html)を使って自分のいる位置をマップ表示するものです。
[地理院タイル](https://maps.gsi.go.jp/development/siyou.html)の詳細については参考記事をご覧ください。

https://maps.gsi.go.jp/development/siyou.html

#環境

- OS:Windows10
- エディタ:Visual Studio Code
- IDE: PlatformIO (VSCodeの拡張機能)
- マイコン: M5stack GO

#必要なもの
- M5stack
- SDカード
- [M5stack用のGPSユニット](https://www.amazon.co.jp/%E3%82%B9%E3%82%A4%E3%83%83%E3%83%81%E3%82%B5%E3%82%A4%E3%82%A8%E3%83%B3%E3%82%B9-M5Stack%E7%94%A8GPS%E3%83%A6%E3%83%8B%E3%83%83%E3%83%88/dp/B07Z62RK1P/ref=asc_df_B07Z62RK1P/?tag=jpgo-22&linkCode=df0&hvadid=342405968082&hvpos=&hvnetw=g&hvrand=14962772570987643142&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009025&hvtargid=pla-895431988274&psc=1)

#M5stackでGPSを使う
[TinyGPS++](http://arduiniana.org/libraries/tinygpsplus/)というライブラリをダウンロードしてGPSの機能を使えるようにします。

http://arduiniana.org/libraries/tinygpsplus/

```c++:GPS.cpp
#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のライブラリマネージャーに登録されているので、インストールして使いましょう。

https://lang-ship.com/blog/work/lovyangfx-1/

https://lang-ship.com/blog/work/lovyangfx-5-png/

#座標の変換計算式
計算はGPSでとった緯度経度のデータを
**緯度経度→ピクセル座標→タイル座標**
の順番で変換していく必要があります。計算式についてはコチラの記事をご覧ください。

https://www.trail-note.net/tech/coordinate/

#マップ表示機能の下準備
最初にM5stackのSDカードに地図データを保存する必要があります。自分のいる地域の地図データをフリーソフトを使ってダウンロードします。
使うソフトはこれ。

https://sites.google.com/site/geopapakaisetu/tairu-de-tuno-ru-shou-fang-fa/sofutoueadetairuwodaunrodo/geopapaofflinetile

このサイトでソフトをダウンロードしてください。ダウンロードしたファイルの中に説明書もあるので参照してください。
使い方については割愛します。

#マップ表示機能実装
```c++:GPSmap.cpp

//計算式 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 をインクルードしているとコンパイルにかなり時間がかかります。(私は５分くらいかかりました...)
Visual Studio Code で使える PlatformIO を使いましょう。

https://qiita.com/lutecia16v/items/1c560bdd7eac7ebeaff7

#参考記事
https://maps.gsi.go.jp/development/siyou.html

http://arduiniana.org/libraries/tinygpsplus/

https://lang-ship.com/blog/work/lovyangfx-1/

https://lang-ship.com/blog/work/lovyangfx-5-png/

https://www.trail-note.net/tech/coordinate/