はじめに
この記事はOpenStreetMap Advent Calendar 2016 17日目の記事です。
2年ぐらい前からOSMをぽちぽち編集するようになって、主に我が町のバス停について詳しく入力したりするのが趣味でした。過去形なのは、一応全部入力が終わったからです。
そんなにバス停を集めてどうしたいのかというと我が町のオープンデータと併せてGTFS形式にバスデータを起こし直してみようかなという理由があったからです。一応それなりのデータはできあがりましたが、この前時刻表が更新されてしまったので、またオープンデータの公開を待って更新していきたいなと思います。
前書きが長くなりましたが、この記事ではそのGTFSを作成する過程でOSMのAPIをたたいてバス停の緯度経度を抽出しようとした際の知見を書き連ねていきたいと思います。
エントリポイント
OSMのAPIは書き込みができる編集用APIと、読み込み専用のOverpass APIの2種類が用意されています。今回は後者のOverpass APIを取り扱います。
Overpass APIは様々なサーバーで提供されています。Wikiによると、以下のようなサイトが利用可能なようです。他にも色々あるらしいですが、とりあえず2つだけエントリポイントを紹介しておきます。
ここからはちょっとエントリポイントから離れて、APIの独自文法について先に学んでいきます。
Overpass QL
Overpass APIは独自のフォーマットを使って叩きます。XMLでも叩けるみたいですが、冗長なのでこの際Overpass QLを覚えてさくっと叩けるようになってみましょう。overpass turboを使うと、QLを書いてその場で実行できるので、練習には最適です。
エリアの絞り込み
何かを抽出するときに、基本的に全世界からの検索になってしまうため出力が膨大になってしまいます。まずはエリアを絞り込んでみましょう。overpass turboの左側に入力していきます。
今回は名前で絞り込んでみましょう。以下のような文法で絞り込めます。文末をセミコロンで表すので、忘れないようにしましょう。
area["name"~"札幌市"];
正規表現っぽいこともできるので、例えばこんな指定方法もあります。
area["name"~"札幌市|北広島市"];
バス停の抽出
バス停は、OSM上ではノード
として登録されることが多いです。そこで、highway
タグに対してbus_stop
が指定されているものを一気に抽出してみましょう。指定したエリアにあるnodeに対して、タグ名とその値を条件として絞り込むことができます。
area["name"~"札幌市|北広島市"];
node(area)["highway"="bus_stop"];
結果の出力
このままではデータを絞り込んだだけで、出力を行っていません。一言付け加えることで結果を出力することができます。ここまで書けたら、overpass turboの左上にある実行
ボタンを押して、結果を見てみましょう。なお、自動でスクロールはしてくれないので、このままやるとすれば札幌のところまで自分でスクロールしてください。
area["name"~"札幌市|北広島市"];
node(area)["highway"="bus_stop"];
out body;
エントリポイントに流し込む
いよいよできあがったクエリをエントリポイントへ流し込んでいきます。やり方はとっても簡単で、すべての行を1行にまとめたあと、パーセントエンコードをかけるだけです。この際、行頭で出力形式をJSONにしておかないとXMLでダウンロードされてくるので注意してください。
[out:json];
area["name"~"札幌市|北広島市"];
node(area)["highway"="bus_stop"];
out body;
エンコードをかけるとこんな感じになります。かなりグロテスクですが我慢しましょう。
%5Bout%3Ajson%5D%3Barea%5B%22name%22~%22札幌市%7C北広島市%22%5D%3Bnode(area)%5B%22highway%22%3D%22bus_stop%22%5D%3Bout%20body%3B
これをエントリポイントにくっつければいいわけなのですが、ちょっとアドレスに付け加えるものがあって、最終的にはhttp://overpass-api.de/api/interpreter?data=
の後ろにエンコードを書けたクエリをくっつけて送信する形になります。Overpass QLはインタプリタが解釈してくれる、ということなのでしょう。
JSONフォーマット
返ってくるJSONは割と素直な形をしています。
{
"version": 0.6,
"generator": "Overpass API",
"osm3s": {
"timestamp_osm_base": "2016-12-16T06:28:02Z",
"timestamp_areas_base": "2016-12-16T04:31:02Z",
"copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL."
},
"elements": [
{
"type": "node",
"id": 3,
"lat": 42.0,
"lon": 141.0,
"tags": {
"highway": "bus_stop",
"name": "hoge",
"ref": "A",
"source": "hoge_ortho"
}
},
{
"type": "node",
"id": 6,
"lat": 42.0,
"lon": 141.0,
"tags": {
"highway": "bus_stop",
"name": "fuga",
"public_transport": "stop_position",
"ref": "B",
"source": "hoge_ortho"
}
}
]
}
elements
を抜き出してから、中身をリストとして扱ってあげるのが良いと思われます。あとは煮るなり焼くなり好きにしましょう!
Pythonなどゆるふわっとした言語であれば連想配列に突っ込めばどうにでもなりますが、Haskellのような言語から利用しようとすると、nodeの仕様上あったり無かったりするタグがあって、静的に型付けするのはちょっと難しそうです。(連想配列でもキーがあるかないかを事前に確認しないと死んでしまいますが...)
終わりに
実はAdvent Calendar初めてで、空いているという情報を聞きつけて急遽執筆してみました。
Overpass QLはデータを引っ張ってくる専用の記法であるため、割と使いやすくできています。ちょっと1行に直したりするのは面倒かもしれませんが、パーセントエンコードなんかは各言語のライブラリを使ってちゃちゃっとやってしまったりすればどうと言うことは無いと思います。
現時点で一番やっかいだと思うのはAPIサーバーがすべて国外にあって、特に結果が多い場合になかなか返ってこないことだと思います。OSMのミラーも含めて、Overpass APIを国内に用意できれば理想的なのでしょうけれども、なかなかそこまでは難しそうですね...
そんなことはさておき、皆さんも気軽にOverpass APIを使っていろいろなサービスやアプリなどを作っていただければと思います。それでは!