Nike: all your runs are belong to us
I like to run, and I like tech, so obviously I like to measure and graph my runs. So I use Nike+. Unfortunately, Nike+ has a rubbish pure-flash website (rant: do people still make pure-flash sites? This is 2010!).
I want a way to get to my run data without having to use Nike’s website, so I’ve decided to code something up. I’m learning Rails at the moment, so I’ve decided to do this as a Rails app. In this post, and probably some subsequent posts, I’ll be showing my progress.
Step 1: getting the data
First off, we need some way of getting the data from Nike. This post on labs.interfacedigital.co.uk shows how to get the raw run data through Nike’s public API. The two URLs we’ll be using are this one for getting a summary of all runs:
http://nikerunning.nike.com/nikeplus/v1/services/app/run_list.jsp?userID=#{user_id}
and this one for getting the details of a single run:
http://nikerunning.nike.com/nikeplus/v1/services/app/get_run.jsp?id=#{run_id}&userID=#{user_id}
The all runs XML looks like this:
<plusService>
<status>success</status>
<runList endIndex="-1" startIndex="0">
<run id="1752070113" workoutType="standard">
<startTime>2010-02-28T13:24:40+00:00</startTime>
<distance>6.264</distance>
<duration>4067000</duration>
<syncTime>2010-02-28T14:34:16+00:00</syncTime>
<calories>470.0</calories>
<name/>
<description/>
<howFelt>3</howFelt>
<weather>2</weather>
<terrain>1</terrain>
<intensity/>
<gpxId/>
<equipmentType>sportband</equipmentType>
</run>
...
</runList>
</plusService>
Step 2: read the data
I’m doing the following in a Rails model called Run. The underlying table has fields for the necessary run attributes. I’m not going to go into this in detail because this isn’t a Rails tutorial.
require 'open-uri'
require 'rexml/document'
...
attributes = {
#:attr #xml tagname
:distance => "distance",
:start_time => "startTime",
:duration => "duration",
:calories => "calories"
}
open(all_runs_url) do |f| #open the XML file using the open-uri library
doc = REXML::Document.new f.read #create a new REXML object
doc.elements.each("plusService/runList/run") do |run_info| #iterate over 'run' elements
run_id = run_info.attributes["id"] #get the Nike id of the run
run = find_or_initialize_by_run_id(run_id) #get a new Run object, either from an existing record
#in the database or create a new one
attributes.each do |key, value| #iterate through attributes hash, which maps object attrs to XML tags
run.write_attribute(key, run_info.elements[value].text)
end
run.calculate_avg_pace #work out the average pace
run.save
end
end
calculate_avg_pace
looks like this:
def calculate_avg_pace
self.avg_pace = duration_in_minutes / distance_in_miles
end
and uses the following virtual attributes:
def duration_in_minutes
duration / (60.0 * 1000)
end
def distance_in_miles
distance * 0.62
end
At some point I’ll refactor so miles/km is an option, but for now I just want to get something working. So far though, so good:
Next steps will be to make the numbers look a bit nicer, for example by showing average pace in mm:ss. Other than that, who knows?
Links
- Github page
- Demo version on Heroku
- SlowGeek - Rasmus Lerdorf’s version of the same thing (discovered while writing this post)
- All your base are belong to us - for those scratching their heads over the title