JA:PBFフォーマット

From OpenStreetMap Wiki
Jump to navigation Jump to search

PBFフォーマット ("Protocolbuffer Binary Format") は、主にXMLフォーマットの代替として意図されているものです。これは、gzip圧縮されたplanetのおよそ半分のサイズ、bzip圧縮されたplanetよりおよそ30%小さいサイズになっています。また、gzip圧縮されたplanetよりおよそ5倍速く書き込み、6倍速く読み込みます。このフォーマットは将来の拡張性と柔軟性をサポートするように設計されました。

基礎となるファイルフォーマットは「ファイルブロック」の細かい部分でのランダムアクセスをサポートするよう選ばれています。デフォルトの設定では各ファイルブロックは独立してデコードでき、最大8000のOSMエンティティを格納しています。タグのハードコーディングは使用されていません。全てのキーと値はopaque stringとして全て格納されています。将来のスケーラビリティのために、64ビットのノード/ウェイ/リレーションIDが想定されています。現在のシリアライザ (Osmosis) はOSMエンティティの順序とタグを保持します。複数の詳細度を柔軟に処理するため、位置とタイムスタンプを表現するのに使用されている分解能、即ち詳細度は1ミリ秒と1ナノ度の倍数に整えられます。デフォルトの倍率は1000ミリ秒と100ナノ度で、赤道上でおよそ1cm以内に相当します。これはOSMデータベースの現在の解像度です。

拡張子は *.osm.pbf です。

現在、PBFのリファレンス実装はOsmosisの実装で、Osmosisのリポジトリ ([1]) にあるOsmosis特有の部分と、[2]にある用途非依存部分の2つの部分に分かれています。この用途非依存部分は(Osmosisや他のJavaベースのPBFリーダーで使用されているように)osmpbf.jarを構築するのに使われていて、また、PBF Protocol Buffer定義(*.protoファイル)のマスター定義を含みます。

ソフトウェアのPBFサポート

OSMプロジェクトで利用される多くのソフトウェアはすでに、従来のXMLフォーマットに加えてPBFをサポートしています。またPBFからOSM XMLと、その逆の変換を行うツールがいくつかあります。

様々なプログラムでどのような種類のPBFファイルがサポートされているかは、PBF/Software Complianceを参照してください。

設計

低レベルのエンコーディング

低レベルの保存形式としてGoogle Protocol Buffersが使われます。仕様定義ファイルと1つ以上のメッセージを入力として、プロトコル バッファ コンパイラーは低レベルのシリアライズ実装を生成します。メッセージは別のメッセージを含むことがあり、階層的な構造を形成します。プロトコル バッファは拡張性にも対応します。新しいフィールドをメッセージに追加することができ、古いクライアントも再コンパイル無しにそれを読むことができます。より詳細は http://code.google.com/p/protobuf/ を参照するか、Googleのオープンソース ブログの該当する記事を読んでください。Googleは公式にはC++、Java、Pythonをサポートしていますが、それ以外の言語のコンパイラーもあります。メッセージ仕様定義の例は:

message Node {
  required sint64 id = 1;
  // 並行する配列
  repeated uint32 keys = 2 [packed = true]; // 文字列ID
  repeated uint32 vals = 3 [packed = true]; // 文字列ID
  optional Info info = 4; // omitmetaでは省略可
  required sint64 lat = 8;
  required sint64 lon = 9;
}

プロトコル バッファでは整数を可変ビット長でエンコードします。整数はバイト内の7ビットでエンコードされ、上位ビットは次のバイトを読むべきかを表します。メッセージが小さな整数群を含むとき、ファイルサイズは最小になります。2つのエンコードがあり、大半が正の整数のためのものと、符号付きの整数のためのものです。標準のエンコードでは、整数[0,127]は1バイトを必要とし、[128,16383]は2バイトを必要とします。符号付きの数のエンコードでは、符号ビットはLSBに置かれます。[-64,63]は1バイト、[-8192,8191]は2バイトを必要とします。さらに詳細なシリアライズ形式については、ウェブサイトを参照してください。

生成されるファイルはJavaパッケージcrosby.binaryを使います。他の言語では生成されたファイルはOSMPBFパッケージ内にあります。

ファイル フォーマット

ファイルにはヘッダーとその後に連続するファイルブロックが含まれます。将来に備えてファイルの内容をランダムアクセスでき、解釈できないデータや不要なデータを読み飛ばせるように設計されています。

フォーマットは、次の形式が繰り返されます。

  • int4: BlobHeaderメッセージの長さ (ネットワーク バイト オーダー)
  • シリアライズされたBlobHeaderメッセージ
  • シリアライズされたBlobメッセージ (サイズはヘッダに記述)

BlobHeaderの現在の定義は:

 message BlobHeader {
   required string type = 1;
   optional bytes indexdata = 2;
   required int32 datasize = 3;
 }
  • type には、このブロックのメッセージのデータのタイプが含まれます。
  • indexdata は任意のblobで、後続のblobについてのメタデータを含むことができます。(例えばOSMデータの場合外接矩形など)。これはインデックスされた*.osm.pdfファイルの将来的な設計を可能とするためのスタブです。
  • datasize には、後続するBlobメッセージのシリアライズされたサイズが含まれます。

(BlobHeaderは以前はBlockHeaderと呼ばれていたことに注意してください。後で説明するHeaderBlockとの混同を避けるためにV1.1で名称が変わりました。)

Blobは今のところ任意のblobデータを格納するために使われます。未圧縮か、zlib/deflate形式で圧縮されます。

 message Blob {
   optional bytes raw = 1; // 未圧縮
   optional int32 raw_size = 2; // 圧縮されているとき、圧縮前のサイズ

   // 可能なデータの圧縮版
   optional bytes zlib_data = 3;

   // 提案中の LZMA 圧縮データ機能。対応は必須ではありません。
   optional bytes lzma_data = 4;

   // 以前は bzip2 圧縮データに使われていました。 2010年に非推奨になりました。
   optional bytes OBSOLETE_bzip2_data = 5 [deprecated=true]; // このタグ番号を再利用しないでください。
 }

注意(2010年12月): lzmaやbzip2をサポートしているエンコーダーは現在ありません。デコーダーの実装をシンプルにするため、bzip2は非推奨となり、LAMAは提案中の拡張に格下げされています。

不正なファイルや壊れたファイルを安全に検出するため、BlobHeaderメッセージとBlobメッセージの最大値に制限を設けます。BlobHeaderの長さは32 KiB (32*1024バイト)より短くあるべき(should)で、64 KiBより短くなければいけません(must)。Blobの圧縮前の長さは16 MiB (16*1024*1024バイト)より短くあるべき(should)で、32 MiBより短くなければいけません(must)。

OSM地物のファイルブロックへのエンコード

現在、OSMデータ向けに2つのファイル ブロックのタイプがあります。これらのテキストのタイプ文字列は、BlobHeaderのtypeフィールドに格納されます。:

  • OSMHeader: シリアライズされた HeaderBlock メッセージを含む Blob です(osmformat.proto を参照)。すべてのファイルブロックには、最初の 'OSMData' ブロックの前にこれらのブロックのうち1つを入れなければなりません。
  • OSMData: シリアライズされたPrimitiveBlockメッセージを持つ(osmformat.proto参照)。ここに地物が含まれます。

この設計により、他のソフトウェアは追加のタイプのファイルブロックを独自の目的のために含めるように拡張することができます。パーサーは自分が認識できないタイプのファイルブロックは無視してスキップすべきです。

OSMHeaderファイルブロックの定義

message HeaderBlock {
  optional HeaderBBox bbox = 1;
  /* このデータセットをパースするのを助ける追加的なタグ */
  repeated string required_features = 4;
  repeated string optional_features = 5;

  optional string writingprogram = 16;
  optional string source = 17; // bboxフィールドから

  /* Osmosisレプリケーションを保てるようにするタグ */

  // レプリケーションのタイムスタンプ、エポックからの秒数、
  // 即ち、Osmosisのstate.txtファイル内の
  // "timestamp=..."フィールドと同じ値
  optional int64 osmosis_replication_timestamp = 32;

  // レプリケーションのシーケンス値 (state.txt内のsequenceNumber)
  optional int64 osmosis_replication_sequence_number = 33;

  // レプリケーションのベースURL (Osmosisのconfiguration.txtファイルから)
  optional string osmosis_replication_base_url = 34;
}

前方/後方互換性のため、パーサーはあるファイルをパース可能かを知る必要があります。required featuresによりそれが分かります。パーサーが解釈できないrequired featureをファイルが含む場合、パーサーはエラーとしてファイルをリジェクトし、どのrequired featureをサポートしていないかを報告しなければなりません。

現在のところ以下のfeatureが定義されています:

  • "OsmSchema-V0.6" — ファイルは OSM v0.6 スキーマによるデータを持つ
  • "DenseNodes" — ファイルは濃縮ノードと濃縮情報を持つ

さらに、ファイルはオプショナルなプロパティを持つことがあり、パーサーはそれを利用できます。例えば、ファイルは予めソートされていて、利用前にソートする必要が無い場合があります。また、ファイル内のウェイは予め計算された外接矩形を持っているかも知れません。プログラムが自分の知らないオプショナルなfeatureに出くわした場合でも、そのファイルを安全に読むことができます。プログラムがあるオプショナルなfeatureを必要として、それが無い場合は、エラーとすることもできます。以下のfeatureが提案されています。:

  • "Has_Metadata" – ファイルは作者とタイムスタンプのメタデータを持つ。
  • "Sort.Type_then_ID" – 地物はタイプ、その次にIDでソートされている。
  • "Sort.Geographic" – 地物は何らかの形式で地理的にソートされている。(現在は未使用)
  • "timestamp=2011-10-16T15:45:00Z" – ファイルのタイムスタンプのための暫定的な解決。代わりにosmosis_replication_timestampを使ってください。

レプリケーション フィールドは何のため?

osmosis_replication_* フィールドは、PBFファイルを利用するツールが、Osmosis用に管理された更新サーバーからデータを追加して、ファイルを最新に保てるようにするためのものです。Osmosisは、planet.openstreetmap.orgサイトの毎日、毎時、毎分の差分を生成するために使われるソフトウェアです。更新をPBFファイルに追加するには、正しい同期ポイントを見つけるために、ファイルがどのレプリケーション状態を表しているかを知る必要があります。

  • osmosis_replication_timestamp - レプリケーションのタイムスタンプ (Unixエポック値)。Osmosisによって書かれるstate.txtから取得される(state.txt内ではUnixエポック値ではなくISO時刻文字列で含まれる)。この値は技術的には、そのファイル内にすべて含まれる最新のトランザクションに対するデータベースの内部的なタイムスタンプ。このタイムスタンプ以前のタイムスタンプを持つオブジェクトすべてが、そのファイルに含まれることを意味するものではない。
  • osmosis_replication_sequence_number - ファイル内に含まれる最新のデータベース トランザクションのシーケンス値。通常はタイムスタンプと一致し、どちらかが分かれば他方も分かるが、両方分かれば何かと簡単になる。
  • osmosis_replication_base_url - レプリケーションの差分のベースURL。例としてhttps://planet.openstreetmap.org/replication/minute/。与えられたIDがどのサーバー(つまりどのデータベース)に関するものかが分かるように。

PBFファイルを処理する際、bboxブロックをコピーするのと同様に、通常はこれらのフィールドをそのまま維持(つまり、入力から出力へコピー)することになるでしょう。ファイルへ更新を適用することが不可能または無意味になるような特別な処理を適用する場合を除いて。

OSMDataファイルブロックの定義

OSM地物をプロトコル バッファにエンコードするため、8000個の地物がまとめられてPrimitiveBlockとなり、それが'OsmData'ファイルブロックのBlob部分へシリアライズされます。

message PrimitiveBlock {
  required StringTable stringtable = 1;
  repeated PrimitiveGroup primitivegroup = 2;

  // 分解能。ナノ度の単位で、このブロック内の座標の格納に使われる。
  optional int32 granularity = 17 [default=100]; 

  // 取り出す座標と分解能のグリッドの間のオフセット。ナノ度の単位。
  optional int64 lat_offset = 19 [default=0];
  optional int64 lon_offset = 20 [default=0]; 

  // 日時の分解能。通常は1970エポックからのミリ秒単位で表される。epoch.
  optional int32 date_granularity = 18 [default=1000]; 


  // 提案中の拡張:
  //optional BBox bbox = XX;
}

PBFファイルを生成する際、全ての文字列(key, value, role, user)を切り離された文字列テーブルに取り出す必要があります。その後、文字列はこのテーブル内へのインデックスとして参照されます。例外として index=0 はDenseNodesをエンコードするときのディリミター(区切り)として使われます。つまりそのスロットに意味のある文字列を格納するのは安全ではありません。したがって、index=0には空の文字列が格納され、そのスロットは使いません。必須ではありませんが、頻繁に使われる文字列が小さいインデックスを持つようにソートすればパフォーマンスに良い影響があります。同じ頻度の文字列を辞書順にソートすれば、deflate圧縮を改善できるでしょう。それぞれのPrimitiveBlockは、解凍に必要な全ての情報を持ち、独立に解凍可能です。文字列テーブルを持ち、位置とタイムスタンプの分解能もエンコードします。

ブロックのサイズの上限が守られる限り、1ブロックの中に任意の数の地物を持つことができます。可能な限り多くの地物をブロックにまとめれば、ファイルサイズは小さくなるでしょう。しかし、いくつかのプログラム(例えば osmosis 0.38)では、単純化のため、PBFフォーマットを書くときに各ブロック内の地物の数を8000までに制限しています。

分解能に加えて、PrimitiveBlockは緯度、経度のオフセット値をエンコードします。これらの値は各座標値に、ナノ度の単位で加算されなければなりません(must)。

latitude = .000000001 * (lat_offset + (granularity * lat))
longitude = .000000001 * (lon_offset + (granularity * lon))

latitudeは度の単位の緯度、granularityはそのPrimitiveBlock内で決められている分解能、lat_offsetはPrimitiveBlock内で決められているオフセット、lat/lonはNode内でエンコードされているか、DenseNode内で差分エンコードされています。経度の式についての説明も同様です。

lat_offsetとlon_offsetが存在する理由は、等高線データ(輪郭線)やその他のデータのように、一定のグリッド上に出現するデータを簡潔に表現するためです。例えば、100マイクロ度のグリッド上のデータを表現したいとしましょう。最高の圧縮率を得るためには、100000ナノ度の分解能を使いたいところです。ただしそれだと(.0001*x,.0001*y)の形式の点しか表現できません。実際のグリッドのデータは(.00003345+.0001*x, .00008634+.0001*y)のような形式かも知れません。lat_offset=3345 と lon_offset=8634 を使えば、この100マイクロ度のグリッドをぴったりと表現できます。

タイムスタンプについては、

millisec_stamp = timestamp*date_granularity

timestampはInfo内でエンコードされているか、DenseInfo内で差分エンコードされたもの、date_granularityはPrimitiveBlock内で決められているもの、millisec_stampは地物の日時で、1970エポックからのミリ秒で測った値です。1970エポックからの秒数で測った日時を得る場合は、millisec_stampを1000で割ります。

それぞれのPrimitiveBlock内では、すべて同じタイプ(node/way/relation)の連続するメッセージが含まれるように、地物をグループ化します。

message PrimitiveGroup {
  repeated Node     nodes = 1;
  optional DenseNodes dense = 2;
  repeated Way      ways = 3;
  repeated Relation relations = 4;
  repeated ChangeSet changesets = 5;
}

1つのPrimitiveGroupは異なるタイプのオブジェクトを含んではいけません(MUST NEVER)。つまり多数のNodeメッセージか、一つのDenseNodeメッセージ、多数のWayメッセージ、多数のRelationメッセージ、多数のChangeSetメッセージのどれかを含みます。これらが混ざったものを持つことはできません。プロトコル バッファのエンコードは、ファイルに書かれたときと同じ順序でオブジェクトを取り出すことが不可能であるのが理由です。これはユーザーを混乱させるでしょう。

文字列にシリアライズされた後、各PrimitiveGroupはBlobファイルブロックに格納されるときにオプションで個別にgzip/deflate圧縮されます。

ウェイとリレーション

他のノードの ID を refs フィールドに持っているウェイやリレーションは、それらが持つノードの連なりは近接したノードIDを持つ傾向を利用し、差分圧縮によって小さな整数にすることができます。(つまり、x_1、x_2、x_3をエンコードする代わりにx_1、x_2-x_1、x_3-x_2をエンコード) それ以外についてはウェイとリレーションのエンコードは想像通りのものです。タグは2つの並行する配列で、一方はキーの文字列IDの配列、他方は値の文字列IDの配列です。

message Way {
   required int64 id = 1;
   // 並行する配列
   repeated uint32 keys = 2 [packed = true];
   repeated uint32 vals = 3 [packed = true];

   optional Info info = 4;

   repeated sint64 refs = 8 [packed = true];  // 差分表現
}

リレーションではメンバーのタイプを表すのにenumを使います。

message Relation {
  enum MemberType {
    NODE = 0;
    WAY = 1;
    RELATION = 2;
  } 
   required int64 id = 1;

   // 並行する配列
   repeated uint32 keys = 2 [packed = true];
   repeated uint32 vals = 3 [packed = true];

   optional Info info = 4;

   // 並行する配列
   repeated int32 roles_sid = 8 [packed = true];
   repeated sint64 memids = 9 [packed = true]; // 差分表現
   repeated MemberType types = 10 [packed = true];
}

Metadataにはオブジェクトに関する地理的でない情報が含まれます。:

message Info {
   optional int32 version = 1 [default = -1];
   optional int32 timestamp = 2;
   optional int64 changeset = 3;
   optional int32 uid = 4;
   optional int32 user_sid = 5; // String IDs

   // visibleフラグは履歴情報を格納するのに使われます。現在のオブジェクトが
   // OSM APIのdelete操作により生成されたことを示します。
   // 書き出し時にこのフラグをセットする場合、HeaderBlockのrequired_features
   // タグに"HistoricalInformation"を加えなければいけません(MUST)。
   // required_featuresタグに"HistoricalInformation"がセットされていて、
   // オブジェクトにこのフラグが無い場合はtrueであると仮定しなければ
   // いけません(MUST)。
   optional bool visible = 6;
}

ノード

ノードは2通りの方法のどちらかでエンコードできます。Node(上記で定義)と、特殊な濃密形式です。濃密形式では'列順'に、IDの配列、緯度の配列、経度の配列にまとめて格納されます。各列は差分エンコードされます。このようにしてヘッダーの負荷を減らし、差分エンコードが効果を発揮します。

すべてのノードのキーと値は文字列IDを持つ1つの文字列配列としてエンコードされます。各ノードのタグは交互の<keyid> <valid>でエンコードされます。1つのノードに対するタグが終わり、次のノードが始まるときの区切りとして1個の文字列ID 0 を使います。格納されるパターンは ((<keyid> <valid>)* '0' )* となります。 例外として、そのブロック内のノードがどれもキー/値のペアを持たない場合、配列には区切りは含まれず、単純に空になります。

message DenseNodes {
   repeated sint64 id = 1 [packed = true]; // 差分表現

   //repeated Info info = 4;
   optional DenseInfo denseinfo = 5;

   repeated sint64 lat = 8 [packed = true]; // 差分表現
   repeated sint64 lon = 9 [packed = true]; // 差分表現

   // 特別に1つの配列にパックされたキーと値。ブロック内のノードがすべてタグなしの場合、空になり得る。
   repeated int32 keys_vals = 10 [packed = true]; 
}

DenseInfoは同じような差分表現をメタデータに対して行います。

message DenseInfo {
   repeated int32 version = 1 [packed = true]; 
   repeated sint64 timestamp = 2 [packed = true]; // 差分表現
   repeated sint64 changeset = 3 [packed = true]; // 差分表現
   repeated sint32 uid = 4 [packed = true]; // 差分表現
   repeated sint32 user_sid = 5 [packed = true]; // usernameの文字列ID。差分表現

   // visibleフラグは履歴情報を格納するのに使われます。現在のオブジェクトが
   // OSM APIのdelete操作により生成されたことを示します。
   // 書き出し時にこのフラグをセットする場合、HeaderBlockのrequired_features
   // タグに"HistoricalInformation"を加えなければいけません(MUST)。
   // required_featuresタグに"HistoricalInformation"がセットされていて、
   // オブジェクトにこのフラグが無い場合はtrueであると仮定しなければ
   // いけません(MUST)。
   repeated bool visible = 6 [packed = true];
}

フォーマットの例

以下では、OSM PBFファイルのバイト列の中身を見てみましょう。小領域からの抽出 bremen.osm.pbf (geofabrik.de, 2011-01-13) を例として使います。

すべてのデータの先頭には変数識別子が付きます。この識別子はタイプとIDで交際されます。ビット0から2はタイプを表し、ビット3以上はIDを表します。以下のタイプが使われます:

  • 0: V (Varint) int32, int64, uint32, uint64, sint32, sint64, bool, enum
  • 1: D (64-bit) fixed64, sfixed64, double
  • 2: S (Length-delimited) string, bytes, embedded messages, packed repeated fields
  • 5: I (32-bit) fixed32, sfixed32, float
00000000  00 00 00 0d - BlobHeaderのバイト数での長さ、ネットワーク バイトオーダー
00000000  __ __ __ __ 0a - S 1 'type'
00000000  __ __ __ __ __ 09 - 長さ 9 バイト
00000000  __ __ __ __ __ __ 4f 53  4d 48 65 61 64 65 72 - "OSMHeader"
00000000  __ __ __ __ __ __ __ __  __ __ __ __ __ __ __ 18 - V 3 'datasize'
00000010  7c - 124 バイトの長さ
00000010  __ 10 - V 2 'raw_size'
00000010  __ __ 71 - 113 バイトの長さ
00000010  __ __ __ 1a - S 3 'zlib_data'
00000010  __ __ __ __ 78 - 長さ 120 バイト

--- 圧縮された部分:
00000010  __ __ __ __ __ 78 9c e3  92 e2 b8 70 eb da 0c 7b  ||.q.xx.....p...{|
00000020  81 0b 7b 7a ff 39 49 34  3c 5c bb bd 9f 59 a1 61  |..{z.9I4<\...Y.a|
00000030  ce a2 df 5d cc 4a 7c fe  c5 b9 c1 c9 19 a9 b9 89  |...].J|.........|
00000040  ba 61 06 7a 66 4a 5c 2e  a9 79 c5 a9 7e f9 29 a9  |.a.zfJ\..y..~.).|
00000050  c5 4d 8c fc c1 7e 8e 01  c1 1e fe 21 ba 45 46 26  |.M...~.....!.EF&|
00000060  96 16 26 5d 8c 2a 19 25  25 05 56 fa fa e5 e5 e5  |..&].*.%%.V.....|
00000070  7a f9 05 40 a5 25 45 a9  a9 25 b9 89 05 7a f9 45  |z..@.%E..%...z.E|
00000080  e9 fa 89 05 99 fa 40 43  00 c0 94 29 0c
--- 解凍後 --->
00000000  0a - S 1 'bbox'
00000000  __ 1a - 長さ 26 バイト
00000000  __ __ 08 d0 da d6 98 3f  10 d0 bc 8d fe 42 18 80
00000010  e1 ad b7 8f 03 20 80 9c  a2 fb 8a 03 - BBOX (4*Varint)
00000010  __ __ __ __ __ __ __ __  __ __ __ __ 22 - S 4 'required_features'
00000010  __ __ __ __ __ __ __ __  __ __ __ __ __ 0e - 長さ 14 バイト
00000010  __ __ __ __ __ __ __ __  __ __ __ __ __ __ 4f 73
00000020  6d 53 63 68 65 6d 61 2d  56 30 2e 36 - "OsmSchema-V0.6"
00000020  __ __ __ __ __ __ __ __  __ __ __ __ 22 - S 4 'required_features'
00000020  __ __ __ __ __ __ __ __  __ __ __ __ __ 0a - 長さ 10 バイト
00000020  __ __ __ __ __ __ __ __  __ __ __ __ __ __ 44 65
00000030  6e 73 65 4e 6f 64 65 73 - "DenseNodes"
00000030  __ __ __ __ __ __ __ __  82 01 - S 16 'writingprogram'
00000030  __ __ __ __ __ __ __ __  __ __ 0f - 長さ 15 バイト
00000030  __ __ __ __ __ __ __ __  __ __ __ 53 4e 41 50 53
00000040  48 4f 54 2d 72 32 34 39  38 34 - "SNAPSHOT-r24984"
00000040  __ __ __ __ __ __ __ __  __ __ 8a 01 - S 17 'source'
00000040  __ __ __ __ __ __ __ __  __ __ __ __ 24 - 長さ 36 バイト
00000040  __ __ __ __ __ __ __ __  __ __ __ __ __ 68 74 74
00000050  70 3a 2f 2f 77 77 77 2e  6f 70 65 6e 73 74 72 65
00000060  65 74 6d 61 70 2e 6f 72  67 2f 61 70 69 2f 30 2e
00000070  36 - "http://www.openstreetmap.org/api/0.6"
<--- 解凍後 ---

00000080  __ __ __ __ __ __ __ __  __ __ __ __ __ 00 00 00
00000090  0d - BlobHeaderのバイト数での長さ、ネットワーク バイトオーダー
00000090  __ 0a - S 1 'type'
00000090  __ __ 07 - 長さ 7 バイト
00000090  __ __ __ 4f 53 4d 44 61  74 61 "OSMData"
00000090  __ __ __ __ __ __ __ __  __ __ 18 - V 3 'datasize'
00000090  __ __ __ __ __ __ __ __  __ __ __ 90 af 05 - 87952 バイトの長さ
00000090  __ __ __ __ __ __ __ __  __ __ __ __ __ __ 10 - V 2 'raw_size'
00000090  __ __ __ __ __ __ __ __  __ __ __ __ __ __ __ 8f
000000a0  84 08 - 131599 バイトの長さ
000000a0  __ __ 1a - S 3 'zlib_data'
000000a0  __ __ __ 88 af 05 - 長さ 87944 バイト

--- 圧縮された部分:
000000a0  __ __ __ __ __ __ 78 9c  b4 bc 09 5c 14 57 ba 28  |......x....\.W.(|
000000b0  5e 75 aa ba ba ba ba 69  16 11 d1 b8 90 b8 1b 41  |^u.....i.......A|
000000c0  10 11 97 98 c4 2d 31 1a  27 b9 9a 49 ee 64 ee 8c  |.....-1.'..I.d..|
000000d0  69 a0 95 8e 40 9b 06 62  32 f7 dd f7 5c 00 01 11  |i...@..b2...\...|
000000e0  11 05 11 11 71 43 45 44  40 05 44 54 14 17 44 44  |....qCED@.DT..DD|
000000f0  40 16 15 dc 00 37 50 44  05 c4 05 7d df 39 55 dd  |@....7PD...}.9U.|
…
--- 解凍後 --->
00000000  0a - S 1 'stringtable'
00000000  __ d4 2e - 長さ 5972 バイト
00000000  __ __ __ 0a - S 1
00000000  __ __ __ __ 00 長さ 0 バイト
00000000  __ __ __ __ __ 0a - S 1
00000000  __ __ __ __ __ __ 07 長さ 7 バイト
00000000  __ __ __ __ __ __ __ 44  65 65 6c 6b 61 72 - "Deelkar"
00000000  __ __ __ __ __ __ __ __  __ __ __ __ __ __ 0a 0a  |.......Deelkar..|
00000010  63 72 65 61 74 65 64 5f  62 79 0a 04 4a 4f 53 4d  |created_by..JOSM|
00000020  0a 0b 45 74 72 69 63 43  65 6c 69 6e 65 0a 04 4b  |..EtricCeline..K|
00000030  6f 77 61 0a 05 55 53 63  68 61 0a 0d 4b 61 72 74  |owa..UScha..Kart|
00000040  6f 47 72 61 70 48 69 74  69 0a 05 4d 75 65 63 6b  |oGrapHiti..Mueck|
…
--- オフセット5975からの解凍後 --->
00000000  12 - S 2 'primitivegroup'
00000000  __ ad d5 07 - length 125613 バイト
00000000  __ __ __ __ 12 - S 2 -- Tag #2 in a 'PrimitiveGroup'の中の2番目のタグ、シリアライズされたDenseNodesを含む
00000000  __ __ __ __ __ a9 d5 07 - その長さは 125609 バイト
00000000  __ __ __ __ __ __ __ __  0a - S 1 - DenseNodesの中の1番目のタグ、パックされた変数の配列 
00000000  __ __ __ __ __ __ __ __  __ df 42 - その長さは 8543 バイト
00000000  __ __ __ __ __ __ __ __  __ __ __ ce ad 0f 02 02  |..........B.....|
00000010  02 02 04 02 02 02 02 02  02 02 02 02 02 02 02 02  |................|
00000020  02 02 02 c6 8b ef 13 02  02 02 02 02 02 02 02 f0  |................|
00000030  ea 01 02 02 02 02 02 02  02 02 02 02 02 02 02 02  |................|

    各varintは連続して格納される。
    8543バイト分を読むまで処理を続けてから、DenseNodesのパージングを再開する。
    varintは差分エンコードされたノードIDの数字。

00000040  02 02 02 04 02 04 02 02  04 02 02 02 02 02 02 02  |................|
00000050  02 02 02 02 02 02 02 02  02 02 02 02 02 02 02 02  |................|
00000060  02 02 02 02 02 02 02 02  02 02 02 02 02 02 02 04  |................|
00000070  02 02 06 44 02 02 02 02  02 02 02 02 02 02 02 02  |...D............|
00000080  68 02 02 02 02 02 02 02  02 02 02 02 04 02 02 06  |h...............|
00000090  02 02 0c 02 02 02 0a 02  02 02 02 06 0c 06 02 04  |................|
000000a0  02 06 02 02 02 02 02 02  02 02 02 02 02 02 02 02  |................|
000000b0  02 02 02 02 02 02 02 02  04 04 02 06 04 04 10 02  |................|
000000c0  04 02 04 18 0a 02 02 02  02 02 02 02 02 02 02 02  |................|
000000d0  04 06 02 02 04 02 02 02  02 04 02 02 02 02 08 02  |................|
000000e0  02 02 02 02 02 02 02 02  02 02 02 cc 06 02 02 02  |................|
000000f0  02 02 02 02 02 02 02 02  04 02 02 02 02 02 02 02  |................|
00000100  02 02 02 02 02 02 02 02  02 02 02 02 02 02 02 02  |................|
00000110  02 02 02 02 02 02 02 02  36 02 02 04 04 04 02 02  |........6.......|
00000120  02 02 02 02 02 02 02 02  02 02 02 02 02 02 02 02  |................|
…
<--- 解凍後 ---

当然ながら、プロトコル バッファ ライブラリはこれらの低レベルのエンコードの詳細をすべて扱えます。

ソースコード

コートベースは2つの部分に分かれています。用途に独立な共通コードはgithub上にあります。プロトコル バッファの定義、fileblockレベルを操作するJavaコードを含みます。http://github.com/scrosby/OSM-binary

残念なことに、osmosis、mkgmap、その分割ツールでは、OSM地物に対して異なる独自の内部表現を使っています。つまり、シリアライズとパージングを行うコードをプログラムごとに再実装する必要があり、実装間で共通のコードは期待するほどありません。osmosis向けのシリアライザーとデシリアライザーはtrunkにあります。

mkgmap分割ツールの旧バージョン(2010年5月頃)向けのデシリアライザーはgithub上にあります: http://github.com/scrosby/OSM-splitter

その他の情報

ダウンロード

PBF 形式による完全な(最新、履歴無しの) OSM Planet が、 https://planet.openstreetmap.org/pbf/ から利用できます。

その他、 Planet.osm からも OSM の抽出データをダウンロードできます。

関連項目

  • PBF Perl Parser
  • OSMCompiler
  • Pythonを使ったPBFパース方法のチュートリアル [3]
  • Protocol Buffers at Wikipedia
  • Osmium OSMファイルを扱うC++/Javascriptフレームワーク
  • Imposm XMLとPBF形式のOpenStreetMapデータをパースするPythonライブラリ
  • libosmpbfreader OpenStreetMapバイナリファイルを読むシンプルなC++ライブラリ
  • osm-read XMLとPBF形式のOpenStreetMapデータをパースするnode.jsライブラリ
  • pbf_parser PBFファイルを簡単にパースするRuby gem
  • osmpbf2sqlite osm pbfからsqliteデータベースへの変換
  • https://github.com/mapbox/pbf は、Mapboxによるjavascriptライブラリ