CODE HOUSE


Hello, I'm a freelance software engineer.


[OSM] osm.pbfを読み込む2 block編

宝を探すおっさんの話

あるところにおっさんが住んでいました。そんなおっさんの所に、ある手紙が届きました。
「宝探しの冒険にでませんか?今がその時です」
明らかに胡散臭い手紙です。その手紙にはさらにこう書いてあります。
あなたの家から100m先の駅のロッカーに、宝物があります。鍵を同封しておきます」
本当に胡散臭いですが、行くだけなら害は無いだろうと、おっさんはその駅へと向かったのでした。

駅のロッカーに着くと、おっさんは手紙に同封されていたキーでロッカーを開けました。なんと、100円玉と更に次の宝への手紙が入っていたのです。 手紙にはこう書いてありました。
次の宝は1500m先の隣の駅のロッカーです

このようにしておっさんは何千個ものロッカーを渡り歩き、数十万円を手にしましたとさ・・。

※この話はフィクションです

osm.pbfファイルの話

さて、冒頭に変な話を書きました。おっさんは何千個ものロッカーを歩いて旅して、最終的に数十万を手に入れました。果たしてこれは割にあう話なのでしょうか?
もしこれが一日で全てのロッカーを歩き回ったというのなら、割のいい話かもしれません。でも、実際におっさんは一年がかりで旅をしました。おっさんは苦労の割には時間の無駄だなぁと思ったようです。

こんな話を書いたのは、osm.pbfの中身がこの話に似ている部分があるからです。つまり、ファイルの先頭に最初のデータの場所が書いてあります。そして最初のデータの先頭に、次のデータの場所 が書いてあります。このようにosm.pbfもファイルの先頭からたどっていかないと、必要なデータにたどり着けないような構造になっているのです。

osm.pbfの中身を読んでみる

Imposm

osm.pbfのファイルを読み込むにはpythonが便利です。pythonは今までほとんど触ったことがありませんでしたが、触れば触るほどにデータの解析などで非常に便利だと思いハマってしまいました。そもそも、osm.pbfを読み込むためのパッケージでちょうどいいのがpythonで見つかったというのが、今回pythonを使う理由です。

Imposm

omniscale/imposm-parser - GitHub

Imposmというのがそのパッケージです。osxかlinuxのみインストールできるようです。windowsはどう頑張ってもインストールできませんでした。また、pythonは2.7でないとインストールできません。osxの場合、多分下記のようにすればインストールできると思います。(インストールで少しハマったのですが、pip install imposmでエラーがでた場合、だいたい何が足りないのかががメッセージに出ると思います)

bash
brew install tokyo-cabinet
pip install shaply
pip install imposm

ファイル内のデータを追いかけてみる

pbf_sample.py
1from imposm.parser.pbf.parser import PBFFile, PrimitiveBlockParser
2
3pbf = PBFFile('osm.pbfファイル名')
4for block in pbf.blob_offsets():
5    blob_pos = block['blob_pos']
6    blob_size = block['blob_size']
7
8    block_parser = PrimitiveBlockParser('osm.pbfファイル名', blob_pos, blob_size)
9    # 以下、読み込んだblockの処理
  • ファイルの先頭で、PBFFile, PrimitiveBlockParserの2つをimportします。ほぼこの2つで、ファイル内の情報にアクセスできると思います。
  • PBFFileはファイル内のデータの場所を順番に教えてくれます。実際のファイルの構造を知らなくてもOKです。
  • PrimitiveBlockParserはファイル内のデータを読み込んでくれます。
    • ファイルのデータはzlibで圧縮されていますが、展開も勝手にしてくれます。
    • さらに、データはGoogle Protocol Bufferという形式で格納されています。しかしこれも勝手に解釈してわかりやすいようにしてくれます。
    • どのようなデータがあるのかは、dense、waysなどのメンバ変数に展開されるので、好きなようにデータを扱えます。
    • また、nodes(), ways(), relations()など、そのデータ内にあるデータを全て順に(yieldで)返してくれる関数が用意されています。

さて準備は整った??

そんなわけで、ファイルの中身さえ見ることができるようになれば、あとは順に読み込むだけ。簡単だ!私は、そう思いました。しかし、33Gのデータは巨大でした。単純に順番に読むだけでは馬鹿みたいな時間がかかったのです。ある処理をするのに最初に何も考えずに組んだプログラムの予想終了時間は約4000日。この巨大なファイルを読み込むには少し工夫が必要だったのです。

さらに次回に続きます・・。