Hacking your RunKeeper data

Hacking your RunKeeper data

This week I’ve started running/jogging, and I’m using RunKeeper on my iPhone to track my progress.

RunKeeper stores all the GPS data from your runs. This data is displayed per run, including a nice map of your route. Most important data can be reached throught the web interface, things like pace (min/km or min/mile) and distance. The most rewarding thing in running is breaking your own records, and RunKeeper has a couple of records:

  • Longest run (distance)
  • Longest run (time)
  • Most calories burned in a run
  • Fastest average pace
  • etc

As you can see, all those statistics are about single runs, what I’m missing are the following records:

Fastest 1 KM, 3 KM, 5K, 10K etc.

For example, when I’ve run a very fast 5.5 km run, I’d love to see this reflected as my 5K personal record, but right now it is lost because I’ve already done a slow 6 km run and a very fast 1 km sprint.

Export data

But luckily RunKeeper has a very useful option for us developers: Settings > Export data.
This results in a ZIP file with GPX files, raw GPS data with time and location!


The first thing I did was download the XSD and generate the JAXB Java code:

$ xjc -p com.royvanrijn.running.gpx gpx.xsd 
parsing a schema...
compiling a schema...

Now I can open the GPX files in Java and extract the GPS locations and time like this:

JAXBContext jc = JAXBContext.newInstance("com.royvanrijn.running.gpx");
Unmarshaller unmarshaller = jc.createUnmarshaller();
JAXBElement<GpxType> root = (JAXBElement<GpxType>) unmarshaller.unmarshal(file);
List<TrkType> tracks = root.getValue().getTrk();

The next thing I did was translate the lat+long+time waypoints into ‘legs’ consisting of meters+seconds.
Those legs can be used to check for a record during longer runs, like this:

public void analyze(List<Leg> legs) {
	List<Leg> cache = new ArrayList<Leg>();
	double runningDistance = 0.0;
	for(Leg leg:legs) {
		//Add next leg to the tail:

		runningDistance += leg.getMeters();
		//If possible remove from head:

		while((runningDistance-cache.get(0).getMeters()) > targetDistance) {
			runningDistance -= cache.remove(0).getMeters();
		if(runningDistance > targetDistance) {
			//Check if the current target distance time is a record:


For example using a target of 5000 meters in a 5070 meter run, this analysis finds the following 5K times:

5000.135743886022	00:29:57
5001.356437683744	00:29:55
5013.145427605677	00:30:01
5009.98623847093	00:30:00
5000.938905254762	00:30:01
5001.8612669243685	00:30:03
5000.555676905546	00:30:05
5000.514815789608	00:30:07
5000.514815789608	00:30:07

The information from RunKeeper website is:

  • Distance: 5.07 km
  • Time: 30:38
  • Pace: 6:03

But when analysing the data more accurately, it could have said:

  • New personal record 3K: 00:16:46
  • New personal record 5K: 00:29:55

I couldn’t believe this feature wasn’t available in RunKeeper… but after a lot of Googling it turns out a lot of other people are looking for this ‘most requested’ feature! With a little bit of Java (100 lines of code) you can get a pretty good result:

Done analyzing all runs:
Fastest 100m run:	00:00:13
Fastest 200m run:	00:00:48
Fastest 400m run:	00:02:01
Fastest 1500m run:	00:07:50
Fastest 3000m run:	00:16:46
Fastest 5000m run:	00:29:55
Fastest 10000m run:	--:--:--

I’m now thinking of making a simple JavaScript application (no backend) that uses the RunKeeper export zip file as input and displays a lot of additional data. Are you interested in this? Please drop a comment and convince me to make this for you guys and girls!