トップ地図アプリMap > 地図アプリMapの基本設計

地図アプリMapの基本設計

地図アプリの基本技術

はじめに

スマホ地図アプリMapのパソコン版はほぼ完成した。 スマホ版の開発はエミュレータでのデバッグも考えたが、PCは 16GBメモリ搭載が望まれるため、 当面は無理である。

開発言語およびOSの違いから、ソースコード上は同じものとはならないが、 新たなレンダリングについてはパソコン版で開発してから、スマホに移植したい。

基本方針

前地図アプリGISではパフォーマンスに重点を置いた。これとほぼ同等のパフォーマンスを維持したいが、 より以上のパフォーマンスを目指すのではなく、プログラムの分かりやすさ・メンテナンスのやりやすさを目指す。

アプリのメモリ使用量は512MBに制限されるが、現状ではさほど厳しいという状況ではない。 建物のマッピングが進めば、全体的なファイルサイズは増大するが、都心部については、すでにマッピング済みの地域が多いため、極端な増加はないであろう。

アイコンや森林などパターン塗りつぶし用画像データ、複雑なライン描画用リソースなどをメモリに 常駐させる場合、これらが占めるメモリ使用量が大きくなる。 メモリ使用量を抑えるために、キャッシュ方式として入れ替えを考えた方が良さそうである。

レンダリング時には、タイルサイズ(256x256画素)と同じサイズのパターン塗りつぶし用画像データを使うが、 このサイズで数多くのパターンをメモリに置くのは非効率である。

要するに、レンダリング用のリソースの効率的な管理方法を考えたい。

OSMParserのモジュール化

OSMLib.java Tag.java は以前から分離していたが、後は OSMParser.java に集約していた。 2,000行を超え、メンテナンスがしずらくなっていた。

まず、次のように分割した。今後、更に、分離独立させるものがあるかもしれない。

・Parser.java    450行  メインプログラム.Node、Wayのパースおよび処理
・Relation.java  400行  Relationのパースおよび処理
・Cull.java      610行  低中ズームレンダリング用データ作成
・OSM.java       470行  OSMバイナリレコードの入出力
・Tag.java       320行  OSMタグの管理
・Lib.java       500行  ライブラリ
----------------------------------------------------------------------
    合計       2,750行

レンダリング用のリソース

交番、病院、店などのアイコンはサイズが小さいため、メモリに常駐しても問題はないだろうが、 ポリゴン塗りつぶし用アイコンの場合、画像演算時にはタイルサイズと同じ大きさにアイコンを敷き詰めた画像データを使う。 大きなサイズで多数データをメモリに常駐させるとメモリ使用量が増大する。 数が多くなったときは、小さいサイズのものを常駐させるとか、スワップを検討したい。

ライン描画に使うPaintインスタンスは多種、多様なものになる。 使う際に、new で生成するのが一番簡単であるが、ガーベージコレクションの対象となるため、 パフォーマンスの低下を招く。

前地図アプリGISでは、使用頻度の高いものは常駐させ、使用頻度の低いものは new で生成しているが、 プログラム的にはスマートではない。

例えば new float[] {2, 3} については double d2_3 = new float[] {2, 3}; あるいは Enum dash { d2_3, ... }; でよいが、setStrokeCap や setStrokeJoin などをどうするか。 Mapnik の LineSymbolizer の仕様も参考にしたい。

急ぐ必要はないので、じっくり検討したい。

最初の実行でインスタンスを生成して、キャッシュに登録する。数十インスタンスであれば逐次探索でもよい。 数百となれば、色とか線の太さなどでインデックスを作成しておけばいいであろう。 同じ色あるいは同じ太さのインスタンスの数は精々数十であろう。

Mapnikの LineSymbolizer

 <LineSymbolizer stroke-linejoin="round" stroke="#f8f8ba" stroke-width="13" stroke-linecap="round"/>
 <LineSymbolizer stroke="#999" stroke-width="4.5" stroke-dasharray="4,2"/>

地図アプリMapでのラインレンダリング

Mapnik の LineSymbolizer と対比すると、ラインの色 stroke は color とする。stroke="#f8f8ba" の場合、 color = 0xfff8f8ba となる。stroke-width は float width とする。

stroke-dasharray は Dash dash とする。stroke-dasharray="4,2" の場合、Dash.d40_20 とする。 dasharray の単位は小数点以下1桁として、10を掛けて整数化する。Dash は enum である。

stroke-linejoin="round"、stroke-linecap="round"等には予め1bitを割り当てておき、全体を int flags で指定する。

    final static int RoundCap  = 0x01;
    final static int RoundJoin = 0x02;
    final static int Rounds = RoundCap | RoundJoin;

    // 使用例
    osm.drawLine(tile, color, width, 0, Dash.d0, Rounds);
void drawLine(TileToRender tile, int color, float width, float dy, Dash dash, int flags);

初期化で、 文字列表現 "d40_20" から、数値 40、20 を切り出し、それを 10 で割って float[] v = { 4, 2 } を作っておく。

enum宣言データから、レンダリングに使う全ての浮動小数点配列を初期化で作成して置く。

レンダリング時にはこの浮動小数点配列を参照するだけである。

enum Dash { d40_20, ... } 

dyパラメータは Mapnik にも存在する。dy だけ、垂直方向に移動したラインを描画する。

要するに drawLineメソッドは、基本的には Mapnik の LineSymbolizer と同等の仕様とする。 内部的には、原則として、ガーベージコレクションが起きないように実装する。

PolygonSymbolizer など、他の Symbolizer についても同様である。

アイコン描画の場合、アプリ起動後、最初のメソッド実行で、ファイル読み込みを行い、 このイメージデータを HashMap に登録しておく。

その日の歩数表示

歩数計アプリは別途使用しており、今のところ、地図には、その日の歩数表示だけでよい。 地図アプリでは、無駄なデータ出力がある。

再起動がない場合、1日1回のファイル出力でもよい。

通常の万歩計アプリの場合、突然の再起動でも、歩数は正しいのかも知れない。 シャットダウンイベントがある場合、現在の値をセーブできるが、常時、現在の歩数をファイルに書き込んでいるかも知れない。

多少の誤差は問題がないので、1分ごとにチェックして、前回の出力値よりも10歩以上大きい時、歩数ログファイルに出力することで十分であろう。

日付が変わったとき、その日の歩数カウンタの値を特定ファイルに出力しておく。

再起動が起きると、歩数カウンタは0に初期化される。歩数ログファイルの最後の行の歩数からその日の最初の歩数カウンタの値を減算すれば、再起動までの歩数が分かる。最大誤差は再起動前の1分間で歩いた歩数となる。

この方法では、年間1~2万回のファイル書き込みが起きる可能性がある。 本体ストレージへの書き込みをSDカードへの書き込みに変えればそれほど気にしなくてもよいかも知れない。

シャットダウン時は、記事[1]によれば Intent.ACTION_SHUTDOWN を Broadcastで発行する、とあるので、 これを受けて、その日の歩数カウンタの初期値を「元の初期値-現在の歩数カウンタの値」に書き換える。 歩数ログファイルには歩数カウンタの値0と日時を出力する。定期的には、日付が変わったときと、 1時間ごとにチェックして、前回の出力と比べて、1000歩以上変わったときにファイル出力する。

リファレンス

[1] [Android]Shutdownの処理の流れ