Googleロケーション履歴を使って整形(pandas)と地図上に表示(folium)させることを考える[初心者のPython3]

#はじめに
Googleでロケーション履歴を見ても、まとめて通った経路を表示させたりはできない。
簡単に自分の通った道を表示させたい。
QGISとかをわざわざ起動させるのも面倒なので
”folium”を使ってhtml形式のmapにしてしまうことを考える。
そうすればこんな感じに表示できる
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486481/14209615-1c35-0055-78bc-bfac029243ce.png)

出来るだけ使うパッケージやモジュールを減らして、初心者(つまり私)でも中身が分かりやすいように心がけていますので
ぜひ、一度書いてみてください。
###Version
* python3 == 3.7.2
* folium == 0.10.0
* pandas == 0.24.2  
（インストール方法は他のサイトさんにまかせます。）

#1.Googleロケーション履歴をダウンロードする
Googleアカウントの”自分のデータをダウンロード”「<https://takeout.google.com/>」にアクセス。
「ロケーション履歴」（JSON形式）にチェックを入れて
指示に従ってアーカイブを作成し、ダウンロードする。
***(以下ではダウンロードして作業ディレクトリに”locate.json”として置いた)***

**JSON形式とは**
Java Scriptの構文をベースにした形式。主にデータの受け渡しに（JavaScript以外の言語でも）使われる。データの受け渡しに特化しているため人にはかなり見にくい（というより人が見るものではない）。

#2.コードを考える
##2-1.CSVファイルにする
###JSON形式のデータをインポート
PythonでJSON形式を扱うならやっぱりPandas。

```python
import pandas as pd
```
でpandasをインポートしてきます。
JSONをpandasのデータフレームで扱うのは

```python
df = pd.read_json('locate.json')
```
でOK。
dfの中身は

| Index            |  locations |
|:----------------:|:-------------:|
| 0                | {'timestampMs': '1410000000000', 'latitudeE7': 340000000, 'longitudeE7': 135000000, 'accuracy': 10}|
|1                 |{'timestampMs': '1410000010000', 'latitudeE7': 340010000, 'longitudeE7': 135001000, 'accuracy': 10}|
|・・・|・・・・・・|

という風になっている。（移動手段などがさらに付属したデータもある。）

* 'timestampMs'はシステム時刻（ミリ秒）
* 'latitudeE7'は小数点以下７桁を含む緯度
* 'longitudeE7'は小数点以下７桁を含む経度
* 'accuracy'は精度

をそれぞれ示している。

###データの整形
時刻のデータを年月日+時間に
緯度経度は小数点を含んだ形式に
それぞれ書き換える。
まず、時刻のデータのフォーマットを変える。
pandasにもto_datetimeというモジュールがあるが、今回はpythonの標準モジュールdatetimeを用いる。

```python
from datetime import datetime
```
例えば"Index"1番目の"locations"内の'timestampMs'を整形して変数timeとするなら

```python
time = datetime.fromtimestamp(float(df.iat[1,0]['timestampMs'])/1000)
```
となる。ミリ秒のデータを秒に直すために1000で割っている。
同様に緯度を考える。
"Index"1番目の"locations"内の'latitudeE7'を整形して変数latとするなら

```python
lat = float(df.iat[1,0]['latitudeE7'])/10000000
```
となる。経度も全く同様にできる。

これらをループで回して、新たなデータフレームd2を作ればきれいに整形ができそうだと思います（よね）。
d2をそのままcsvファイルにしたら出力までできそうです。

それがこちらです。

```python:seikei.py
def seikei():
    t =[]
    l = []
    l2 = []
    df = pd.read_json('locate.json')
    i = 0
    while True:
        try:
            time = datetime.fromtimestamp(float(df.iat[i, 0]['timestampMs'])/1000)
            lon = float(df.iat[i, 0]['longitudeE7'])/10000000
            lat = float(df.iat[i, 0]['latitudeE7'])/10000000
            t.append(time)
            l.append(lon)
            l2.append(lat)
        except IndexError:
            break
        i+=1
    d2 = pd.DataFrame({'date':t, 'lat':l2, 'lon':l})
    d2['date'] = d2['date'].dt.strftime('%Y-%m-%d %H:%M:%S')
    d2.to_csv('hist.csv')
if __name__=='__main__'
    seikei()
```
i=0からiを1ずつ増やしてすべての'Index'に対して処理をしています。
`while True`　で条件文が真にして無限ループを作り、
`try ~ except IndexError`　でループを抜け出す(breakする)ルールを作っています。

また、処理が終わったものは`append`でリストでまとめています。
最後にリストをデータフレームに直し、csvファイルにする前にstr(文字列)に直しています。
（今回はデータの抜けなどがない前提で作っています。）

##2-2.htmlファイルにする
###foliumを使う
ここでは簡単に今回使う部分だけを説明する。
もっと詳しく知りたい方は公式サイトをチェック
(また追って投稿もします。)

|[![folium](https://python-visualization.github.io/folium/_images/folium_logo.jpg)][1]|[folium公式Githubのページ](https://python-visualization.github.io/folium/#folium)|
|----------------------|--------------|
[1]:https://python-visualization.github.io/folium/#folium

まずはインポート

```python
import folium
```
最も簡単に指定した場所を中心？としたMapを保存するためには

```python
m = folium.Map(location=[35.0, 135.0], zoom_start=5)
m.save('map1.html')
```
を実行します。
作られたファイル（ここではmap1.html）をクリックして開くとブラウザで地図が開きます。
下の例はただの図です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486481/42972b54-cbfe-06f8-bc97-461019e63816.png" width=500>

線を引くのにはfolliumのモジュールPolyLineを使います。
使い方は次のようにlocationの引数を渡します。

```python
m = folium.Map(location=[35.0, 135.0], zoom_start=5)
loc = [[35.0,135.0],[35.2,135.2]]
folium.PolyLine(locations=loc).add_to(m)
m.save('map1.html')
```
例えばlocに先ほどのデータフレームd2の'lat','lon'を渡せば大体の線は引けそうです。
上の`loc = `の部分を`loc = d2[['lat','lon']].values.tolist()`に変えて先ほどの`seikei.py` の関数`seikei()`の最後にコピペして一度実行してみてください。
場所によってよさげな場所と下の図のように変なところに伸びた線とができてしまうと思います。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/486481/513871db-ce0e-643b-c1bf-5b3687992384.png" width=500>

大枠はここまででできていると思います。
ここからはいくつか条件を加えることでエラーのような線をなくしていきたいと思います。

##2-3.仕上げ
まずは元のデータに付いていた精度を表す'accuracy'によって地図にプロットするかしないかを決めます。
例えば

```python
accuracy = float(df.iat[i,0]['accuracy'])
if accuracy < 30:
    
```
みたいな条件を付けることが考えられます。

```python:seikei.py
def seikei():
    t =[]
    l = []
    l2 = []
    df = pd.read_json('locate.json')
    i = 0
    while True:
        try:
            accuracy = float(df.iat[i,0]['accuracy'])
            if accuracy < 30:
                time = datetime.fromtimestamp(float(df.iat[i, 0]['timestampMs'])/1000)
                lon = float(df.iat[i, 0]['longitudeE7'])/10000000
                lat = float(df.iat[i, 0]['latitudeE7'])/10000000
                t.append(time)
                l.append(lon)
                l2.append(lat)
        except IndexError:
            break
        i+=1
    d2 = pd.DataFrame({'date':t, 'lat':l2, 'lon':l})
    d2['date'] = d2['date'].dt.strftime('%Y-%m-%d %H:%M:%S')
    d2.to_csv('hist.csv')
    m = folium.Map(location=[35.0, 135.0], zoom_start=5)
    loc = d2[['lat','lon']].values.tolist()
    folium.PolyLine(locations=loc).add_to(m)
    m.save('map1.html')
if __name__=='__main__'
    seikei()
```

また、GPSを切っていた期間やデータ同士が遠く離れている点を線で結ぶと長い直線となってしまうので
ある半径内(※実際には簡単のため度単位で処理をしているので注意が必要)にデータがないときは線で結ばないことにします。
線で結んでいくデータセットごとに異なった番号を振り識別をするようにします。
counterという変数で識別を行い、データフレームに組み込みます。
今回は一つ手前の緯度経度と今の緯度経度それぞれの差の二乗をとって、閾値以上（今回は0.005）ならcounterの数字を増やすことにした。

```python:seikei.py
def seikei():
    t =[]
    l = []
    l2 = []
    df = pd.read_json('locate.json')
    i = 0
    count=[]
    counter = 0
    while True:
        try:
            accuracy = float(df.iat[i,0]['accuracy'])
            if accuracy < 30:
                time = datetime.fromtimestamp(float(df.iat[i, 0]['timestampMs'])/1000)
                lon = float(df.iat[i, 0]['longitudeE7'])/10000000
                lat = float(df.iat[i, 0]['latitudeE7'])/10000000
                if i>0 and abs((l[-1]-lon)**2+(l2[-1]-lat)**2)>0.005:
                     counter+=1
                t.append(time)
                l.append(lon)
                l2.append(lat)
        except IndexError:
            break
        i+=1
    d2 = pd.DataFrame({'date':t, 'lat':l2, 'lon':l,'numbering':count})
#    d2['date'] = d2['date'].dt.strftime('%Y-%m-%d %H:%M:%S')
#    d2.to_csv('hist.csv')
    df_dict = {}
    for name, group in d2.groupby('numbering'):
        df_dict[name] = group
    m = folium.Map(location=[35.0, 135.0], zoom_start=5)
    for d in df_dict:
        loc = df_dict[d][['lat','lon']].values.tolist()
        folium.PolyLine(locations=loc).add_to(m)
    m.save('map1.html')
if __name__=='__main__'
    seikei()
```

このコード内の

```python
for name, group in d2.groupby('numbering'):
    df_dict[name] = group
```
については
[分析ノート　データサイエンティストのメモ書き](https://analytics-note.xyz/programming/dataframe-split/)
をかなりの参考（引用）とさせていただいた。
今までのコードだとかなりの時間がかかっていたので非常に助けになったコードです。


程よい分量のコードなので実際に動かしてみてくださいね。


-----------------------
コード更新2021/4/22

それでは、
質問やエラー報告などなどは
Twitter([@ohcelot](https://twitter.com/ohcelot))まで

また、二次利用などなどは自由で良いですが、ご連絡いただければ私が喜びます。
(無いとは思いますが商業利用はダメです。)












-----------------------------------