5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-12-18

#前置き
秋葉原でM5stack用のGPSユニットを見つけたので買ってみました。緯度経度の情報はすぐにとれたので、カーナビのようにマップを表示してみました。

IMG_20211219_014646.jpg

#概要
この装置はM5stack用のGPSユニットと国土交通省の「国土地理院」が提供する地理院タイルを使って自分のいる位置をマップ表示するものです。
地理院タイルの詳細については参考記事をご覧ください。

#環境

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

#必要なもの

#M5stackでGPSを使う
TinyGPS++というライブラリをダウンロードしてGPSの機能を使えるようにします。

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

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

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

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

#マップ表示機能実装

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

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

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?