Golangによるosm.pbf読み込み1

前回の記事では、VSCodeを使ってGolangの開発環境を構築しました。

今回は、いきなり応用編ということで、Geofabrik社より頒布されているosm.pbf(OpenStreetMapのデータ)の件数をカウントするサンプルプログラムを動作させてみたいと思います。

osm.pbfとは

osm.pbfとは、OpenStreetMap(以降、OSM)に蓄積されている地図データを符号化して頒布しているデータ形式です。
下図のように、貢献者から集めたOSMデータはデータベース化されており、その頒布をGeofabrik社(ドイツ)が無償で行っています。

psm.pbfが作成されるまで

Geofabrik社は、OSMのベクトル地図データを、XML(①)、シェープファイル(②)、プロトコルバッファ(③)の3つの形式で提供さしています。
3つの形式の中でもプロトコルバッファ形式を利用するメリットは、XML形式に比べてファイルサイズが小さい点と、OSMデータベースの生データからほぼ欠落がないということです。

osm.pbfのフォーマットは以下に公開されています。

https://wiki.openstreetmap.org/wiki/JA:PBFフォーマット

osm.pbfについて、もう少し詳しく知りたい方は、以下の記事をご覧ください。

osm.pbfのパースにGolangを使うメリット

ではなぜ、osm.pbfのパース(読み込み・変換という意味です)にGolangを使うのでしょうか?

例えばPythonを使ってosm.pbfのパースは可能ですが、Golangの方がコンパイル済みのネイティブコードで実行されますし、スレッドを使った並行処理を行うコードを作成しやすいので、ハードウェアリソースを最大限に生して処理を高速化できるメリットがあります。

GolangもプロトコルバッファもGoogle社が仕様を決めていますので、もしかすると相性がいい可能性もあります。

事前準備

前置きはこのくらいにして、作業を進めていきましょう。

今回の環境は以下となります。

  • マシン Mac Book Pro2020(Intel Corei5 1.4GHz 4コア、8スレッド)、メモリ16GB
  • OS macOS Vig Sur ver 11.2.3
  • 開発ツール VSCode June2021(version 1.58)

osm.pbfのダウンロード

Geofabrik社のサイト(以下)から任意のosm.pbfをダウンロードします。

http://download.geofabrik.de/asia.html

今回は、日本全体(japan.osm.pbf、1.5GB)と四国(shikoku.osm.pbf、64MB)を使用します。

Geofabrik社のダウンロード画面

osmpbfのインストール

今回は、osmpbfというライブラリを使ってパース処理を作成します。
github上に公開されているモジュールを go get コマンド(PythonのpipやLinuxのyum、apt-getに相当)にて取得します。

コマンドラインからgo getにてインストールします。

% go get github.com/qedus/osmpbf

VSCode用のソースフォルダの作成

PC上の任意の場所にソース用のフォルダを作成します。
ソース用フォルダは、$GOPATH/src以外の場所で構いません。

実際のコード

今回は、以下に示されているサンプルコードを少しだけ修正して使用します。

https://pkg.go.dev/github.com/qedus/osmpbf

ソースファイル名readosm.goとして以下を作成しました。

package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"runtime"
	"time"

	"github.com/qedus/osmpbf"
)

func main() {

	fmt.Println("Start:", time.Now())

	f, err := os.Open("./japan-latest.osm.pbf")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	d := osmpbf.NewDecoder(f)

	// use more memory from the start, it is faster
	d.SetBufferSize(osmpbf.MaxBlobSize)

	// start decoding with several goroutines, it is faster
	err = d.Start(runtime.GOMAXPROCS(-1))
	if err != nil {
		log.Fatal(err)
	}

	var nc, wc, rc uint64
	for {
		if v, err := d.Decode(); err == io.EOF {
			break
		} else if err != nil {
			log.Fatal(err)
		} else {
			switch v := v.(type) {
			case *osmpbf.Node:
				// Process Node v.
				nc++
			case *osmpbf.Way:
				// Process Way v.
				wc++
			case *osmpbf.Relation:
				// Process Relation v.
				rc++
			default:
				log.Fatalf("unknown type %T\n", v)
			}
		}
	}

	fmt.Printf("Nodes: %d, Ways: %d, Relations: %d\n", nc, wc, rc)

	fmt.Println("End:", time.Now())
}

私が手を加えたのは、以下の3行となります。

  • 開始時刻の表示(16行目)
  • osm.pbfファイル名の変更(18行目)
  • 終了時刻の表示(58行目)

実行前には、ソースのあるディレクトリ(VSCodeのターミナル)にて以下のコマンドを発行しファイルgo.modを作成します。readosmは作成したソースファイル名です。

% go mod init readosm

すべての準備が整いましたので、VSCode上で実行します。

結果は、四国は1秒未満、日本全体は約90秒でした。
なお、日本全体を2回目の実行では40秒弱に短縮されました。おそらく、OSのメモリキャッシュが効いているのだと思います。

Start: 2021-07-16 23:15:24.803634 +0900 JST m=+0.000491915
Nodes: 204570114, Ways: 26982674, Relations: 121552
End: 2021-07-16 23:16:03.620584 +0900 JST m=+38.816277216

個人的にはかなりの速さだと感じました。
CPUコアをすべて使用している効果だと思います。
アクティビティモニタ(Windowsのリソースモニタ的なもの)を確認すると、コアが全て使用されていることがわかります。

アクティビティモニタ

今回はここまでとして、次回以降、コードの詳細を確認していこうと思います。

まとめ

今回は、Golangを使って外部ライブラリを導入し、サンプルプログラムを動作させることを確認しました。
言語の仕様確認は、次回の記事で書いていこうと思います。

今回のソースコードは、以下のGitHubリポジトリのディレクトリchap1にアップロードしてあります。

https://github.com/takamotobiz/learngo

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

11 − 5 =