Working with the Strava API's Activity data

In my last post I described how to set up your Strava API, make a simple request to obtain your Strava user data and display it on a web page.  In this post I'm going to get into the more fun stuff: activity data.

There are two types of activity request, these are:


  • getLoggedInAthleteActivities: Returns a list of the logged in user's activities along with a summary of basic details for each of them such as distance, moving time, etc.
  • getActivityById: Returns a single activity with a high level of detail, including segment efforts, photos, etc
For this post I'm going to describe the process for obtaining the list of athlete activities with basic level data.  You may be thinking 'if there's a way of getting all of the activity detail, why wouldn't we just do that?' to explain why I need to first explain what 'Rate Limits' are.

You may have seen this in your Strava App settings page:

What this means is that the amount of requests you may make to Strava for data is limited to 600 every 15 minutes or 30000 per day.  What happens if you go over the limit is that you will be locked out and unable to make any more requests for the rest of the time period and, I believe, if you repeatedly go over your limit, strava will de-authorise your token, meaning no more app play!  In order to get the detailed activity data you need to make 1 request per 1 activity, so we are limited to 600 activities per 15 minutes.  To get the less detailed activity data you can get 200 activities per 1 request.  Also, in order to get the individual activity data with all the detail, you need the activity identifiers, so getting the activity summary data is the first step to that process anyway.

A request for Strava activities looks like this:


<?php
   $activityData =  json_decode(file_get_contents("https://www.strava.com/api/v3/activities?per_page=200&page=1&scope=view_private&access_token=$clientToken"), true);
?>
You will notice, not just because I've highlighted them red, that the request contains some parameters, these are:

  • per_page: the number of activities per request, the maximum is 200
  • page: page 1 is the first 200 activities, page 2 is the second 200 activities, etc. (the most recent activity comes first)
  • scope: either view all activities including ones marked as private or just activities not marked as private.
There are other parameters that you can use that involve setting the date of the activity from which to get the data.

The above request will return the last 200 activities you have recorded with Strava and 200 is the maximum amount of activities per single request, so you need to do some smart coding to get all your activity data if you have recorded more than 200 activities.  These are the steps in English before I give you the code:
  • Create a page counter variable
  • Create a variable that will count how many activities were returned in the requests
  • Create a container array that will hold all the returned data
  • Create a 'while' loop that will keep requesting data as long as the previous request returned more than zero activities, will increment your page counter by 1 every request and will add all the data from the last request into the all data container array
Here's my code for doing this:

<?php

   $pages = 1;                          // The page (request) number

   $activityData = json_decode(file_get_contents("https://www.strava.com/api/v3/activities?page=$pages&per_page=200&scope=view_private&access_token=$clientToken"), true);
   $pagelength = count($activityData);     // the amount of activities returned by the request
   $allActivityData = ($activityData);     // the container that will hold all of the returned data

// To ensure all the data is returned we need to do a loop until an empty page is returned or requests reaches 50 (this is to safeguard against exceeding rate limits)

   while ($pagelength > 0 && $pages < 50) {
     $pages += 1;
     $activityData = json_decode(file_get_contents("https://www.strava.com/api/v3/activities?page=$pages&per_page=200&scope=view_private&access_token=$clientToken"), true);
     $allActivityData = array_merge($allActivityData,$activityData); 
     $pagelength = count($activityData); 
    }
?>
You will notice that one of the conditions in the while loop is that pages (requests) should not exceed 49, this is just my personal bit of paranoia - I have managed to exceed rate limits before because of bad coding (it can be as simple as accidentally typing '>' more than instead of '<' less than), so that is just there to stop requests just going on and on because I've made a mistake.

The above code will result in an array ($allActivityData) full of a mass of data, so the next step is to extract that data so that you can play with it.  

You can find out what data is returned by the request using the method described in my last post, which is to enter the request into the address bar of a new browser tab:

Note: You should adjust the request parameters so that it only returns 1 activity to reduce the confusion of returning 200 activities then trying to plough through it all.

https://www.strava.com/api/v3/activities?page=1&per_page=1&scope=view_private&access_token=enter_Your_Token_here

Then paste the result into a text editor and sort it by entering a new line after every comma.

Tip: If you don't enjoy the manual sorting of these big files in a text editor, do this:

  1. Do the request via the address bar of the browser, same as above
  2. When you see the result, right click on the browser screen and click 'Save as', then save it as a csv file (name it any file name you want followed by .csv)
  3. Open this file in excel - all the data will be in cell A1
  4. Click on A1, click 'data' > 'text to columns'
  5. In the text to columns wizard click - 'delimited' > [next] > 'Comma' > [next] > finish
  6. The result will be all the data sitting horizontally across the top of the spreadsheet, 
  7. Select all the data and click copy
  8. Paste into cell A2 selecting 'Paste special' > 'Transpose'
The result won't be as neat as if you manually sort it, but it saves a lot of messing around.

Once you have an idea of which bits of data you want, you need to loop through the $allActivityData and do something with each type of data, this can be: 'echo' it on to the screen, sort it into new arrays, put it in a database or whatever you need to do.  For this example I'm just going to 'echo' it onto the screen in a table so we can have a look at what we're working with.  

To do this I'm going to first create a table element and enter the headers of the table, like so:


<section>
   <table>
     <thead>
       <tr>
         <th>Date</th>
         <th>Name</th>
         <th>Type</th>
         <th>Dist</th>
         <th>Gain</th>
         <th>Time</th>
         <th>Speed</th>
         <th>Max</th>
       </tr>
     </thead>
     <tbody>
In the next bit, I'm going to enter some PHP code to loop through my activity data and put a new line in my table for each activity it finds.

Here's the code for doing so:


<?php
// Get the activity data and display in the table  
   foreach ($allActivityData as $activity) {
       $date = $activity["start_date_local"];
       $name = $activity["name"];
       $type = $activity["type"];
       $dist = $activity["distance"];
       $gain = $activity["total_elevation_gain"];
       $time = $activity["moving_time"];
       $aveSpeed = $activity["average_speed"];
       $max = $activity["max_speed"];
     
       echo 
  "<tr>
     <td>$date</td>
     <td>$name</td>
     <td>$type</td>
     <td>$dist</td>
     <td>$gain</td>
     <td>$time</td>
     <td>$aveSpeed</td>
     <td>$max</td>
   </tr>";
      }
?>
     </tbody>
   </table>
</section>

The result of the above should look something like this:

It looks pretty gross, right? And not just because we haven't done any CSS styling, the numbers mean nothing (I don't think I rode 19182.7 km to get to work this morning!)  Clearly we need to find out what the numbers mean and do some conversions.  

We also need to keep in mind that if this web page is going to eventually be for public use,  not everyone uses the same units of measurement, therefore,in converting the Distance data, for example, we need to convert it to either Kilometres or Miles depending on the user's measurement preference.

In my last post, 'Getting Access to the Strava API' I explained how to get the basic user data which includes the user's measurement preference, before we move on, if you haven't been doing so already, we should incorporate the code I have been explaining in this post with the code from my previous post, the entire file should now look like this:


<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="utf-8">
   <title>My Strava API</title>
   <meta name="Description" content="Strava API.">
</head>

<?php

// My credentials 
   $clientId = "*****";       //**== Enter your strava client ID ==**
   $clientSecret = "*****";   //**== Enter your strava client Secret ==**
   $clientToken = "*****";    //**== Enter your strava Access Token ==**

   $data = file_get_contents("https://www.strava.com/api/v3/athlete/?access_token=$clientToken");
   $summary = json_decode($data, true);
   
// User details Name, photo, location, etc
   $userId = $summary["id"];
   $userFirstname = $summary["firstname"];
   $userLastname = $summary["lastname"];
   $userPhoto = $summary["profile_medium"];
   $userCity = $summary["city"];
   $userState = $summary["state"];
   $userCountry =  $summary["country"]; 
   $userJoinedDate = $summary["created_at"];
// The date that the user started using strava is returned in this format - YYYY-MM-DDTHH:MM:SSZ
// which you can format into something more friendly:
   $JoinedDateFormatted = date("D - d M Y" ,strtotime($userJoinedDate));
   $userDistPref = $summary["measurement_preference"];
   $userActivityPref = $summary["athlete_type"]; 
// The user's preffered activity is either 1 or 0, which translates as 1 = run, 0 = ride   
   if($activityPref == 1){
     $prefType = "Run";
    } else {
     $prefType = "Ride";
    }
?>
<body>

<header>
   
   <div>
     <h1>My Strava API Application</h1>
     <p>This is my file for playing with strava data</p>
   </div>
   
   <div>
     <h4>Logged in as:</h4>
     <p><?php echo $userFirstname . " " .  $userLastname;?></p>
     <img src = "<?php echo $userPhoto;?>" alt "Strava profile image" />
     <p>
       <?php if ($userCity != "") {echo $userCity ." | ";}?>
       <?php if ($userState != "") {echo $userState ." | ";}?>
       <?php if ($userCountry != "") {echo $userCountry;}?>
     </p>
     <p>
       Strava member since: <?php echo $JoinedDateFormatted;?><br />
       Preferred Measurements: <?php echo $userDistPref;?><br />
       Preferred Activity: <?php echo $prefType;?>
   </div>
   
</header>
       
<?php

   $pages = 1;                             // The page number
   $activityData = json_decode(file_get_contents("https://www.strava.com/api/v3/activities?page=$pages&per_page=200&scope=view_private&access_token=$clientToken"), true);
   $pagelength = count($activityData);     // the ammount of activities returned by the request
   $allActivityData = ($activityData);     // the container that will hold all of the returned data

// To ensure all the data is returned we need to do a loop until an empty page is returned or requests reaches 50 (this is to safeguard against exceeding rate limits)
   while ($pagelength > 0 && $pages < 50) {
     $pages += 1;
     $activityData = json_decode(file_get_contents("https://www.strava.com/api/v3/activities?page=$pages&per_page=200&scope=view_private&access_token=$clientToken"), true);
     $allActivityData = array_merge($allActivityData,$activityData); 
     $pagelength = count($activityData); 
    }
?>
<section>
   <table>
     <thead>
       <tr>
         <th>Date</th>
         <th>Name</th>
         <th>Type</th>
         <th>Dist</th>
         <th>Gain</th>
         <th>Time</th>
         <th>Speed</th>
         <th>Max</th>
       </tr>
     </thead>
     <tbody>
<?php
// Get the activity data and display in the table  
   foreach ($allActivityData as $activity) {
       $date = $activity["start_date_local"];
       $name = $activity["name"];
       $type = $activity["type"];
       $dist = $activity["distance"];
     $gain = $activity["total_elevation_gain"];
       $time = $activity["moving_time"];
     $aveSpeed = $activity["average_speed"];
       $max = $activity["max_speed"];
     
     echo 
       "<tr>
         <td>$date</td>
         <td>$name</td>
         <td>$type</td>
         <td>$dist</td>
         <td>$gain</td>
         <td>$time</td>
         <td>$aveSpeed</td>
         <td>$max</td>
       </tr>";
    }
?>
     </tbody>
   </table>
</section>

</body>
</html>
You can use the Strava API documentation for Summary Activities to search for the units of measurement returned by the Strava API, however the measurement you are looking for is not always listed so you may have to just work some things out yourself by looking at what was returned and comparing it with what it looks like on the actual Strava Site.

Anyway,  for the purposes of keeping this post going I can tell you that:
  • distance: metres
  • gain: metres
  • time: seconds
  • speed (average and max): metres per second
We can convert these values to the correct measurements by dividing the returned data by the following:
Strava DataMetric ConversionImperial Conversion
Distance (m) / 1000 (km) / 1609.344 (miles)
Gain (m) / 1 (m) / 0.3048 (feet)
Speed (m/s) / 0.2777777777778 (km/h) / 0.44704 (miles/h)


So, we can put some conditional variables in our code with which we can divide the data by and, while we're at it, we may as well create some labels for the values, like so:

<?php
   if($userDistPref === "meters"){
     $distDivider = 1000;
     $GainDivider = 1;
     $speedDivider = 0.2777777777778;
     $distLabel = " (km)";
     $gainLabel = " (m)";
     $speedLabel = " (km/h)";
   } else {
     $distDivider = 1609.344;
     $GainDivider = 0.3048;
     $speedDivider = 0.44704;
     $distLabel = " (Mi)";
     $gainLabel = " (f)";
     $speedLabel = " (mph)";
   }    
 ?> 

So now lets go ahead and use the measurement converters to display the data in our table as we would expect it to look by changing the previous code around extracting data from Strava.

Note: Just to make sure we don't get any long decimals - which is very likely considering we are doing divisions by some long decimals - we'll use the PHP round() function to 1 decimal place when using our dividers.

Change the 'for each' loop that we created earlier to now look like this:


<?php
// Get the activity data and display in the table 
   foreach ($allActivityData as $activity) {
       $date = $activity["start_date_local"];
       $name = $activity["name"];
       $type = $activity["type"];
       $dist = round($activity["distance"] / $distDivider,1);
       $gain = round($activity["total_elevation_gain"] / $GainDivider,1);
       $time = $activity["moving_time"];
       $aveSpeed = round($activity["average_speed"] / $speedDivider,1);
       $max = round($activity["max_speed"] / $speedDivider,1);
     
     echo 
       "<tr>
         <td>$date</td>
         <td>$name</td>
         <td>$type</td>
         <td>$dist $distLabel</td>
         <td>$gain $gainLabel</td>
         <td>$time</td>
         <td>$aveSpeed $speedLabel</td>
         <td>$max $speedLabel</td>
       </tr>";
    }

?>

This should now be starting to look legible, but we haven't done anything with the activity date formatting and the activity time is still displaying seconds. so:

Format the activity date by changing this:
  $date = $activity["start_date_local"];
To this:
  $date = date("D - d M Y", strtotime($activity["start_date_local"]));

Change the activity time from seconds to minutes by changing this:
  $time = $activity["moving_time"];
To this:
  $time = round($activity["moving_time"] / 60,1);


And finally, just because I cant bear to look at an un-formatted table for much longer, please add some styling in the <head> element, something like this will suffice:
<style>
   table{
     border-collapse: collapse;
   }
   th, td {
     border: 1px solid rgb(200,200,200);
     text-align: left;
     font-size: 14px;
   }
</style>

If you have been following along, you should now have something that looks like this:

Which is where I'm going to leave things!

In this post I have explained how to get all your activity data from Strava, convert the values and display them in a table.  I hope this is enough to get you started with  and to build on to create some awesome applications.  

Just a note to say that I've taken many short cuts in the above explanations to keep it easy to follow and get some results on the screen without needing University level computing qualifications (which I certainly don't have!)  

If you are interested in where I went to from here then please check out my application - Solo Challenger

Comments

Popular posts from this blog

Introduction

Getting access to the Strava API