#!/usr/bin/perl use strict; use warnings; my($app_svnid) = '$HeadURL$ $LastChangedRevision$'; ## no critic (RequireInterpolationOfMetachars) use lib substr(`ade-config ade_share_prefix`,0,-1) . '/include'; ## no critic (ProhibitBacktickOperators) use ADE; use Getopt::Long qw(:config no_ignore_case); use Geo::Gpx; use Fatal qw( close closedir umask ); # obviate checking close()'s return code my(%gpxsplit_chunk_defined_errors) = ( gpxsplit_chunk_err_misc => { fmt => '%s' }, ); # Options my ($opt_split_mode, $opt_split_mode_parameters_ref, $opt_suffixes_ref, $opt_routepoint_renumber_flag); my ($routepoint_index); sub gpxsplit_chunk { my($errstack_ref) = @_; my($rc, $in_handle); # Register application-specific errors ADE::register_error_types(\%gpxsplit_chunk_defined_errors); # Defaults for options $opt_split_mode = undef; $opt_suffixes_ref = [ map { sprintf '%02d', $_ } ( 0..99 ) ]; $opt_routepoint_renumber_flag = 0; # Register options if (($rc=ADE::register_options($errstack_ref, '', 'count:i,regex:s,suffixes:s,renumber', 'main::gpxsplit_chunk_opt_handler_%s')) != $ADE::OK) { return($rc); } # Register handler functions if (($rc=ADE::set_callbacks($errstack_ref, \&gpxsplit_chunk_usage_help, \&gpxsplit_chunk_version, \&gpxsplit_chunk_paths)) != $ADE::OK) { return($rc); } # Process options ADE::debug($errstack_ref, 10, 'gpxsplit_chunk: processing options ...'); if (($rc=ADE::process_options($errstack_ref)) != $ADE::OK) { return($rc); } # Sanity checks and derivations if (not defined $opt_split_mode) { ADE::show_bad_usage($errstack_ref); } elsif ($opt_split_mode eq 'count') { if (${$opt_split_mode_parameters_ref}[0] < 2) { ADE::error($errstack_ref, 'gpxsplit_chunk_err_misc', "${$opt_split_mode_parameters_ref}[0]: invalid split count (hint: must be at least 2)"); return($ADE::FAIL); } } elsif ($opt_split_mode eq 'regex') { # Check regex is valid } $routepoint_index = 0; # Guts start here if ($#ARGV+1 == 0) { if (($rc=process_input_file($errstack_ref, undef, $opt_split_mode, $opt_split_mode_parameters_ref, $opt_suffixes_ref)) != $ADE::OK) { return($rc); } } else { foreach my $file (@ARGV) { if (($rc=process_input_file($errstack_ref, $file, $opt_split_mode, $opt_split_mode_parameters_ref, $opt_suffixes_ref)) != $ADE::OK) { return($rc); } } } return($ADE::OK); } sub gpxsplit_chunk_opt_handler_count { my($errstack_ref, $split_mode_parameter_string) = @_; $opt_split_mode = 'count'; $opt_split_mode_parameters_ref = [ int $split_mode_parameter_string ]; return $ADE::OK; } sub gpxsplit_chunk_opt_handler_regex { my($errstack_ref, $split_mode_parameter_string) = @_; $opt_split_mode = 'regex'; $opt_split_mode_parameters_ref = [ $split_mode_parameter_string ]; return $ADE::OK; } sub gpxsplit_chunk_opt_handler_suffixes { my($errstack_ref, $suffixes_string) = @_; $opt_suffixes_ref = [ split /,/, $suffixes_string ]; return $ADE::OK; } sub gpxsplit_chunk_opt_handler_renumber { my($errstack_ref) = @_; $opt_routepoint_renumber_flag = 1; return $ADE::OK; } sub gpxsplit_chunk_version { my($errstack_ref, $version_text_ref) = @_; return(ADE::extract_version($errstack_ref, $app_svnid, $version_text_ref)); } sub gpxsplit_chunk_paths { my($errstack_ref, $pathlist_text_ref) = @_; my($rc); ${$pathlist_text_ref} = undef; return($ADE::OK); } sub gpxsplit_chunk_usage_help { my($errstack_ref, $usage_text_short_ref, $usage_text_long_ref) = @_; ${$usage_text_short_ref} = "[ ]\n"; ${$usage_text_long_ref} = " --count= split every routepoints\n" . " --regex= split at routepoints like \n" . " --suffixes=,... append suffixes to routes\n" . " --renumber renumber routepoints\n"; return($ADE::OK); } sub process_input_file { my($errstack_ref, $file, $split_mode, $parameter_list_ref, $suffixes_ref) = @_; my($in_gpx, $in_count, $in_name, $out_file_index, $in_handle); my($rc); if (not defined $file) { $in_handle = \*STDIN; } else { # Due to BTS#1056346, here we put the *name* of an *unopened* file into $in_handle and # leave it to Geo::Gpx->new() to open it. This is to avoid passing an opened filehandle # to Geo::Gpx->new(), which it would mishandle. $in_handle = $file; # And since we did that, we don't need to open the file so this is commented out. #if (!open $in_handle, '<', $file) { # ADE::error($errstack_ref, 'gpxsplit_chunk_err_misc', "$file: couldn't open"); # return($ADE::FAIL); #} } ADE::info($errstack_ref, sprintf '%s: processing file ...', defined $file ? $file : 'stdin'); $in_gpx = Geo::Gpx->new(input => $in_handle); ADE::debug($errstack_ref, 10, sprintf 'process_input_file: input contains %d route(s)', $#{$in_gpx->routes()}+1); foreach my $i (0..$#{$in_gpx->routes()}) { if (($rc=process_input_route($errstack_ref, ${$in_gpx->routes()}[$i], $split_mode, $parameter_list_ref, $suffixes_ref)) != $ADE::OK) { # Due to BTS#1056346, we did not *open* a file, so we do not need to close it. See above # for more details. #close $in_handle if defined $file; return($rc); } } # Due to BTS#1056346, we did not *open* a file, so we do not need to close it. See above # for more details. #close $in_handle if defined $file; return($ADE::OK); } sub process_input_route { my($errstack_ref, $route_ref, $split_mode, $parameter_list_ref, $suffixes_ref) = @_; my($in_count, $in_name, $out_file_index, $out_fh, $i, $out_name, @in_points, @out_points, $in_index, @suffixes, @in_name_components); my($rc, $route_prefix, $route_suffix); $in_name = ${$route_ref}{'name'}; ADE::info($errstack_ref, "$in_name: processing route ..."); @in_points = @{${$route_ref}{'points'}}; $in_count = $#{${$route_ref}{'points'}}+1; @suffixes = @{$suffixes_ref}; # Split on space or hyphen (not on dot!!!) *and capture whatever the separator was* (with round brackets) # so that we can put it back after inserting the chunk number. @in_name_components = split /([- ])/, $in_name, 2; $route_prefix = $in_name_components[0]; if ($#in_name_components+1 == 3) { $route_suffix = $in_name_components[1] . $in_name_components[2]; } else { $route_suffix = ''; } $out_file_index = 0; $in_index = 0; while ($in_index != $#in_points+1) { # Copy over a point push @out_points, $in_points[$in_index]; # If have just copied split point then write route and initialise the next route # with the *same* routepoint (i.e. that point is to appear in *two* routes). if (($split_mode eq 'count' and $#out_points+1 == ${$parameter_list_ref}[0]) or ($split_mode eq 'regex' and $in_points[$in_index]{'name'} =~ /^${$parameter_list_ref}[0]$/)) { ADE::debug($errstack_ref, 10, 'process_input_route: flushing complete route ...'); if (not defined $suffixes[$out_file_index]) { ADE::error($errstack_ref, 'gpxsplit_chunk_err_misc', 'ran out of suffixes! (hint: do you need to provide more?)'); return($ADE::FAIL); } $out_name = sprintf '%s.%s%s', $route_prefix, $suffixes[$out_file_index], $route_suffix; if (($rc=write_route($errstack_ref, $out_name, \@out_points)) != $ADE::OK) { return($rc); } $out_file_index++; undef @out_points; push @out_points, $in_points[$in_index]; } #last if ($in_index >= $#in_points); $in_index++; } # Flush any partially written route (which must consist of *more* than 1 routepoint; # if the route consists of a *single* routepoint then that routepoint # has already been written as the last routepoint in the previously written file). if ($#out_points+1 > 1) { if (not defined $suffixes[$out_file_index]) { ADE::error($errstack_ref, 'gpxsplit_chunk_err_misc', 'ran out of suffixes! (hint: do you need to provide more?)'); return($ADE::FAIL); } ADE::debug($errstack_ref, 10, 'process_input_route: flushing partial route ...'); $out_name = $in_name_components[0] . '.' . $suffixes[$out_file_index] . join '', splice @in_name_components, 1; if (($rc=write_route($errstack_ref, $out_name, \@out_points)) != $ADE::OK) { return($rc); } $out_file_index++; } return($ADE::OK); } sub write_route { my($errstack_ref, $routename, $out_points_ref) = @_; my($rc, $out_fh, $out_gpx, $width, $fs_compliant_routename); if ($opt_routepoint_renumber_flag) { # Brackets for precendence not function calls (PBP says not to use them for the latter) $width = int(0.999+log($#{$out_points_ref}+1)/log 10); ADE::debug($errstack_ref, 10, "write_route: renumbering routepoints with width $width ..."); foreach my $i (0..$#{$out_points_ref}) { ${$out_points_ref}[$i]{'name'} = sprintf 'RPT%0*d', $width, $routepoint_index++; } } $out_gpx = Geo::Gpx->new(); # Change all '/' to '-' to allow them to be written to filesystem. ($fs_compliant_routename = $routename) =~ s/\//-/g; if (!open $out_fh, '>', "$fs_compliant_routename.gpx") { ADE::error($errstack_ref, 'gpxsplit_chunk_err_misc', 'open() failed'); return($ADE::FAIL); } $out_gpx->routes_add($out_points_ref, 'name' => $routename); printf $out_fh $out_gpx->xml('1.0'); close $out_fh; return($ADE::OK); } ADE::main(\&gpxsplit_chunk);