トップ地図アプリMap1 > OSMバイナリレコードファイルの作成

OSMバイナリレコードファイルの作成

はじめに

OSMのデータ形式(xml)は比較的にシンプルである。 しかし、日本地図領域全体を表すOSMデータは XML形式(プレーンテキスト)では2024年4月末では 42.3GB である。 到底、全体をメモリに読込める大きさではない。

また、直接、地図を描ける仕様のでーたではない。

一挙に、地図を描けるようなデータ形式にするのは、簡単ではない。このため、いくつかの手順を踏んで、 最終的に地図のレンダリングに使うバイナリレコードファイルを作成する。

OSMバイナリレコードファイルの作成

OSMファイルは大きくは3つのセクションに分かれる。 Nodeセクション、Wayセクション、Relationセクションである。

Nodeは単独でひとつの地図上の Point情報となるケースもあるが、殆どのNode は Way を構成する点の座標を表す。

Wayは Node ID列であり、座標値そのものは Nodeを見ないと分からない。 Nodeの数は膨大なため、Node ID を座標値に置き換える作業は簡単ではない。

Encoder

そのため、まず、最初に、Node ID を座標値に置き替え、Wayオブジェクトを座標値列データ(バイナリ)とする。

極座標に 107 をかけて、整数化すると、丁度4バイト(int型)で表せる。

highway=motorway、amenity=post_office といったOSMタグはキー(highway、amenity)、 値(motorway、post_office)ともコード化して2バイト整数(short型)で表す。

OSMタグには admin_level=7、layer=-1 といった整数値を値とするものもある。この場合、 キーが short、値が int(4バイト)となる。ele=1023.6(標高)のように、値が float型になるものもある。 name(名称) や ref(道路番号、略号など) など値が文字列となるものもある。

これらOSMタグのコード化、バイナリ化もここで行う。

この段階のレコード形式は次のようになる。

Encoder.java は約300行であり、さほど複雑ではない。 しかし、巨大なメモリを使用するため、次のステップの Parser との一体化は避けたい。

Encoderの出力フォーマット
length, osm_id, lon, lat, {key, val}*                         ----- Node   
length, osm_id, num_nds, {lon, lat}*, {key, val}*             ----- Way  
length, osm_id, num_members, {type, ref, role}*, {key, val}*  ----- Relation

length、lon、lat は4バイト、osm_id は8バイトである。key は2バイト、 val は2、4、2*n バイト である。文字列は UTF-16コードである。

2024年4月下旬の japan-latest.osm に対する結果は文字コードを UTF-8 とした場合、 node 106MB、way 2.99GB、relation 40.5MB となった。

UTF-16とした場合は node 110MB、way 3.02GB、relation 49.7MB となった。 name:en を含んでいることから、英字の比重が高いようだ。

UTF-8では、文字列の長さが奇数になるケースがある。 余分に1バイト(0x00)を加えて偶数に調整することもできるが、UTF-16ではそのまま偶数になる。

OSMバイナリレコードはメモリ上では short配列とした方が好都合である。

この段階では relation ファイルは小さいが、次の段階で生成されるバイナリレコードファイルの サイズは大きくなる。

Parser

森林、湖、都道府県境界など巨大な多角形は Relation で定義される。 Way をつなぎ合わせて、閉ループを構成する。例えば、東京都には島嶼部があるため、 複数の outer polygon となる。一つの outer polygon にはいくつかの inner polygon を 含むことがある。市区町村境界で言えば、他市の飛び地が inner polygon となる。 他市にとっては outer polygon の一つとなる。

広域森林には一般に平地、湖、住宅地などがあり、これらが inner polygon となる。

Parserの大きな仕事は Relation処理である。 一つの relation に複数の outer polygon があった場合には、outer polygon 毎の レコードとする。 つまり、以下の multipolygon は outer polygon は一つで、inner polygon が一つ以上となる。 ひとつの relation から複数のレコードが生まれる。

バス路線relation の場合、複数のバス停、複数の道路がメンバーとなる。 道路メンバーをつなぎ合わせたものが、バス路線となる。

道路とは別にバス路線レコードを作成することもできるが、 現在はそうせず、道路レコードにバス路線relationのID を付加している。 バス路線relationのタグである。name(バス路線名)やref(バス路線番号)は独立したファイルとしている。

バス路線レコードが道路レコードに比べて、小さければ、 このような方法は採らず、独立したレコードにする方が分かりやすい。必ずしも、繋ぎ合わせる必要はなく、 osm_idが同じ複数のレコードでよいので、処理は簡単である。 独立レコードとしたとき、ファイルサイズがどの程度増えるか調べた方がよい。

このバス路線relationのIDは {key,val}* に含めている。 つまり、Encoder の出力では {key,val}* は OSMタグデータだけであるが、 Parserの出力には OSMタグ以外のデータも含まれる。

多角形領域の中心にはアイコンや名称を描画するが、この中心は幾何学的な重心ではなく、 算出は楽ではない。この仕事も Parser で行う。この中心座標も タグ部に含める。 小さな無名の建物がレコードの半数を超えるが、このレコードには中心座標はいらない。

polygon/multipolygon レコードには並び替えのために way_area(面積)が必要となる。 空間検索結果については極座標をタイルXY平面座標に変換するが、 way_area算出時間はこれと同程度と推測される。実測して、時間がかかるようならば、 レコードに含ませる。

way_area はレコードの並び替えで使用する。 レンダリング上は相対的な値でよいので、空間検索結果のレコードについて、 極座標をタイル座標に変換した後、polygon/multipolygon の way_area(面積)を求めてもよい。 座標変換よりも簡単な計算で求まるため、レンダリング時間の増加は小さい。

タグ部の長さ(バイト単位)が奇数のときは 0x00 を追加して、 レコード全体の長さが2の倍数になるように調整する。

文字コードを UTF-16 とすれば、調整なしに文字列エリアの長さが2の倍数になる。

num_nds は極めて稀に2バイトで表せないことがある。現在は、レンダリングしていないレコードのため、 除外することもできるが、将来のことを考えて、2バイトと4バイトをサポートする。 Parserの場合、レコードの出力箇所が複数あるため、この段階では4バイトに統一しておく方がトラブルが少ない。 最終的に Devider で殆どの num_nds を2バイトで表し、例外的に、4バイトも許す。

num_nds を前にまとめて、{lon,lat}* を一まとめにできるが、 階層的な方がプログラムは楽である。

head,                       {lon,lat},  tags_length, {key,val}*  ... point 
head,             num_nds,  {lon,lat}*, tags_length, {key,val}*  ... line 
head,             num_nds,  {lon,lat}*, tags_length, {key,val}*  ... polygon 
head, num_polys, {num_nds, {lon,lat}*}*, tags_length, {key,val}*  ... multipolygon
head:
  第0,1bit(0x03) 0: point、 1: line、 2: polygon、 3: multipolygon

japan-high 2.91GB、japan-mid 317MB、japan-low 27.6MBとなった。

kanto-high 588MB、kanto-mid 53.3MB、kanto-low 5.24MBとなった。

Devider

最後に、矩形領域に分割する。way_area は Parser 自体で使用するため、Parser の出力に加えたが、 Devider で追加してもよい。

osm_id は特定のレコードに対してのみ、Parser で タグ部に追加する。

特定の polygon/multipolygon について中心座標を Devider でタグ部に追加する。

リファレンス