KMLをGeoJSONに変換する方法と、私が「KMLは爆発しろ」と思う理由

「ケイエムエルみな殺すべし……ハイクを詠め。カイシャクしてやる」

先日、上記のような発表があって驚愕していたのですが、愚痴っても仕方がないので各自治体から公開される地理データがKMLになっても困らないようにと調べたGeoJSONへの変換方法を記載しておきます。

ちなみに今回はGoogle「マイマップ」で公開されている群馬県が誇る「焼まんじゅうマップ」を例題として使用させていただきました。
メニューにある下記の何と呼んでいいのか分からないボタンをクリックするとKMLをダウンロードできます。

ヨクワカラナイアイコン

なお、KMLが嫌いな理由は最後の方に書いてあります。

(GMLは、また別の機会に)

ogr2ogr web client

手っ取り早くKMLをGeoJSONに変換するなら「ogr2ogr web client」を使うのが簡単です。KML/KMZファイルを選択して「Convert to GeoJSON」ボタンを押すだけです。

他にも、「2GeoJSON」というサービスもあります。

ogr2ogr

Webサービスは手軽に使えて便利ですが「データの更新があった際に毎回手動でコンバートをするのはメンドウ! 自働化したい!」と思われる方もいらっしゃるでしょう。ogr2ogrコマンドを利用するとコンバート作業をバッチ処理に組み込めます。

参考:OGR のコマンド解説と使い方レシピ

ogr2ogr -f GeoJSON 焼きまんじゅうマップ.geojson 焼きまんじゅうマップ.kml

一点問題があって、KMLの場合は問題ないのですがKMZ(KMLをZIP圧縮したもの)の場合、一度KMLにunzipしてからogr2ogrでコンバート処理を行う必要があるみたいです。(ogr2ogrにunzipオプションのようなものがあるかな?と思ったのですが見つかりませんでした)

togeojsonライブラリを使う

togeojsonライブラリはMapbox社製のGeoJSONコンバータです。node.jsを実行環境としていてサーバーサイドでコンバート処理が行えるのはもちろん、npmでインストールする際にグローバルオプションをつければshellコマンドとしても利用できます。また、通常のjsライブラリとしてscriptタグで読み込めばフロントエンドでも動くという汎用性の高いライブラリです。

shellコマンドとして実行する

togeojson 焼きまんじゅうマップ.kml > 焼きまんじゅうマップ.geojson

サーバーサイドで実行

var tj = require('togeojson')
var fs = require('fs'),
    
var jsdom = require('jsdom').jsdom;

var kml = jsdom(fs.readFileSync('焼きまんじゅうマップ.kml', 'utf8'));

var converted = tj.kml(kml);

console.log(converted);

※jsdomがver4.xになってからio.jsに移行したらしくnode.jsで動かそうとすると「jsdom 4.x onward only works on io.js, not Node.js」とエラーが表示されます。

xmlをパースしているだけらしいので、他の方法が見つかったらここに追記します。

フロントエンドで実行する

example

description問題

さて、このようにKMLをGeoJSONに変換するのは比較的簡単です……変換するだけなら。
コンバートした「焼きまんじゅうマップ(GeoJSON)」の一部を見てみましょう。

{
	"type": "Feature",
	"geometry": {
		"type": "Point",
		"coordinates": [
			139.085541,
			36.383978,
			0
		]
	},
	"properties": {
		"name": "日赤前たなかや(田中屋日赤前店)",
		"description": "<img src="http://pds.exblog.jp/imgc/i=http%253A%252F%252Fpds.exblog.jp%252Fpds%252F1%252F201204%252F07%252F20%252Fa0243720_1730475.jpg,small=800,quality=75,type=jpg" height="200" width="auto" /><br><br>★日赤前たなかや (http://mesousa2.exblog.jp/14998494/)※クリックでブログ記事<br>◆群馬県前橋市朝日町3-12-7<br>◆9:00~18:30 ◆月曜休み ◆駐車場6台<br>◆焼まんじゅう(¥170)、あん入り焼まんじゅう(¥250)、<br>黒ごまあん入り焼まんじゅう(¥250)、ミックス(¥210)、<br>ひとくち焼まんじゅう(¥70)、タレのミニボトル(小~中~大)",
		"gx_media_links": "http://pds.exblog.jp/imgc/i=http%253A%252F%252Fpds.exblog.jp%252Fpds%252F1%252F201204%252F07%252F20%252Fa0243720_1730475.jpg,small=800,quality=75,type=jpg"
	}
},

問題はpropertiesの中の「description」の項目です。
Googleが策定している基本的なKMLドキュメントの構造は以下です。

  • XML ヘッダー。すべての KML ファイルの 1 行目です。この行の前にスペースや他の文字を入力することはできません。
  • KML 名前空間宣言。すべての KML 2.2 ファイルの 2 行目です。
  • 次の要素を含む目印オブジェクト:

「description」は地図上でマーカーをクリックした際にバルーン(info Window)の内容が書きこまれる場所となっています。KMLでは属性データの取り扱いが厳密に決められていないのか、結果、descriptionの一項目の中に全ての情報が登録されていることが非常に多いです。しかもHTMLで。

(Google Earthなどそもそも属性データをテーブルとして登録するようなUIが用意されていないため、ユーザーが直感的にGoogle Earthを使ってデータを作ると上記のようなdescriptionにすべて詰め込まれたデータができあがります。マイマップも同様です)

改めてマイマップで公開されていた「焼きまんじゅうマップ」データを見てみましょう。
「description」の中に、画像(imgタグ)、住所、営業時間、定休日、詳細へのリンクがひとまとめされて書きこまれています。

このデータから各店の焼きまんじゅう画像を取り出して表示したい場合はどうしたらいいでしょうか?
descriptionの値を独自にパースしなければなりません。
このデータを住所を元にカテゴライズしたい場合はどうしたらいいでしょうか?
descriptionの値を独自にパースしなければなりません。
定休日ごとにマーカーの色を変えたい場合は?
descriptionの値を独自にパースしなければなりません。
ブログ記事へのURL一覧を取得したい場合は?
descriptionの値を独自にパースしなければなりません。

スゲーめんどうです。

マイマップで公開されているデータとか、Google Earthなどからエクスポートしたデータとか全部こういう仕様なので滅んでほしいと願ってやみません。

【追記】

同じようにdescriptionの中身に苦しんでいたのか、NSA(アメリカ国家安全保障局)がKMLのdescriptionをパースするQGISプラグインを作成していました。

ありがたい。

QGIS KML Tools

こういったプラグインが作られるぐらい、全世界の人がKMLのdescriptionに悩まされているということですね。