File Coverage

blib/lib/Geo/TCX/Trackpoint.pm
Criterion Covered Total %
statement 224 246 91.0
branch 83 124 66.9
condition 16 30 53.3
subroutine 36 39 92.3
pod 8 8 100.0
total 367 447 82.1


line stmt bran cond sub pod time code
1             use strict;
2 6     6   87103 use warnings;
  6         19  
  6         144  
3 6     6   29  
  6         21  
  6         252  
4             our $VERSION = '1.01';
5              
6             =encoding utf-8
7              
8             =head1 NAME
9              
10             Geo::TCX::Trackpoint - Class to store and edit TCX trackpoints
11              
12             =head1 SYNOPSIS
13              
14             use Geo::TCX::Trackpoint;
15              
16             =head1 DESCRIPTION
17              
18             This package is mainly used by the L<Geo::TCX> module and serves little purpose on its own. The interface is documented mostly for the purpose of code maintainance.
19              
20             L<Geo::TCX::Trackpoint> provides a data structure for TCX trackpoints and provides accessor methods to read and edit trackpoint data.
21              
22             TCX trackpoints are different from GPX trackpoints in that they contain tags such as C<AltitudeMeters>, C<DistanceMeters>, C<HeartRateBpm>, C<Time>, and potentially C<Cadence>, C<SensorState>. Also the coordinates are tagged with longer-form fields as C<LatitudeDegrees>, C<LongitudeDegrees>.
23              
24             =cut
25              
26             use Geo::Calc;
27 6     6   2873 use Geo::Gpx::Point;
  6         19400109  
  6         296  
28 6     6   3575 use Carp qw(confess croak cluck);
  6         1198375  
  6         237  
29 6     6   53 use vars qw($AUTOLOAD %possible_attr);
  6         14  
  6         269  
30 6     6   36  
  6         13  
  6         5538  
31             # file-scoped lexicals
32             my @attr = qw/ LatitudeDegrees LongitudeDegrees /;
33             $possible_attr{$_} = 1 for @attr;
34              
35             =head2 Constructor Methods
36              
37             =over 4
38              
39             =item new( $xml_str )
40              
41             Takes an xml string argument containing coordinates contained within the C<Position> xml tag (optional) as recorded by Garmin Edge devices and returns a basic C<Geo::TCX::Trackpoint> object containing only coordinates.
42              
43             $str_basic = '<Position><LatitudeDegrees>45.304996</LatitudeDegrees><LongitudeDegrees>-72.637243</LongitudeDegrees></Position>';
44             $tp_basic = Geo::TCX::Trackpoint->new( $str_basic );
45              
46             =item Geo::TCX::Trackpoint::Full::new( $xml_str, $previous_pt )
47              
48             Takes an xml string argument in the form of a Garmin TCX trackpoint, as recorded by Garmin Edge devices, and returns a C<Geo::TCX::Trackpoint::Full> object containing fields that are supplementary to coordinates. See the list of fields in the AUTOLOAD section below.
49              
50             $str_full = '<Trackpoint><Time>2014-08-11T10:25:26Z</Time><Position><LatitudeDegrees>45.304996</LatitudeDegrees><LongitudeDegrees>-72.637243</LongitudeDegrees></Position><AltitudeMeters>211.082</AltitudeMeters><DistanceMeters>13.030</DistanceMeters><HeartRateBpm><Value>80</Value></HeartRateBpm></Trackpoint>';
51              
52             $tp_full = Geo::TCX::Trackpoint::Full->new( $str_full );
53              
54             I<$previous_pt> is optional and if specified will be interpreted as the previous trackpoint and be used to keep track of the distance and time that have elapsed since the latter. See the methods below to access these "elapsed" fields. If no previous trackpoint is provided, the elapsed time will remain undefined and the elapsed distance will set to the C<DistanceMeters> field of the trackpoint.
55              
56             =back
57              
58             =cut
59              
60             my ($proto, $pt_str) = (shift, shift);
61             croak 'too many arguments specified' if @_;
62 3563     3563 1 8773 my $class = ref($proto) || $proto;
63 3563 50       7296 $pt_str =~ s,\</*Position\>,,g; # Lat and Long are contained in that tag, not needed
64 3563   33     8949 my $pt = {};
65 3563         17254 bless($pt, $class);
66 3563         6185  
67 3563         5266 # initialize fields/attr
68             while ($pt_str =~ m,\<([^<>]*)\>(.*?)\</([^<>]*)\>,gs) {
69             # or could simply state =~ m,\<(.*?)\>(.*?)\</.*?\>,gs)
70 3563         15757 croak 'Could not match identical attr' unless $1 eq $3;
71             croak 'field not allowed' unless $possible_attr{$1};
72 7126 50       17242 $pt->{$1} = $2
73 7126 50       14523 }
74 7126         26003 return $pt
75             }
76 3563         7369  
77             =over 4
78              
79             =item clone()
80              
81             Returns a deep copy of a C<Geo::TCX::Trackpoint> instance.
82              
83             $clone = $trackpoint->clone;
84              
85             =back
86              
87             =cut
88              
89             my $clone;
90             eval(Data::Dumper->Dump([ shift ], ['$clone']));
91             confess $@ if $@;
92 98     98 1 3376 return $clone
93 98         380 }
94 98 50       450  
95 98         462 =head2 AUTOLOAD Methods
96              
97             =cut
98              
99             =over 4
100              
101             =item I<field>( $value )
102              
103             Methods with respect to certain fields can be autoloaded and return the current or newly set value.
104              
105             For Basic trackpoints, LatitudeDegrees and LongitudeDegrees are the supported fields.
106              
107             For Full trackpoints, supported fields are: LatitudeDegrees, LongitudeDegrees, AltitudeMeters, DistanceMeters, HeartRateBpm, Cadence, and SensorState.
108              
109             Some fields may contain a value of 0. It is safer to check if a field is defined with C<< if (defined $trackpoint->Cadence) >> rather than C<< if ($trackpoint->Cadence) >>.
110              
111             Caution should be used if setting a I<$value> as no checks are performed to ensure the value is appropriate or in the proper format.
112              
113             =back
114              
115             =cut
116              
117             my $self = shift;
118             my $attr = $AUTOLOAD;
119             $attr =~ s/.*:://;
120             return unless $attr =~ /[^A-Z]/; # skip DESTROY and all-cap methods
121 101     101   183 croak "invalid attribute method: -> $attr()" unless $possible_attr{$attr};
122 101         154 $self->{$attr} = shift if @_;
123 101         414 return $self->{$attr}
124 101 100       668 }
125 64 50       165  
126 64 100       128 =head2 Object Methods
127 64         573  
128             =cut
129              
130             =over 4
131              
132             =item to_gpx()
133              
134             Returns a trackpoint as a L<Geo::Gpx::Point>.
135              
136             =back
137              
138             =cut
139              
140             my ($pt, %attr) = @_; # call to new() will handle error check
141             my %fields = ( lat => $pt->LatitudeDegrees, lon => $pt->LongitudeDegrees );
142             $fields{ele} = $pt->AltitudeMeters if defined $pt->AltitudeMeters;
143             $fields{time} = $pt->{_time_epoch} if defined $pt->Time;
144             return Geo::Gpx::Point->new( %fields, %attr );
145 1     1 1 4 }
146 1         6  
147 1 50       5 =over 4
148 1 50       5  
149 1         12 =item to_geocalc()
150              
151             Returns a trackpoint as a L<Geo::Calc> object.
152              
153             =back
154              
155             =cut
156              
157             my $pt = shift;
158             croak "to_geocalc() takes no arguments" if @_;
159             return Geo::Calc->new( lat => $pt->LatitudeDegrees, lon => $pt->LongitudeDegrees );
160             }
161              
162             =over 4
163 1     1 1 910  
164 1 50       4 =item to_basic()
165 1         6  
166             Returns a trackpoint as a C<Geo::TCX::Trackpoint> object with only position information (i.e coordinates).
167              
168             =back
169              
170             =cut
171              
172             my $pt = shift;
173             croak "to_geocalc() takes no arguments" if @_;
174             my $newpt = {};
175             bless($newpt, 'Geo::TCX::Trackpoint');
176             $newpt->LatitudeDegrees( $pt->LatitudeDegrees );
177             $newpt->LongitudeDegrees( $pt->LongitudeDegrees );
178             return $newpt
179 3     3 1 483 }
180 3 50       10  
181 3         6 =over 4
182 3         7  
183 3         15 =item distance_to ( $trackpoint )
184 3         11  
185 3         13 Calculates and returns the distance to the specified I<$trackpoint> object using the L<Geo::Calc> module.
186              
187             =back
188              
189             =cut
190              
191             my ($from, $to) = (shift, shift);
192             croak 'expects a single trackpoint as argument' if @_ or ! $to->isa('Geo::TCX::Trackpoint');
193             my $g = Geo::Calc->new( lat => $from->LatitudeDegrees, lon => $from->LongitudeDegrees );
194             my $dist = $g->distance_to( { lat => $to->LatitudeDegrees, lon => $to->LongitudeDegrees } );
195             return $dist
196             }
197              
198             =over 4
199 14     14 1 426  
200 14 50 33     142 =item xml_string()
201 14         176  
202 14         2557 returns a string containing the XML representation of the object, equivalent to the string argument expected by C<new()>.
203 14         146698  
204             =back
205              
206             =cut
207              
208             my $pt = shift;
209             my %opts = @_;
210              
211             my $newline = $opts{indent} ? "\n" : '';
212             my $tab = $opts{indent} ? ' ' : '';
213             my $n_tabs = $opts{n_tabs} ? $opts{n_tabs} : 4;
214              
215             my $str;
216             $str .= $newline . $tab x ($n_tabs + 1) . '<Position>';
217 0     0 1 0 $str .= $newline . $tab x ($n_tabs + 2) . '<LatitudeDegrees>' . $pt->LatitudeDegrees . '</LatitudeDegrees>';
218 0         0 $str .= $newline . $tab x ($n_tabs + 2) . '<LongitudeDegrees>' . $pt->LongitudeDegrees . '</LongitudeDegrees>';
219             $str .= $newline . $tab x ($n_tabs + 1) . '</Position>';
220 0 0       0 return $str
221 0 0       0 }
222 0 0       0  
223             =over 4
224 0         0  
225 0         0 =item summ()
226 0         0  
227 0         0 For debugging purposes, summarizes the fields of the trackpoint by printing them to screen. Returns true.
228 0         0  
229 0         0 =back
230              
231             =cut
232              
233             my $pt = shift;
234             croak 'summ() expects no arguments' if @_;
235             my %fields;
236             foreach my $key (keys %{$pt}) {
237             print "$key: ", $pt->{$key}, "\n"
238             }
239             return 1
240             }
241              
242             use strict;
243 0     0 1 0 use warnings;
244 0 0       0  
245 0         0 use DateTime::Format::ISO8601;
246 0         0 use Carp qw(confess croak cluck);
  0         0  
247 0         0  
248             our $VERSION = '1.01';
249 0         0 our @ISA=qw(Geo::TCX::Trackpoint);
250              
251              
252             { # lexical scope for that package
253 6     6   46  
  6         14  
  6         135  
254 6     6   26 use vars qw($AUTOLOAD %possible_attr);
  6         13  
  6         274  
255              
256 6     6   3600 our ($LocalTZ, $Formatter);
  6         4767823  
  6         504  
257 6     6   89 $LocalTZ = DateTime::TimeZone->new( name => 'local' );
  6         14  
  6         861  
258             $Formatter = DateTime::Format::Strptime->new( pattern => '%a %b %e %H:%M:%S %Y' );
259             my $formatter_xsd = DateTime::Format::Strptime->new( pattern => '%Y-%m-%dT%H:%M:%SZ' );
260             # ... to avoid looking up timezone each time Trackpoint->new is called
261              
262             # file-scoped lexicals
263             my @attr = qw/ LatitudeDegrees LongitudeDegrees AltitudeMeters DistanceMeters Time HeartRateBpm Cadence SensorState /;
264             $possible_attr{$_} = 1 for @attr;
265 6     6   44  
  6         13  
  6         13350  
266             my ($proto, $pt_str, $previous_pt) = (shift, shift, shift);
267             if (ref $previous_pt) {
268             croak 'second argument must be a Trackpoint object' unless $previous_pt->isa('Geo::TCX::Trackpoint')
269             }
270             croak 'too many arguments specified' if @_;
271             my $class = ref($proto) || $proto;
272              
273             my $chomped_str = $pt_str;
274             if ( $chomped_str =~ m,\s*^\<Trackpoint\>(.*)\</Trackpoint\>\s*$,gs ) {
275             $chomped_str = $1
276             }
277             # contrary to Track, the <Trackpoint>...</Trackpoint> are optional
278 3536     3536   13530  
279 3536 100       8006 # Extract the Position tag and create a basic positional trackpoint
280 3450 50       10214 my $pt;
281             if ( $chomped_str =~ s/(<Position>.*<\/Position>)//g ) {
282 3536 50       7380 $pt =$class->SUPER::new( $1 )
283 3536   33     10936 } else {
284             # $DB::single=1;
285 3536         4509 # I put a debug flag here because I want to see instances where
286 3536 50       17442 # a trackpoint does not have coordinates and see how I should address those
287 3536         8235 # croak 'no <Position>...</Position> xml tag in string'
288             # call it anyway for now until I figure out how to handle those
289             $pt = {};
290             bless($pt, $class);
291             }
292 3536         4737 $chomped_str =~ s,\</*Value\>,,g; # HeartRateBpm value contained in that tag, not needed
293 3536 100       21813  
294 3535         9957 # initialize fields/attr
295             while ($chomped_str=~ m,\<([^<>]*)\>(.*?)\</([^<>]*)\>,gs) {
296             # or could simply state =~ m,\<(.*?)\>(.*?)\</.*?\>,gs)
297             croak 'Could not match identical attr' unless $1 eq $3;
298             croak 'field not allowed' unless $possible_attr{$1};
299             $pt->{$1} = $2
300             }
301 1         3  
302 1         3 # for debugging -- allow trackpoints with only coordinates but inspect them in debugger
303             $pt->{_noTime} = 1 unless defined $pt->{Time};
304 3536         18515 $pt->{_noDist} = 1 unless defined $pt->{DistanceMeters};
305             if ($pt->{_noTime} or $pt->{_noDist}) {
306             # commented out as I am building my databases, way too many files to parse to inspect them now, will uncomment when I am done parsing my databases
307 3536         12772 # $DB::single=1
308             }
309 13350 50       26484  
310 13350 50       23713 $pt->_reset_distance( $pt->{DistanceMeters}, $previous_pt ) unless $pt->{_noDist};
311 13350         47109 unless ($pt->{_noTime}) {
312             my $orig_time_string = $pt->{Time};
313             $pt->_reset_time( $pt->{Time}, $previous_pt ) unless $pt->{_noTime};
314             print "strange ISO time not equal to time string from TCX file for this trackpoint\n"
315 3536 50       7325 if $orig_time_string ne $pt->{_time_iso8601};
316 3536 50       6205 }
317 3536 50 33     12198 return $pt
318             }
319              
320             my $self = shift;
321             my $attr = $AUTOLOAD;
322 3536 50       11456 $attr =~ s/.*:://;
323 3536 50       6613 return unless $attr =~ /[^A-Z]/; # skip DESTROY and all-cap methods
324 3536         5200 croak "invalid attribute method: -> $attr()" unless $possible_attr{$attr};
325 3536 50       11808 $self->{$attr} = shift if @_;
326             return $self->{$attr}
327 3536 50       9420 }
328              
329 3536         9490 =head2 Object Methods for class Geo::TXC::Trackpoint::Full
330              
331             =over 4
332              
333 28975     28975   44305 =item distance_elapsed( $value, force => true/false )
334 28975         32519  
335 28975         69323 Returns the elapsed distance (in meters) of a point as initially computed when the trackpoint was created. The value is never reset unless C<< force => 1 >> is specified.
336 28975 100       106980  
337 21069 50       34406 C<force> is needed internally by L<Geo::TCX::Lap>'s C<split()> and L<Geo::TCX::Track>'s <merge()> methods. Use with caution.
338 21069 50       30703  
339 21069         56616 =back
340              
341             =cut
342              
343             my ($pt, $value) = (shift, shift);
344             my %opts = @_;
345             if (defined $value) {
346             croak "need to specify option 'force => 1' to set a value" unless $opts{force};
347             $pt->{_distance_elapsed} = sprintf '%.3f', $value
348             }
349             return $pt->{_distance_elapsed}
350             }
351              
352             =over 4
353              
354             =item Time()
355              
356             Returns the C<Time> field of a trackpoint.
357 1057     1057   1374  
358 1057         1202 =back
359 1057 100       1508  
360 2 50       6 =cut
361 2         10  
362              
363             =over 4
364 1057         2666  
365             =item time_dt ()
366              
367             =item time_datetime ()
368              
369             Return a L<DateTime> object corresponding to the time of a trackpoint.
370              
371             =back
372              
373             =cut
374              
375             # we never store a DateTime object but provide a method to create one
376 5676     5676   24285  
377             =over 4
378              
379             =item time_local( $trackpoint )
380              
381             Returns the formatted local time of the trackpoint. The local time is always represented based on the locale of the system that calls this method, not that of where the trackpoint was recorded. It is not possible to know in which time zone a trackpoint was recorded at this stage.
382              
383             =back
384              
385             =cut
386              
387              
388             =over 4
389              
390 0     0   0 =item time_add( @duration )
391 4404     4404   9784  
392             =item time_subtract( @duration )
393              
394             Perform L<DateTime> math on the timestamps of each lap's starttime and trackpoint by adding the specified time duration and return true.
395              
396             The duration can be provided as an actual L<DateTime::Duration> object or an array of arguments as per the syntax of L<DateTime>'s C<add()> or C<subtract()> methods, which expect a hash of keys such as
397             years => 3,
398             months => 5,
399             weeks => 1,
400             days => 1,
401             hours => 6,
402             minutes => 15,
403             seconds => 45,
404 2     2   7 nanoseconds => 12000,
405             end_of_month => 'limit'
406              
407             where only the relevant keys need to be specified i.e. C<< time_add( minutes > 30, seconds > 15) >>.
408              
409             =back
410              
411             =cut
412              
413             my ($pt, $dur) = shift;
414             if (ref $_[0] and $_[0]->isa('DateTime::Duration') ) {
415             $dur = shift
416             } else { $dur = DateTime::Duration->new( @_ ) }
417             my $dt = $pt->time_datetime;
418             $dt->add( $dur );
419             $pt->_set_time_keys( $dt );
420             return 1
421             }
422              
423             my ($pt, $dur) = shift;
424             if (ref $_[0] and $_[0]->isa('DateTime::Duration') ) {
425             $dur = shift
426             } else { $dur = DateTime::Duration->new( @_ ) }
427             my $dt = $pt->time_datetime;
428             $dt->subtract( $dur );
429             $pt->_set_time_keys( $dt );
430             return 1
431             }
432 436     436   1145  
433 436 100 66     2151 =over 4
434 63         90  
435 373         2064 =item time_epoch()
436 436         39691  
437 436         188094 Returns the epoch time of a point.
438 436         354823  
439 436         5257 =back
440              
441             =cut
442              
443 423     423   1165  
444 423 100 66     1992 =over 4
445 63         96  
446 360         1572 =item time_elapsed( $value, force => true/false )
447 423         33202  
448 423         176503 Returns the elapsed time of a point as initially computed when the trackpoint was created. The value is never reset unless C<< force => 1 >> is specified.
449 423         377802  
450 423         4856 C<force> is needed internally by L<Geo::TCX::Lap>'s constructor, C<split()>, and C<reverse()> methods as well as L<Geo::TCX::Track>'s <reverse()>. Use with caution.
451              
452             =back
453              
454             =cut
455              
456             my ($pt, $value) = (shift, shift);
457             my %opts = @_;
458             if (defined $value) {
459             croak "need to specify option 'force => 1' to set a value" unless $opts{force};
460             $pt->{_time_elapsed} = $value
461             }
462             return $pt->{_time_elapsed}
463 132     132   394 }
464              
465             =over 4
466              
467             =item time_duration( $datetime or $trackpoint or $string or $integer )
468              
469             Returns a L<DateTime::Duration> object containing the duration between the timestamps of two trackpoints. Consistent with the documentation for L<DateTime::Duration> the "duration is relative to the object from which I<$datetime> is subtracted". The duration will be positive if the timestamp of I<$datetime> occurs prior to the trackpoint, otherwise it will be negative.
470              
471             This method accepts four forms for the argument: a L<DateTime> object such as that returned by C<< $pt->time >>, an ISO8601 string such as that returned by C<< $pt->Time >>, a Trackpoint object, or an integer than can be interpreted as an epoch time.
472              
473             These duration objects are useful to pass to C<time_add()> or C<time_subtract>.
474              
475             =back
476              
477             =cut
478 4322     4322   5289  
479 4322         5062 my $self = shift;
480 4322 100       5850 my ($dt, $datetime);
481 53 50       175 # first arg can time DateTime or trackpoint, and epoch time, or a time string
482 53         133 if (ref $_[0]) {
483             if ( $_[0]->isa('DateTime') ) {
484             $datetime = $_[0]
485 4322         8259 } else {
486             croak 'object as argument must be either a DateTime or a Trackpoint instance'
487             unless $_[0]->isa('Geo::TCX::Trackpoint');
488             $datetime = $_[0]->time_datetime
489             }
490             } elsif ($_[0] =~ /^(\d+)$/) {
491             $datetime = DateTime->from_epoch( epoch => $1 )
492             } else {
493             $datetime = DateTime::Format::ISO8601->parse_datetime( $_[0] )
494             }
495             $dt = $self->time_datetime;
496              
497             my $dur = $dt->subtract_datetime( $datetime );
498             return $dur
499             }
500              
501             my $pt = shift;
502 5     5   11 my %opts = @_;
503 5         9  
504             my $newline = $opts{indent} ? "\n" : '';
505 5 100       43 my $tab = $opts{indent} ? ' ' : '';
    100          
506 2 50       14 my $n_tabs = $opts{n_tabs} ? $opts{n_tabs} : 4;
507 0         0  
508             my $str;
509 2 50       12 $str .= $newline . $tab x $n_tabs . '<Trackpoint>';
510             $str .= $newline . $tab x ($n_tabs + 1) . '<Time>' . $pt->Time . '</Time>';
511 2         7 if (defined $pt->LatitudeDegrees) {
512             $str .= $newline . $tab x ($n_tabs + 1) . '<Position>';
513             $str .= $newline . $tab x ($n_tabs + 2) . '<LatitudeDegrees>' . $pt->LatitudeDegrees . '</LatitudeDegrees>';
514 1         7 $str .= $newline . $tab x ($n_tabs + 2) . '<LongitudeDegrees>' . $pt->LongitudeDegrees . '</LongitudeDegrees>';
515             $str .= $newline . $tab x ($n_tabs + 1) . '</Position>';
516 2         7 }
517             $str .= $newline . $tab x ($n_tabs + 1) . '<AltitudeMeters>'. $pt->AltitudeMeters . '</AltitudeMeters>';
518 5         2402 $str .= $newline . $tab x ($n_tabs + 1) . '<DistanceMeters>'. $pt->DistanceMeters . '</DistanceMeters>';
519             if (defined $pt->HeartRateBpm) {
520 5         1811 $str .= '<HeartRateBpm><Value>'. $pt->HeartRateBpm . '</Value></HeartRateBpm>'
521 5         1400 }
522             if (defined $pt->Cadence) {
523             $str .= '<Cadence>'. $pt->Cadence . '</Cadence>'
524             }
525 1238     1238   1456 if (defined $pt->SensorState) {
526 1238         2662 $str .= '<SensorState>'. $pt->SensorState . '</SensorState>'
527             }
528 1238 100       2085 $str .= $newline . $tab x $n_tabs . '</Trackpoint>';
529 1238 100       1763 return $str
530 1238 50       1954 }
531              
532 1238         1221 # Internal methods and functions
533 1238         2146  
534 1238         2310 my ($pt, $time, $previous_pt) = @_;
535 1238 100       3275 $previous_pt = pop if ref $_[-1] and $_[-1]->isa('Geo::TCX::Trackpoint');
536 1237         2334 delete $pt->{_time_elapsed}; # by design, immutable in _set_*
537 1237         3383 $pt->_set_time_keys($time, $previous_pt);
538 1237         3941 return 1
539 1237         2559 }
540              
541 1238         3568 my ($pt, $epoch, $previous_pt) = @_;
542 1238         3727 my $dt = DateTime->from_epoch( epoch => $epoch );
543 1238 100       3069 delete $pt->{_time_elapsed};
544 1023         2454 $pt->_set_time_keys( $dt, $previous_pt );
545             return 1
546 1238 50       3070 }
547 0         0  
548             my ($pt, $distance, $previous_pt) = @_;
549 1238 50       3239 if (ref $previous_pt) {
550 0         0 croak 'second argument must be a Trackpoint object' unless $previous_pt->isa('Geo::TCX::Trackpoint')
551             }
552 1238         2248 delete $pt->{_distance_elapsed};
553 1238         3913 $pt->_set_distance_keys($distance, $previous_pt);
554             return 1
555             }
556              
557             # Expects a I<$time_string> in a format parseable by L<DateTime::Format::ISO8601>'s C<parse_datetime> constructor
558             # . sets the time-related fields for the trackpoint. Returns true.
559 3538     3538   5944 # . if the _time_elapsed key for the point is not already defined and another trackpoint object is also provided,
560 3538 100 66     15669 # e.g. the previous trackpoint, it will also set it (as number of seconds since the timestamp of that previous point)
561 3538         4848 # . allows a DateTime obj as argument instead of $time which is required by methods that need to modify time so
562 3538         7325 # that we can update the keys to be consistent with the new time e.g. time_add(), time_subtract(), _reset_time_from_epoch()
563 3538         5645  
564             my ($pt, $time, $previous_pt) = (shift, shift);
565             $previous_pt = pop if ref $_[-1] and $_[-1]->isa('Geo::TCX::Trackpoint');
566              
567 46     46   68 my $dt;
568 46         140 if ( ref( $time ) and $time->isa('DateTime') ) {
569 46         10697 $dt = $time
570 46         112 } else {
571 46         197 $pt->{Time} = $time;
572             $dt = $pt->time_datetime
573             }
574              
575 3584     3584   6994 $pt->{Time} = _time_format($dt);
576 3584 100       6695 $pt->{_time_iso8601} = _time_format($dt);
577 3496 50       8947 $pt->{_time_local} = _time_format($dt, local => 1);
578             $pt->{_time_epoch} = $dt->epoch;
579 3584         5393  
580 3584         8421 if ( ! exists $pt->{_time_elapsed} ) { # i.e. immutable here
581 3584         4842 if ( $previous_pt ) {
582             $pt->{_time_elapsed} = $pt->{_time_epoch} - $previous_pt->{_time_epoch}
583             } else { $pt->{_time_elapsed} = undef }
584             }
585             return 1
586             }
587              
588             my $dt = shift;
589             # !! TODO: check that ref is not a Garmin Object (croack that function is not a class method)
590             my %opts = @_;
591             if ($opts{'local'}) {
592 4443     4443   8246 $dt->set_formatter( $Formatter ); # see pattern in $Formatter
593 4443 100 66     16043 $dt->set_time_zone( $LocalTZ )
594             } else {
595 4443         5627 $dt->set_formatter( $formatter_xsd )
596 4443 100 66     13936 }
597 905         1921 return $dt->stringify
598             }
599 3538         4863  
600 3538         6765 # Expects a decimal-number or integer and sets the C<DistanceMeters> field for the trackpoint and returns true
601             # . if the _distance_elapsed key for the point is not already defined and another trackpoint object is also provided,
602             # e.g. the previous trackpoint, it will also set it (number of meters from that previous point)
603 4443         1435438  
604 4443         829959 my ($pt, $meters, $previous_pt) = shift;
605 4443         768194 $previous_pt = pop if ref $_[-1] and $_[-1]->isa('Geo::TCX::Trackpoint');
606 4443         849529 $meters = shift;
607              
608 4443 100       38791 my $meters_formatted;
609 3584 100       6826 $meters_formatted = sprintf("%.3f", $meters) if defined $meters;
610              
611 3496         6328 $pt->{DistanceMeters} = $meters_formatted;
  88         192  
612              
613 4443         15686 if ( ! exists $pt->{_distance_elapsed} ) { # i.e. immutable here
614             if ( $previous_pt ) {
615             my $dist_elapsed = $pt->DistanceMeters - $previous_pt->DistanceMeters;
616             $pt->{_distance_elapsed} = sprintf("%.3f", $dist_elapsed)
617 13329     13329   18415 } else { $pt->{_distance_elapsed} = $meters_formatted }
618             }
619 13329         23072 return 1
620 13329 100       25681 }
621 4443         12082  
622 4443         170243 }
623              
624 8886         20495 =head1 EXAMPLES
625              
626 13329         399955 Coming soon.
627              
628             =head1 AUTHOR
629              
630             Patrick Joly
631              
632             =head1 VERSION
633              
634 5128     5128   7823 1.01
635 5128 100 66     16779  
636 5128         7500 =head1 SEE ALSO
637              
638 5128         5707 perl(1).
639 5128 50       33918  
640             =cut
641 5128         8155  
642             1;
643 5128 100       8858  
644 3584 100       6159  
645 3496         16019 A trackpoint string looks like:
646 3496         16227  
647 88         237 <Time>2014-08-11T10:55:26Z</Time><Position><LatitudeDegrees>45.293131</LatitudeDegrees><LongitudeDegrees>-72.650505</LongitudeDegrees></Position><AltitudeMeters>368.591</AltitudeMeters><DistanceMeters>3844.748</DistanceMeters><HeartRateBpm><Value>128</Value></HeartRateBpm>