これまでは、簡単化も図ったが、どちらかと言えば、パフォーマンスに拘り、プログラムは複雑になってきた。 Map4 では原点に立ち戻り、シンプル化を目指した。使用開始はさらに数か月先と考えているが、ここで、一度、整理しておきたい。
シンプル化された面は多々あるが、管理データをバイナリレコードに含めたなど複雑さを増した面も ある。
タイル毎のレンダリングの宿命として、境界での文字列描画の途切れ回避が容易ではなく、これがプログラムの複雑化を招く。 もっと、スマートなプログラムを目指したい。
アプリの要になるのは、OSM地図のレンダリングに使うOSMバイナリレコードのフォーマットである。
現在は Encoder、Parser、Devider、ByteCoder の4ステップで最終的なOSMバイナリレコードを作成している。 特に、可変長バイトコード化は当初は考えていなかったため、手順が増えた。 いずれ、DeviderとByteCoderは一体化したい。
バイナリレコードフォーマットは簡略化したが、管理データを含めたため、その分、複雑になってしまった。
OSMデータ提供サイトからは圧縮形式(pbf)のファイルをダウンロードする。日本地図領域のファイルサイズは 2024年3月26日時点で 1.87GBであるが、xmlテキストファイルに解凍すると、42GBになる。
アプリ開発段階ではファイル形式を修正するたびに、ファイルの作り直しとなる。
xml形式は分かりやすく、柔軟性が高いが、解釈に時間がかかる。そこで単純なバイナリ形式に変換する。
OSMデータはNode、Way、Relationの三つのオブジェクトで構成される。
Nodeセクション、Wayセクションは巨大なため、パソコンでは日本地図領域の全データを同時にメモリに載せることはできない。 そのため、1パスで、xmlファイルから最終的なOSMバイナリレコードファイルを作り出すことはできない。
このため、第一フェーズとしては、xmlファイルをバイナリ化して、 Nodeセクション、Wayセクション、Relationセクションに分けて三つのファイルに出力する。
今回は次のようなフォーマットとしている。xml ファイルでは、Wayセクションのノード列は ノードID(osm_id)列であるが、 それを座標値に置き換えて出力する。 文字コードは UTF-8 とする。
length, osm_id, lon, lat, {key, val}* ----- Node length, osm_id, num_nodes, {lon, lat}*, {key, val}* ----- Way length, osm_id, num_members, {type, ref, role}*, {key, val}* ----- Relation
japan.osm(42GB) の変換結果は node0.obf(106MB)、way0.obf(1GB)、way1.obf(1GB)、way2.obf(995MB)、relation0.obf(38.7MB)、全体で3.11GB となった。
OSM の xmlデータには Nodeデータが無数にあるが、殆どが Way を構成する頂点(ノード)の座標値であり、 Wayレコードに吸収された。単独で意味を持つごく少数のノードのみが node0.obf に格納されている。
このフォーマットは Map4の開発当初より変更していない。 いわば、テキスト形式をバイナリ形式に変換しただけであり、これでは地図のレンダリングはできない。
デジタル地図を構成するデータは 点と線と面 である。OSMでは Node は点(point)、Way は線(line)か面(polygon)を表す。 Relation はそれらからなる総合体である。例えば、バス路線relationは、バス停(point) とバス路線(line) からなる。 都道府県や市区町村境界 relation は、polygon または multipolygon である。 multipolygon は一般的には複数の outer polygon とそれぞれの outer polygon には 0 個以上の inner polygon を含む。 inner polygon は例えば、横浜市境界relationにとっては 横浜市(outer polygon)にある鎌倉市の飛び地(inner polygon)のようなもの。鎌倉市境界relation にとっては、 outer polygon は鎌倉市本体の outer polygon 以外に、この飛び地も outer polygon の一つである。
広域森林も木のない所は inner polygon であるが、この inner polygon は別のレコードでは 公園polygon であったり、 住宅地 polygon であったりすることがある。
relation は自分だけのメンバーを持つこともあれば、 他の独立した point、line、polygon、relation をメンバーとすることもある。
Map4ではレンダリングに一切使わない relation も多い。また、レンダリングに使わないタグも多くある。 これらのタグは OSMバイナリレコードには含めていない。
現在のフォーマットを下に示す。最終形ではないので、いずれ、もう少し分かりやすくすべきであろう。 見かけの項目を減らすことに拘り過ぎている。{num_nodes}* が分かりにくい。また、type と length を分離した方がいい。
head, num_nodes, {lon,lat}*, {key,val}* ... point/line/polygon head, {num_nodes}*, {lon,lat}*, {key,val}* ... multipolygon head: 第0,1bit(0x03) 0: point、 1: line、 2: polygon、 3: multipolygon 下位3バイト レコード長 num_nodes: multipolygonの場合、最終要素(最終inner polygonノード数)の最上位ビットは 1 とする。
メッシュ(zoom相当)に分割するのみで、バイナリレコードフォーマットは上記と同じ。
ブロック分割されたファイル毎に実行する。
レコードフォーマットは以下の通りとする。
head(1), length, [num_nodes], {lon,lat}*, {key,val}* ... point/line/polygon head(1), length, num_polys, {num_nodes}*, {lon,lat}*, {key,val}* ... multipolygon head: 第0,1bit(0x03) 0: point、 1: line、 2: polygon、 3: multipolygon
head(レコード type) は1バイト固定、 length、 num_polys、num_nds は 符号なし可変長バイトコード である。
length には、head と length 自体の長さは含まれない。
先頭の lon、lat は4バイト固定整数ペアで、次のノードから 可変長バイトコード(符号あり)とする。
ただし、multipolygonの場合、各polygon毎に先頭は4バイト固定整数ペアとする。
タグ部 {key,val}* は入力ファイルのものと同じ。
レコード毎に空間検索に使う境界ボックスなどの管理データを持つ。
メモリ上では int配列とする。先頭は flag;type;length、flag は width、height が各2バイト、合わせて4バイトの時 0、それぞれ4バイトの時 1 とする。type 0(Pointレコード)のときは width;height はなし(共に値は1なので省略)。
head;length(4), minlon(4), minlat(4), width;height(0/4/8) head: 第0,1bit(0x03) 0: point、 1: line、 2: polygon、 3: multipolygon 第4bit(0x10) 0: width, height は2バイト、 1: width, height は4バイト 第5bit(0x20) 0: nameタグなし、 1: nameタグあり
nameタグの有無により、空間検索でのマージンを変える。
バイナリレコードはバイト配列としてそのままメモリに読込む。 ファイル先頭部の管理でーたは int配列に格納する。
class Block { byte[] vals; // OSMバイナリレコード(ファイル全体をbyte配列として読込んだもの) int[] idxs; // 境界ボックスなど // その他 }
Windows版地図では、最初の数年間は、事前にタイルを生成して、それを Windowsタブレットにコピーして地図を表示していた。 Android版ではその方法はとらず、リアルタイムで表示時点でタイル画像をレンダリングしている。 画面全体のタイル画像を1秒前後で生成できれば、さほど不都合はない。
しかし、地図の精度を上げて見栄えを追求すると、それだけレンダリングに時間がかかる。 そこで、Map4では、一度レンダリングで作成したタイル画像はストレージに保存することにした。二度目からは保存したタイル画像を読み込む。 初めての場合のみ、作成し、保存する。
現在、中・低ズームでこの方法を採用している。
高ズームではレンダリングが状況によって変わることがある。例えば、バス路線図の場合、選択範囲のバス路線図を表示したり、 選択した特定の路線だけを強調表示したりしている。
これに対しては、ベースとなるタイル地図画像だけをファイルにセーブしておき、 選択範囲のバス路線のレンダリングはベース画像を読み込んで、そこにバス路線だけを上書きすればよい。
将来的には、地図に同時に全ての地物のアイコンや名称を表示するのではなく、選択した特定の地物だけを表示できるようにしたい。 このとき、問題になるのは、アイコンや名称(文字)の重なりである。
店のアイコンや名前などを除いたものをベース地図とする。地名、道路名、交差点名、信号機などはベース地図に含まれる。 ベース地図に含まれるアイコンや文字列との重なりを回避する場合、画像データからある場所に文字やアイコンが描画されているか どうかを判断するのは簡単ではない。レンダリングでは、描画領域を矩形としてリストに登録している。 登録済みの矩形と重なる場合には、描画を止めている。
保存するのはタイル画像だけではなく、画像とセットで、ベース地図の矩形リストを保存しておく。 ベース地図にアイコンや文字列を上書きするときは、この矩形リストを読み込み、重なり回避を続ける。