File Coverage

blib/lib/GPS/Track.pm
Criterion Covered Total %
statement 36 83 43.3
branch 8 20 40.0
condition 3 11 27.2
subroutine 10 18 55.5
pod 4 6 66.6
total 61 138 44.2


line stmt bran cond sub pod time code
1             package GPS::Track;
2              
3 1     1   822005 use 5.018000;
  1         5  
4 1     1   4 use strict;
  1         4  
  1         32  
5 1     1   9 use warnings;
  1         3  
  1         35  
6 1     1   422 use Moo;
  1         6409  
  1         5  
7 1     1   1908 use GPS::Track::Point;
  1         3  
  1         28  
8 1     1   873 use XML::Simple;
  1         7979  
  1         6  
9 1     1   93 use Try::Tiny;
  1         2  
  1         790  
10              
11             our $VERSION = '0.02';
12              
13             has "onPoint" => (
14             is => "rw",
15             isa => sub {
16             GPS::Track::_validateOnPoint(shift);
17             }
18             );
19              
20             sub BUILD {
21 4     4 0 28974 my $self = shift;
22 4         16 my $args = shift;
23              
24 4 100       22 if(exists $args->{onPoint}) {
25 1         4 GPS::Track::_validateOnPoint($args->{onPoint});
26             }
27              
28 4         28 return $args;
29             }
30              
31             sub _validateOnPoint {
32 9     9   29 my $candidate = shift;
33              
34 9 100 100     157 if(defined($candidate) && ref($candidate) ne "CODE") {
35 5         172 die "Not a CODE-Ref to onPoint!"
36             }
37             }
38              
39              
40             sub parse {
41 0     0 1 0 my $self = shift;
42 0         0 my $file = shift;
43              
44 0         0 my $tcx = $self->convert($file);
45 0         0 return $self->parseTCX($tcx);
46             }
47              
48             sub convert {
49 0     0 1 0 my $self = shift;
50              
51 0 0       0 my $file = shift or die "No file supplied to parse!";
52 0 0       0 die "The file '$file' does not exist!" unless(-e $file);
53              
54             # identify dies on unknown formats!
55 0         0 my $format = $self->identify($file);
56              
57 0         0 my $xml = undef;
58 0 0       0 if($format eq "gpx") {
    0          
    0          
59 0         0 $xml = $self->_convertGPX($file);
60             }
61             elsif($format eq "fit") {
62 0         0 $xml = $self->_convertFIT($file);
63             }
64             elsif($format eq "tcx") {
65 0         0 $xml = $self->_convertTCX($file);
66             }
67              
68 0         0 return $xml;
69             }
70              
71             sub parseTCX {
72 0     0 0 0 my $self = shift;
73 0         0 my $xml = shift;
74              
75             # use a faster parser
76 0         0 local $XML::Simple::PREFERRED_PARSER = "XML::SAX::ExpatXS";
77              
78 0         0 my @options = ( ForceArray => ['Course', 'Trackpoint'] );
79 0         0 my $data = XMLin($xml, @options);
80              
81 0         0 my @courses = @{$data->{Courses}->{Course}};
  0         0  
82              
83 0         0 my @retval;
84              
85 0         0 foreach my $course (@courses) {
86 0         0 my @trackpoints = @{$course->{Track}->{Trackpoint}};
  0         0  
87 0         0 foreach my $p (@trackpoints) {
88             # Parse the ISO8601 DateTime
89 0         0 my $time = undef;
90             try {
91 0     0   0 $time = DateTime::Format::ISO8601->parse_datetime($p->{Time});
92 0         0 };
93              
94             my $gpsTrackPoint = GPS::Track::Point->new(
95             lat => $p->{Position}->{LatitudeDegrees},
96             lon => $p->{Position}->{LongitudeDegrees},
97             time => $time,
98             ele => $p->{AltitudeMeters} || undef,
99             spd => $p->{Extensions}->{TPX}->{Speed} || undef,
100             bpm => $p->{HeartRateBpm}->{Value} || undef,
101             cad => $p->{Cadence} || undef,
102 0   0     0 );
      0        
      0        
      0        
103              
104             # fire onPoint Callback
105 0 0       0 $self->onPoint()->($gpsTrackPoint) if(defined($self->onPoint));
106              
107             # push back point
108 0         0 push(@retval, $gpsTrackPoint);
109             }
110             }
111              
112 0         0 return @retval;
113             }
114              
115             sub identify {
116 8     8 1 202 my $self = shift;
117 8         27 my $filename = shift;
118              
119 8         24 my $suffix = "";
120 8 100       71 if($filename =~ /\.(\w+)$/) {
121 6         29 $suffix = lc($1);
122             }
123              
124 8         40 my %validSuffixes = (
125             gpx => 1,
126             fit => 1,
127             tcx => 1,
128             );
129              
130 8 100       117 die "File '$filename' has an unknown dataformat!" unless(exists $validSuffixes{$suffix});
131              
132 5         54 return $suffix;
133             }
134              
135             sub _convertFIT {
136 0     0     my $self = shift;
137 0           my $file = shift;
138 0           $self->gpsbabel_convert("garmin_fit", $file);
139             }
140              
141             sub _convertGPX {
142 0     0     my $self = shift;
143 0           my $file = shift;
144 0           return $self->gpsbabel_convert("gpx", $file);
145             }
146              
147             sub _convertTCX {
148 0     0     my $self = shift;
149 0           my $file = shift;
150              
151 0           $self->gpsbabel_convert("gtrnctr", $file);
152             }
153              
154             sub gpsbabel_convert {
155 0     0 1   my $self = shift;
156 0           my $sourceFormat = quotemeta(shift);
157 0           my $file = quotemeta(shift);
158              
159 0           my $tcx = `gpsbabel -i $sourceFormat -f $file -o gtrnctr -F -`;
160 0           return $tcx;
161             }
162              
163             1;
164             __END__
165             =head1 NAME
166              
167             GPS::Track - Perl extension for parsing GPS Tracks
168              
169             =head1 SYNOPSIS
170              
171             use GPS::Track;
172             my $track = GPS::Track->new;
173             my @trackPoints = $track->parse($filename);
174            
175             # Parse-Callback
176             my $track = GPS::Track->new(onPoint => sub { my $trackPoint = shift; });
177             my @trackPoints = $track->parse($filename);
178              
179             =head1 DESCRIPTION
180              
181             GPS::Track tries to parse common GPS Tracks recorded by diffrent GPS/Sports trackers.
182              
183             Under the hood the conversion is done by calling gpsbabel on your system.
184              
185             B<WARNING:> This is a early Alpha! Use at your own risk!
186              
187             =head1 ATTRIBUTES
188              
189             =head2 onPoint
190              
191             Callback which gets called for every parsed L<GPS::Track::Point>. Gets the parsed L<GPX::Track::Point> passed as argument.
192              
193             $track->onPoint(sub { my $trackPoint = shift; $trackPoint->lon; });
194              
195             Usefull for "in place statistics" to prevent useless looping over all points more than once.
196              
197             =head1 METHODS
198              
199             =head2 parse($filename)
200              
201             Tries to parse the given filename and returning all the parsed L<GPX::Track::Point>s as an array.
202              
203             Additionally if the 'onPoint' attribute is defined, it will be called for every parsed point.
204              
205             =head2 convert($filename)
206              
207             Converts the file from the identified format to the internaly used XML format.
208              
209             my $xml = $track->convert($filename);
210              
211             =head2 identify($filename)
212              
213             Tries to identify the type of file by looking at the suffix.
214              
215             TODO: Interpret file magic bytes.
216              
217             my $format = $track->identify($filename);
218              
219             =head1 INTERNAL METHODS
220              
221             =head2 _convertFIT
222              
223             Convert a .FIT file to TCX
224              
225             =head2 _convertGPX
226              
227             Convert .GPX file to TCX
228              
229             =head2 _convertTCX
230              
231             Convert .TCX fit to TCX
232              
233             This may seem "useless". But in reality GPSBabel does a lot of cleanup for us when converting TCX 2 TCX.
234              
235             =head2 gpsbabel_convert
236              
237             Calls the gpsbabel binary and fetches the result vom STDOUT and returns a string as TCX.
238              
239             my $tcx = $track->gpsbabel_convert($sourceFormat, $sourceFile)
240              
241             =head1 SEE ALSO
242              
243             To be done.
244              
245             =head1 AUTHOR
246              
247             Sven Eppler, E<lt>cpan@sveneppler.deE<gt>
248              
249             =head1 COPYRIGHT AND LICENSE
250              
251             Copyright (C) 2017 by Sven Eppler
252              
253             This library is free software; you can redistribute it and/or modify
254             it under the same terms as Perl itself, either Perl version 5.24.0 or,
255             at your option, any later version of Perl 5 you may have available.
256              
257              
258             =cut