Use "#!/usr/bin/env perl" in all Perl scripts
[gpstools.git] / gpst
blobe07abe0a4e6d493ed4f8be9850c5f4eea5d01af8
1 #!/usr/bin/env perl
3 #=======================================================================
4 # gpst
5 # File ID: 082106ec-f924-11dd-b757-0001805bf4b1
6 # Converts between various GPS formats
8 # Character set: UTF-8
9 # ©opyleft 2002– Øyvind A. Holm <sunny@sunbase.org>
10 # License: GNU General Public License version 3 or later, see end of
11 # file for legal stuff.
12 #=======================================================================
14 use strict;
15 use warnings;
16 use Getopt::Long;
17 use Time::Local qw { timegm_nocheck };
19 BEGIN {
20 push(@INC, "$ENV{'HOME'}/bin/src/gpstools");
23 use GPST;
24 use GPSTdate;
25 use GPSTdebug;
26 use GPSTgeo;
27 use GPSTxml;
29 $| = 1;
31 our $Debug = 0;
33 our %Opt = (
34 # Initial values for command line arguments {{{
36 'chronology' => 0,
37 'create-breaks' => 0,
38 'debug' => 0,
39 'double-y-scale' => 0,
40 'epoch' => 0,
41 'fix' => 0,
42 'from-date' => "",
43 'help' => 0,
44 'inside' => 0,
45 'output-format' => "gpsml",
46 'outside' => 0,
47 'pos1' => "",
48 'pos2' => "",
49 'require' => "",
50 'round' => "",
51 'save-to-file' => "\n", # \n = undefined, it’s banned in filenames anyway.
52 'short-date' => 0,
53 'skip-dups' => 0,
54 'strip-whitespace' => 0,
55 'time-shift' => 0,
56 'undefined' => "",
57 'verbose' => 0,
58 'version' => 0,
60 # }}}
63 our $progname = $0;
64 $progname =~ s/^.*\/(.*?)$/$1/;
65 our $VERSION = "0.00";
67 Getopt::Long::Configure("bundling");
68 GetOptions(
69 # Command line options {{{
71 "chronology" => \$Opt{'chronology'},
72 "create-breaks|t" => \$Opt{'create-breaks'},
73 "debug" => \$Opt{'debug'},
74 "double-y-scale|y" => \$Opt{'double-y-scale'},
75 "epoch|e" => \$Opt{'epoch'},
76 "fix" => \$Opt{'fix'},
77 "from-date=s" => \$Opt{'from-date'},
78 "help|h" => \$Opt{'help'},
79 "inside" => \$Opt{'inside'},
80 "output-format|o=s" => \$Opt{'output-format'},
81 "outside" => \$Opt{'outside'},
82 "pos1=s" => \$Opt{'pos1'},
83 "pos2=s" => \$Opt{'pos2'},
84 "require|r=s" => \$Opt{'require'},
85 "round|R=s" => \$Opt{'round'},
86 "save-to-file|S=s" => \$Opt{'save-to-file'},
87 "short-date|s" => \$Opt{'short-date'},
88 "skip-dups|d" => \$Opt{'skip-dups'},
89 "strip-whitespace|w" => \$Opt{'strip-whitespace'},
90 "time-shift|T=i" => \$Opt{'time-shift'},
91 "undefined|n=s" => \$Opt{'undefined'},
92 "verbose|v+" => \$Opt{'verbose'},
93 "version" => \$Opt{'version'},
95 # }}}
96 ) || die("$progname: Option error. Use -h for help.\n");
98 my %Dat;
100 my $PAUSE_LIMIT = 2 * 60; # Antall sekunder mellom to punkter det må til før en move legges inn.
101 my $Udef = "?";
102 my $DIGIT = '[0-9\.\-\+]'; # Used in regexps
103 $GPST::Spc = $Opt{'strip-whitespace'} ? "" : " ";
104 my $Spc = $GPST::Spc; # FIXME
105 my $found_move = 0; # Settes til 1 hvis en /^# move$/ blir funnet.
106 my $first_time = 0;
107 my $last_time = 0;
108 my ($last_lon, $last_lat, $last_line) =
109 ( 1000, 1000, ""); # Vi kan jo teoretisk sett være i Greenwich eller på ekvator
110 my ($lat1, $lon1, $lat2, $lon2) =
111 (-1000, -1000, 1000, 1000);
113 my %Poscount = ();
115 if ($Opt{'output-format'} =~ /^(gpx|pgtab)$/) {
116 $Opt{'require'} .= "p";
118 my %Req = (
119 'ele' => ($Opt{'require'} =~ /e/) ? 1 : 0,
120 'position' => ($Opt{'require'} =~ /p/) ? 1 : 0,
121 'time' => ($Opt{'require'} =~ /t/) ? 1 : 0,
123 $Opt{'require'} =~ /[^ept]/
124 && die("$0: Unknown flag in --require (-r) value\n");
126 $Opt{'debug'} && ($Debug = 1);
127 $Opt{'help'} && usage(0);
128 if ($Opt{'version'}) {
129 print_version();
130 exit(0);
133 if ($Opt{'pos1'} =~ /^($DIGIT+),($DIGIT+)$/) {
134 $lat1 = $1;
135 $lon1 = $2;
137 if ($Opt{'pos2'} =~ /^($DIGIT+),($DIGIT+)$/) {
138 $lat2 = $1;
139 $lon2 = $2;
141 if ($lat1 > $lat2) {
142 my $Tmp = $lat1;
143 $lat1 = $lat2;
144 $lat2 = $Tmp;
146 if ($lon1 > $lon2) {
147 my $Tmp = $lon1;
148 $lon1 = $lon2;
149 $lon2 = $Tmp;
152 if ($Opt{'epoch'} && $Opt{'short-date'}) {
153 die("$progname: Cannot mix the --epoch (-e) and --short-date (-s) options\n");
156 if ($Opt{'inside'} && $Opt{'outside'}) {
157 die("$progname: Cannot mix the --inside and --outside options\n");
160 # To avoid printing out extra "/> at the start of svg output:
161 my $svg_start_thing = "";
163 my %Round = ();
165 if (defined($Opt{'round'})) {
166 my $R = $Opt{'round'};
167 $R =~ s/([a-z]+)=(\d+)/($Round{$1}=$2, "")/eg;
170 length($Opt{'undefined'}) && ($Udef = $Opt{'undefined'});
172 $Opt{'save-to-file'} eq "\n" && print_header(*STDOUT);
174 my @first_lines;
175 my $xml_data;
176 my $data_line = "";
178 my $from_stdin = scalar(@ARGV) ? 0 : 1;
180 $from_stdin && push(@ARGV, "-");
182 for my $curr_file (@ARGV) {
183 # Scan through stdin or specified files and send every GPS entry to
184 # print_entry()
185 # {{{
186 print(STDERR "$progname: Opening \"$curr_file\" for read\n") if $Opt{'verbose'};
187 if (open(my $curr_fp, "<$curr_file")) {
188 # {{{
189 while (<$curr_fp>) {
190 $data_line = $_;
191 %Dat = (
192 'year' => '', 'month' => '', 'day' => '',
193 'hour' => '', 'min' => '', 'sec' => '',
194 'epoch' => '',
195 'date-format' => '',
196 'lat' => '', 'lon' => '',
197 'ele' => '',
198 'desc' => '',
199 'error' => "",
200 'what' => 'tp',
201 'curr_file' => $curr_file,
204 $Opt{'epoch'} && ($Dat{'date-format'} = "epoch");
205 $Opt{'short-date'} && ($Dat{'date-format'} = "short");
207 if ($Opt{'save-to-file'} ne "\n") {
208 push(@first_lines, $_);
210 s/^# error // && ($Dat{'error'} = "error");
211 s/^# ?// && ($Dat{'error'} = "desc");
212 $xml_data = "";
213 if (m#^<(e?tp)\b(.*?)>(.*?)</(e?tp)>\s*$#) {
214 # gpsml — The main storage format {{{
215 my ($Elem, $Props, $Data) =
216 ( $1, $2, $3);
217 my $err_str = ($Props =~ /\berr="(.*?)"/) ? $1 : "error";
218 $Elem eq "etp" && ($Dat{'error'} = $err_str);
219 my $Time = "";
220 $Data =~ m#<time>(.*?)</time># && ($Time = $1);
221 $Time =~ s{
222 (\d\d\d\d)-?(\d\d)-?(\d\d)[T ](\d\d):?(\d\d):?([\d\.]+?)Z
224 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
225 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
226 ( $1, $2, $3,
227 $4, $5, $6);
229 }ex;
230 $Data =~ m#<lat>($DIGIT*?)</lat># && ($Dat{'lat'} = $1);
231 $Data =~ m#<lon>($DIGIT*?)</lon># && ($Dat{'lon'} = $1);
232 $Data =~ m#<ele>($DIGIT*?)</ele># && ($Dat{'ele'} = $1);
233 $Data =~ m#<desc>(.*?)</desc># && ($Dat{'desc'} = $1);
234 print_entry(%Dat);
235 # }}}
236 } elsif (m#^<break\b.*?/>#) {
237 $found_move = 1;
238 } elsif (m#^<(title|pause)\b.*?>(.*?)</(title|pause)>#) {
239 $Dat{'what'} = $1;
240 $Dat{$1} = $2;
241 print_entry(%Dat);
242 } elsif (m#^<desc\b.*?>(.*$)#s) {
243 $Dat{'what'} = "desc";
244 my $Txt = $1;
245 until ($Txt =~ m#</desc>#s) {
246 $Txt .= <$curr_fp>;
248 $Txt =~ s#^(.*)(</desc>.*$)#$1#s;
249 $Dat{'desc'} = $Txt;
250 print_entry(%Dat);
251 } elsif (/<gpx\b/) {
252 $xml_data = $_;
253 $xml_data .= join("", <$curr_fp>);
254 if (!length($Opt{'output-format'})) {
255 $Opt{'output-format'} = "gpx";
256 print_header(*STDOUT);
258 read_xmlfile($xml_data);
259 last;
260 } elsif (/^move$/) {
261 $found_move = 1;
262 } elsif (m#^(\d+)\t($DIGIT+)\t($DIGIT+)\t($DIGIT)#) {
263 # CSV format, epoch style {{{
264 my ($ep_time, $lon_val, $lat_val, $Alt) =
265 ( $1, $2, $3, $4);
266 $Dat{'epoch'} = $ep_time;
267 ($Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
268 $Dat{'day'}, $Dat{'month'}, $Dat{'year'},
269 $Dat{'wday'}, $Dat{'yday'}) = gmtime($ep_time);
270 $Dat{'month'}++; # Urgh Ⅰ
271 $Dat{'year'} += 1900; # Urgh Ⅱ
272 print_entry(%Dat);
273 # }}}
274 } elsif (
276 (\d\d\d\d)-?(\d\d)-?(\d\d)[T\ ](\d\d):?(\d\d):?(\d\d)Z?\t
277 ($DIGIT+)\t($DIGIT+)\t($DIGIT)
280 # CSV format, human-readable date format {{{
281 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
282 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'},
283 $Dat{'lon'}, $Dat{'lat'}, $Dat{'ele'}) =
284 ($1, $2, $3,
285 $4, $5, $6,
286 $7, $8, $9);
287 print_entry(%Dat);
288 # }}}
289 } elsif (/^Trackpoint\t/) {
290 # Trackpoint\tN60.41630 E5.31675\t09.02.2006 20:24:37 (UTC)\t13.6 m\t\t93.9 m\t00:00:06\t56 kph\t123° true {{{
292 # Trackpoint\t
293 # N60.41630 E5.31675\t
294 # 09.02.2006 20:24:37 (UTC)\t
295 # 13.6 m\t
296 # \t
297 # 93.9 m\t
298 # 00:00:06\t
299 # 56 kph\t
300 # 123° true
301 my $Orig = $_;
302 $Orig =~ s/[\r\n]+$//;
303 my ($Marker_f, $Position_f, $Time_f, $Alt_f, $Depth_f,
304 $Leglength_f, $Legtime_f, $Legspeed_f, $Legcourse_f) =
305 split(/\t/, $Orig .
306 # Nødløsning for å unngå at variabler
307 # blir udefinert.
308 "\t\t\t\t\t\t\t\t\t\t"
310 # D(join("",
311 # "Position_f=\"$Position_f\" \x7B\x7B\x7B\n",
312 # "Time_f=\"$Time_f\"\n",
313 # "Alt_f=\"$Alt_f\"\n",
314 # "Depth_f=\"$Depth_f\"\n",
315 # "Leglength_f=\"$Leglength_f\"\n",
316 # "Legtime_f=\"$Legtime_f\"\n",
317 # "Legspeed_f=\"$Legspeed_f\"\n",
318 # "Legcourse_f=\"$Legcourse_f\" \x7D\x7D\x7D\n",
319 # ));
320 my ($NS, $WE,
321 $Alt_unit,
322 $Leglength,
323 $Legtime_hour, $Legtime_min, $Legtime_sec,
324 $Legspeed, $Legspeed_unit,
325 $Legcourse
326 ) = ("", "", "", "", "", "", "", "", "", "", "", "", "",
327 "", "", "", "", "", "", "", "", "", "");
328 ($Position_f =~ /^(N|S)([\d\.]+) (W|E)([\d\.]+)/) &&
329 ($NS = $1, $Dat{'lat'} = $2, $WE = $3, $Dat{'lon'} = $4);
330 ($Time_f =~ /^(\d+)\.(\d+)\.(\d+) (\d+):(\d+):(\d+) \((.+?)\)/) &&
331 ($Dat{'day'} = $1, $Dat{'month'} = $2, $Dat{'year'} = $3,
332 $Dat{'hour'} = $4, $Dat{'min'} = $5, $Dat{'sec'} = $6);
333 ($Alt_f =~ /^($DIGIT+) (.*?)/) &&
334 ($Dat{'ele'} = $1, $Alt_unit = $2);
335 # D("ele = \"$Dat{'ele'}\"");
336 ($NS eq "S") && ($Dat{'lat'} = 0-$Dat{'lat'});
337 ($WE eq "W") && ($Dat{'lon'} = 0-$Dat{'lon'});
338 # MapSource in win xp writes YYYY, but YY in win98se.
340 defined($Dat{'year'})
341 && $Dat{'year'} =~ /\d/
342 && $Dat{'year'} < 1900
343 ) && ($Dat{'year'} += 2000);
344 print_entry(%Dat);
345 # }}}
346 } elsif (/^Track\t(.*?)\t/) {
347 $Dat{'title'} = txt_to_xml($1);
348 $Dat{'what'} = "title";
349 $found_move = 1;
350 print_entry(%Dat);
351 } elsif (
354 (\d\d)/(\d\d)/(\d\d\d\d)\ (\d\d):(\d\d):(\d\d)\t
355 (.+)\xB0(.+)'(.+)"\t
356 (.+)\xB0(.+)'(.+)"
359 # T 09/01/2002 11:51:26 60°23'36.3" 5°19'35.9" {{{
360 my ($lat_d, $lat_m, $lat_s, $lon_d, $lon_m, $lon_s);
361 ($Dat{'month'}, $Dat{'day'}, $Dat{'year'},
362 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'},
363 $lat_d, $lat_m, $lat_s,
364 $lon_d, $lon_m, $lon_s) =
365 ($1, $2, $3,
366 $4, $5, $6,
367 $7, $8, $9,
368 $10, $11, $12);
369 $Dat{'lat'} = 1.0*($lat_d+($lat_m/60)+($lat_s/3600));
370 $Dat{'lon'} = 1.0*$lon_d+($lon_m/60)+($lon_s/3600);
371 print_entry(%Dat);
372 # }}}
373 } elsif (
375 1\ (\S+)\ (\S+)\ (\S+)\ (\S+)\x20
376 (\d\d)/(\d\d)/(\d\d\d\d)\ (\d\d):(\d\d):(\d\d)
379 # 1 60.3938222 5.3238754 17.3 0 09/01/2002 14:18:23 {{{
380 ($Dat{'lat'}, $Dat{'lon'}, $Dat{'speed'},
381 $Dat{'unkn'},
382 $Dat{'month'}, $Dat{'day'}, $Dat{'year'},
383 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
384 ($1, $2, $3,
386 $5, $6, $7,
387 $8, $9, $10);
388 print_entry(%Dat);
389 # }}}
390 } elsif (/^
391 # @020721221336N6048353E00701826S015-00001E4859N1673U0000 {{{
392 # Regexp {{{
393 (@) # @
394 (\d\d) # Year
395 (\d\d) # Month
396 (\d\d) # Day
397 (\d\d) # Hours
398 (\d\d) # Minutes
399 (\d\d) # Seconds
400 ([NS]) # N|S
401 (\d\d) # Latitude degree
402 (\d\d) # Latitude minute
403 (\d\d\d) # Latitude minute decimals
404 ([EW]) # E|W
405 (\d\d\d) # Longitude degree
406 (\d\d) # Longitude minute
407 (\d\d\d) # Longitude minute degree
408 (....) # Accurancy
409 (......) # Elevation
410 (...............)
411 # }}}
412 /x) {
413 my ($NS, $EW, $lat_deg, $lat_degmin, $lat_mindec, $lon_deg,
414 $lon_degmin, $lon_mindec);
415 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'}, $Dat{'hour'},
416 $Dat{'min'}, $Dat{'sec'}, $NS, $lat_deg,
417 $lat_degmin, $lat_mindec, $EW,
418 $lon_deg, $lon_degmin, $lon_mindec,
419 $Dat{'accur'}, $Dat{'ele'}, $Dat{'unknown'}) =
420 ($2+2000, $3, $4, $5,
421 $6, $7, $8, $9,
422 $10, $11, $12,
423 $13, $14, $15,
424 $16, $17, $18);
425 my $ep_time = timegm_nocheck(
426 $Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
427 $Dat{'day'}, $Dat{'month'}-1, $Dat{'year'}
429 $last_time = $ep_time;
430 my $tmp_lon = $lon_deg + $lon_degmin/60 + $lon_mindec/60000;
431 my $tmp_lat = $lat_deg + $lat_degmin/60 + $lat_mindec/60000;
432 ($NS eq "S") && ($tmp_lat = 0-$tmp_lat);
433 ($EW eq "W") && ($tmp_lon = 0-$tmp_lon);
434 $Dat{'lat'} = $tmp_lat;
435 $Dat{'lon'} = $tmp_lon;
436 print_entry(%Dat);
437 # }}}
438 } elsif (/^(@)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(_{42})/) {
439 # @020721221336__________________________________________ {{{
440 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
441 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}, $Dat{'rest'}) =
442 ($2+2000, $3, $4,
443 $5, $6, $7, $8);
444 $Dat{'error'} = "nosignal";
445 print_entry(%Dat);
446 # }}}
447 } elsif (/^xmaplog /) {
448 # NOP
449 } elsif (/^$/) {
450 ($Opt{'output-format'} eq "csv")
451 && ($Opt{'save-to-file'} eq "\n")
452 && print("\n");
453 } elsif (/^Pause: /) {
454 # NOP, is here to cope with old files I’ve lying around.
455 } elsif ($Dat{'error'} eq "desc") {
456 my $Comment = $_;
457 if (defined($Comment)) {
458 $Comment =~ s/^\s*(.*?)\s*$/$1/;
459 if ($Opt{'output-format'} eq "gpsml") {
460 $Dat{'desc'} = txt_to_xml($Comment);
461 $Dat{'what'} = "desc";
462 print_entry(%Dat);
465 } else {
466 $Opt{'verbose'} && warn("Line $.: Unknown: \"$_\"\n");
469 # }}}
470 } else {
471 warn("$progname: $curr_file: Cannot open file for read: $!\n");
473 # }}}
476 print_footer(*STDOUT);
478 exit(0);
480 sub read_xmlfile {
481 # {{{
482 my $Txt = join("", @_);
483 $Txt =~ s/<!--(.*?)-->//gs;
484 $Txt =~ s#(<gpx\b.*?>.*?</gpx>)#print_gpx($1)#gse;
485 # }}}
486 } # read_xmlfile()
488 sub print_gpx {
489 # {{{
490 my $Orig = shift;
491 my $Str = $Orig;
492 # D("print_xml_gps(\"$Orig\")\n");
493 $Str =~ s/<!--(.*?)-->//gs;
494 my $fromdate_str = "";
495 if ($Opt{'from-date'}) {
496 $fromdate_str = "date >= '$Opt{'from-date'}' AND ";
498 if ($Opt{'output-format'} =~ /^(pgwtab|pgwupd)$/) {
499 # {{{
500 $Str =~
502 <wpt\b(.*?)>(.*?)</wpt>
505 my $attr_wpt = $1;
506 my $el_wpt = $2;
507 my ($Lat, $Lon, $Name, $Ele, $Type, $Time, $Cmt, $Desc, $Src, $Sym) =
508 ('\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N');
510 $attr_wpt =~ /.*lat="($DIGIT+?)"/s &&
511 ($Lat = postgresql_copy_safe($1));
512 $attr_wpt =~ /.*lon="($DIGIT+?)"/s &&
513 ($Lon = postgresql_copy_safe($1));
514 $el_wpt =~ /.*<name\b(.*?)>(.*?)<\/name>/s &&
515 ($Name = postgresql_copy_safe(xml_to_txt($2)));
516 $el_wpt =~ /.*<ele\b(.*?)>(.*?)<\/ele>/s &&
517 ($Ele = postgresql_copy_safe(xml_to_txt($2)));
518 $el_wpt =~ /.*<type\b(.*?)>(.*?)<\/type>/s &&
519 ($Type = postgresql_copy_safe(xml_to_txt($2)));
520 $el_wpt =~ /.*<time\b(.*?)>(.*?)<\/time>/s &&
521 ($Time = postgresql_copy_safe(xml_to_txt($2)));
522 $el_wpt =~ /.*<cmt\b(.*?)>(.*?)<\/cmt>/s &&
523 ($Cmt = postgresql_copy_safe(xml_to_txt($2)));
524 $el_wpt =~ /.*<desc\b(.*?)>(.*?)<\/desc>/s &&
525 ($Desc = postgresql_copy_safe(xml_to_txt($2)));
526 $el_wpt =~ /.*<src\b(.*?)>(.*?)<\/src>/s &&
527 ($Src = postgresql_copy_safe(xml_to_txt($2)));
528 $el_wpt =~ /.*<sym\b(.*?)>(.*?)<\/sym>/s &&
529 ($Sym = postgresql_copy_safe(xml_to_txt($2)));
531 if (length($Opt{'round'})) {
532 if (defined($Round{'lat'}) && length($Lat)) {
533 ($Lat = 1.0 * sprintf("%.$Round{'lat'}f", $Lat));
535 if (defined($Round{'lon'}) && length($Lon)) {
536 ($Lon = 1.0 * sprintf("%.$Round{'lon'}f", $Lon));
538 if (defined($Round{'ele'}) && $Ele ne '\N') {
539 ($Ele = 1.0 * sprintf("%.$Round{'ele'}f", $Ele));
543 if ($Opt{'output-format'} eq "pgwtab") {
544 print(
545 join("\t",
546 "($Lat,$Lon)",
547 $Name,
548 $Ele,
549 $Type,
550 $Time,
551 $Cmt,
552 $Desc,
553 $Src,
554 $Sym
555 ) . "\n"
557 } elsif ($Opt{'output-format'} eq "pgwupd") {
558 $Name =~ s/'/''/gs;
559 print(join("\n",
560 "BEGIN;",
561 "$Spc${Spc}UPDATE logg SET name = clname(coor) " .
562 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
563 "$Spc${Spc}UPDATE logg SET dist = cldist(coor) " .
564 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
565 "COMMIT;"
566 ) . "\n");
569 }gsex;
570 # }}}
571 } else {
572 # {{{
573 $Str =~
575 <trk\b(.*?)>(.*?)</trk>
578 my $el_trk = $2;
579 $el_trk =~
581 <name\b(.*?)>(.*?)</name>
583 my %tmp_dat = ();
584 $tmp_dat{'title'} = $2;
585 $tmp_dat{'what'} = "title";
586 $tmp_dat{'error'} = "";
587 print_entry(%tmp_dat);
589 }sex;
590 $el_trk =~
592 <trkseg\b(.*?)>(.*?)</trkseg>
595 my $el_trkseg = $2;
596 $el_trkseg =~
598 <trkpt\b(.*?)>(.*?)</trkpt>
601 my ($attr_trkpt, $el_trkpt) =
602 ( $1, $2);
603 %Dat = (
604 'year' => '', 'month' => '', 'day' => '',
605 'hour' => '', 'min' => '', 'sec' => '',
606 'epoch' => '',
607 'date-format' => '',
608 'lat' => '', 'lon' => '',
609 'ele' => '',
610 'desc' => '',
611 'error' => "",
612 'what' => 'tp',
614 ($attr_trkpt =~ /\blon="(.*?)"/) && ($Dat{'lon'} = $1);
615 ($attr_trkpt =~ /\blat="(.*?)"/) && ($Dat{'lat'} = $1);
616 ($el_trkpt =~ m#<ele\b.*?>(.*?)</ele>#) && ($Dat{'ele'} = $1);
617 if (
618 $el_trkpt =~
620 <time>(\d\d\d\d)-?(\d\d)-?(\d\d)T
621 (\d\d):?(\d\d):?([\d\.]+)Z</time>
624 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
625 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
626 ($1, $2, $3, $4, $5, $6);
628 print_entry(%Dat);
630 }gsex;
631 $found_move = 1;
632 }gsex;
633 $found_move = 1;
634 }gsex;
635 # }}}
637 # }}}
638 } # print_gpx()
640 sub print_header {
641 # {{{
642 my $out_fp = shift;
643 if ($Opt{'output-format'} eq "gpsml") {
644 print($out_fp join("",
645 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
646 "<gpsml>\n",
647 "<track>\n",
649 } elsif ($Opt{'output-format'} eq "gpstrans") {
650 print($out_fp "Format: DMS UTC Offset: 0.00 hrs " .
651 "Datum[100]: WGS 84\n");
652 } elsif ($Opt{'output-format'} eq "gpx") {
653 print($out_fp join("",
654 qq{<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n},
655 qq{<gpx\n},
656 qq{$Spc${Spc}version="1.1"\n},
657 qq{$Spc${Spc}creator="gpst - http://sunny256.github.com/gpstools/"\n},
658 qq{$Spc${Spc}xmlns="http://www.topografix.com/GPX/1/1"\n},
659 qq{$Spc${Spc}xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n},
660 qq{$Spc${Spc}xsi:schemaLocation="http://www.topografix.com/GPX/1/1 },
661 qq{http://www.topografix.com/GPX/1/1/gpx.xsd"\n},
662 qq{>\n},
663 qq{$Spc$Spc<trk>\n},
664 qq{$Spc$Spc$Spc$Spc<trkseg>\n},
666 } elsif ($Opt{'output-format'} eq "ps") {
667 print($out_fp ps_header(532, 6034, 533, 6040));
668 print($out_fp "*u\n");
669 } elsif ($Opt{'output-format'} eq "svg") {
670 print($out_fp join("",
671 "<?xml version=\"1.0\" standalone=\"no\"?>\n",
672 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
673 "$Spc$Spc\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
674 "<svg height=\"1000\" width=\"1000\" viewBox=\"23 70 2 2\"\n",
675 "$Spc${Spc}xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">\n",
676 "$Spc$Spc<title></title>\n",
677 "$Spc$Spc<desc></desc>\n",
680 # }}}
681 } # print_header()
683 sub print_footer {
684 # Print footer {{{
685 my $out_fp = shift;
686 if ($Opt{'output-format'} eq "gpsml") {
687 print($out_fp join("",
688 "</track>\n",
689 "</gpsml>\n",
691 } elsif ($Opt{'output-format'} eq "gpx") {
692 print($out_fp join("",
693 "$Spc$Spc$Spc$Spc</trkseg>\n",
694 "$Spc$Spc</trk>\n",
695 "</gpx>\n",
697 } elsif ($Opt{'output-format'} eq "poscount") {
698 my @output = ();
699 while (my ($l_name, $l_val) = each %Poscount) {
700 $l_name =~ /^(.+?),(.+?)$/
701 && push(@output, "$1\t$2\t$l_val\n");
703 print($out_fp sort @output);
704 } elsif ($Opt{'output-format'} eq "ps") {
705 print($out_fp join("",
706 "*U\n",
707 "%%Trailer\n",
708 "%%EOF\n",
710 } elsif ($Opt{'output-format'} eq "svg") {
711 print($out_fp "\"/>\n</svg>\n");
713 # }}}
714 } # print_footer()
716 sub print_entry {
717 # Print a GPS entry with time, latitude, longitude and elevation in
718 # various formats
719 # {{{
720 my %Dat = @_;
721 defined($Dat{'desc'}) || ($Dat{'desc'} = "");
722 defined($Dat{'ele'}) || ($Dat{'ele'} = "");
723 defined($Dat{'lat'}) || ($Dat{'lat'} = "");
724 defined($Dat{'lon'}) || ($Dat{'lon'} = "");
725 defined($Dat{'year'}) || ($Dat{'year'} = "");
726 my $print_time = length($Dat{'year'}) ? 1 : 0;
727 my $print_pos;
728 if (!$Req{'position'} && $Opt{'output-format'} eq "gpsml") {
729 $print_pos = (length($Dat{'lat'}) || length($Dat{'lon'})) ? 1 : 0;
730 } else {
731 $print_pos = (length($Dat{'lat'}) && length($Dat{'lon'})) ? 1 : 0;
733 if (!$print_pos) {
734 $Dat{'lat'} = $Dat{'lon'} = "";
736 my $print_ele = length($Dat{'ele'}) ? 1 : 0;
737 my $print_desc = length($Dat{'desc'}) ? 1 : 0;
738 my $Line = "";
739 # D("print_entry(\"" . join("\", \"", @_) . "\");");
740 my $ep_time;
742 if (length($Opt{'round'})) {
743 for my $Tmp (qw{ lat lon ele }) {
744 if (defined($Round{$Tmp}) && length($Dat{$Tmp})) {
745 # D("Tmp = '$Tmp'");
746 ($Dat{$Tmp} = num_expand(1.0 * sprintf("%.$Round{$Tmp}f", $Dat{$Tmp})));
751 if ($Opt{'output-format'} eq "poscount") {
752 # FIXME: Sort output in some way
753 if (!length($Dat{'error'})) {
754 $Dat{'lat'} = num_expand($Dat{'lat'});
755 $Dat{'lon'} = num_expand($Dat{'lon'});
756 my $Name = "$Dat{'lon'},$Dat{'lat'}";
757 defined($Poscount{$Name}) || ($Poscount{$Name} = 0);
758 $Poscount{$Name}++;
760 return;
763 if ($print_time) {
764 $ep_time = timegm_nocheck(
765 $Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
766 $Dat{'day'}, $Dat{'month'} - 1, $Dat{'year'}
768 if ($Opt{'time-shift'}) {
769 # D("ep_time før: '$ep_time'");
770 $ep_time += $Opt{'time-shift'};
771 # D("ep_time etter: '$ep_time'");
772 ($Dat{'sec'}, $Dat{'min'},$Dat{'hour'}, $Dat{'day'},
773 $Dat{'month'}, $Dat{'year'}) = gmtime($ep_time);
774 $Dat{'year'} += 1900;
775 $Dat{'month'}++;
777 $Dat{'epoch'} = $ep_time;
778 $Dat{'year'} = sprintf("%04u", $Dat{'year'});
779 $Dat{'month'} = sprintf("%02u", $Dat{'month'});
780 $Dat{'day'} = sprintf("%02u", $Dat{'day'});
781 $Dat{'hour'} = sprintf("%02u", $Dat{'hour'});
782 $Dat{'min'} = sprintf("%02u", $Dat{'min'});
783 $Dat{'sec'} = sprintf("%02u", $Dat{'sec'});
784 if ($Opt{'chronology'}) {
785 if ($last_time > $ep_time && !length($Dat{'error'})) {
786 warn(sprintf(
787 "%s: $Dat{'curr_file'}: \"%sZ\": Next date is %s in the past (%sZ)\n",
788 $progname, sec_to_string($last_time, "T"),
789 sec_to_readable($last_time-$ep_time),
790 sec_to_string($ep_time, "T")
792 # FIXME: Make --fix work with gpx.
793 if ($Opt{'fix'} && ($Opt{'output-format'} !~ /^gpx$/)) {
794 $Dat{'error'} = "chrono";
796 } elsif ($last_time == $ep_time && !length($Dat{'error'})) {
797 warn(sprintf(
798 "%s: $Dat{'curr_file'}: \"%sZ\": Duplicated time\n",
799 $progname, sec_to_string($last_time, "T")
801 # FIXME: Make --fix work with gpx.
802 if ($Opt{'fix'} && ($Opt{'output-format'} !~ /^gpx$/)) {
803 $Dat{'error'} = "duptime";
807 } else {
808 $ep_time = 0;
809 $Dat{'year'} = 0;
810 $Dat{'month'} = 0;
811 $Dat{'day'} = 0;
812 $Dat{'hour'} = 0;
813 $Dat{'min'} = 0;
814 $Dat{'sec'} = 0;
817 if ($Opt{'save-to-file'} ne "\n") {
818 # {{{
819 $print_time || return;
820 my $base_name = "$Dat{'year'}$Dat{'month'}$Dat{'day'}T" .
821 "$Dat{'hour'}$Dat{'min'}$Dat{'sec'}Z" .
822 "$Opt{'save-to-file'}";
823 my $file_name = $base_name;
824 if (-e $file_name) {
825 for (my $a = 1; (-e $file_name) && ($a < 1000); $a++) {
826 $file_name = "$base_name.dup_$a";
828 if (-e $file_name) {
829 die("$progname: $base_name: File already exists, and ran " .
830 "out of attempts to create unique file name\n");
832 if ($Opt{'verbose'}) {
833 warn("$progname: $base_name: File already exists, using " .
834 "unique name \"$file_name\" instead\n");
837 if (open(my $to_fp, ">", $file_name)) {
838 print_header(*$to_fp);
839 print($to_fp (
840 $from_stdin
841 ? @first_lines
842 : ()),
843 (length($xml_data)
844 ? $xml_data
845 : <>)
846 ) || die("$progname: $file_name: Cannot write to file: $!\n");
847 print_footer(*$to_fp);
848 close($to_fp);
849 if ($Opt{'output-format'} eq "gpsml") {
850 printf("<include>%s</include>\n",
851 txt_to_xml($file_name));
852 } elsif ($Opt{'output-format'} eq "gpx") {
853 printf("<!-- Saved unconverted data to \"%s\" -->\n",
854 txt_to_xml($file_name));
855 } else {
856 print("$progname: Saved unconverted data to \"$file_name\"\n");
858 exit 0;
859 } else {
860 die("$progname: $file_name: Cannot create file: $!\n");
862 # }}}
865 my $pause_len = 0;
866 my $do_print = 1;
868 if ($Dat{'what'} eq "tp") {
869 # {{{
870 if ($Opt{'require'}) {
871 $Req{'time'} && !$print_time && return;
872 $Req{'position'} && !$print_pos && return;
873 $Req{'ele'} && !$print_ele && return;
876 if ($Opt{'inside'} || $Opt{'outside'}) {
877 if (
878 ($Dat{'lat'} < $lat1) ||
879 ($Dat{'lat'} > $lat2) ||
880 ($Dat{'lon'} < $lon1) ||
881 ($Dat{'lon'} > $lon2)
883 $Opt{'inside'} && return;
884 } else {
885 $Opt{'outside'} && return;
889 if ($Opt{'output-format'} eq "ps") {
890 $Dat{'lon'} *= 100;
891 $Dat{'lat'} *= 100;
894 if (
895 $Opt{'skip-dups'}
896 && ($Dat{'lon'} eq $last_lon)
897 && ($Dat{'lat'} eq $last_lat)
899 if ($Opt{'output-format'} eq 'gpsml') {
900 $Dat{'error'} = "dup";
901 } else {
902 $do_print = 0;
904 } else {
905 $do_print = 1;
908 if (
909 $Opt{'create-breaks'}
910 && $ep_time-$last_time > $PAUSE_LIMIT
911 && $last_time
913 $pause_len = $ep_time-$last_time;
914 # D("pause_len set to '$pause_len'");
917 $Line .= pause_entry($pause_len, $ep_time, $last_time);
918 # }}}
921 if ($do_print) {
922 # Valid data was found, send to stdout {{{
923 unless ($first_time) {
924 $first_time = $ep_time;
926 $Line .= gen_entry($print_pos, $pause_len, $print_time, $ep_time, $print_ele, %Dat);
927 # }}}
930 if (!$last_time && $Opt{'output-format'} eq "ps") {
931 $Line .= "$Dat{'lon'} $Dat{'lat'} m\n";
934 if ($do_print) {
935 if ($found_move) {
936 if ($Opt{'output-format'} eq "gpsml") {
937 $Line = "<break/>\n$Line";
939 (!$pause_len && ($Opt{'output-format'} eq "xgraph"))
940 && ($Line .= "move $Line");
941 ($Opt{'output-format'} eq "clean") && ($Line .= "\n");
942 if ($Opt{'output-format'} eq "gpx") {
943 $Line .= "$Spc$Spc$Spc$Spc</trkseg>\n" .
944 "$Spc$Spc$Spc$Spc<trkseg>\n";
946 $found_move = 0;
948 print($Line);
950 $print_time && ($last_time = $ep_time);
951 if ($print_pos) {
952 $last_lon = $Dat{'lon'};
953 $last_lat = $Dat{'lat'};
955 $last_line = $data_line;
956 $svg_start_thing = "\"/>\n";
957 # }}}
958 } # print_entry()
960 sub gen_entry {
961 # Generate trackpoint entry, calls trackpoint() {{{
962 my ($print_pos, $pause_len, $print_time, $ep_time, $print_ele, %Dat) = @_;
963 my $Line = "";
964 if ($Opt{'double-y-scale'} && length($Dat{'lat'})) {
965 $Dat{'lat'} *= 2;
967 if ($Opt{'output-format'} eq "gpsml") {
968 if ($Dat{'what'} eq "tp") {
969 $Dat{'format'} = "gpsml";
970 $Line .= trackpoint(%Dat);
971 } elsif ($Dat{'what'} =~ /^(pause|desc|title)$/) {
972 $Line .= sprintf("<%s>%s</%s>\n",
974 $Dat{$1},
975 $1);
977 } elsif ($Opt{'output-format'} eq "pgtab") {
978 if ($Dat{'what'} eq "tp" && !length($Dat{'error'})) {
979 $Dat{'format'} = "pgtab";
980 $Line .= trackpoint(%Dat);
982 } elsif ($Opt{'output-format'} eq "xgraph") {
983 if ($print_pos && !length($Dat{'error'})) {
984 $Dat{'format'} = "xgraph";
985 $Line .= trackpoint(%Dat);
987 } elsif($Opt{'output-format'} eq "gpstrans") {
988 if ($print_pos && !length($Dat{'error'})) {
989 $Dat{'format'} = "gpstrans";
990 $Line .= trackpoint(%Dat);
992 } elsif($Opt{'output-format'} eq "gpx") {
993 if ($Dat{'what'} eq "tp") {
994 $Dat{'format'} = "gpx";
995 $Line .= trackpoint(%Dat);
997 } elsif ($Opt{'output-format'} eq "clean") {
998 if ($Dat{'what'} eq "tp" && !length($Dat{'error'})) {
999 $Dat{'format'} = "clean";
1000 $Line .= trackpoint(%Dat);
1002 } elsif ($Opt{'output-format'} eq "ps") {
1003 $Line .= (
1004 $pause_len
1005 ? "f\n$Dat{'lon'} $Dat{'lat'} m\n"
1006 : "$Dat{'lon'} $Dat{'lat'} l\n"
1008 } elsif ($Opt{'output-format'} eq "svg") {
1009 $Line .= (
1010 ($last_lon == 1000) || $pause_len
1011 ? join("",
1012 "$svg_start_thing<path\n",
1013 " stroke=\"blue\"\n",
1014 " stroke-width=\"0.001\"\n",
1015 " fill=\"none\"\n",
1016 " d=\"\n",
1017 "M $Dat{'lon'} $Dat{'lat'}\n")
1018 : "L $Dat{'lon'} $Dat{'lat'}\n"
1020 } elsif ($Opt{'output-format'} eq "ygraph") {
1021 if (!length($Dat{'error'})) {
1022 $Dat{'lat'} = num_expand($Dat{'lat'});
1023 $Dat{'lon'} = num_expand($Dat{'lon'});
1024 $Dat{'ele'} = num_expand($Dat{'ele'});
1025 my $Time = $print_time ? ($ep_time - $first_time) * 1 : 0;
1026 $Line .= "\"Time = $Time.0\n$Dat{'lon'} $Dat{'lat'}\n\n";
1028 } elsif ($Opt{'output-format'} eq "csv") {
1029 # {{{
1030 if (!length($Dat{'error'})) {
1031 $Dat{'format'} = "csv";
1032 $Dat{'lat'} = num_expand($Dat{'lat'});
1033 $Dat{'lon'} = num_expand($Dat{'lon'});
1034 $Dat{'ele'} = num_expand($Dat{'ele'});
1035 $Line .= join("\t",
1036 $print_time
1037 ? $Opt{'epoch'}
1038 ? $ep_time
1039 : $Opt{'short-date'}
1040 ? "$Dat{'year'}$Dat{'month'}$Dat{'day'}T" .
1041 "$Dat{'hour'}$Dat{'min'}$Dat{'sec'}Z"
1042 : "$Dat{'year'}-$Dat{'month'}-$Dat{'day'}T" .
1043 "$Dat{'hour'}:$Dat{'min'}:$Dat{'sec'}Z"
1044 : "",
1045 $Dat{'lon'},
1046 $Dat{'lat'},
1047 $print_ele ? $Dat{'ele'} : "", # Elevation
1048 "\n"
1051 # }}}
1052 } elsif ($Opt{'output-format'} eq "pgwtab") {
1053 # FIXME: NOP at the moment.
1054 } else {
1055 die("$progname: \"$Opt{'output-format'}\": " .
1056 "Unknown output format\n");
1058 return($Line);
1059 # }}}
1060 } # gen_entry()
1062 sub pause_entry {
1063 # {{{
1064 my ($pause_len, $ep_time, $last_time) = @_;
1065 my $Line = "";
1066 if ($pause_len) {
1067 if ($Opt{'output-format'} eq "gpsml") {
1068 $Line .= sprintf("<pause>%s</pause>\n",
1069 sec_to_readable($ep_time-$last_time));
1070 } elsif ($Opt{'output-format'} eq "clean") {
1071 $pause_len && ($Line .= "\n");
1072 } elsif ($Opt{'output-format'} eq "csv") {
1073 $Line .= sprintf("# Pause: %s\n# move\n",
1074 sec_to_readable($ep_time-$last_time));
1075 } elsif ($Opt{'output-format'} eq "xgraph") {
1076 $pause_len && ($Line .= "move ");
1079 return($Line);
1080 # }}}
1081 } # pause_entry()
1083 sub ps_header {
1084 # Send a Postscript header to stdout {{{
1085 my ($bl_lon, $bl_lat, $br_lon, $br_lat) = @_;
1086 my $Date = sec_to_string(time);
1087 return(join("",
1088 "%!PS-Adobe-3.0 EPSF-3.0\n",
1089 "%%Creator: gpst\n",
1090 "%%Title:\n",
1091 "%%CreationDate: $Date\n",
1092 "%%BoundingBox: $bl_lon $bl_lat $br_lon $br_lat\n",
1093 "%%DocumentData: Clean7Bit\n",
1094 "%%EndComments\n",
1095 "%%BeginProlog\n",
1096 "/bd { bind def } bind def\n",
1097 "/incompound false def\n",
1098 "/m { moveto } bd\n",
1099 "/l { lineto } bd\n",
1100 "/c { curveto } bd\n",
1101 "/F { incompound not {fill} if } bd\n",
1102 "/f { closepath F } bd\n",
1103 "/S { stroke } bd\n",
1104 "/*u { /incompound true def } bd\n",
1105 "/*U { /incompound false def f} bd\n",
1106 "/k { setcmykcolor } bd\n",
1107 "/K { k } bd\n",
1108 "%%EndProlog\n",
1109 "%%BeginSetup\n",
1110 "%%EndSetup\n",
1112 # }}}
1113 } # ps_header()
1115 sub print_version {
1116 # Print program version {{{
1117 print("$progname v$VERSION\n");
1118 # }}}
1119 } # print_version()
1121 sub usage {
1122 # Send the help message to stdout {{{
1123 my $Retval = shift;
1125 if ($Opt{'verbose'}) {
1126 print("\n");
1127 print_version();
1129 print(<<END);
1131 Converts between various GPS formats.
1133 Usage: $progname [options] [file [files [...]]]
1134 $progname -S [file [files [...]]]
1135 $progname -u [file [files [...]]]
1137 Options:
1139 --chronology
1140 Check for broken chronology, warn about entries with an old
1141 timestamp.
1142 -d, --skip-dups
1143 Skip duplicated coordinates.
1144 -e, --epoch
1145 Use seconds since 1970-01-01 00:00:00 GMT as date format.
1146 --fix
1147 Comment out entries which is obviously wrong. Use together with
1148 --chronology to fix those kind of errors. Does not work with GPX
1149 output yet.
1150 --from-date x
1151 Used by the pgwupd format. Specifies from which date waypoints
1152 should be updated. No checks for valid date format here, let
1153 PostgreSQL take care of that. All variants it understands can be
1154 used here.
1155 -h, --help
1156 Show this help.
1157 --inside
1158 Print only trackpoints inside a rectangle specified by --pos1 and
1159 --pos2.
1160 -n, --undefined x
1161 Use x as undefined value. Default: "$Udef".
1162 -o, --output-format x
1163 Use output format x:
1164 clean
1166 gpsml (Default)
1167 gpstrans
1168 gpx (Not complete)
1169 pgtab
1170 pgwtab
1171 pgwupd
1172 poscount
1173 ps (Unfinished)
1174 svg (Unfinished)
1175 xgraph
1176 ygraph
1177 --outside
1178 Print only trackpoints outside a rectangle specified by --pos1 and
1179 --pos2.
1180 --pos1 x
1181 --pos2 x
1182 Specifies one corner where x is in "lat,lon" format (decimal
1183 degrees, negative for west or south) of area rectangle used by the
1184 --inside and --outside options.
1185 -r, --require x
1186 Specify requirements for trackpoints to be written. x is a string
1187 with the following flags:
1189 Print only waypoints which have an elevation.
1191 Print only waypoints which have a position.
1193 Print only waypoints which have a timestamp.
1194 -R, --round x=y[,x2=y2[...]]
1195 Round trackpoint element x to y decimals. Example:
1196 --round lat=4,lon=5,ele=1
1197 -s, --short-date
1198 Use short date format.
1199 -S, --save-to-file x
1200 Save the unconverted data to a file with a filename starting with
1201 the timestamp of the first trackpoint. The parameter string x is
1202 added at the end of the filename. For the time being this option
1203 will ignore all other options. Note: If several files are specified
1204 on the command line, all data will be saved into only one file. This
1205 behaviour may change in the future.
1206 -t, --create-breaks
1207 Create breaks in track between points with a difference more than
1208 $PAUSE_LIMIT seconds.
1209 -T x, --time-shift x
1210 Move time of trackpoint x seconds forwards or backwards. x can be a
1211 positive or negative integer.
1212 -v, --verbose
1213 Increase level of verbosity. Can be repeated.
1214 --version
1215 Print version information.
1216 -w, --strip-whitespace
1217 Strip all unnecessary whitespace.
1218 -y, --double-y-scale
1219 Double Y scale (latitude) to get it right in gnuplot.
1220 --debug
1221 Print debugging messages.
1224 exit($Retval);
1225 # }}}
1226 } # usage()
1228 sub msg {
1229 # Print a status message to stderr based on verbosity level {{{
1230 my ($verbose_level, $Txt) = @_;
1232 if ($Opt{'verbose'} >= $verbose_level) {
1233 print(STDERR "$progname: $Txt\n");
1235 # }}}
1236 } # msg()
1238 __END__
1240 # Law talk {{{
1241 # Copyleft © Øyvind A. Holm <sunny@sunbase.org>
1243 This program is free software: you can redistribute it and/or modify it
1244 under the terms of the GNU General Public License as published by the
1245 Free Software Foundation, either version 3 of the License, or (at your
1246 option) any later version.
1248 This program is distributed in the hope that it will be useful, but
1249 WITHOUT ANY WARRANTY; without even the implied warranty of
1250 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1251 See the GNU General Public License for more details.
1253 You should have received a copy of the GNU General Public License along
1254 with this program.
1255 If not, see L<http://www.gnu.org/licenses/>.
1256 # }}}
1258 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :