読者です 読者をやめる 読者になる 読者になる

プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

オフラインでの地図表示と経路検索 (OpenStreetMap と Graphhopper)

ある android アプリを作ろうと思い立ったのですが、そのためには、オフラインでの地図表示と経路検索を行う必要が出てきました。 当初は、Google Map での実現を考えたのですが、オフライン地図に対応していない地域もあるため、あきらめました。

で、次にあがった候補が、 OpenStreetMap です。OSM の Wiki を調べてみるとオフライン動作についての情報がまとまったページがありました。

ここを見ると、経路検索をオフラインで行えるものとして、 Graphhopper というライブラリが使えそうです。 日本語の情報もあまりないようなので、Graphhopper のデモapkを動かすところまで試したことをまとめておきます。

作業環境は以下の通りです。

  • Windows 10 Pro, 1607, 64bit なお、Bash on Ubuntu on Windows (以下 BoW) も併用してます
  • Android Studio
  • Nexus 5 (6.0.1)

Graphhopperの準備

Graphhopper を見ると商用サービスのように思えますが、オープンソース版があります。 このページのリンクをたどると、Github のプロジェクトページにたどり着きます。

github.com

ここの『For Mobile Apps』から、androidでのデモの説明ページに行きます。

graphhopper/index.md at master · graphhopper/graphhopper · GitHub

android版のデモapkはgraphhopperのリポジトリに含まれているようです。 さて、デモを動かすには、デモ版のソースコードと各種データが必要になりますが、この説明ページの内容が少しわかりにくいので、自分の行った手順とまとめておきます。

まずは、Maps のセクションを参考に、デモapkに必要なデータを準備します。

  1. OpenStreetMap の raw データをダウンロードします。今回は、三重県を含むデータで試したので、kinki のデータを落としました。 なお、いろいろと書いてありますがよくわからない面もあったので、標準的と思われる、 kansai-latest.osm.pbf というファイルをダウンロードしました。
  2. map データを落とします。ダウンロードサイトから japan.map をダウンロードしました。なお、mapデータとはあとでも出てくる mapsforge という地図表示ライブラリで使うデータになります。

以下の作業は、Graphhopperのリポジトリをcloneしてから行います。なお、この部分は BoW 上で作業しています。また、シェルスクリプトを動作させるために、java と zip/unzip を BoW 上でインストールしています。

  1. $ git clone git://github.com/graphhopper/graphhopper.git graphhopper
    
    なお、 git clone を実行したディレクトリに kansai-latest.osm.pbf および japan.map ファイルがあるものとします

  2. $ cd graphhopper
    $ ./graphhopper.sh import ../kansai-latest.osm.pbf
    
    kansai-latest.osm-gh というディレクトリが作成されて、中に必要なデータが作成されます

  3. C:\Users\mor> adb push kansai-latest.osm-gh/edges /sdcard/Download/graphhopper/maps/kansai-gh/
    
    kansai-latest.osm-gh ディレクトリ内のすべてのファイル(edges, geometry など)を、androidの外部ストレージ領域にコピーします。 この際、コピー先に kansai-gh (地域名-gh)のフォルダを用意しておきます。

  4. C:\Users\mor> adb push japan.map /sdcard/Download/graphhopper/maps/kansai-gh/
    
    japan.map もandroidの外部ストレージにコピーします

  5. C:\Users\mor> adb shell
    $ cd /sdcard/Download/graphhopper/maps/kansai-gh/
    $ mv japan.map kansai.map
    
    japan map を kansai.map にリネームします

ここまでできれば、データの準備は完了です。

なお、説明ページではzip圧縮したファイルを使うこともできるとあったので、androidへのコピー処理が簡単にできるかと思い、試したのですが、最終的なデモ動作がうまくいきませんでした。

デモapkプロジェクトの読み込み

以下のページに Android Studio でのデモapkを動作させる手順があります。

graphhopper/android-studio-setup.md at master · graphhopper/graphhopper · GitHub

ただ、自分の環境だと、Android Studio が Windows 上にあるので、 BoW 上の先ほどcloneしたリポジトリとは別に、graphhopperのリポジトリをcloneしておきます。 また、リポジトリのブランチをmasterではなく、 0.8.2 のタグに切り替えておきます。

あとは、説明ページと同様に、 graphhopper/android をオープンするプロジェクトとして指定すれば完了です。

サンプルapkの動作

オープンしたプロジェクトを実行します。正常に実行できると下記の様な画面が表示されると思います。

f:id:junichim:20170515005829p:plain:w300

Local のラベルの隣のスピナーで、オフラインデータとして設定した地域名(今回の場合は kansai)を選択して、OKボタンを押すと地図が表示されます (ただ、最初に表示される場所は、海の中のため、適当にズームアウトしないと陸地が表示されません)。なお、地図で日本を表示しようとした場合、後述の対策が必要です。

地図が表示されれば、ロングタップで経路を求める始点を指定します。

f:id:junichim:20170515005844p:plain:w300

赤線で囲んだ緑の旗(伊勢神宮の外宮前あたりを指しています)が始点を表しています。なお、この際、親切にメッセージを表示したり、音やバイブレーションで設定完了を教えてくれないので注意深く画面を見てください。始点が表示されていれば、設定できた、という感じです。

経路を求める終点は同じく、ロングタップで設定します。 終点を設定すると自動的に経路を求めて表示します。下記画面内の赤線で囲った赤の旗が終点、ちょっと見にくいですが37という数字が振られた道路を経由している緑の点線が経路です。

f:id:junichim:20170515005857p:plain:w300

トーストでも情報を出してくれますが、消えるのが早すぎて何を出しているのか正直分かりません(距離ぐらいはあったかな?)。

次の経路を求めたければ、地図上のどこかの地点をロングタップして、再度始点・終点を指定することになります。 一応、これで、オフラインの動作ができているということになりそうです。

なお、このデモapkの他の動作は次のようになっているようです。 Remoteスピナーから、地域名を選択して『downloding』ボタンを押すと、該当地域の地図データおよび経路データをダウンロードしてくれます。 で、ダウンロード後のデータは、ローカルに保存され、次回起動からはローカルデータとしてアクセスできます。 (デモだけなら、データの準備いらないんじゃ・・・)

注意

実は、デモapkそのままでは、日本のデータ(japan.map)が対象の場合、正しく日本の地図が表示されません。

f:id:junichim:20170515005909p:plain:w300

Android Studio につないで実行してみると、どうも地図データのブロックサイズが制限オーバーの様です。

f:id:junichim:20170515003734p:plain

調べてみると、このGraphhopperのデモアプリでは、地図の表示に mapsforge というライブラリを利用しているようなのですが、そのライブラリ側のデータ読み込み時の制限に引っかかっているようです。 ネットにも類似の議論がありました。

github.com

上記の記事からもわかるように、動的に読み込みバッファサイズを変更できるようなので、その部分だけ手直ししてみます。 具体的には、デモapkの MainActivity.java の 346行目付近に MapFile を生成する処理があったので、その直前にバッファサイズを変更するメソッドを追加してみました。

    void loadMap(File areaFolder) {
        logUser("loading map");
        ReadBuffer.setMaximumBufferSize(7000000); // for test
        MapDataStore mapDataStore = new MapFile(new File(areaFolder, currentArea + ".map"));

なお、指定のバッファサイズは、上記ログ画面で出力されているサイズをカバーするように適当に設定しています。

この状態で試すと問題なく表示できました。 ただし、レンダリングに結構時間がかかっているので、テスト時にはご注意ください。

まとめ

今回試してみた印象としては、

  • オフラインでの地図表示、経路検索はできそう
  • 経路検索の条件指定などは詳しく調べる必要がある
  • 地図のレンダリングは思っていたより遅い(端末のせいか?)
  • 地図データのセットアップ方法を考える必要がありそう

という感じでした。必要に応じて詳しく調べていきたいと思います。