File Coverage

blib/lib/Geo/TCX/Trackpoint.pm
Criterion Covered Total %
statement 225 247 91.0
branch 83 124 66.9
condition 16 30 53.3
subroutine 37 40 92.5
pod 8 8 100.0
total 369 449 82.1


line stmt bran cond sub pod time code
1             use strict;
2 7     7   102471 use warnings;
  7         37  
  7         194  
3 7     7   46  
  7         14  
  7         348  
4             our $VERSION = '1.02';
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 7     7   3758 use Geo::Gpx::Point;
  7         25670023  
  7         427  
28 7     7   4812 use Carp qw(confess croak cluck);
  7         7813568  
  7         307  
29 7     7   74 use vars qw($AUTOLOAD %possible_attr);
  7         94  
  7         343  
30 7     7   46  
  7         14  
  7         6799  
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 4010     4010 1 11576 my $class = ref($proto) || $proto;
63 4010 50       9531 $pt_str =~ s,\</*Position\>,,g; # Lat and Long are contained in that tag, not needed
64 4010   33     12218 my $pt = {};
65 4010         21591 bless($pt, $class);
66 4010         8511  
67 4010         7068 # initialize fields/attr
68             while ($pt_str =~ m,\<([^<>]*)\>(.*?)\</([^<>]*)\>,gs) {
69             # or could simply state =~ m,\<(.*?)\>(.*?)\</.*?\>,gs)
70 4010         19903 croak 'Could not match identical attr' unless $1 eq $3;
71             croak 'field not allowed' unless $possible_attr{$1};
72 8020 50       21891 $pt->{$1} = $2
73 8020 50       18532 }
74 8020         33978 return $pt
75             }
76 4010         10809  
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 3680 return $clone
93 98         440 }
94 98 50       554  
95 98         536 =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, 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 106     106   198 croak "invalid attribute method: -> $attr()" unless $possible_attr{$attr};
122 106         157 $self->{$attr} = shift if @_;
123 106         393 return $self->{$attr}
124 106 100       712 }
125 66 50       175  
126 66 100       158 =head2 Object Methods
127 66         651  
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         7  
147 1 50       5 =over 4
148 1 50       4  
149 1         15 =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 973  
164 1 50       7 =item to_basic()
165 1         7  
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 481 }
180 3 50       13  
181 3         7 =over 4
182 3         10  
183 3         16 =item distance_to ( $trackpoint )
184 3         15  
185 3         21 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 15     15 1 431  
200 15 50 33     297 =item xml_string()
201 15         212  
202 15         3031 returns a string containing the XML representation of the object, equivalent to the string argument expected by C<new()>.
203 15         194796  
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.02';
249 0         0 our @ISA=qw(Geo::TCX::Trackpoint);
250              
251              
252             { # lexical scope for that package
253 7     7   69  
  7         19  
  7         197  
254 7     7   45 use vars qw($AUTOLOAD %possible_attr);
  7         16  
  7         354  
255              
256 7     7   51 our ($LocalTZ, $Formatter);
  7         17  
  7         246  
257 7     7   43 $LocalTZ = DateTime::TimeZone->new( name => 'local' );
  7         17  
  7         671  
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 7     7   50  
  7         17  
  7         16029  
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 3980     3980   17522  
279 3980 100       10487 # Extract the Position tag and create a basic positional trackpoint
280 3887 50       13277 my $pt;
281             if ( $chomped_str =~ s/(<Position>.*<\/Position>)//g ) {
282 3980 50       8595 $pt =$class->SUPER::new( $1 )
283 3980   33     13753 } else {
284             # $DB::single=1;
285 3980         5854 # I put a debug flag here because I want to see instances where
286 3980 50       22645 # a trackpoint does not have coordinates and see how I should address those
287 3980         11433 # 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 3980         5741 $chomped_str =~ s,\</*Value\>,,g; # HeartRateBpm value contained in that tag, not needed
293 3980 100       28682  
294 3979         11608 # 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         2  
302 1         3 # for debugging -- allow trackpoints with only coordinates but inspect them in debugger
303             $pt->{_noTime} = 1 unless defined $pt->{Time};
304 3980         24883 $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 3980         16903 # $DB::single=1
308             }
309 15032 50       35028  
310 15032 50       31129 $pt->_reset_distance( $pt->{DistanceMeters}, $previous_pt ) unless $pt->{_noDist};
311 15032         62201 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 3980 50       8869 if $orig_time_string ne $pt->{_time_iso8601};
316 3980 50       7863 }
317 3980 50 33     15257 return $pt
318             }
319              
320             my $self = shift;
321             my $attr = $AUTOLOAD;
322 3980 50       14812 $attr =~ s/.*:://;
323 3980 50       8580 return unless $attr =~ /[^A-Z]/; # skip DESTROY and all-cap methods
324 3980         6982 croak "invalid attribute method: -> $attr()" unless $possible_attr{$attr};
325 3980 50       13230 $self->{$attr} = shift if @_;
326             return $self->{$attr}
327 3980 50       11064 }
328              
329 3980         11966 =head2 Object Methods for class Geo::TXC::Trackpoint::Full
330              
331             =over 4
332              
333 19567     19567   35661 =item DistanceMeters()
334 19567         24540  
335 19567         51628 Returns the C<DistanceMeters> field of a trackpoint.
336 19567 100       101410  
337 11217 50       20589 =back
338 11217 50       18607  
339 11217         27688 =cut
340              
341              
342             =over 4
343              
344             =item distance_elapsed( $value, force => true/false )
345              
346             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.
347              
348             C<force> is needed internally by L<Geo::TCX::Lap>'s C<split()> and L<Geo::TCX::Track>'s <merge()> methods. Use with caution.
349              
350             =back
351              
352             =cut
353              
354 10728     10728   29843 my ($pt, $value) = (shift, shift);
355             my %opts = @_;
356             if (defined $value) {
357             croak "need to specify option 'force => 1' to set a value" unless $opts{force};
358             $pt->{_distance_elapsed} = sprintf '%.3f', $value
359             }
360             return $pt->{_distance_elapsed}
361             }
362              
363             =over 4
364              
365             =item Time()
366              
367             Returns the C<Time> field of a trackpoint.
368              
369 1057     1057   1791 =back
370 1057         1650  
371 1057 100       1839 =cut
372 2 50       19  
373 2         14  
374             =over 4
375              
376 1057         3020 =item time_dt ()
377              
378             =item time_datetime ()
379              
380             Return a L<DateTime> object corresponding to the time of a trackpoint.
381              
382             =back
383              
384             =cut
385              
386             # we never store a DateTime object but provide a method to create one
387              
388 6120     6120   26116 =over 4
389              
390             =item time_local( $trackpoint )
391              
392             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.
393              
394             =back
395              
396             =cut
397              
398              
399             =over 4
400              
401             =item time_add( @duration )
402 0     0   0  
403 4848     4848   9989 =item time_subtract( @duration )
404              
405             Perform L<DateTime> math on the timestamps of each lap's starttime and trackpoint by adding the specified time duration and return true.
406              
407             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
408             years => 3,
409             months => 5,
410             weeks => 1,
411             days => 1,
412             hours => 6,
413             minutes => 15,
414             seconds => 45,
415             nanoseconds => 12000,
416 2     2   9 end_of_month => 'limit'
417              
418             where only the relevant keys need to be specified i.e. C<< time_add( minutes > 30, seconds > 15) >>.
419              
420             =back
421              
422             =cut
423              
424             my ($pt, $dur) = shift;
425             if (ref $_[0] and $_[0]->isa('DateTime::Duration') ) {
426             $dur = shift
427             } else { $dur = DateTime::Duration->new( @_ ) }
428             my $dt = $pt->time_datetime;
429             $dt->add( $dur );
430             $pt->_set_time_keys( $dt );
431             return 1
432             }
433              
434             my ($pt, $dur) = shift;
435             if (ref $_[0] and $_[0]->isa('DateTime::Duration') ) {
436             $dur = shift
437             } else { $dur = DateTime::Duration->new( @_ ) }
438             my $dt = $pt->time_datetime;
439             $dt->subtract( $dur );
440             $pt->_set_time_keys( $dt );
441             return 1
442             }
443              
444 436     436   965 =over 4
445 436 100 66     1487  
446 63         108 =item time_epoch()
447 373         1320  
448 436         37386 Returns the epoch time of a point.
449 436         200248  
450 436         374935 =back
451 436         4125  
452             =cut
453              
454              
455 423     423   958 =over 4
456 423 100 66     1533  
457 63         112 =item time_elapsed( $value, force => true/false )
458 360         1238  
459 423         35743 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.
460 423         193356  
461 423         416077 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.
462 423         4224  
463             =back
464              
465             =cut
466              
467             my ($pt, $value) = (shift, shift);
468             my %opts = @_;
469             if (defined $value) {
470             croak "need to specify option 'force => 1' to set a value" unless $opts{force};
471             $pt->{_time_elapsed} = $value
472             }
473             return $pt->{_time_elapsed}
474             }
475 140     140   418  
476             =over 4
477              
478             =item time_duration( $datetime or $trackpoint or $string or $integer )
479              
480             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.
481              
482             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.
483              
484             These duration objects are useful to pass to C<time_add()> or C<time_subtract>.
485              
486             =back
487              
488             =cut
489              
490 4724     4724   7093 my $self = shift;
491 4724         6626 my ($dt, $datetime);
492 4724 100       7912 # first arg can time DateTime or trackpoint, and epoch time, or a time string
493 58 50       203 if (ref $_[0]) {
494 58         169 if ( $_[0]->isa('DateTime') ) {
495             $datetime = $_[0]
496             } else {
497 4724         10937 croak 'object as argument must be either a DateTime or a Trackpoint instance'
498             unless $_[0]->isa('Geo::TCX::Trackpoint');
499             $datetime = $_[0]->time_datetime
500             }
501             } elsif ($_[0] =~ /^(\d+)$/) {
502             $datetime = DateTime->from_epoch( epoch => $1 )
503             } else {
504             $datetime = DateTime::Format::ISO8601->parse_datetime( $_[0] )
505             }
506             $dt = $self->time_datetime;
507              
508             my $dur = $dt->subtract_datetime( $datetime );
509             return $dur
510             }
511              
512             my $pt = shift;
513             my %opts = @_;
514 5     5   12  
515 5         11 my $newline = $opts{indent} ? "\n" : '';
516             my $tab = $opts{indent} ? ' ' : '';
517 5 100       30 my $n_tabs = $opts{n_tabs} ? $opts{n_tabs} : 4;
    100          
518 2 50       17  
519 0         0 my $str;
520             $str .= $newline . $tab x $n_tabs . '<Trackpoint>';
521 2 50       11 $str .= $newline . $tab x ($n_tabs + 1) . '<Time>' . $pt->Time . '</Time>';
522             if (defined $pt->LatitudeDegrees) {
523 2         9 $str .= $newline . $tab x ($n_tabs + 1) . '<Position>';
524             $str .= $newline . $tab x ($n_tabs + 2) . '<LatitudeDegrees>' . $pt->LatitudeDegrees . '</LatitudeDegrees>';
525             $str .= $newline . $tab x ($n_tabs + 2) . '<LongitudeDegrees>' . $pt->LongitudeDegrees . '</LongitudeDegrees>';
526 1         6 $str .= $newline . $tab x ($n_tabs + 1) . '</Position>';
527             }
528 2         8 $str .= $newline . $tab x ($n_tabs + 1) . '<AltitudeMeters>'. $pt->AltitudeMeters . '</AltitudeMeters>';
529             $str .= $newline . $tab x ($n_tabs + 1) . '<DistanceMeters>'. $pt->DistanceMeters . '</DistanceMeters>';
530 5         2221 if (defined $pt->HeartRateBpm) {
531             $str .= '<HeartRateBpm><Value>'. $pt->HeartRateBpm . '</Value></HeartRateBpm>'
532 5         2019 }
533 5         1604 if (defined $pt->Cadence) {
534             $str .= '<Cadence>'. $pt->Cadence . '</Cadence>'
535             }
536             if (defined $pt->SensorState) {
537 1238     1238   1797 $str .= '<SensorState>'. $pt->SensorState . '</SensorState>'
538 1238         2710 }
539             $str .= $newline . $tab x $n_tabs . '</Trackpoint>';
540 1238 100       2486 return $str
541 1238 100       2384 }
542 1238 50       2142  
543             # Internal methods and functions
544 1238         1560  
545 1238         2500 my ($pt, $time, $previous_pt) = @_;
546 1238         2796 $previous_pt = pop if ref $_[-1] and $_[-1]->isa('Geo::TCX::Trackpoint');
547 1238 100       3819 delete $pt->{_time_elapsed}; # by design, immutable in _set_*
548 1237         2635 $pt->_set_time_keys($time, $previous_pt);
549 1237         3959 return 1
550 1237         4242 }
551 1237         3215  
552             my ($pt, $epoch, $previous_pt) = @_;
553 1238         4131 my $dt = DateTime->from_epoch( epoch => $epoch );
554 1238         3157 delete $pt->{_time_elapsed};
555 1238 100       3411 $pt->_set_time_keys( $dt, $previous_pt );
556 1023         3047 return 1
557             }
558 1238 50       3655  
559 0         0 my ($pt, $distance, $previous_pt) = @_;
560             if (ref $previous_pt) {
561 1238 50       3715 croak 'second argument must be a Trackpoint object' unless $previous_pt->isa('Geo::TCX::Trackpoint')
562 0         0 }
563             delete $pt->{_distance_elapsed};
564 1238         2545 $pt->_set_distance_keys($distance, $previous_pt);
565 1238         4115 return 1
566             }
567              
568             # Expects a I<$time_string> in a format parseable by L<DateTime::Format::ISO8601>'s C<parse_datetime> constructor
569             # . sets the time-related fields for the trackpoint. Returns true.
570             # . if the _time_elapsed key for the point is not already defined and another trackpoint object is also provided,
571 3982     3982   7664 # e.g. the previous trackpoint, it will also set it (as number of seconds since the timestamp of that previous point)
572 3982 100 66     18728 # . allows a DateTime obj as argument instead of $time which is required by methods that need to modify time so
573 3982         7335 # that we can update the keys to be consistent with the new time e.g. time_add(), time_subtract(), _reset_time_from_epoch()
574 3982         9690  
575 3982         7115 my ($pt, $time, $previous_pt) = (shift, shift);
576             $previous_pt = pop if ref $_[-1] and $_[-1]->isa('Geo::TCX::Trackpoint');
577              
578             my $dt;
579 46     46   88 if ( ref( $time ) and $time->isa('DateTime') ) {
580 46         190 $dt = $time
581 46         13121 } else {
582 46         143 $pt->{Time} = $time;
583 46         237 $dt = $pt->time_datetime
584             }
585              
586             $pt->{Time} = _time_format($dt);
587 4028     4028   8596 $pt->{_time_iso8601} = _time_format($dt);
588 4028 100       8426 $pt->{_time_local} = _time_format($dt, local => 1);
589 3933 50       11596 $pt->{_time_epoch} = $dt->epoch;
590              
591 4028         6503 if ( ! exists $pt->{_time_elapsed} ) { # i.e. immutable here
592 4028         10703 if ( $previous_pt ) {
593 4028         5676 $pt->{_time_elapsed} = $pt->{_time_epoch} - $previous_pt->{_time_epoch}
594             } else { $pt->{_time_elapsed} = undef }
595             }
596             return 1
597             }
598              
599             my $dt = shift;
600             # !! TODO: check that ref is not a Garmin Object (croack that function is not a class method)
601             my %opts = @_;
602             if ($opts{'local'}) {
603             $dt->set_formatter( $Formatter ); # see pattern in $Formatter
604 4887     4887   10177 $dt->set_time_zone( $LocalTZ )
605 4887 100 66     19093 } else {
606             $dt->set_formatter( $formatter_xsd )
607 4887         7220 }
608 4887 100 66     14658 return $dt->stringify
609 905         1529 }
610              
611 3982         6364 # Expects a decimal-number or integer and sets the C<DistanceMeters> field for the trackpoint and returns true
612 3982         8246 # . if the _distance_elapsed key for the point is not already defined and another trackpoint object is also provided,
613             # e.g. the previous trackpoint, it will also set it (number of meters from that previous point)
614              
615 4887         1829208 my ($pt, $meters, $previous_pt) = shift;
616 4887         1045268 $previous_pt = pop if ref $_[-1] and $_[-1]->isa('Geo::TCX::Trackpoint');
617 4887         1004818 $meters = shift;
618 4887         1089816  
619             my $meters_formatted;
620 4887 100       46086 $meters_formatted = sprintf("%.3f", $meters) if defined $meters;
621 4028 100       8636  
622             $pt->{DistanceMeters} = $meters_formatted;
623 3933         8991  
  95         270  
624             if ( ! exists $pt->{_distance_elapsed} ) { # i.e. immutable here
625 4887         20054 if ( $previous_pt ) {
626             my $dist_elapsed = $pt->DistanceMeters - $previous_pt->DistanceMeters;
627             $pt->{_distance_elapsed} = sprintf("%.3f", $dist_elapsed)
628             } else { $pt->{_distance_elapsed} = $meters_formatted }
629 14661     14661   23902 }
630             return 1
631 14661         28368 }
632 14661 100       33635  
633 4887         14862 }
634 4887         220850  
635             =head1 EXAMPLES
636 9774         25378  
637             Coming soon.
638 14661         514726  
639             =head1 AUTHOR
640              
641             Patrick Joly
642              
643             =head1 VERSION
644              
645             1.02
646 5572     5572   9901  
647 5572 100 66     21527 =head1 SEE ALSO
648 5572         8671  
649             perl(1).
650 5572         6948  
651 5572 50       39968 =cut
652              
653 5572         10083 1;
654              
655 5572 100       11340  
656 4028 100       7770 A trackpoint string looks like:
657 3933         8448  
658 3933         19921 <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>