Skip to content

Instantly share code, notes, and snippets.

@aaronpk
Created June 8, 2015 05:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aaronpk/40d6bb333447ff4265a4 to your computer and use it in GitHub Desktop.
Save aaronpk/40d6bb333447ff4265a4 to your computer and use it in GitHub Desktop.
<?php
chdir(dirname(__FILE__));
chdir('..');
require('vendor/autoload.php');
require_once('includes/inc.php');
require_once('includes/database.php');
$places = array(
array(
'name' => 'Home',
'latitude' => Config::$homeLat,
'longitude' => Config::$homeLng,
'radius' => 200
),
array(
'name' => 'Esri Portland',
'latitude' => 45.52154,
'longitude' => -122.6775348,
'radius' => 200
)
);
if(array_key_exists(1, $argv) && $argv[1] == 'auth') {
if(array_key_exists(2, $argv)) {
$ch = curl_init('https://runkeeper.com/apps/token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query(array(
'grant_type' => 'authorization_code',
'code' => $argv[2],
'client_id' => Config::$runkeeperClientID,
'client_secret' => Config::$runkeeperClientSecret,
'redirect_uri' => 'http://localhost/'
)));
$response = curl_exec($ch);
$data = json_decode($response);
print_r($data);
} else {
echo "https://runkeeper.com/apps/authorize?response_type=code&redirect_uri=http://localhost/&client_id=" . Config::$runkeeperClientID . "\n";
}
die();
}
function rk_request($path) {
$ch = curl_init('https://api.runkeeper.com' . $path);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Authorization: Bearer ' . Config::$runkeeperToken
));
$response = curl_exec($ch);
if($response) {
return json_decode($response);
} else {
return null;
}
}
$lastImportFilename = Config::dataDir().'last-runkeeper.txt';
$lastDate = file_get_contents($lastImportFilename);
$should_continue = true;
$request_url = '/fitnessActivities?page=0&pageSize=50&noEarlierThan='.$lastDate.'&modifiedNoEarlierThan='.$lastDate;
// Find all existing runkeeper IDs to see if we've already imported a run
$existingItemIDs = array();
foreach(explode("\n",shell_exec('grep runkeeper content/metrics/*/*/*/*.md')) as $line) {
if(preg_match('/activity\/(\d+)/', $line, $match)) {
$existingItemIDs[] = $match[1];
}
}
while($should_continue) {
echo "Fetching $request_url\n";
if($activities = rk_request($request_url)) {
if($activities->items) {
foreach($activities->items as $item) {
echo $item->uri . "\n";
if(preg_match('/fitnessActivities\/(\d+)/', $item->uri, $match)) {
// Check if it's already been imported
if(!in_array($match[1], $existingItemIDs)) {
$post = process_rk_item($item);
if($post) {
file_put_contents($lastImportFilename, $post->date->format('Y-m-d'));
}
} else {
echo "Skipping because it's already been imported\n\n";
}
} else {
echo "Encountered an unknown activity\n\n";
}
}
if(property_exists($activities, 'next') && $activities->next) {
$request_url = $activities->next;
echo "Continuing...\n\n";
} else {
$should_continue = false;
echo "Stopping because no 'next' URL was found for: $request_url\n\n";
}
} else {
$should_continue = false;
echo "Stopping because no items found in this request: $request_url\n\n";
}
} else {
$should_continue = false;
echo "Stopping because request failed: $request_url\n\n";
}
}
echo "Done\n";
function process_rk_item($item) {
global $places;
if($activity=rk_request($item->uri)) {
$path = $activity->path;
if(count($path) == 0) {
echo "skipping because no path data was found\n\n";
return false;
}
$first = $path[0];
$last = $path[count($path)-1];
$tz = getTimezoneForLocation($first->latitude, $first->longitude);
if($tz) {
$timezone = new DateTimeZone($tz->timezone);
} else {
$timezone = null;
}
// The date from Runkeeper does not include a timezone, so assume it is the timezone
// that corresponds to the location of the start of the run
$start_date = new DateTime($activity->start_time, $timezone);
$end_date = new DateTime($activity->start_time, $timezone);
$end_date->add(new DateInterval('PT' . round($activity->duration) . 'S'));
echo $activity->activity."\n";
echo $start_date->format('c') . "\n";
// echo $end_date->format('c') . "\n";
switch($activity->type) {
case 'Cycling':
$type = 'bikeride';
break;
case 'Running':
$type = 'run';
break;
case 'Walking':
$type = 'walk';
break;
}
$post = p3k\Post::newPage('metrics', $start_date, $type);
echo $post->fullURL . "\n";
echo $post->filename . "\n";
if(file_exists(Config::contentDir().$post->filename)) {
echo "already imported\n\n";
return false;
}
if(property_exists($activity, 'notes') && $activity->notes) {
$post->set('title', trim($activity->notes));
}
$context = p3k\ReverseGeocoder::adr($first->latitude, $first->longitude);
if($tz) {
$post->set('timezone', $tz->timezone);
}
if($context) {
$post->set('locality', $context->localityName);
$post->set('region', $context->regionName);
$post->set('country', $context->countryName);
}
$post->set('latitude', $first->latitude);
$post->set('longitude', $first->longitude);
foreach($places as $place) {
if(gc_distance($place['latitude'], $place['longitude'], $first->latitude, $first->longitude) <= $place['radius']) {
$post->set('place-name', $place['name']);
}
}
$post->set('syndication', array($activity->activity));
$routeData = array(
'start' => array(
'date' => $start_date->format('c'),
'latitude' => $first->latitude,
'longitude' => $first->longitude,
),
'end' => array(
'date' => $end_date->format('c'),
'latitude' => $last->latitude,
'longitude' => $last->longitude,
),
'meters' => (int)round($activity->total_distance),
'seconds' => (int)round($activity->duration)
);
if(property_exists($activity, 'average_heart_rate')) {
$routeData['heartrate'] = $activity->average_heart_rate;
}
$post->set('route', $routeData);
$geojson = array();
foreach($activity->path as $point) {
$feature = array(
'type' => 'Feature',
'geometry' => array(
'type' => 'Point',
'coordinates' => array($point->longitude, $point->latitude)
),
'properties' => array(
'date' => date('Y-m-d\TH:i:s', $start_date->format('U')+round($point->timestamp)).'Z',
'altitude' => $point->altitude
)
);
if(property_exists($activity, 'heart_rate')) {
foreach($activity->heart_rate as $hr) {
if($hr->timestamp == $point->timestamp) {
$feature['properties']['heartrate'] = $hr->heart_rate;
}
}
}
$geojson[] = $feature;
}
$post->body = json_encode(array(
'path' => $geojson,
));
$filesAdded = array(Config::contentDir().$post->filename);
@mkdir($post->assetFolder, 0755, true);
file_put_contents($post->assetFolder.'/runkeeper.json', json_encode($activity));
$filesAdded[] = $post->assetFolder.'/runkeeper.json';
// If there are any photos, download those too!
if(property_exists($activity, 'images')) {
$photos = array();
foreach($activity->images as $i=>$image) {
$fn = 'photo-'.$i.'.jpg';
file_put_contents($post->assetFolder.'/'.$fn, file_get_contents($image->uri));
$filesAdded[] = $post->assetFolder.'/'.$fn;
$photos[] = $fn;
}
$post->set('photos', $photos);
}
$post->save();
p3k\irc\alert('[runkeeper:' . $type . '] ' . round($routeData['meters']/1000,1) . 'km ' . round($routeData['seconds']/60) . ' minutes ' . $post->fullURL);
// Generate the two map images
$mapImages = $post->generateMapImages(false);
$filesAdded = array_merge($filesAdded, $mapImages);
\DeferredTask::queue('GitClient', 'addCommitPush', array($filesAdded, '[route] imported from runkeeper: '.$post->fullURL));
echo "\n";
return $post;
} else {
echo "Error: request for $item failed\n";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment