#!/usr/bin/perl # Copyright Jerry Nicholls - $Date: 2008/06/25 08:28:29 $ # Run this script with -e to get my email address should you wish to contact me use Getopt::Long; use Pod::Usage; use strict; # Prototypes sub ParseTCX(); sub GPXHeader($$); sub GPXTrackSegment($$); sub GPXTrackPoint($$); sub GPXFooter($); sub Activity(); sub Lap($); sub TrackPoint($); sub SportOption($$); # Statics our $s_version = '$Revision: 1.19 $'; our $s_address = '6a65727279406e6967687477617463682e6f72672e756b'; # Globals our $g_help = 0; our $g_email = 0; our $g_list = 0; our $g_one = 0; our $g_multiTrack = 0; our $g_course = 0; our $g_id = ''; our $g_pattern = ''; our $g_sport = '"biking"'; our $g_output = 'tcx2gpx'; GetOptions('help|?' => \$g_help, 'email' => \$g_email, 'list' => \$g_list, '1' => \$g_one, 'multitrack' => \$g_multiTrack, 'course' => \$g_course, 'id=s' => \$g_id, 'pattern=s' => \$g_pattern, 'sport=s' => \&SportOption, 'output=s' => \$g_output) or pod2usage(2); pod2usage(1) if $g_help; if ($g_email) { print "My email address is " . pack('H*', $s_address) . "\n"; exit; } # Remove the .gpx extension to the filename, ensuring that there is one $g_output =~ s/\.gpx$//; $g_output .= 'tcx2gpx' if $g_output =~ m|[\/]$|; # Do we just want to parse the file for entries ? if ($g_list) { my $wanted = 0; if ($g_course) { while (<>) { if ($wanted && /([^<]+)<\/name>/oi) { print "$1\n"; $wanted = 0; } $wanted = 1 if //i; } } else { while (<>) { print "$1\n" if ($wanted && /([^<]+)<\/id>/oi); $wanted = 1 if /) { # Parse either activities or courses as selected if ($g_course ? //oi : /{minLon} if $bounds->{minLon} < $minLon; $minLat = $bounds->{minLat} if $bounds->{minLat} < $minLat; $maxLon = $bounds->{maxLon} if $bounds->{maxLon} > $maxLon; $maxLat = $bounds->{maxLat} if $bounds->{maxLat} > $maxLat; } open FH, "> $filename" or die "$0: $filename - $!\n"; GPXHeader(\*FH, { time => $time, minLon => $minLon, minLat => $minLat, maxLon => $maxLon, maxLat => $maxLat }); } # Now go through the activities that we were interested in. foreach my $activity (@activities) { my ($tmpID, $bounds, $laps) = @{$activity}[0..2]; if (! $g_one) { $filename = $g_output . ($g_id ? '.gpx' : "_$tmpID.gpx"); open FH, "> $filename" or die "$0: $filename - $!\n"; GPXHeader(\*FH, { time => $tmpID, minLon => $bounds->{minLon}, minLat => $bounds->{minLat}, maxLon => $bounds->{maxLon}, maxLat => $bounds->{maxLat} }); } if ($g_multiTrack) { my $i = 0; foreach my $lap (@{$laps}) { $i++; print FH " \n $tmpID($i)\n"; GPXTrackSegment(\*FH, $lap); print FH " \n"; } } else { print FH " \n $tmpID\n"; foreach my $lap (@{$laps}) { GPXTrackSegment(\*FH, $lap); } print FH " \n"; } if (! $g_one) { GPXFooter(\*FH); close FH; } } if ($g_one) { GPXFooter(\*FH); close FH; } } #============================================================================== # GPX related functions #------------------------------------------------------------------------------ # Output a suitable GPX header chunk. Takes a fileRef and a hashRef. sub GPXHeader($$) { my $file = shift; my $args = shift; print {$file} << "EOF"; Garmin International EOF } #------------------------------------------------------------------------------ # Output a suitable GPX track segment. Takes a fileref and an arrayref. sub GPXTrackSegment($$) { my $file = shift; my $lap = shift; print {$file} " \n"; foreach my $trackPoint (@{$lap}) { GPXTrackPoint($file, $trackPoint); } print {$file} " \n"; } #------------------------------------------------------------------------------ # Output a suitable GPX trackpoint. Takes a fileref and an arrayref. sub GPXTrackPoint($$) { my $file = shift; my $args = shift; print {$file} << "EOF"; $args->[2] EOF } #------------------------------------------------------------------------------ # Output a suitable GPX footer chunk. Takes a fileRef. sub GPXFooter($) { my $file = shift; print {$file} <<"EOF"; EOF } #============================================================================== # TCX related functions #------------------------------------------------------------------------------ # Scan for all the laps in an activity sub Activity() { my $tmpID = ''; my @laps = (); my $bounds = { minLon => 180.0, minLat => 180.0, maxLon => -180.0, maxLat => -180.0 }; while (<>) { last if /<\/activity>/oi; last if /<\/course>/oi; # New ID ? if (! $tmpID && $g_course ? /([^<]+)/oi : /([^<]+)/oi) { $tmpID = $1; # Skip this ID if it's not the one we're looking for last if ($g_id && $tmpID ne $g_id); last if ($g_pattern && $tmpID !~ /${g_pattern}/); } # Tally the laps if ($g_course ? /]*)?>/oi : /]*)?>/oi) { my $lap = Lap($bounds); push @laps, $lap if defined $lap; } } return @laps ? [$tmpID, $bounds, \@laps] : undef; } #------------------------------------------------------------------------------ # Scan for all the trackpoints in a lap sub Lap($) { my $bounds = shift; my @trackPoints = (); while (<>) { # End of lap ? last if /<\/lap>/oi; last if /<\/track>/oi; # We're only interested in trackpoints if (//oi) { my $trackPoint = TrackPoint($bounds); push @trackPoints, $trackPoint if defined $trackPoint; } } return @trackPoints ? \@trackPoints : undef; } #------------------------------------------------------------------------------ # Scan for a trackpoint's details. sub TrackPoint($) { my $bounds = shift; my $latitude = undef; my $longitude = undef; my $elevation = undef; my $time = undef; while (<>) { last if /<\/trackpoint>/oi; if (/([^<]+)/oi) { $latitude = $1; $bounds->{minLat} = $1 if $1 < $bounds->{minLat}; $bounds->{maxLat} = $1 if $1 > $bounds->{maxLat}; } if (/([^<]+)/oi) { $longitude = $1; $bounds->{minLon} = $1 if $1 < $bounds->{minLon}; $bounds->{maxLon} = $1 if $1 > $bounds->{maxLon}; } $elevation = $1 if /([^<]+)/oi; $time = $1 if /