Skip to content

Instantly share code, notes, and snippets.

@mrvdb
Last active February 5, 2020 07:38
Show Gist options
  • Save mrvdb/c517f216b54facfc6ce9685720d331ba to your computer and use it in GitHub Desktop.
Save mrvdb/c517f216b54facfc6ce9685720d331ba to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# -*- mode:python -*-
#
# Converts suunto zip files (which contain json files) to a gpx file
# until Suunto gets their act together and let me have my own data in
# a normal way.
# Needs: python 3.7
#
# These zip files are producted by the Suunto app and typically on
# android are located at:
# <Internal Storage>/Android/data/com.stt.suunto/files/smlzip/
#
import sys
import gpxpy
import gpxpy.gpx
import json
import datetime
import zipfile
import math
# The relevant json snippet which is in a json array 'Samples'
# looks like this:
# {
# "Attributes": {
# "suunto/sml": {
# "Sample": {
# "GPSAltitude": 94, <-- assuming meters
# "Latitude": 0.88924328690504395, <-- in radians
# "Longitude": 0.10261437316962527, <-- in radians
# "UTC": "2019-04-18T07:25:29.000+00:00" <-- assumed what gps thinks of time?
# }
# }
# },
# "Source": "suunto-123456789", <-- number is the id of the device
# "TimeISO8601": "2019-04-18T09:25:29.000+02:00" <-- assumed what watch thinks of time?
# },
# Suunto specific stuff
SUUNTO_SAMPLES = 'samples.json'
def gpx_track(zip):
# Three keys make a trackpoint
TRKPT = ('Latitude', 'Longitude', 'UTC')
# Create the main gpx object
gpx = gpxpy.gpx.GPX()
# We are getting one track presumably from the points we recorded
track = gpxpy.gpx.GPXTrack()
gpx.tracks.append(track)
# Points are added to segments (take breaks into account later?)
segment = gpxpy.gpx.GPXTrackSegment()
track.segments.append(segment)
# Read in the json file
with zip.open(SUUNTO_SAMPLES, 'r') as json_data:
suunto_json = json.load(json_data)
for s in suunto_json["Samples"]:
if "Sample" in s["Attributes"]["suunto/sml"]:
sample = s["Attributes"]["suunto/sml"]["Sample"]
# See if we have enough for a trackpoint
if all(key in sample for key in TRKPT):
# Lat and long are radians
# To decimal degrees = * 180 / PI
lat = (sample["Latitude"] * 180) / math.pi
lon = (sample["Longitude"] * 180) / math.pi
# I'm assuming this is in meters, and we can use it as is
ele = sample['GPSAltitude'] if 'GPSAltitude' in sample else 0
time = datetime.datetime.fromisoformat(sample['UTC'])
# Create the gpx point
point = gpxpy.gpx.GPXTrackPoint(latitude=lat, longitude=lon,
time=time, elevation=ele)
segment.points.append(point)
# Write out the gpx file
with open(zip.filename.replace(".zip", ".gpx"), "w") as out:
out.write(gpx.to_xml())
if __name__ == "__main__":
# Argument is expected to be a zip file
with zipfile.ZipFile(sys.argv[1]) as zip:
# There should be a samples.json in the zip file
if SUUNTO_SAMPLES in zip.namelist():
gpx_track(zip)
else:
print("No track samples found in zip file!")
@mike-kilo
Copy link

Hi Marcel,

Great script, comes really useful for my mother's Suunto watch, the GPX generated was accepted by Strava.

I had huge problems installing Python 3.8 (I should update my system soon). I went on and modified your script a bit so it runs Python 3.5, see my fork.

Regards,

Mike

@mrvdb
Copy link
Author

mrvdb commented Feb 3, 2020

Hi @mike-kilo ,

Not sure if you missed it, but I'm keeping track of this script in a "real" repository now: https://github.com/mrvdb/suuntoapp2gpx

At the moment, the script isn't different from the gist though.

@mike-kilo
Copy link

Hi Marcel,

I did miss it indeed. Would you care for a pull request (for Python 3.4 - 3.6) or you'd rather stick to Python 3.7?

@mrvdb
Copy link
Author

mrvdb commented Feb 5, 2020 via email

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