Geo-TCX
view release on metacpan or search on metacpan
lib/Geo/TCX.pm view on Meta::CPAN
my $fname = $first_arg;
my $file_to_read = $first_arg;
croak 'first argument must be a filename' unless -f $fname;
if ($fname =~ /(?i:\.fit$)/) {
my ($fh, $tmp_fname) = tempfile();
_convert_fit_to_tcx( $fname, $tmp_fname );
$file_to_read = $tmp_fname;
$fname =~ s/(?i:.fit)$/.tcx/
}
$txt = do { local(@ARGV, $/) = $file_to_read; <> };
$o->set_filename($fname)
}
$txt =~ s,\r,,g; # if it's a windows file
$txt =~ s,>\s+<,><,g;
$o->{tag_creator} = $1 if $txt =~ s/(<Creator.*<\/Creator>)//;
# Activities/Activity - are as recorded by an EDGE 705 device
# Courses/Course - are as converted by an EDGE 705 device from an Activity
$o->{tag_xml_version} = $1 if $txt =~ /(<.xml version[^>]*>)/;
$o->{tag_trainingcenterdatabase} = $1 if $txt =~ /(<TrainingCenterDatabase.*<\/TrainingCenterDatabase>)/;
$o->{tag_activities} = $1 if $txt =~ /(<Activities.*<\/Activities>)/;
$o->{tag_activity} = $1 if $txt =~ /(<Activity.*<\/Activity>)/;
$o->{tag_courses} = $1 if $txt =~ /(<Courses.*<\/Courses>)/;
$o->{tag_course} = $1 if $txt =~ /(<Course(?!s).*<\/Course>)/;
# Id seems only for Activities/Activity...
if ($o->{tag_activity}) {
$o->{tag_id} = $1 if $o->{tag_activity} =~ /<Activity.*<Id>(.*)<\/Id>/;
$o->{tag_activity_type} = $1 if $o->{tag_activity} =~ /<Activity Sport="([^"]+)"/;
}
# ... and Name only for Courses/Course
if ($o->{tag_course}) {
# will pick up device name under Creator if we are not specific about the Course tag
$o->{tag_name} = $1 if $o->{tag_course} =~ /<Course.*<Name>(.*)<\/Name>/
}
$o->{tag_author} = $1 if $txt =~ /(<Author.*<\/Author>)/;
$o->_parse_author_tag if $o->{tag_author};
my @Lap;
if ( $o->{tag_activity} ) {
my $i = 0;
my $lap;
while ( $o->{tag_activity} =~ /(\<Lap StartTime=.*?\>.*?\<\/Lap\>)/g ) {
my ($lapstring, $last_point_previous_lap);
$lapstring = $1;
$last_point_previous_lap = $lap->trackpoint(-1) if $i > 0;
$lap = Geo::TCX::Lap->new($lapstring, ++$i, $last_point_previous_lap);
push @{ $o->{Laps} }, $lap
}
}
if ( $o->{tag_course} ) {
# in Courses, data is structured as <Lap>...</Lap><Lap>...</Lap><Track>...</Track><Track>...</Track>
# actually, not sure just seem like it's one long ... track, not multiple ones, which complicates things
my $xml_str = $o->{tag_course};
my (@lap_tags, @lap_endpoints, @track_tags);
if ( $xml_str =~ m,(<Lap>.*</Lap>),s ) {
my $str = $1;
@lap_tags = split(/(?s)<\/Lap>\s*<Lap>/, $str );
if (@lap_tags == 0) { push @lap_tags, $str }
}
for my $i (0 .. $#lap_tags) {
my ($end_pos, $end_pt);
if ( $lap_tags[$i] =~ m,<EndPosition>(.*)</EndPosition>,s ) {
$end_pt = Geo::TCX::Trackpoint->new( $1 );
push @lap_endpoints, $end_pt
}
# since split removed tags sometimes at ^ of string for other at $
# let's remove them all and add back
$lap_tags[$i] =~ s,</?Lap>,,g;
$lap_tags[$i] =~ s,^,<Lap>,g;
$lap_tags[$i] =~ s,$,</Lap>,g
}
my $track_str;
if ( $xml_str =~ m,(<Track>.*</Track>),s ) {
$track_str = $1;
}
my $t = Geo::TCX::Track->new( $track_str );
if (@lap_tags ==1) { $track_tags[0] = $track_str }
else {
my ($t1, $t2);
for my $i (0 .. $#lap_tags ) {
if ($i < $#lap_tags) {
($t1, $t2) = $t->split_at_point_closest_to( $lap_endpoints[$i] );
push @track_tags, $t1->xml_string;
$t = $t2
} else { push @track_tags, $t->xml_string } # ie don't split the last track portion
}
}
my $lap;
for my $i (0 .. $#lap_tags) {
my ($lapstring, $last_point_previous_lap);
$lapstring = $lap_tags[$i] . $track_tags[$i];
$last_point_previous_lap = $lap->trackpoint(-1) if $i > 0;
$lap = Geo::TCX::Lap->new($lapstring, ++$i, $last_point_previous_lap);
push @{ $o->{Laps} }, $lap
}
}
my $n = $o->laps;
die "cannot find any laps, must not be a *.tcx file or str" unless $n;
print "\nFound " . $n, ($n > 1 ? " Laps": " Lap"), "\n\n";
$o->{_txt} = $txt; # only for debugging
$o->set_wd( $opts{work_dir} || $opts{wd} );
return $o
}
=head2 Constructor Methods (object)
=over 4
=item activity_to_course( key/values )
returns a new <Geo::TCX> instance as a course, based on the current activity.
All I<key/values> are optional:
Z<> C<< lap => I<#> >>: converts lap number I<#> to a course, dropping all other laps. All laps are converted if C<lap> is omitted.
Z<> C<< course_name => I<$string> >>: the name for the course. The name will be the lap's C<StartTime> if a value is not specified.
Z<> C<< filename => I<$filename> >>: will call C<set_filename()> with this value.
Z<> C<< work_dir => I<$work_dir> >>: if omitted, it will be set to the same as that of the current object.
=back
=cut
sub activity_to_course {
my $clone = shift->clone;
my %opts = @_;
croak 'this instance is already a course' if $clone->is_course;
my $wd = $opts{work_dir} || $opts{wd} || $clone->set_wd();
my (@laps, $course);
@laps = $opts{lap} ? ($opts{lap}) : (1 .. $clone->laps);
for my $lap_i (@laps) {
my $str = $clone->save_laps( [ $lap_i ], nosave => 1, course => 1, course_name => $opts{course_name} );
my $course_i = Geo::TCX->new( \$str, work_dir => $wd );
if ( defined $course ) {
push @{ $course->{Laps} }, $course_i->lap(1)
} else { $course = $course_i }
}
$course->set_filename( $opts{filename} );
( run in 0.584 second using v1.01-cache-2.11-cpan-39bf76dae61 )