トップ地図アプリGIS > OSMバイナリレコードファイル形式

OSMバイナリレコードファイル形式

はじめに

パフォーマンスにこだわらず、プログラムを簡素にしたい。

リレーションのメンバーを並び替え、整った形にすることは必須である。 それをマルチポリゴンレコードなどとして出力することは簡単である。 しかし、レコードサイズが大きく、境界ボックスの領域が広大になることが多く、 ブロック分割が難しい。そこで、別の方法も検討したい。

案1: レコードサイズおよび境界ボックスがともに大きいレコードは osm_id名の単独ファイルとして ブロック分割ファイルとは別扱いとする。ブロックファイルには単独ファイルの境界ボックス、osm_idを置く。

案2: OSMバイナリレコードファイル上のリレーションは小さいものとして、レンダリング時にメンバーをつなぎ合わせる。

リレーション処理

行政境界(boundary=administrative)は relation で定義される。島しょ部や飛び地があると、 複数の outer polygon で構成される。 A市にB市の飛び地があると、A市にとってはその飛び地は outer polygon の中の inner polygon(穴)となり、 B市にとっては メインの outer polygon とは別の outer polygon となる。

つまり、行政境界は一つ以上の outer polygon で構成され、 個々の outer polygon の中には 0個以上の inner polygon が含まれる。

レコード形式としては まず outer polygon 次にその outer polygon に含まれる inner polygon を続け、 次に、第二 outer polygon、その inner polygon という風に秩序正しく並べる。

問題は OSMの relation の member は必ずしもそのような順番になっていないことである。 エディタ JOSM でも秩序正しく並んでいる方が分かりやすいが、 規則正しく並べることが OSMの規約になっていない。JOSMが規則正しく並び替えてくれるわけではない。

レンダリング上は複数の outer polygon は分解して、outer polygon はひとつ、inner polygon は 0 個以上 とする方がよい。

一つの outer polygon でエラーが検出された場合、全体をエラーとするのか、その outer polygonだけを エラーとするか。

inner polygon のエラーであった場合、その inner polygon だけをエラーとして除外するのか、 それとも 親の outer polygon だけをエラーとするか、あるいは全体をエラーとするか、 はレンダリングソフト次第である。

10年近く前から数年間 osm2pgsql を使ったが、最初の頃はエラーに寛容であったが、 そのうち、厳しくなったように記憶している。OSMの精度を上げようということであろうか。

自作プログラムでは、outer、inner ともエラーがあれば捨てる。outer が捨てられた場合、 そこの inner polygon は親ナシになるので、捨てられる。

例えば、kanto.osm の場合、outer polygon が中部地方や東北地方にまたがる場合、 メンバーの一部が欠けることがある。従って、エラーで捨てられるものが japan.osm より多い。

japan.osm でも他国のデータが一部不完全な形で含まれるため、エラーとして捨てるものがある。

一方、航路や高速バス路線などは、メンバーが欠けることがある。 また、エラーがあり、一つにつながらないケースもある。 これらの場合、レンダリング上はエラーとはせず、メンバー毎に独立して描画できる。

行政境界線自体はエラーがあってもメンバー毎に描画できる。 しかし、境界線にそって内側に描画する行政区域名が描けない。 境界線の内か外かは polygon が作れないと判断できない。

個々の境界線データに進行方向の右手がどこ、左手がどこ、といったデータが登録されているわけではない。

以上のように、relationについては、パソコンでの事前処理が必須である。 これまでは osm2pgsql と同様に、multipolygon の場合、outer polygon毎のレコードを作成してきた。

同じ方法を採ることになるかも知れないが、 別の方法も考えてみた。

メンバーは順序正しく並び替え、必要に応じて、少し、情報を追加しておく。

レンダリング時に簡単にメンバーが取り出せるならば、その時点で multipolygon レコードを組み立てることができる。 順に取り出してつなげるだけである。

リレーションのメンバーレコードは適当なサイズの osm_id順に並んだファイルに格納しておく。 単独で point/line/polygon レコードでもある場合にはブロックファイルと重複する。

合計10~20MB程度の専用キャッシュを置く。

Encoder

Encoderの出力フォーマット:   文字コードは UTF-16。
length, osm_id, uid, time, lon, lat, {key, val}*                         ----- Node   
length, osm_id, uid, time, bbox, num_nds, {lon, lat}*, {key, val}*       ----- Way  
length, osm_id, uid, time, num_members, {type, ref, role}*, {key, val}*  ----- Relation
bbox: minlon, minlat, maxlon, maxlat

lengthは自分を含むレコード長(単位:バイト)。

uid、userはペアでコード化する。現在(2024年10月)、japan.osm で、4万弱のため、2バイトでもよいが、 4バイトを割り当てる。

データ更新時刻 time は分単位として、4バイトを割り当てる。精度はもっと下げてもよいので、2バイト化も可能である。 しかし、レコードサイズの縮小に拘らない方がよい。

role は enum で定義し、レコード上は2バイトで表す。inner、outer などいくつかについては、 レンダリングに関係するが、レンダリングでは無視するものが殆どである。 【ソースデータ例】

  <node id='31253515' timestamp='2022-01-09T09:59:40Z' uid='7707735' user='zyxzyx'
 visible='true' version='5' changeset='115935292' lat='35.6536052' lon='139.7601994' />

japan のファイルサイズは node 136MB、way 3.99GB、relation 37.0MB、計4.13GBとなった。

当面、レンダリング対象外のrelationを除くと、relation は 28MBとなった。

maxId=2D922B13B, nodes=FF19CB7, ways=23BD110, rels=28F11(167697), 実行時間: 32.77分

relation member のノードは 153,588 nodes, 11.9 MB、ウェイは 677,412 ways, 217.8 MB であった。 ウェイレコードは平均で 300B を超える。

全体の5%程度であるから、ブロックファイルとの重複は問題でない。 同じようにzoom 8か9で分割すれば、入れ替え頻度はzoom 12分割のブロックファイルより小さくなるので、 osm_idによるメンバー取り出し平均時間は十分に小さいであろう。

レコードに組み立てた場合、行政境界などでは同じウエイが2回以上使われる。また、 境界ボックスは大きくなるため、zoom 12だけの分割はできず、zoom 7、8といった分割も必要となる。  

現在は、バス路線や行政境界名の描画はブロックファイルの総サイズを抑えるために、工夫をしている。 このような工夫をやめ、プログラムをシンプルにすると、ブロックファイルの総サイズは更に何割か大きくなる。

つまり、relationレコードを事前に組み立て、色んな工夫をやめると、総サイズが大きくなりすぎる。 SDカードの容量は問題ないが、パソコンからスマホ/タブレットへのファイル転送時間が増大する。

しかし、プログラムの簡単化を一番重視すると、総サイズの増大を許容すべきかも知れない。

レンダリング対象外の巨大なポリゴン/マルチポリゴンを除外する

北海道、本州、四国、九州などは除外する。ファイルサイズの縮小は僅かである(37.0 -> 36.7MB)。

Parser

length, head, osm_id, uid, time, lon, lat, {key, val}*                               ----- Node   
length, head, osm_id, uid, time, bbox, num_nds, {lon, lat}*, {key, val}*             ----- Way  
length, head, osm_id, uid, time, bbox, num_members, {type, ref, role}*, {key, val}*  ----- Relation
bbox: minlon, minlat, maxlon, maxlat

最終的に Android機に読込むバイナリレコードファイルを作成する。

現在、ハイズームは zoom 13 と zoom 10 で分割している。

建物や住宅内道路など境界バックスが小さいレコードが無数にあるため、zoom 12か13での分割が必須である。

一方、航路は単純な wayレコードであるが、境界ボックスが非常に大きい。 このような大きな境界ボックスをもつレコードのために、zoom 7~10 での分割も必要となる。

OSMのマルチポリゴンには2つ以上の outer polygon をもつ relation がある。 この場合、outer polygon に続いて、その outer polygon に含まれる inner polygon を続ける。 その後に、次の outer polygon を続ける。

個々の outer polygon、inner polygon は順につなげれば、閉ループになるように並び替えておく。 このとき、反転が必要となった場合には、way id(ref)を負に変えておく。

バス路線などラインレコ―ドではエラーがあってもレンダリングする。メンバーを並び替える必要もない。

ポリゴン、マルチポリゴンの場合、閉ループが作れない場合、エラーとなる。全体をエラーとするか、 一部だけをエラーとするかは選択できるようにしておく。

relationのエラーはログファイルに出力しておく。

osm_idによるレコード探索

レンダリング処理では osm_id によるメンバー探索がいる。

Encoderと同じ形式で分割しても osm_id でバイナリサーチできるが、プログラムが少し必要で、検索時間がかかる。 osm_idとオフセットだけをファイル先頭におき、レコードから osm_id と length を除いた方がバイナリサーチは やりやすい。ファイルサイズは変わらず、バイナリサーチはやりやすい。しかし、ブロックのレコード数は分割前には 分からない。一時的に管理ファイルとレコード本体ファイルを別々に作成して、後で一体化するとか、 最初は、Encoderと同じ形式で作り、後で作り変えるなどが考えられる。

順次検索でもさほど処理時間がかからないならば、プログラムは一番簡単である。

レンダリング時にポリゴン/マルチポリゴンを組み立てるには、一つまたは複数のブロックから、wayレコードを とりださなければならない。総合的なレンダリング時間が数倍になることも覚悟の上で、 プログラムの簡単化を模索している。 まずは、順次検索として、もしも、探索時間が問題になるようならバイナリサーチに変更したい。

relationについてはかなりの処理がいるが、 node と way については、単純にブロック分割するだけである。 しかし、polygon についてはいずれ中心位置の算出が必要になる。

まずは node と way を zoom 12 と zoom 7 で分割してみる。

分割ファイルは先頭に head(2または4バイト) を持つ。length や osm_id の上位バイトは使われていないので、 ここにフラグを置くことも可能であるが、ファイルサイズをけちるのはやめたい。

フラグとしては、node/way判別、relationメンバー判別など。

relationメンバーは数%程度と思われるので、別ファイルにする方が検索が速いが、 同じファイルでフラグチェックで済ます方がシンプルで探索時間はさほど増えないだろう。

nodeとwayを別ファイルにする案もあるが、一本化の方がシンプルであろう。

空間検索では全レコードについて境界ボックスをチェックしている。 メンバーの osm_id(ref)を小さい順に並び替えておけば、全レコードを一度スキャンするだけで、 メンバーの offset を求めることができる。

境界ボックスが大きい マルチポリゴン の場合、メンバーが含まれるブロックファイルが複数になるが、 読込むブロックファイルはこれまでと変わらない。

総合レンダリング時間は増える可能性が高いが、ブロックファイルのサイズは小さくなるため、時間短縮の効果もある。 現在、空間検索時間がレンダリング時間に占める割合は数10%程度である。

ハイズームでのレンダリング時間の増加は精々数10%であろう。 中、低ズームは場合によっては1本化する。タイル画像ファイルは一度生成したものを表示するので、 中ズームは数倍、低ズームは10倍、レンダリングに時間がかかってもよい。プログラムをシンプルにしたい。

大きなポリゴン、マルチポリゴン

innerポリゴンは単独でポリゴンレコードとなっていることがある。relationのメンバーとなっていた場合、 重複が起きてもいいので、全メンバーを一つの代表ブロックに置く。 他のブロックでは、代表ブロックだけを知らせるデータを置く。

パソコンでは relation の全wayメンバーをメモリに載せることは容易である。スマホ/タブレットでは、 実際には、ブロックの入れ替えが起きるが、インタフェース上は osm_id を引数として、wayレコードを取りだせる。 ブロックファイルの入れ替えはこれまでよりも増えるため、relation関連のレンダリング時間は増える。 逆に、単独wayレコードのレンダリング時間は若干速くなる。relation関連のレコードは僅かなため、タイル当たりのレンダリング時間は極端に大きくなることはないであろう。

wayレコード自体には境界ボックスが巨大なものは少ないため、これまでハイズームは zoom 12(13)、zoom 7(8)の二つのzoom で分けていたが、これを zoom 12(11~13)のみへの分割とする。

分割前が 4.1GBで、zoom 12分割では最大が 23.5MB、全体で4.81GBとなった。2割の増加は、ブロックをまたがる way は複数の ブロックに置くことによる。

zoom 11分割では重複が減り、全体が 4.52GB(重複1割)となるが、最大が 74MB と大きくなる。最大は 40MB程度に抑えたいので、zoom 12分割がよいだろう。

レンダリング時には近隣ブロックはメモリに読込まれているため、重複をやめて、近隣ブロックから wayレコードを 取り出すことも可能である。relationに採用する方法を node、way にも適用することになるが、量が多いため、レンダリング対象 となるレコードの抽出に時間がかかる。パフォーマンス上は2割の重複の方が優利であろう。

実時間でポリゴン/マルチポリゴンを組み立てる場合もそれをキャッシングしたり、ファイルにセーブすることになる。

事前にosm_id別にポリゴン/マルチポリゴンを作って置き、ブロックファイルにはosm_id、境界ボックスやタグなどのみ 登録しておく案もある。重複によるファイルサイズの増加は小さくなる。ブロックやタイルと同じように、 relationレコードのキャッシュは必要になる。

実時間組み立てプログラムが簡単であれば、それがベストなので、もう少し時間をかけて考える。

リファレンス

[1] OpenStreetMap Data Extracts