I have been using Suunto Ambit 2 as the device for recording any sports activities e.g. running, trekking, or swimming. Recorded activities can be updated in the cloud Suunto Movescount, and see the summary of activities, e.g., speed, pace, elevation gain or elevation loss. Suunto Movescount is a growing sports community where you can create your own sports diary to collect and share your activities as well as customize your compatible Suunto watch.
Sometimes, when I do not have a watch with me, I use my iphone's MovesCount app to record the activities as alternative.
Unfortunatelly, With the iphone and watch the recording performance varies substantially:
When there is no signal, the recording performances of iphone's MovesCount app, especially the net elevation gain and loss, are just awefully unreliable.
In this post, I attempt to clean the GPS data from the iphone's MovesCount app, and make the performance conparable to the watch.
First, let's look at the GPS data from the two devices.
I recorded my hiking activity in Nepal using the two devices: Mountaineering move with Suunto Ambit 2 watch and Trekking move with Phone.
I downloaded the GPS data from my movescount page.
- Click Tools -> Exprot as KML.
from IPython.display import Image
Image("kml_photo.png",width='100%',height=100)
The exported kml files are saved in the two locations.
dir_move = "./data/"
path_phone_move_kml = dir_move + '/phone/Move_2018_05_01_06_52_33_Trekking.kml'
path_watch_move_kml = dir_move + '/Move_2018_05_01_06_51_39_Mountaineering_Lobche_Gorekshep_EBC.kml'
Extract the GPS coordinate infomation and the altitude infomation from the kml file, and save it into a panda dataframe.
import os
import numpy as np
import fastkml
import pandas as pd
import matplotlib.pyplot as plt
def get_coordinates(path_kml):
'''
extract lat, lon, altitude info from the kml data
'''
doc = open(path_kml, 'rb+').read()
k = fastkml.KML()
k.from_string(doc)
kmlstr = k.to_string()
coordstr = kmlstr.split("" )[1].split("")[0]
coordlst = coordstr.split(" ")
l = []
for point in coordlst:
pointlst = point.split(",")
l.append(pointlst)
df = pd.DataFrame(l,columns = ["lon","lat","altitude"])
for col in df.columns:
df[col] = pd.to_numeric(df[col], errors='coerce')
return(df)
df_phone = get_coordinates(path_phone_move_kml)
df_watch = get_coordinates(path_watch_move_kml)
## In this activity, the first 230 samples from the watch and the 14500 samples from the phone roughtly agree
df_watch = df_watch.iloc[1:230,:]
df_phone = df_phone.iloc[:14500,:]
print(df_phone.shape,df_watch.shape)
df_phone[:5]
Let's plot the GPS coordinate infos from the two devices using gmplot¶
The latitude and longitude pairs seem to agree between the records from the two devies.
from gmplot import gmplot
filenm = "my_map.html"
# Place map
gmap = gmplot.GoogleMapPlotter(27.971815, 86.828963, 14)
gmap.plot(df_phone["lat"], df_phone["lon"], 'cornflowerblue', edge_width=2)
gmap.plot(df_watch["lat"], df_watch["lon"], '#3B0B39', edge_width=2)
# Draw
gmap.draw(filenm)
import sys
sys.path.append("../../trip/")
## my google api key
from personal import MY_API_KEY
def jupyter_display(gmplot_filename, google_api_key):
"""
Hack to display a gmplot map in Jupyter
reference
https://github.com/vgm64/gmplot/issues/16
"""
with open(gmplot_filename, "r+") as f:
f_string = f.read()
url_pattern = "https://maps.googleapis.com/maps/api/js?libraries=visualization&sensor;=true_or_false"
f_string = f_string.replace(url_pattern, url_pattern + "&key;=%s" % google_api_key)
f.write(f_string)
jupyter_display(filenm, MY_API_KEY)
Image("my_map.png",width='100%',height=100)
## from IPython.display import IFrame
## IFrame(filenm, width=990, height=800)
We see that the elevation gain and loss infomation are substantially different! I know that the elvation gain of this trail around 5200m elevation was never 1535m (I know I cannot finish the trail otherwise!), and the recording from the watch seems more right!
from math import sin, cos, sqrt, atan2, radians
def get_distance_btw2pts(lat1,lon1,lat2,lon2):
'''
approximate radius of earth in km
https://stackoverflow.com/questions/19412462/getting-distance-between-two-points-based-on-latitude-longitude
'''
lat1 = radians(lat1)
lon1 = radians(lon1)
lat2 = radians(lat2)
lon2 = radians(lon2)
R = 6373.0
dlon = lon2 - lon1
dlat = lat2 - lat1
a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
c = 2 * atan2(sqrt(a), sqrt(1 - a))
distance = R * c
return(distance)
def get_distance(df_phone):
count = 1
df_phone["dist"] = np.NaN
for lat1, lat2, lon1, lon2 in zip(df_phone["lat"][:-1],df_phone["lat"][1:],
df_phone["lon"][:-1],df_phone["lon"][1:]):
dis = get_distance_btw2pts(lat1,lon1,lat2,lon2)
df_phone["dist"].iloc[count] = dis
count += 1
print(" distance = {:3.1f}km".format(np.sum(df_phone["dist"])))
return(df_phone)
def get_alt_change(alt):
diff_alt = alt[1:] - alt[:-1]
gain = np.sum(diff_alt[diff_alt > 0])
loss = np.sum(diff_alt[diff_alt < 0])
print(" Elevation gain {:3.0f}m, Elevation loss {:3.0f}m".format(gain,loss))
print(" N samples = {:3d}".format(len(alt)))
return gain, loss
print("watch")
get_distance(df_watch)
get_alt_change(df_watch["altitude"].values)
print("phone")
get_distance(df_phone)
get_alt_change(df_phone["altitude"].values)
print("-------------")
Cleaning the GPS records from iphone¶
To clean the GPS records from iphone, let's learn about the recording frequency of the difference devices:
Recording frequency
Devise | Move Type | Recording frequency |
---|---|---|
Phone | Trekking | Every 1 second |
Suunto Ambit 2 watch | Trekking | Every 10 seconds |
Suunto Ambit 2 watch | Mountaineering | Every 10 seconds |
Suunto Ambit 2 watch | Running | Every 1 second |
Despite that the iphone's recording performance is not so great, the recording frequency of the Trekking activity using iphone (every 1 second) is higher than Trekking or the Mountaineering of Suunto Ambit 2 watch (every 10 seconds). This suggests that we can take the rolling average of every 10 samples to clean the latitudes, longitudes and altitudes infomation from the iphone record.
GPS data cleaning¶
By taking the rolling average, the discripancies between the watch and phone records become somewhat more similar (yet they still differ).
def get_rolling_average(vec, k=5):
rvec = np.zeros(len(vec))
for t in range(len(vec)):
start = np.max([t - k,0])
end = np.min([t + k,len(vec)])
rvec[t] = np.mean(vec[start:end])
return(rvec)
for col in df_phone.columns:
df_phone[col] = get_rolling_average(df_phone[col].values, k=5)
print("watch")
get_distance(df_watch)
get_alt_change(df_watch["altitude"].values)
print("phone")
get_distance(df_phone)
get_alt_change(df_phone["altitude"].values)
print("-------------")