これまでは、簡単化も図ったが、どちらかと言えば、パフォーマンスに拘り、プログラムは複雑になってきた。 Map4 では原点に立ち戻り、シンプル化を目指す。
OSMバイナリレコード形式についても抜本的に変更する。
アプリの要になるのは、OSM地図のレンダリングに使うOSMバイナリレコードのフォーマットである。
{key,val} は OSMタグを表すが、必要に応じて、way_area、osm_id、{cx,cy}(ポリゴンの中心)などもここに含める。
head, {lon,lat}, tags_length, {key,val}* ... point head, bbox num_nds, {lon,lat}*, tags_length, {key,val}* ... line head, bbox, num_nds, {lon,lat}*, tags_length, {key,val}* ... polygon head, bbox, num_polys, {num_nds, {lon,lat}*}*, tags_length, {key,val}* ... multipolygon head: 最上位バイト: 第0,1bit(0x03) 0:point、1:line、2:polygon、3:multipolygon 下位3バイト: record length(headを含む)
num_nds は 215未満のときは2バイト、そうでないときは4バイトとする。 現在は4バイトになるのは1レコードのみ。
point の {lon,lat}、line、polygon の先頭の {lon,lat}、multipolygon の polygon 毎の先頭の {lon,lat} は それぞれ4バイトペアとする。line、polygon、multipolygon中の各polygon の2番目以降は前との差分を2バイトペア で表す。差分が2バイトで表せないほど大きい場合は、ノードを内挿(追加)して、必ず2バイトで表せるようにする。
bbox(boundary box: minlon, minlat, w, h)の minlon、minlat は4バイト、w、h は2バイトか4バイトで、 bboxフラグで判断する。
bboxは空間検索に使うが、実際の値より大きくしても支障はない。抽出レコードが大幅に増えるわけでもないだろう。 w、h が2バイトで表せないときは、2、4、8、... で割って、切り上げ、2バイトに収まるようにして、 2で割った回数を headの最上位の空きビットに置いてもよい。3、4ビットで済むはずである。
w、h が2バイトを超えたときは無限扱いでも、検索結果に大差がないかも知れない。いずれ、実測したい。 それの方法で、bbox を常に 12バイトとするか、何もせず、16バイトにしても、差は4バイトに過ぎない。
num_nds はたった1レコードのために、4バイトにするのはもったいないが、 bbox の w、h はそのままでは2バイトで表せないケースが何割かある。 2バイトと4バイトを併存させるのはなるべく避けたい。
タグの値については、パフォーマンス上は short型を追加した方が良いが、その効果は小さいため、これまで通り、 int、long、float、String とする。OSMタグではないが、osm_id もタグ部に置く。ノードの osm_id は long型となる。 int型の osm_id4 も置く。
全体としては OSMバイナリレコードの数値は short型が中心となるため、 可変長バイトコードに比べて、それほどサイズ上のロスがなく、プログラムが分かりやすく、パフォーマンスが良い。
レンダリングに使用する OSMタグの キーは enum Key で指定する。これに含まれない OSMタグは無視する。 OSMタグの値は enum Val で指定する。ここに含まれない場合、その OSMタグを無視するのではなく、値を Val.unknown とする。
shop、office、building などは無数の値が使われる。マッパーの好みによるものもあり、 事前に enum Val に漏らさず含めるのは得策ではない。shop アイコンは ● とか ■ とする。 office、buildingは今の所、アイコンはなく、名称のみである。
name:en も含んでいるため、ASCIIコードの比重が高く、文字コードは UTF-8 とした方が、 ファイルサイズは少し小さくなる。しかし、2バイト境界になるよう文字列末尾に 0x00 を補充する措置がいる。
UTF-16とすれば、2バイト単位となり、全体のファイルサイズは少し大きくなるが、プログラムは分かりやすい。
name:en は現在は使っていないので、やめてもよい。
文字列型タグを増やす場合、ファイル単位の辞書式にした方がいいだろう。
バイナリレコードは short配列としてそのままメモリに読込む。
動的にメモリを確保すると、中低ズームで地図を広範囲に表示した場合、 ガーベージコレクションが頻発して、パフォーマンスが低下したり、メモリ不足が起きやすい。
例えば、2MBエリアを100個分プールして置き、一つのバイナリレコードファイルの読み込みに 複数ブロックを使用する。
一つのレコードが二つのブロックにまたがることは避ける。 余りが少ない時は次のレコードがないことは分かる。そうでないときは、余りの先頭の4バイトを 0 とする。 レコードの場合、先頭の4バイトが 0 となることはないので、レコードの終わりを判断できる。
空間検索では、これまでと異なり、複数のブロックをスキャンすることになるが、 プログラム的にはさほど変わらない。
空きブロックはチェインでつなぎ、取り出しは先頭として、解放も先頭にする。チェインは一方向で済む。
レコードで読み込みで使うブロックも同じ一方向のチェインとする。
データベースでは空間インデックスが使われるが、自作地図アプリではデータベースは一切使用していない。
OSMバイナリレコードは予めメッシュ分割しており、一つのバイナリレコードファイルでの検索は単純な順次検索である。
地図アプリMap3では、zoom 13以上のレンダリングは zoom 13で分割したものを使う。 しかし、広域森林のような巨大なポリゴンは、zoom 13では無数のメッシュ(タイルと同じ)にまたがるため、 重複が多くなる。これを避けるために、zoom 13分割では重複が多すぎるレコードは zoom 7 で分割している。
分割を2段階にするのではなく、境界ボックスが巨大となるレコードは分割せず、レコード毎のファイルにする案もある。 ファイル名は通し番号に境界ボックスの4値を並べたものとする。
zoom 7で分割しても重複はあるため、個別ファイルとした方が全体のファイルサイズは小さくなり、 ファイル読み込み時間は短くなる。
パフォーマンスの向上は期待できるが、分割プログラムやキャッシュ方法は異なるため、プログラムの負担はやや大きくなる。 シンプル化には反するので、大きな効果が期待できなければ見送る。