map-matchingでランニングのgpsデータを補正してみる

はじめに

ランニングをする際にアプリを使って距離やスピードを測っているのですが、全く同じコースを走った場合でもgpsの誤差によってあるときは9.6km、またあるときは10.1kmのように走行距離が大きく変わってしまうケースがあります。今回はmap matchingという方法を用いてgpsの誤差を補正することでこの誤差の改善し、正確な距離を取得できないかを試みます。

実施したこと

実施した手順は以下になります。

  1. runkeeperよりgpsデータを取得

  2. mapboxのapiを用いてmap matchingを実行、緯度経度を補正

  3. 評価

1. runkeeperよりgpsデータを取得

ランニングアプリよりgpsデータを取得します。 私はrunkeeper(無料版)というアプリを用いているので、下記の手順に従いファイルを抽出します。

得られたファイルはgpx形式で、アクティビティ毎にファイルが分割されています。 他のアプリを利用している場合にも、似たような処理で抽出できるのではないかと思います。

2. mapboxのapiを用いてmap matchingを実行、緯度経度を補正

map mathchingはgpsデータと道路情報を元にgpsデータの誤差を補正する方法で、カーナビ等に用いられているようです。

map matching のアルゴリズムは理解しきれていないのですが、下記のlyft社の記事を参照すると状態空間モデルやカルマンフィルタを用いたものなど複数の手法があるようです。

mapboxにはmap matchingを実施してくれるapiがあるので、今回はそれを下記に参考に実装します。

事前にmapboxへの登録とトークンの払い出しが必要です。 料金は21年5月現在100,000リクエストまで無料なので、遊びで使う分には無料で行えると思います。

実装はこんな感じ。

from gpx_converter import Converter
from mapbox import MapMatcher
import numpy as np
from geopy.distance import geodesic

gpxfile="./01-runkeeper-data-export-2021-05-04-084136/2021-04-18-122351.gpx"

#gpxをdict形式に変換
dic = Converter(input_file=gpxfile).gpx_to_dictionary(latitude_key='latitude', longitude_key='longitude')

latlon=[]
for i,j in zip(dic["latitude"],dic["longitude"]):
    latlon.append([j,i])
    
#datetimeはjson型にできないためstrに変換
dic["time"]=[i.isoformat() for i in dic["time"]]

service = MapMatcher(access_token="XXXX")

#map matching api は一度に100件までしか処理できないため分割処理
corrected=[]
dist=0
for i in range(len(latlon)//100+1):
    #apiの入力形式に合わせる
    line={}
    line["type"]="Feature"
    line["properties"]={"coordTimes":dic["time"][i*100:(i+1)*100]}
    line["geometry"]={"type": "LineString","coordinates":latlon[i*100:(i+1)*100]}
    
    response = service.match(line, profile='mapbox.walking')
    print(i,response)
    
    #結果を統合
    for i in response.geojson()['features']:
        corrected.extend(i["properties"]["matchedPoints"])
        dist+=i["properties"]["distance"]

correctedに補正後の緯度経度情報が入ります。

3. 評価

結果を地図上にプロットして補正の結果を確認します。

青色が元のデータ、赤色が補正後のデータです。

うまくいっているケース。誤差でぐねぐねしてしまっているものが、ちゃんと直線に補正されています。

f:id:rmizutaa:20210505115933p:plain

うまくいかなかったケース。往復で同じコースを走っているため線が二重になっています。 実際は常に大通りの右側を走っていますが、元のデータも補正後のデータも実態と乖離してしまっています。

f:id:rmizutaa:20210505115948p:plain

map mathingは基本的には取得地点の近くにある道路が選択されやすいと思うので、この方法では元のgpsデータに大きな乖離がある場合の補正は難しそうです。 また、このアプリでは基本的には3~4秒に1回gpsデータを取得しているのですが、中には数十秒データが取得できていない時間帯もありました。 1kmあたり6分のペースで走っていた場合、秒速換算すると2.77mなので、もし10秒間取得できない場所27.7m進むことになりますが、この間にカーブがあったりするとどうしてもショートカットするようなデータになってしまいます。 車に対してランニングは位置の自由度が高いので、それなりに誤差がでるのは仕方がないかなと思います。 (そもそもこれを正確に測りたいという需要があまりなさそう)

まとめ

ランニングアプリから抽出したgpsデータをmapboxのapiを用いてmap matchingし、誤差の補正ができるかを試みました。 結果としては確かに補正ができている部分もありますが、誤差が大きかったり、時間粒度が空いてしまっている場合など、そもそも補正が困難なgpsデータもそれなりに存在しているために、この補正によって正確な結果が出せるとは言えない結果になりました。 google mapで右クリック→距離の測定で手動でも距離が測れるので、ランニングの距離を正確に把握しておきたい場合はこちらを使用する方が良さそうです。

使用したコードは下記になります。

blog/gps_mapmatching_fix.ipynb at master · rmizuta3/blog · GitHub