【電装班】Android用マップアプリの作り方

あけましておめでとうございます

今年もよろしくお願いします

18年度電装班長の藤田(マイケル)です

学科の課題に追われていて最近はこれといったネタがないので、今回のプログではちょうど最近の活動で行っているAndroid Studioを使ったマップアプリ開発の方法を軽くご紹介します。

情報系に興味のある人向けとなりますのでそうでない方には完成形だけみて頂ければ幸いです。

早速ですが、1ステップずつ噛み砕いてご紹介していきましょう。

  1. Android Studioをインストールする。
    1. Android StudioはAndroid端末用アプリを開発するためにGoogle社が提供しているIDEです。
    2. Androidアプリは基本的にJava言語をベースに作るものなので、開発の際はJava言語を予め知っておくことをオススメします。
  2. Android Studio で新しいプロジェクトを始める。
    1. アプリケーション名などは当然ですがご自由に決めちゃってもらって構いません。
    2. SDKバージョンは API 17 以降にしてください。これ以前ですと正常に動作しなくなる可能性があります。
    3. アクティビティは”Empty Activity”(空のアクティビティ)を選択してたらこの後が楽です。今回はこれを選択します。
    4. アクティビティ名やレイアウト名はそのままで構いません。
  3. Mapboxのアカウントを作る。
    1. 上のリンクにクリックしたらアカウント作成ページに飛びます。
    2. 開発においてAPIにアクセスするために、アカウントに紐付けられたAccess Tokenが必要になります。
    3. Mapboxは様々なプラットフォームでマップ表示させたいときに使える非常に便利なAPIです。Android以外にもiOS, Javascript(Webなど), Unity, QtでこのAPIを使うことができます。
    4. 作成されるデフォルトのマップはGoogle Mapsと同様の操作ができます。
    5. 座標を指定してマップに直接画像を貼ったり、線を描いたり、様々な機能があります。
  4. プロジェクトにMapbox Android SDKをインストールする。
    1. こちら の公式マニュアルを基に説明します。
    2. Android Studioの画面左側にある”Grade Scripts”欄にある”build.gradle(Module:app)”を開き、一番外のスコープ(dependenciesの上とか)に
      repositories {
          mavenCentral()
      }
      

      をつけます。また、dependenciesの中に

      implementation 'com.mapbox.mapboxsdk:mapbox-android-sdk:5.3.1'
      

      を付け加えます。

    3. ここで画面の上の方にGradleファイルが変更されたので同期するようにと言われるので同期する。
    4. これでインストールは完了。
  5. アプリによる現在位置のアクセスを許可する。
    1. 画面左側の”manifests”の中にある”AndroidManifest.xml”の<manifest>タグの中に
      <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

      を付け加えて下さい。

  6. オンラインマップを表示してみる
    1. このリンクに飛んでもらって手順通りやればオンラインマップは表示できます。
    2. ちなみにここでさっき取得したアカウントに紐付けられたAccess Tokenが必要になります。先程のリンクで”Sign in to your Mapbox account to display your access token.”と書いてあるところをクリックしてサインインすれば自動的に表示してくれます。(既にログインしている場合はAccess Tokenが表示されています)
    3. USBに接続されたAndroid端末、またはエミュレーターを用意して、Android Studioの上の方にある再生ボタンをクリックすればアプリが実行されます。

 

オフラインに対応させる

(ここからはWeb開発経験があると理解し易いです)

  • 普段マップを使う場合はマップのデータをネット上から引っ張ってくることが多い。
  • 例えば、OpenStreetMapはhttp://a.tile.openstreetmap.org/${z}/${x}/${y}.png のような形式でマップデータを配信している。${z}, ${x}, ${y}に数字を代入すればそれに対応するマップの画像が得られる。
  • こんな感じ(http://a.tile.openstreetmap.org/1/1/0.png)で配信されてる。
  • オフラインに対応させるためにはAndroid内にマップデータ配信サーバー立ててしまえば良い。
  • Androidでサーバーを建てる方法は探せばネットに載ってるので是非探してみてください。
  • やりかたはたくさんありますが、Java標準装備の
    java.net.ServerSocket, java.net.Socket

    とか使えば建てられると思います。

  • 日本全国のマップデータはこのリンクでダウンロードすることができます。
    • OpenStreetMap vector tilesだけ無料です。
    • ファイルの大きさはだいたい1.62GBしかありません。
    • このマップデータをSDカードとかに入れて使うと良いかもです。
    • vector tilesと書かれているマップデータはpng形式のものではなくpbf形式のものをgzipで圧縮してsqliteデータベース化したもの(.mbtiles形式)になっています。
    • ざっくり言うと、クライアントからサーバー側に3つの引数 x, y, z が渡されたとき、データベースをx, y, zに対して
      select tile_data from tiles where tile_column=? and tile_row=? and zoom_level=?

      というクエリーをして、得られたバイト列をクライアントに投げ返せば良いのです。

    • Androidを使ってデータベースのクエリー方法はこのGitHubレポジトリ https://github.com/djcoin/MBTilesDroidSpitter を参考にすると良いかもです。
    • ここで気をつけるべき点は3つあります。
      1. クエリーで得られたバイト列はgzipで圧縮されたものなので返答時のHTTPヘッダーに
        Content-Type: application/x-protobuf
        Content-Encoding: gzip
        Access-Control-Allow-Origin: *
        Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
        Cache-Control: public, max-age=604800
        

        を入れる必要があります。これがないとクライアントは解凍してくれません。

      2. MapboxのAPIが使用する座標系とOpenStreetMapが配信するマップの座標系が異なる(原点の位置が違う)。クライアント(Mapbox)側のyをサーバー側で
        y = (1 << z) - 1 - y; // y = 2^z - 1 - y;

        のように変換してからデータベースをクエリーすれば問題は解消されます。

      3. Androidのセキュリティ上、データベース内にandroid_metadataテーブルがないと読取りが行えないため、sqlite3で以下のコマンドを実行する必要がある。<your_database>にはデータベースのファイル名(“???.mbtiles”)が入る。
        sqlite3 <your_database> "CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT DEFAULT 'en_US'); DELETE FROM android_metadata; INSERT INTO android_metadata VALUES ('en_US');"
        
  • クライアント側(Mapbox)のプログラムについて
    • サーバーを建てたあとはそのサーバーからマップデータを引っ張るクライアントを作る必要があります。
    • 先程のオンラインマップで使ったMainActivityクラスのonCreate()メソッドを
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          createServer(); // サーバーを建てるメソッド
          Mapbox.getInstance(this, "MY-ACCESS-TOKEN");
          setContentView(R.layout.activity_main);
          mapView = (MapView) findViewById(R.id.mapView);
          mapView.onCreate(savedInstanceState);
          mapView.getMapAsync(new OnMapReadyCallback() {
              @Override
              public void onMapReady(MapboxMap mapboxMap) {
                  mapView.setStyleUrl("asset://mapStyle.json");
              }
          });
      }

      のような感じにしてあげて、アプリのメインディレクトリの”assets”というディレクトリ(作る必要あり)の中に”mapStyle.json”というファイルを作って、中身にこれ(6行目のtilesに(サーバーのURL/z/x/y.pbf)を入れる)を入れれば(たぶん)オフラインで使えるようになる。

 

オフラインマップを作成し、それに回転数ゲージとかつけたら以下のようなものが出来上がりました。(機内モードで地図が表示されています)
IMG_1235

(ちなみに画面上の青い点は桶川・ホンダエアポートです)
今後はこれに機速・高度計・機体の現在位置などの表示をつけていきます。

 

2018年度執行代電装班長 藤田一輝

 

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です