Skip to content

Instantly share code, notes, and snippets.

@KainokiKaede
Last active May 5, 2018 23:28
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KainokiKaede/7264280 to your computer and use it in GitHub Desktop.
Save KainokiKaede/7264280 to your computer and use it in GitHub Desktop.
These scripts fetch storyline from Moves (http://moves-app.com/) and convert it into gpx file. moves-fetch.py does the first task, and movesjson2gpx.py does the second. To execute moves-fetch.py, you have to sign in to Moves as a developer (https://dev.moves-app.com/login). For further instruction, please read the comments and the code itself. F…
"""
1. Go to https://dev.moves-app.com/apps and register a new app.
client_id and client_secret will be given, so paste it to the variables below.
2. `$ python moves-fetch.py --requesturl` will open the web browser.
Follow the instructions and authenticate the app.
You will be redirected to a web page with an error message.
Copy the string between `code=` and `&`.
That is your request_token, so paste it below.
3. `$ python moves-fetch.py --accesstoken` will output access token to stdout.
Copy the token and paste it below.
4. `$ python moves-fetch.py YYYYMMDD YYYYMMDD` will create the json file between
the days you specified.
5. If you need the file in gpx, please consider using my `movesjson2gpx.py`.
"""
import requests
import datetime
import time
import json
import argparse
import webbrowser
client_id = ''
client_secret = ''
request_token = ''
access_token = ''
api_url = 'https://api.moves-app.com/api/1.1'
oauth_url = 'https://api.moves-app.com/oauth/v1'
def create_request_url():
url = oauth_url
url += '/authorize?response_type=code'
url += '&client_id=' + client_id
url += '&scope=activity%20location'
return url
def get_access_token():
url = oauth_url
url += '/access_token?grant_type=authorization_code'
url += '&code=' + request_token
url += '&client_id=' + client_id
url += '&client_secret=' + client_secret
url += '&redirect_uri='
responce = requests.post(url)
access_token = responce.json()['access_token']
return access_token
def get(access_token, endpoint):
url = api_url
url += endpoint
url += '?access_token=' + access_token
return requests.get(url).json()
def get_storyline(access_token, date):
url = api_url
url += '/user/storyline/daily/{date}'.format(date=date.strftime('%Y%m%d'))
url += '?trackPoints=true'
url += '&access_token=' + access_token
return requests.get(url).json()
def get_storylines(access_token, startdate, enddate, wait_sec=3):
a_day = datetime.timedelta(days=1)
date = startdate
storylines = []
while date <= enddate:
print date.strftime('%F')
try:
storyline = get_storyline(access_token, date)
except:
storyline = []
print 'Error occured on {date}'.format(date=date.strftime('%F'))
storylines.append(storyline)
if date == enddate: wait_sec = 0
time.sleep(wait_sec)
date += a_day
return storylines
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Fetch Storyline in JSON file from Moves API.')
parser.add_argument('startdate', nargs='?', default=None)
parser.add_argument('enddate', nargs='?', default=None)
parser.add_argument('--requesturl', action='store_true')
parser.add_argument('--accesstoken', action='store_true')
parser.add_argument('--stdout', action='store_true')
args = parser.parse_args()
if args.requesturl:
url = create_request_url()
print url
webbrowser.open(url)
elif args.accesstoken:
print get_access_token()
else:
assert args.startdate is not None
startdate = datetime.datetime.strptime(args.startdate, '%Y%m%d')
if args.enddate is None:
enddate = startdate
else:
enddate = datetime.datetime.strptime(args.enddate, '%Y%m%d')
storylines = get_storylines(access_token, startdate, enddate)
storylines_json = json.dumps(storylines, indent=4)
if not args.stdout:
outfilename = startdate.strftime('%F') + '-' + enddate.strftime('%F')
outfilename += '-moves-storyline.json'
outfile = open(outfilename, 'w')
outfile.write(storylines_json)
outfile.close()
else: print storylines_json
"""
This is a simple code to convert Moves JSON file to gpx files.
Each day in JSON file will be converted into different gpx file,
one file for a single day.
If you think this behavior annoying, please feel free to rewrite the code:D
Usage: 1. Get JSON file. You can use my `moves-fetch.py`.
2. Feed the file to this code as an argument.
3. You get .gpx files! (hopefully.)
"""
import argparse
import json
import datetime
from xml.dom.minidom import parseString
outputfilename = '{date}-moves-trackpoints.gpx'
# Prepare to convert Moves-style time format string.
timefmtstr = "%Y%m%dT%H%M%S" # Could not use %Z, so I had to set tz myself.
class MyTZ(datetime.tzinfo): # http://docs.python.org/2/library/datetime.html
def __init__(self, tzstr):
self.timedeltasec = int(tzstr[:3])*3600 + int(tzstr[3:5])*60
def utcoffset(self, dt): return datetime.timedelta(seconds=self.timedeltasec)
def tzname(self, dt): return self.tzstr
def dst(self, dt): return datetime.timedelta(0)
def strptime(timestr):
mytz = MyTZ(timestr[-5:-1])
return datetime.datetime.strptime(timestr[:-5], timefmtstr).replace(tzinfo=mytz)
# A function to convert location to gpx trkpt string.
writetimefmtstr = "%FT%TZ"
def trkptstr(datetime_, lon, lat):
timestr = datetime_.strftime(writetimefmtstr)
writestr = '<trkpt lat="{lat}" lon="{lon}">'.format(lat=lat, lon=lon)
writestr += '<time>{timestr}</time></trkpt>'.format(timestr=timestr)
return writestr
def json2gpx(inputfile):
# Load json file.
locdata = json.loads(inputfile.read()); # If fail, check validity of json file.
inputfile.close()
# Dissolve the json file, and obtain all the lon-lat data.
# Moves data format has several days in a huge single array.
# Each item represents single day, so first of all,
# create a loop in which only single day exists.
for singleday in locdata:
# Single day is an array of len=0, so throw the shell array.
singleday = singleday[0]
# Each day has "date" and "segements".
# Here, we use "date" to create filename of GPX file.
date = datetime.datetime.strptime(singleday['date'], '%Y%m%d')
print('Processing: ' + date.strftime('%F'))
# Prepare output string.
outputstr = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>\n'
outputstr += '<gpx xmlns="http://www.topografix.com/GPX/1/1"><trk><name>{date} Trackpoints from Moves Activity Tracker</name><trkseg>'.format(date=date.strftime("%F"))
# We use "segments" to obtain location data.
# each "segments" has several segment data, so create a loop for it.
# Sometimes, segments is 'null', so look for it.
if not singleday['segments']: continue
for segment in singleday['segments']:
# Segments have several "type"s. Each types contain a location info
# in a different way, so make conditions of them.
if segment['type'] == 'place':
# "place" type has a single location info, and start & end time.
# "place" has more infos than these, but I don't need them.
# If you are interested, please read the json file you've got.
starttime = strptime(segment['startTime'])
endtime = strptime(segment['endTime'])
location = segment['place']['location']
lon, lat = location['lon'], location['lat']
assert type(lon)==float and type(lat)==float
# print starttime, endtime, lon, lat # for debug.
outputstr += trkptstr(starttime, lon, lat)
outputstr += trkptstr(endtime, lon, lat)
elif segment['type'] == 'move':
# "move" type has "activities", which is an array of activities.
# An activity has "trackPoints", which is an array of time and loc.
for activity in segment['activities']:
for location in activity['trackPoints']:
time = strptime(location['time'])
lon, lat = location['lon'], location['lat']
assert type(lon)==float and type(lat)==float
# print time, lon, lat # for debug.
outputstr += trkptstr(time, lon, lat)
outputstr += '</trkseg></trk></gpx>'
# Basically you don't need to prettify an xml, but just in case.
outputstr = parseString(outputstr).toprettyxml(encoding="utf-8")
# Export to a file.
outputfile = open(outputfilename.format(date=date.strftime("%F")), 'w')
outputfile.write(outputstr)
outputfile.close()
if __name__ == '__main__':
# create the parser
parser = argparse.ArgumentParser(
description='Convert Moves JSON file to gpx files.')
parser.add_argument('inputfile', type=argparse.FileType('r'))
args = parser.parse_args()
json2gpx(args.inputfile)
@KainokiKaede
Copy link
Author

Patched to use api version 1.1 .

@themiurgo
Copy link

Thanks for these scripts! Moves now provides a way to download all the data as a gpx, but it's not practical to download this massive file every time.

Just for reference, I use this to assign a geotag to my pictures, via exiftool. However, I had to change the datetime string format to include timezone information, as without it exiftool was not geolocating correctly the pictures. Here's the change I made:
writetimefmtstr = "%FT%T%z"

@davidmashburn
Copy link

Thanks for posting this! I had just created about half of this functionality -- you saved me a lot of time not having to implement the rest :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment