line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Geo::TCX::Track; |
2
|
7
|
|
|
7
|
|
45
|
use strict; |
|
7
|
|
|
|
|
13
|
|
|
7
|
|
|
|
|
191
|
|
3
|
7
|
|
|
7
|
|
35
|
use warnings; |
|
7
|
|
|
|
|
12
|
|
|
7
|
|
|
|
|
284
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
our $VERSION = '1.03'; |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=encoding utf-8 |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=head1 NAME |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
Geo::TCX::Track - Class to store and edit a TCX track and its trackpoints |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=head1 SYNOPSIS |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
use Geo::TCX::Track; |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
=head1 DESCRIPTION |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
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. |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
L<Geo::TCX::Track> provides a data structure for tracks in TCX files as well as methods to store, edit and obtain information from its trackpoints. |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=cut |
24
|
|
|
|
|
|
|
|
25
|
7
|
|
|
7
|
|
3370
|
use Geo::TCX::Trackpoint; |
|
7
|
|
|
|
|
21
|
|
|
7
|
|
|
|
|
359
|
|
26
|
7
|
|
|
7
|
|
56
|
use Carp qw(confess croak cluck); |
|
7
|
|
|
|
|
13
|
|
|
7
|
|
|
|
|
369
|
|
27
|
7
|
|
|
7
|
|
43
|
use Data::Dumper; |
|
7
|
|
|
|
|
20
|
|
|
7
|
|
|
|
|
386
|
|
28
|
7
|
|
|
7
|
|
40
|
use overload '+' => \&merge; |
|
7
|
|
|
|
|
13
|
|
|
7
|
|
|
|
|
60
|
|
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
=head2 Constructor Methods (class) |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
=over 4 |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=item new( xml_string ) |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
takes an I<xml_string> in the form recorded by Garmin devices (and its TCX format) and returns a track object composed of various L<Geo::TCX::Trackpoint> objects. |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
The string argument is expected to be flat i.e. no line breaks as per the example below. |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
$xml_string = '<Track><Trackpoint><Time>2014-08-11T10:25:23Z</Time><Position><LatitudeDegrees>45.305054</LatitudeDegrees><LongitudeDegrees>-72.637287</LongitudeDegrees></Position><AltitudeMeters>210.963</AltitudeMeters><DistanceMeters>5.704</DistanceMeters><HeartRateBpm><Value>75</Value></HeartRateBpm></Trackpoint></Track>'; |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
$t = Geo::TCX::Track->new( $xml_string ); |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
=back |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
=cut |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub new { |
49
|
66
|
|
|
66
|
1
|
425
|
my ($proto, $track_str, $previous_pt) = (shift, shift, shift); |
50
|
66
|
100
|
|
|
|
310
|
if (ref $previous_pt) { |
51
|
32
|
50
|
|
|
|
165
|
croak 'second argument must be a Trackpoint object' unless $previous_pt->isa('Geo::TCX::Trackpoint') |
52
|
|
|
|
|
|
|
} |
53
|
66
|
50
|
|
|
|
263
|
croak 'new() takes only one or two arguments' if @_; |
54
|
66
|
|
33
|
|
|
386
|
my $class = ref($proto) || $proto; |
55
|
66
|
|
|
|
|
173
|
my ($chomped_str, $t); |
56
|
66
|
50
|
|
|
|
1014
|
if ( $track_str =~ m,\s*^\<Track\>(.*)\</Track\>\s*$,gs ) { |
57
|
66
|
|
|
|
|
1252
|
$chomped_str = $1 |
58
|
0
|
|
|
|
|
0
|
} else { croak 'not a proper track string' } |
59
|
|
|
|
|
|
|
|
60
|
66
|
|
|
|
|
198
|
$t = {}; |
61
|
66
|
|
|
|
|
288
|
$t->{Points} = []; |
62
|
|
|
|
|
|
|
|
63
|
66
|
|
|
|
|
489
|
while ($chomped_str=~ m,(\<Trackpoint\>.*?\</Trackpoint\>),gs) { |
64
|
3921
|
|
|
|
|
12483
|
my $pt = Geo::TCX::Trackpoint::Full->new($1, $previous_pt); |
65
|
3921
|
|
|
|
|
6929
|
$previous_pt = $pt; |
66
|
3921
|
|
|
|
|
5214
|
push @{$t->{Points}}, $pt |
|
3921
|
|
|
|
|
30886
|
|
67
|
|
|
|
|
|
|
} |
68
|
66
|
|
|
|
|
277
|
bless($t, $class); |
69
|
66
|
|
|
|
|
425
|
return $t |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
=head2 Constructor Methods (object) |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=over 4 |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
=item merge( $track, as_is => boolean, speed => value ) |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
Returns a new merged with the track specified in I<$track>. |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
$merged = $track1->merge( $track2 ); |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
Adjustments for the C<DistanceMeters> and C<Time> fields of each trackpoint in the track are made unless C<as_is> is set to true. |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
If a I<value> is passed to field C<speed>, that value will be used to ajust the time elapsed between the first point of the I<$track> and the last point of the track to be merged with. Otherwise the speed will be estimated based on the total distance and time elapsed of all the trackpoints in the I<$track>. C<speed> has not effect if C<as_is> is true. |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=back |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
=cut |
89
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
sub merge { |
91
|
3
|
|
|
3
|
1
|
19
|
my ($x, $y) = (shift, shift); |
92
|
3
|
50
|
|
|
|
23
|
croak 'both operands must be Track objects' unless $y->isa('Geo::TCX::Track'); |
93
|
3
|
|
|
|
|
14
|
$x = $x->clone; |
94
|
3
|
|
|
|
|
32
|
$y = $y->clone; |
95
|
3
|
|
|
|
|
17
|
my %opts = @_; # option are as_is => boole and speed => value |
96
|
|
|
|
|
|
|
|
97
|
3
|
100
|
|
|
|
15
|
unless ($opts{as_is}) { |
98
|
2
|
|
50
|
|
|
16
|
$opts{tolerance} ||= 50; |
99
|
|
|
|
|
|
|
|
100
|
2
|
|
|
|
|
5
|
my ($gap, $msg); |
101
|
2
|
|
|
|
|
11
|
$gap = $x->trackpoint(-1)->distance_to( $y->trackpoint(1) ); |
102
|
|
|
|
|
|
|
$msg = 'distance between the two tracks to merge is ' . $gap . ' meters, which ' |
103
|
2
|
|
|
|
|
48
|
. 'is larger than the tolerance of ' . $opts{tolerance} . ' meters'; |
104
|
2
|
50
|
|
|
|
13
|
croak $msg if $gap > $opts{tolerance}; |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# |
107
|
|
|
|
|
|
|
# Distance: adjust DistanceMeters of all trackpoints, elapsed of just the 1st one |
108
|
2
|
|
|
|
|
6
|
my $dist_to_add; |
109
|
2
|
|
|
|
|
9
|
$dist_to_add = $x->trackpoint(-1)->DistanceMeters + $gap - $y->trackpoint(1)->distance_elapsed; |
110
|
|
|
|
|
|
|
|
111
|
2
|
|
|
|
|
13
|
$y->distance_net; |
112
|
2
|
|
|
|
|
8
|
$y->distance_add( $dist_to_add ); |
113
|
|
|
|
|
|
|
|
114
|
2
|
|
|
|
|
7
|
$y->trackpoint(1)->distance_elapsed( $gap, force => 1); |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# |
117
|
|
|
|
|
|
|
# Time: adjust Time of all trackpoints, elapsed of just the 1st one |
118
|
|
|
|
|
|
|
|
119
|
2
|
|
|
|
|
6
|
my ($duration, $speed, $elapsed_t); |
120
|
2
|
|
|
|
|
8
|
$duration = $y->trackpoint(1)->time_duration( $x->trackpoint(-1) ); |
121
|
2
|
100
|
|
|
|
16
|
$speed = $opts{speed} ? $opts{speed} : $y->_speed_meters_per_second; |
122
|
2
|
|
|
|
|
11
|
$elapsed_t = sprintf '%.0f', $gap / $speed; |
123
|
|
|
|
|
|
|
|
124
|
2
|
|
|
|
|
12
|
$y->time_subtract( $duration ); |
125
|
2
|
|
|
|
|
14
|
$y->time_add( DateTime::Duration->new( seconds => $elapsed_t )); |
126
|
|
|
|
|
|
|
|
127
|
2
|
|
|
|
|
16
|
$y->trackpoint(1)->time_elapsed($elapsed_t, force => 1); |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
# my $epoch_gap = $x->trackpoint(-1)->time_epoch + $delay; |
131
|
|
|
|
|
|
|
# my $delta_epoch = $epoch_gap - $y->trackpoint(1)->time_epoch; |
132
|
|
|
|
|
|
|
# |
133
|
|
|
|
|
|
|
# my $delta_dist = $x->trackpoint(1)->DistanceMeters; |
134
|
|
|
|
|
|
|
# adjust DistanceMeters of x points, netting to 0 at point 1 |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
# now applying both the distance netting and delta_epoch to each y point |
137
|
|
|
|
|
|
|
# for my $pt (@{$y->{Points}}) { |
138
|
|
|
|
|
|
|
# my $epoch = $pt->time_epoch; |
139
|
|
|
|
|
|
|
# $epoch += $delta_epoch; |
140
|
|
|
|
|
|
|
# $pt->time_epoch( $epoch ); |
141
|
|
|
|
|
|
|
# $pt->DistanceMeters( $pt->DistanceMeters - $delta_dist ); |
142
|
|
|
|
|
|
|
# push @{$x->{Points}}, $pt |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
|
145
|
3
|
|
|
|
|
8
|
my @points_to_merge = @{$y->{Points}}; |
|
3
|
|
|
|
|
23
|
|
146
|
3
|
|
|
|
|
10
|
for my $pt (@points_to_merge) { |
147
|
64
|
|
|
|
|
75
|
push @{$x->{Points}}, $pt |
|
64
|
|
|
|
|
112
|
|
148
|
|
|
|
|
|
|
} |
149
|
3
|
|
|
|
|
28
|
return $x |
150
|
|
|
|
|
|
|
} |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=over 4 |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=item split( # ) |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
Returns a 2-element array of C<Geo::TCX::Track> objects with the first consisting of the track up to and including point number I<#> and the second consisting of the all trackpoints after that point. |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
($track1, $track2) = $merged->split( 45 ); |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
Will raise exception unless called in list context. |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=back |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=cut |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
sub split { |
168
|
10
|
|
|
10
|
1
|
2863
|
my ($t, $pt_no) = @_; |
169
|
10
|
50
|
|
|
|
35
|
croak 'split() expects to be called in list context' unless wantarray; |
170
|
10
|
|
|
|
|
42
|
my $n_pts = $t->trackpoints; |
171
|
10
|
|
|
|
|
47
|
my ($t1, $t2) = ($t->clone, $t->clone); |
172
|
10
|
|
|
|
|
53
|
my @slice1 = @ { $t1->{Points} } [0 .. $pt_no - 1]; |
|
10
|
|
|
|
|
157
|
|
173
|
10
|
|
|
|
|
42
|
my @slice2 = @ { $t1->{Points} } [$pt_no .. $n_pts- 1]; |
|
10
|
|
|
|
|
73
|
|
174
|
10
|
|
|
|
|
54
|
$t1->{Points} = \@slice1; |
175
|
10
|
|
|
|
|
127
|
$t2->{Points} = \@slice2; |
176
|
10
|
|
|
|
|
80
|
return $t1, $t2 |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
# keep undocumented for now, serves little purpose unless Lap.pm would want to call it directly, which it does not at this time. |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# =over 4 |
182
|
|
|
|
|
|
|
# |
183
|
|
|
|
|
|
|
# =item split_at_point_closest_to( $point or $trackpoint or $coord_str ) |
184
|
|
|
|
|
|
|
# |
185
|
|
|
|
|
|
|
# Equivalent to C<split()> but splits at the trackpoint that lies closest to a given L<Geo::Gpx::Point>, L<Geo::TCX::Trackpoint>, or a string that can be interpreted as coordinates by C<< Geo::Gpx::Point->flex_coordinates >>. |
186
|
|
|
|
|
|
|
# |
187
|
|
|
|
|
|
|
# =back |
188
|
|
|
|
|
|
|
# |
189
|
|
|
|
|
|
|
# =cut |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
sub split_at_point_closest_to { |
192
|
2
|
|
|
2
|
0
|
127
|
my ($t, $to_pt) = (shift, shift); |
193
|
2
|
50
|
|
|
|
11
|
croak 'split() expects to be called in list context' unless wantarray; |
194
|
2
|
50
|
33
|
|
|
17
|
croak 'split_at_point_closest_to() expects a single argument' if ! defined $to_pt or @_; |
195
|
|
|
|
|
|
|
# can leverage most of the checks that will be done by point_closest_to |
196
|
2
|
50
|
|
|
|
22
|
$to_pt = Geo::Gpx::Point->flex_coordinates( \$to_pt ) unless ref $to_pt; |
197
|
2
|
|
|
|
|
192
|
my ($closest_pt, $min_dist, $pt_no) = $t->point_closest_to( $to_pt ); |
198
|
|
|
|
|
|
|
# here we can print some info about the original track and where it will be split |
199
|
2
|
|
|
|
|
50
|
my ($t1, $t2) = $t->split( $pt_no ); |
200
|
2
|
|
|
|
|
28
|
return $t1, $t2 |
201
|
|
|
|
|
|
|
} |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
=over 4 |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=item reverse() |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
Returns a clone of a track with the order of the trackpoints reversed. |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
$reversed = $track->reverse; |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=back |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
=cut |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
sub reverse { |
216
|
2
|
|
|
2
|
1
|
11
|
my $orig_t = shift; |
217
|
2
|
|
|
|
|
9
|
my $t = $orig_t->clone; |
218
|
2
|
|
|
|
|
15
|
my $n_points = $t->trackpoints; |
219
|
2
|
|
|
|
|
23
|
$t->{Points} = []; |
220
|
2
|
|
|
|
|
15
|
my ($previous_pt, $previous_pt_orig); |
221
|
|
|
|
|
|
|
|
222
|
2
|
|
|
|
|
10
|
for my $i (1 .. $n_points) { |
223
|
48
|
|
|
|
|
176
|
my $pt = $orig_t->trackpoint($n_points - $i + 1)->clone; |
224
|
|
|
|
|
|
|
|
225
|
48
|
100
|
|
|
|
142
|
if ($i == 1) { |
226
|
2
|
|
|
|
|
29
|
$pt->_reset_distance( 0 ); |
227
|
2
|
|
|
|
|
8
|
$pt->_reset_time( $orig_t->trackpoint(1)->Time ) |
228
|
|
|
|
|
|
|
} else { |
229
|
46
|
|
|
|
|
143
|
$pt->_reset_distance( $previous_pt->DistanceMeters + $previous_pt_orig->distance_elapsed, $previous_pt ); |
230
|
46
|
|
|
|
|
131
|
$pt->_reset_time_from_epoch( $previous_pt->time_epoch + $previous_pt_orig->time_elapsed, $previous_pt) |
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
|
233
|
48
|
|
|
|
|
94
|
$previous_pt = $pt; |
234
|
48
|
|
|
|
|
148
|
$previous_pt_orig = $orig_t->trackpoint($n_points - $i + 1)->clone; |
235
|
|
|
|
|
|
|
# need copy of the original previous pt bcs elapsed fields of $pt got updated above |
236
|
48
|
|
|
|
|
105
|
push @{$t->{Points}}, $pt |
|
48
|
|
|
|
|
194
|
|
237
|
|
|
|
|
|
|
} |
238
|
2
|
|
|
|
|
13
|
return $t |
239
|
|
|
|
|
|
|
} |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=over 4 |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
=item clone() |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
Returns a deep copy of a C<Geo::TCX::Track> instance. |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
$c = $track->clone; |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=back |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=cut |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
sub clone { |
254
|
50
|
|
|
50
|
1
|
113
|
my $clone; |
255
|
50
|
|
|
|
|
483
|
eval(Data::Dumper->Dump([ shift ], ['$clone'])); |
256
|
50
|
50
|
|
|
|
681
|
confess $@ if $@; |
257
|
50
|
|
|
|
|
249
|
return $clone |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
=head2 Object Methods |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
=over 4 |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
=item trackpoint( # ) |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
returns the trackpoint object corresponding to trackpoint number I<#> for the track. |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
I<#> is 1-indexed but C<-1>, C<-2>, …, still refer to the last, second to last, …, points respectively. |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
=back |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
=cut |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
sub trackpoint { |
275
|
6886
|
|
|
6886
|
1
|
11328
|
my ($t, $point_i) = (shift, shift); |
276
|
6886
|
50
|
|
|
|
12393
|
croak 'trackpoints are 1-indexed, point 0 does not exist' if $point_i eq 0; |
277
|
6886
|
50
|
33
|
|
|
18660
|
croak 'requires a single integer as argument' if ! $point_i or @_; |
278
|
6886
|
100
|
|
|
|
11572
|
$point_i-- if $point_i > 0; # 1-indexed but want -1 to still refer to last |
279
|
6886
|
|
|
|
|
17164
|
return $t->{Points}[$point_i] |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
=over 4 |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
=item trackpoints( qw/ # # ... / ) |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
returns an array of L<Geo::TCX::Trackpoint> objects for the number of points specified in list if specified, or all trackpoints if called without arguments. |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
=back |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
=cut |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
sub trackpoints { |
293
|
171
|
|
|
171
|
1
|
411
|
my ($t, @point_list) = @_; |
294
|
171
|
|
|
|
|
378
|
my $points = $t->{Points}; |
295
|
171
|
|
|
|
|
281
|
my @points; |
296
|
171
|
100
|
|
|
|
413
|
if (@point_list) { |
297
|
2
|
|
|
|
|
7
|
map --$_, @point_list; # decrement to get array indices |
298
|
2
|
|
|
|
|
7
|
@points = @$points[@point_list]; |
299
|
169
|
|
|
|
|
1197
|
} else { @points = @$points } |
300
|
|
|
|
|
|
|
return @points |
301
|
171
|
|
|
|
|
903
|
} |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
=over 4 |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
=item distance_add( $meters ) |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
=item distance_subtract( $meters ) |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
=item distance_net() |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
Add or subtract to the DistanceMeters field of all points in a Track. Does not impact any other fields of trackpoints. Return true. |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
C<distance_net> is equivalent to C<< $t->distance_subtract( $t->trackpoint(1)->DistanceMeters - $t->trackpoint(1)->distance_elapsed ) >>. |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
=back |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
=cut |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
sub distance_add { |
320
|
22
|
|
|
22
|
1
|
72
|
my ($t, $meters) = (shift, shift); |
321
|
22
|
|
|
|
|
47
|
for my $i (0 .. $#{$t->{Points}}) { |
|
22
|
|
|
|
|
101
|
|
322
|
1544
|
|
|
|
|
2267
|
my $tp = $t->{Points}[$i]; |
323
|
1544
|
|
|
|
|
2862
|
$tp->_set_distance_keys( $tp->DistanceMeters + $meters ) |
324
|
|
|
|
|
|
|
} |
325
|
22
|
|
|
|
|
53
|
return 1 |
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
sub distance_subtract { |
329
|
19
|
|
|
19
|
1
|
63
|
my ($t, $meters) = (shift, shift); |
330
|
19
|
|
|
|
|
113
|
$t->distance_add( - $meters ); |
331
|
19
|
|
|
|
|
39
|
return 1 |
332
|
|
|
|
|
|
|
} |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
sub distance_net { |
335
|
18
|
|
|
18
|
1
|
51
|
my $t = shift; |
336
|
18
|
|
|
|
|
77
|
my $tp1 = $t->trackpoint(1); |
337
|
18
|
|
|
|
|
87
|
$t->distance_subtract( $tp1->DistanceMeters - $tp1->distance_elapsed ); |
338
|
18
|
|
|
|
|
56
|
return 1 |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
=over 4 |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
=item time_add( @duration ) |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
=item time_subtract( @duration ) |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
Perform L<DateTime> math on the timestamps of each trackpoint in the track by adding or subtracting the specified duration. Return true. |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
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. See the pod for C<< Geo::TCX::Trackpoint->time_add() >>. |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
=back |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
=cut |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
sub time_add { |
356
|
9
|
|
|
9
|
1
|
124
|
my $t = shift; |
357
|
9
|
100
|
66
|
|
|
112
|
if (ref $_[0] and $_[0]->isa('DateTime::Duration') ) { |
358
|
3
|
|
|
|
|
11
|
my $dur = shift; |
359
|
3
|
|
|
|
|
7
|
$t->{Points}[$_]->time_add($dur) for (0 .. $#{$t->{Points}}) |
|
3
|
|
|
|
|
30
|
|
360
|
|
|
|
|
|
|
} else { |
361
|
6
|
|
|
|
|
31
|
my @dur= @_; |
362
|
6
|
|
|
|
|
20
|
$t->{Points}[$_]->time_add(@dur) for (0 .. $#{$t->{Points}}) |
|
6
|
|
|
|
|
46
|
|
363
|
|
|
|
|
|
|
} |
364
|
9
|
|
|
|
|
42
|
return 1 |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
sub time_subtract { |
368
|
7
|
|
|
7
|
1
|
19
|
my $t = shift; |
369
|
7
|
100
|
66
|
|
|
60
|
if (ref $_[0] and $_[0]->isa('DateTime::Duration') ) { |
370
|
3
|
|
|
|
|
9
|
my $dur = shift; |
371
|
3
|
|
|
|
|
7
|
$t->{Points}[$_]->time_subtract($dur) for (0 .. $#{$t->{Points}}) |
|
3
|
|
|
|
|
26
|
|
372
|
|
|
|
|
|
|
} else { |
373
|
4
|
|
|
|
|
13
|
my @dur= @_; |
374
|
4
|
|
|
|
|
11
|
$t->{Points}[$_]->time_subtract(@dur) for (0 .. $#{$t->{Points}}) |
|
4
|
|
|
|
|
34
|
|
375
|
|
|
|
|
|
|
} |
376
|
7
|
|
|
|
|
38
|
return 1 |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
=over 4 |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
=item point_closest_to( $point or $trackpoint ) |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
Takes any L<Geo::Gpx::Point> or L<Geo::TCX::Trackpoint> and returns the trackpoint that is closest to it on the track. |
384
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
If called in list context, returns a three element array consisting of the trackpoint, the distance from the coordinate to the trackpoint (in meters), and the point number of that trackpoint in the track. |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
=back |
388
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
=cut |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
# ::Lap calls it by inheritance from Geo::TCX's split_at_point_closest_to() |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
sub point_closest_to { |
394
|
4
|
|
|
4
|
1
|
25
|
my ($t, $to_pt) = (shift, shift); |
395
|
4
|
50
|
|
|
|
18
|
croak 'closest_to() expects a single argument' if @_; |
396
|
4
|
|
|
|
|
14
|
my $class = ref( $to_pt ); |
397
|
4
|
50
|
33
|
|
|
66
|
unless ($class->isa('Geo::TCX::Trackpoint') or $class->isa('Geo::Gpx::Point')) { |
398
|
0
|
|
|
|
|
0
|
croak 'point_closest_to() expects a Geo::TCX::Trackpoint of Geo::Gpx::Point as argument' |
399
|
|
|
|
|
|
|
} |
400
|
|
|
|
|
|
|
|
401
|
4
|
|
|
|
|
24
|
my $gc = $to_pt->to_geocalc; |
402
|
4
|
|
|
|
|
1134
|
my ($closest_pt, $min_dist, $pt_no); |
403
|
4
|
|
|
|
|
13
|
for (0 .. $#{$t->{Points}}) { |
|
4
|
|
|
|
|
38
|
|
404
|
250
|
|
|
|
|
759
|
my $pt = $t->{Points}[$_]; |
405
|
250
|
|
|
|
|
1682
|
my $lat = $pt->LatitudeDegrees; |
406
|
250
|
|
|
|
|
1051
|
my $lon = $pt->LongitudeDegrees; |
407
|
250
|
100
|
66
|
|
|
1276
|
if (!$lat or !$lon) { |
408
|
1
|
|
|
|
|
339
|
print "point number ", ($_ + 1), " doesn't have coordinates\n"; |
409
|
|
|
|
|
|
|
next |
410
|
1
|
|
|
|
|
7
|
} |
411
|
249
|
|
|
|
|
1215
|
my $distance = $gc->distance_to({ lat => $lat, lon => $lon }); |
412
|
249
|
|
66
|
|
|
1182584
|
$min_dist ||= $distance; # the first iteration |
413
|
249
|
|
66
|
|
|
754
|
$closest_pt ||= $pt; # the first iteration |
414
|
249
|
100
|
|
|
|
800
|
if ($distance < $min_dist) { |
415
|
125
|
|
|
|
|
209
|
$closest_pt = $pt; |
416
|
125
|
|
|
|
|
178
|
$min_dist = $distance; |
417
|
125
|
|
|
|
|
291
|
$pt_no = $_ + 1 |
418
|
|
|
|
|
|
|
} |
419
|
|
|
|
|
|
|
} |
420
|
4
|
50
|
|
|
|
134
|
return ($closest_pt, $min_dist, $pt_no) if wantarray; |
421
|
0
|
|
|
|
|
0
|
return $closest_pt |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
=over 4 |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
=item xml_string( # ) |
427
|
|
|
|
|
|
|
|
428
|
|
|
|
|
|
|
returns a string containing the XML representation of the object, equivalent to the string argument expected by C<new()>. |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
=back |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
=cut |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
sub xml_string { |
435
|
21
|
|
|
21
|
1
|
54
|
my $t = shift; |
436
|
21
|
|
|
|
|
104
|
my %opts = @_; |
437
|
|
|
|
|
|
|
|
438
|
21
|
100
|
|
|
|
95
|
my $newline = $opts{indent} ? "\n" : ''; |
439
|
21
|
100
|
|
|
|
82
|
my $tab = $opts{indent} ? ' ' : ''; |
440
|
21
|
100
|
|
|
|
80
|
my $n_tabs = $opts{n_tabs} ? $opts{n_tabs} : 3; |
441
|
|
|
|
|
|
|
|
442
|
21
|
|
|
|
|
72
|
my $str .= $newline . $tab x $n_tabs . '<Track>'; |
443
|
|
|
|
|
|
|
# here, create a accessor that lists how many points there are in the track and do for my $i (1.. # of trackpoints) |
444
|
21
|
|
|
|
|
41
|
for my $pt (@{$t->{Points}}) { |
|
21
|
|
|
|
|
76
|
|
445
|
|
|
|
|
|
|
# looks like I coded this to ignore points without a Position as point 2014-08-11T10:25:40Z |
446
|
|
|
|
|
|
|
# look into this |
447
|
|
|
|
|
|
|
# next unless ($pt->LatitudeDegrees); |
448
|
1238
|
|
|
|
|
3512
|
$str .= $pt->xml_string( indent => $opts{indent}, n_tabs => ($n_tabs + 1) ) |
449
|
|
|
|
|
|
|
} |
450
|
21
|
|
|
|
|
66
|
$str .= $newline . $tab x $n_tabs . '</Track>'; |
451
|
21
|
|
|
|
|
399
|
return $str |
452
|
|
|
|
|
|
|
} |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
=over 4 |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
=item summ() |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
For debugging purposes, summarizes the fields of the track by printing them to screen. Returns true. |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
=back |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
=cut |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
sub summ { |
465
|
0
|
|
|
0
|
1
|
0
|
my $t = shift; |
466
|
0
|
0
|
|
|
|
0
|
croak 'summ() expects no arguments' if @_; |
467
|
0
|
|
|
|
|
0
|
my %fields; |
468
|
0
|
|
|
|
|
0
|
foreach my $key (keys %{$t}) { |
|
0
|
|
|
|
|
0
|
|
469
|
0
|
|
|
|
|
0
|
print "$key: ", $t->{$key}, "\n" |
470
|
|
|
|
|
|
|
} |
471
|
0
|
|
|
|
|
0
|
return 1 |
472
|
|
|
|
|
|
|
} |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
=head2 Overloaded Methods |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
=over 4 |
477
|
|
|
|
|
|
|
|
478
|
|
|
|
|
|
|
=item + |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
merge two tracks by calling C<$track = $track1 + $track2>. |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
=back |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
=cut |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
# |
487
|
|
|
|
|
|
|
# internal methods |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
sub _speed_meters_per_second { |
490
|
1
|
|
|
1
|
|
3
|
my $t = shift; |
491
|
1
|
|
|
|
|
2
|
my ($distance, $speed); |
492
|
1
|
|
|
|
|
4
|
$distance = $t->trackpoint(-1)->DistanceMeters - $t->trackpoint(1)->DistanceMeters + $t->trackpoint(1)->distance_elapsed; |
493
|
1
|
|
|
|
|
5
|
$speed = $distance / $t->_totalseconds; |
494
|
1
|
|
|
|
|
2
|
return $speed |
495
|
|
|
|
|
|
|
} |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
sub _totalseconds { |
498
|
1
|
|
|
1
|
|
3
|
my ($t, $totalseconds) = (shift, 0); |
499
|
1
|
|
|
|
|
4
|
$totalseconds += $t->trackpoint($_)->time_elapsed for (1 .. $t->trackpoints); |
500
|
1
|
|
|
|
|
3
|
return $totalseconds |
501
|
|
|
|
|
|
|
} |
502
|
|
|
|
|
|
|
|
503
|
|
|
|
|
|
|
=head1 EXAMPLES |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
Coming soon. |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
=head1 AUTHOR |
508
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
Patrick Joly |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
=head1 VERSION |
512
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
1.03 |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
=head1 SEE ALSO |
516
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
perl(1). |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
=cut |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
1; |
522
|
|
|
|
|
|
|
|