Saturday, January 28, 2012

simple Strava to CSV ride decoder with Perl

Slight detour from my project... Just wanted confidence I could decode JSON data, at least in Perl.

I wrote a little decode for Strava ride data using Perl. The code, which I call "Strava_to_csv", requires a command line option specified as follows:

Strava_to_csv -activity activity-number

where activity-number is the number of the activity.

It's nothing fancy, and not very robust. It expects all data to be scalar except for "latlng", which is an array of two numbers, the first the latitude, the second the longitude. It worked for me.

This code uses the JSON module version 2.53 from CPAN. There's a lot of stuff there about encoding type, but since Strava is unicode, it worked simply.

The code is written for Linux and maybe OS/X: it uses a system call to the "wget" command to download the URL (a Perl package for HTML could be used instead).

Anyway, here's my code. This hardly took any effort at all, maybe 30 minutes, which makes me wonder why it's taken me until now to get around to doing it. Since my blog has such narrow columns many of the lines may wrap around (sorry).

Okay, back to Java...

#! /usr/bin/perl
use strict;
use JSON;
use Getopt::Long;

my $activity;

my %options = (
  "activity=s" => \$activity
);

die "$0 : illegal command line options specified: @ARGV\n"
  unless GetOptions( %options );

die("$0: must specify activity with -activity option")
  unless(defined $activity);

my $url = "http://app.strava.com/api/v1/streams/$activity";

# slurp
undef $\;

open FP, "wget -O - $url |"
  or die("ERROR opening url $url\n");

my $s = <FP>;

my $json = JSON::PP->new;

my $data = $json->decode($s);

# check to make sure array lengths are the same

my $ndata;
my $k0;
my @keys = keys %$data;
for my $k ( @keys ) {
  my $l = scalar @{$data->{$k}};

  die("zero length data element $k found\n")
    unless ($l);

  unless (defined $ndata) {
    $ndata = $l;
    $k0 = $k;
  }
  elsif ($l != $ndata) {
    die("keys $k0 ($ndata) and $k ($l) reference arrays of different length\n");
  }
  # special case: we expect latlong to be an array of length 2
  if ($k eq "latlng") {
    unless((ref $data->{$k}->[0] eq "ARRAY") && (@{$data->{$k}->[0]} == 2)) {
      die("$k expected to reference data arrays of length 2\n");
    }
  }
  elsif (ref $data->{$k}->[0] ne "") {
    die("data element $k expected to be scalar; reference found instead.");
  }
}

# print label line
my @labels;
for my $k ( @keys ) {
  push @labels, ($k eq "latlng") ? "lat,lng" : $k;
}
print join(",", @labels), "\n";

# print data lines
for my $n ( 0 .. $ndata - 1 ) {
  my @data;
  for my $k ( @keys ) {
    if ($k eq "latlng") {
      push @data, @{$data->{$k}->[$n]};
    }
    else {
      push @data, $data->{$k}->[$n];
    }
  }
  print join(",", @data), "\n";
}

1 comment:

djconnel said...

There was at least one formatting problem (<FP> disappeared) that I fixed just now.