移動の軌跡をpythonを用いて地図に表示する&画像化する

移動したときの軌跡を表示

今やGPSがくっついた端末を持ち運ぶことも多いので、 移動の軌跡を表示したい、なんていう要望もあるかと。

そんなことをやってみました。 本当は業務で必要になって土日に勉強しただけだが…。

Python for Data Visualization

Python for Data Visualization

【改訂新版】[オープンデータ+QGIS]統計・防災・環境情報がひと目でわかる地図の作り方

【改訂新版】[オープンデータ+QGIS]統計・防災・環境情報がひと目でわかる地図の作り方

folium

pythonに地図表示ライブラリのfoliumというのがあったのでこちらを使ってみた。

python-visualization.github.io

これはブラウザ上でいろんなレイヤを作成するライブラリなので、 基本的にはhtml/css/svgを吐き出す装置だと思ってくれればよい。

使い方

一般的には下記の使い方手順。

  1. マップを作成する(キャンバスのようなもの)

    権利とかにうるさくないopenstreetmapで生成が基本

  2. 地図の上に表示したいものを追加する

    線とか、アイコンとか、ポリゴンとか

  3. 表示する or 保存する

    jupyter notebookを使っていればノートブックに表示もできるし、 htmlを出力して別途使うことができる。

表示するデータ

今回は山手線の駅と東海道新幹線の駅をAさんとBさんが歩いてみたことにする。

とてもこんな速度で歩けるとも思えないが…。

日時 緯度 経度
A 2019/2/22 12:00 35.619700 139.728553
A 2019/2/22 12:30 35.626446 139.723444
A 2019/2/22 13:00 35.626446 139.723444
A 2019/2/22 13:30 35.633998 139.715828
A 2019/2/22 14:00 35.633998 139.715828
B 2019/2/22 12:00 35.681382 139.766084
B 2019/2/22 12:30 35.655767 139.753262
B 2019/2/22 13:00 35.655767 139.753262
B 2019/2/22 13:30 35.630152 139.740440
B 2019/2/22 14:00 35.630152 139.740440

こんなデータの持ち方をしているとする。

(githubに使ったcsvは用意)

GitHub - kazuhitogo/gps_vis

表示の仕様

2種類やるとする。

  1. 一枚に2つの移動を色を変えて表示

    ついでに各線をクリックするとポップアップで誰が通ったのかも表示する

  2. 一人に一枚の地図を用意し、それをhtmlに保存し、画像にも保存する。

一枚に2つの移動を色を変えて表示

さて、コーディング

# あとで使うライブラリも併せて読み込み
import os
import folium
import pandas as pd
from time import sleep as slp
from selenium import webdriver
import glob
from selenium.webdriver.chrome.options import Options

# csvをpandas dataframeに保存
data = pd.read_csv("./gps.csv",encoding="shift_jis")

# 今回はA,Bの人しか居ないが、
# 一応何人来てもいいように人のリストを作り、
# それをforループで回す
person_list = data["人"].unique()

# 地図に表示する色
# foliumでサポートしているのは下記19色
color_list=['red','blue','green','purple','orange','darkred','lightred','beige','darkblue','darkgreen','cadetblue','darkpurple','white','pink','lightblue','lightgreen','gray','black','lightgray']

# 地図オブジェクトを作成
m = folium.Map(tiles='OpenStreetMap')

for idx,person in enumerate(person_list):
    # 一人分のデータだけをdata_tempに格納する
    data_temp = data[data["人"] == person]
    
    # data_tempの順番を日時で昇順ソート
    data_temp = data_temp.sort_values('日時', ascending=True)
    
    # data_tempの緯度経度だけを
    data_temp_lat_lon = data_temp[["緯度","経度"]]
    
    # 緯度経度を配列に格納
    locs = data_temp_lat_lon.values
    
    # 色を指定
    line_color = color_list[idx%len(color_list)]
    
    # 地図に線を追加する。緯度経度の配列をそのまま線として使う
    folium.PolyLine(locs,color=line_color,popup=person).add_to(m)
    
# 地図の表示範囲を緯度経度の最低最大とする
m.fit_bounds([[data["緯度"].min(),data["経度"].min()], [data["緯度"].max(),data["経度"].max()]])

# 地図を表示する
m

そうするといい感じに表示されます。

f:id:kazuhitogo:20190324125616p:plain
一枚に二人を表示

一人に一枚の地図を用意し、それをhtmlに保存し、画像にも保存する

さて、こちらのほう。

htmlに保存するところまではfolium側でメソッドが用意されています。

m = folium.Map(tiles='OpenStreetMap')
m.save("hoge.html")

で保存できるのですが、

そこで表示される画像を保存したい、だとfoliumだけでは対応できません。

そこで、ご存知seleniumを使ってスクショを取って保存を使いました。

さて、コーディング(続き)

for person in person_list:
    # 一人分のデータだけをdata_tempに格納する
    data_temp = data[data["人"] == person]
    
    # data_tempの順番を日時で昇順ソート
    data_temp = data_temp.sort_values('日時', ascending=True)
    
    # data_tempの緯度経度だけをデータフレームに残す
    data_temp_lat_lon = data_temp[["緯度","経度"]]
    
    # 地図オブジェクトを生成
    m = folium.Map(tiles='OpenStreetMap')
    
    # 緯度経度を配列に格納
    locs = data_temp_lat_lon.values
    
    # 地図に線を追加する。緯度経度の配列をそのまま線として使う
    folium.PolyLine(locs).add_to(m)
    
    # 地図の表示範囲を緯度経度の最低最大とする
    m.fit_bounds([[data_temp["緯度"].min(),data_temp["経度"].min()], [data_temp["緯度"].max(),data_temp["経度"].max()]])
    
    # htmlに保存する
    # ./html/xx.htmlに保存される
    m.save(outfile="./html/"+person+".html")

    # seleniumでブラウザを開く
    browser = webdriver.Chrome()
    
    # 画面を最大化
    browser.maximize_window()
    
    # URLを指定(ローカルファイル)
    tmpurl="file:///./html/" + person + ".html"

    # URLを開く
    browser.get(tmpurl)
    
    # 一応3秒寝かす
    slp(3)
    
    # スクリーンショットを取る
    browser.save_screenshot("./png/"+person+".png")
    
    # ブラウザを閉じる
    browser.quit()

そうするといい感じに画像を保存してくれる。 f:id:kazuhitogo:20190325114716p:plain

f:id:kazuhitogo:20190325114725p:plain

ちなみに線ではなくMarkerやCircleで表示したい場合は、

    folium.PolyLine(locs).add_to(m)

の部分を

    for loc in locs:
        folium.Circle(loc).add_to(m)

ないし

    for loc in locs:
        folium.Marker(loc).add_to(m)

にするとよろし。

これをまたパワポに自動で貼り付けるというのもやったのだが、それはまた別の話。