蛇ノ目の記

技術のことも。そうでないことも。

OverpyでOpenStreetMapのデータを取得して位置情報を可視化する話

4月末に近づいた頃にようやく今月初めての記事。あまりに書かなすぎた。

だいぶ前になるけど、OpenStreetMap(OSM)からガソリンスタンドの位置情報を取得してプロットしたデータアートめいた画像を見た。なにこれかっこいい。

地図ではなく全くの白紙にプロットしてるにも関わらず、欧州・北アフリカの海岸線や主要道路が見て取れる。

あまりにかっこいい画だったので日本でも似たようなことをやってみた。緯度経度をそのまま座標としてプロットしただけで日本列島とわかる姿になる。沖縄だけでなく東京都の島嶼部なんかも確認できる。

gas stations in Japan

今回のリポジトリはこちら。

github.com

OSMからデータを取得するライブラリ

openstreetmap pythonなどと検索するとosmapiがヒットする。しかしこちらはOSMにデータを書き込む用途に向いたライブラリ。

データの取得だけが目的なのであればoverpyを使うのがよい。

A Python Wrapper to access the Overpass API.

とあるようにOverpass APIPython用ラッパーだ。

Overpass API is 何

公式Wiki - Overpass APIには以下のようにある。

Overpass API(別名 OSM3S)とは読み出し専用のAPIであり、OSM地図データの中から個別に選択された部分を取り出します。Webを介したデータベースとして動作します。利用者はAPIに対してクエリを送り、クエリに対応したデータ セットを受け取ります。

クエリはOverpass QLと呼ばれ、OSMからデータを読み出すためのSQLのようなものといえる。

公式Wiki - Overpass API/言語ガイド

OSMのデータには、駅や店舗、施設などを表すノード、道路や河川、鉄道路線を表すウェイ、ノードとウェイを組み合わせたリレーションがあるようだ。

Overpass QLを書いてみる

東京都にあるガソリンスタンドを抽出するOverpass QLを書いてみる。

area["name"~"東京都"];
node(area)["amenity"="fuel"];
out;

WebベースのOSMデータマイニングツールOverpass turboでの(実行結果)http://overpass-turbo.eu/s/yb9にアクセスしてクエリを実行すると取得結果をOSM上で確認できる。

データ取得範囲は area["name"~"東京都|神奈川県"];のように|で区切ることで複数指定できる。新宿区といったより細かい範囲も指定可能。

node(area)["amenity"="fuel"]; でノードの種類を指定している。OSMではキー=値という形のタグでオブジェクトに情報を与えている。OSMのタグ情報はtaginfoが詳しい。

鉄道の駅であれば "railway"="station"、コンビニは"shop"="convenience"と指定する。ただ、コンビニの場合はコンビニエンスストア以外の小さな商店なども含まれているようなので、主要なコンビニリストを作ってノードの他のタグnameをフィルタリングしたりする必要がありそう。

overpyを使ってみる

overpyでデータを取得してシリアライズするスクリプトを書いた。

pickleではシリアライズできなかったのでpickleの拡張モジュールであるdillを使った。

日本全国を範囲指定するので、都道府県を並べたテキストファイルの中身を結合してクエリに突っ込んでいる。

overpass_plot/script at master · NaoY-2501/overpass_plot · GitHub

from datetime import datetime
import os

import dill
import overpy


def get_prefs():
    prefs = ''
    with open('pref.txt', mode='r') as f:
        for row in f.readlines():
            prefs += '{}|'.format(row.rstrip()) 
    return prefs[:-1]


def input_node_info():
    key = input('Input node key:')
    tag = input('Input tag of key:')
    return key, tag


def save_result(result, key, tag):
    now = datetime.now()
    date = '{}{}{}'.format(now.year, now.month, now.day)
    filename = 'data/{}-{}_{}.pkl'.format(date, key, tag)
    
    print('Save as {}'.format(filename))
    
    if not os.path.exists(filename):
        with open(filename, 'wb') as f:
            dill.dump(result, f)
        print('Save complete.')

        
def fetch_result(key, tag):
    api = overpy.Overpass()
    prefs = get_prefs()
    query = (
        'area["name"~"{prefs}"];\n'
        'node(area)["{key}"="{tag}"];\n'
        'out;'
    ).format(prefs=prefs, key=key, tag=tag)
    print('Fetch query...')
    result = api.query(query)
    print('Fetch complete')
    save_result(result, key, tag)


def main():
    key, tag = input_node_info()
    fetch_result(key, tag)


if __name__ == "__main__":
    main()

bokehでプロットする

可視化にはズームして、分布を細かく見たいという理由でbokehを使った。実際にやってみると重くてズームしづらいことに気づいた。bokehがGitHub上ではプレビューできないので保存した画像を貼る。

駅の分布

stations in Japan

駅をプロットするので自然と路線そのものが浮かび上がっている。

コンビニの分布

convenience stores in Japan

流石にコンビニ全国各地にありすぎだろ……。小さい商店なんかも含まれてることが想像できるので、これは見直す必要がある。

というわけでOSMラッパーライブラリOverpyの紹介と実験結果の話だった。