Last active
May 5, 2018 23:28
-
-
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…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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) |
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"
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
Patched to use api version 1.1 .