Vote Charlie!

Restoring deleted Runkeeper activity from Google Location History

Posted at age 29.

After accidentally deleting my Runkeeper activity today, I was able to mostly restore it thanks to Google’s somewhat creepy location history. Perhaps some day Runkeeper will be more user friendly.

I got into this mess because sometime back in September or earlier, in my Runkeeper account activities started showing up that were apparently imported from Fitbit. I had not made any changes to my API integrations in a long time, and I did not have time to look into the issue since I was deep in my job search process at the time. (By the way, I am now working for Fitbit, as it turns out, and should therefore make clear what I post here are my own personal thoughts.) I suspected this was due to Fitbit automatically creating activities on the Fitbit side, as I had recently seen some in the Fitbit app despite never manually logging activities with Fitbit.

runkeeper-fitbit-activity.png

runkeeper-fitbit-activity.png

Anyway, I want my Runkeeper to only track my runs I log with Runkeeper, so I manually deleted all the imported activities, which were easily identifiable as they showed a time and not a distance, and they had no maps upon clicking. I opened a tab for each one to make deleting them all slightly easier, and of course I got a little trigger happy and deleted the activity on the final tab, which was my run from today.

The original Runkeeper activity was still open in yet another tab, taunting me.

The original Runkeeper activity was still open in yet another tab, taunting me.

After briefly cursing, I immediately put my Android into airplane mode so The Cloud could not compel my lowly phone to delete the activity as well. I then opened Runkeeper and searched for an activity export link. No such luck.

I took a screenshot of the original route to assist me in redrawing it, should my other efforts fail.

I took a screenshot of the original route to assist me in redrawing it, should my other efforts fail.

Then I tried the export GPX link on the browser window I still had open with the deleted activity, hoping it would be provided by some CDN despite the activity no longer existing. No such luck.

Then I put my phone back online and attempted to “Resend to Runkeeper”, in hopes Runkeeper would recreate the activity or create an identical one. No such luck. The app was apparently resending for eons. I assumed Runkeeper recognized the activity but refused to have a reasonable, data preserving default action.

runkeeper-android-resending.png

runkeeper-android-resending.png

I briefly looked for cached routes in the Android filesystem. I saw someone else had the same question. What I found was not a convenient set of activity files I could simply reimport. It appears Runkeeper keeps all its data packed in a series of binary files I was not about to try decoding.

runkeeper-android-files.png

runkeeper-android-files.png

I next turned to Google Location History, and was pleased to find my route mostly intact, and creepy as that felt. I hunted for an export or download link, but Google is also not that user friendly.

google-location-history.png

google-location-history.png

A Google search told me I needed to use Google Takeout.

google-takeout-location.png google-takeout-generating.png google-takeout-download.png

Once I had my exported location data, I assumed I would find a folder of GPS tracklogs corresponding to the activities separated out in the web interface. No such luck. The exported data was a single 143MB file formatted like this:

<?xml version='1.0' encoding='UTF-8'?>
<kml xmlns='http://www.opengis.net/kml/2.2' xmlns:gx='http://www.google.com/kml/ext/2.2'>
    <Document>
        <Placemark>
            <open>1</open>
            <gx:Track>
                <altitudeMode>clampToGround</altitudeMode>
                <when>2017-12-04T02:06:22Z</when>
                <gx:coord>-122.4904125 37.7445373 30</gx:coord>
                <when>2017-12-04T02:05:52Z</when>
                <gx:coord>-122.4904125 37.7445373 30</gx:coord>

I used the screenshot of my original activity to calculate the time window I needed to isolate and convert to UTC. Next I needed to transform the file to something Runkeeper could understand. I started with an example exported activity from Runkeeper and modified it.

It started like this:

<?xml version="1.0" encoding="UTF-8"?>
<gpx
  version="1.1"
  creator="Runkeeper - http://www.runkeeper.com"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://www.topografix.com/GPX/1/1"
  xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"
  xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1">
<trk>
  <name><![CDATA[Running 11/25/17 5:09 pm]]></name>
  <time>2017-11-26T01:09:58Z</time>
<trkseg>
<trkpt lat="37.744275000" lon="-122.490380000"><ele>62.8</ele><time>2017-11-26T01:09:58Z</time></trkpt>
<trkpt lat="37.744316000" lon="-122.490484000"><ele>62.7</ele><time>2017-11-26T01:10:04Z</time></trkpt>
...
</trkseg>
</trk>
</gpx>

I ran the following regular expression replacement to convert the XML data from Google’s format to Runkeeper’s.

Pattern: <gx:coord>([-.\d]+) ([-.\d]+) ([-.\d]+)</gx:coord>\s+<when>([-\dT:Z]+)</when>

Replacement: <trkpt lat="\2" lon="\1"><ele>\3</ele><time>\4</time></trkpt>

I replaced the timestamp in L. 11 with the last timestamp from my target data, and I adjusted the date and time in L. 10 as well, noting it is in my local timezone. I am not sure those two steps are necessary.

Then I reversed the lines of data to be in normal chronological order.

After all that, I was able to import the activity into Runkeeper and make a few GPS adjustments to get it pretty close to the original.

Side note: I quickly gave up trying to associate the photo I took earlier with the run, as Runkeeper’s support for photos is extremely buggy. There is no way to delete a photo once it is added to the activity (except perhaps if you actually took the photo with Runkeeper, but not if you added it to the activity afterward), and I had multiple copies of a photo on the activity, which I think prevented it from making it to Facebook at all, and thus I did not get the nifty generated photo with the embedded stats.

Help get me elected by purchasing products mentioned in this entry!