Revert "roundgpx: Behave just like gpst, truncate decimals instead of rounding"
[gpstools.git] / gpst
blob8c1cf39ce4d5a836c4e98763c3ef852528484373
1 #!/usr/bin/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 our %Cmd = (
114 'gpsbabel' => '/usr/local/bin/gpsbabel',
117 my %Poscount = ();
119 if ($Opt{'output-format'} =~ /^(gpx|pgtab)$/) {
120 $Opt{'require'} .= "p";
122 my %Req = (
123 'ele' => ($Opt{'require'} =~ /e/) ? 1 : 0,
124 'position' => ($Opt{'require'} =~ /p/) ? 1 : 0,
125 'time' => ($Opt{'require'} =~ /t/) ? 1 : 0,
127 $Opt{'require'} =~ /[^ept]/
128 && die("$0: Unknown flag in --require (-r) value\n");
130 $Opt{'debug'} && ($Debug = 1);
131 $Opt{'help'} && usage(0);
132 if ($Opt{'version'}) {
133 print_version();
134 exit(0);
137 if ($Opt{'pos1'} =~ /^($DIGIT+),($DIGIT+)$/) {
138 $lat1 = $1;
139 $lon1 = $2;
141 if ($Opt{'pos2'} =~ /^($DIGIT+),($DIGIT+)$/) {
142 $lat2 = $1;
143 $lon2 = $2;
145 if ($lat1 > $lat2) {
146 my $Tmp = $lat1;
147 $lat1 = $lat2;
148 $lat2 = $Tmp;
150 if ($lon1 > $lon2) {
151 my $Tmp = $lon1;
152 $lon1 = $lon2;
153 $lon2 = $Tmp;
156 if ($Opt{'epoch'} && $Opt{'short-date'}) {
157 die("$progname: Cannot mix the --epoch (-e) and --short-date (-s) options\n");
160 if ($Opt{'inside'} && $Opt{'outside'}) {
161 die("$progname: Cannot mix the --inside and --outside options\n");
164 # To avoid printing out extra "/> at the start of svg output:
165 my $svg_start_thing = "";
167 my %Round = ();
169 if (defined($Opt{'round'})) {
170 my $R = $Opt{'round'};
171 $R =~ s/([a-z]+)=(\d+)/($Round{$1}=$2, "")/eg;
174 length($Opt{'undefined'}) && ($Udef = $Opt{'undefined'});
176 $Opt{'save-to-file'} eq "\n" && print_header(*STDOUT);
178 my @first_lines;
179 my $xml_data;
180 my $data_line = "";
182 my $from_stdin = scalar(@ARGV) ? 0 : 1;
184 $from_stdin && push(@ARGV, "-");
186 for my $curr_file (@ARGV) {
187 # Scan through stdin or specified files and send every GPS entry to
188 # print_entry()
189 # {{{
190 print(STDERR "$progname: Opening \"$curr_file\" for read\n") if $Opt{'verbose'};
191 if (open(my $curr_fp, "<$curr_file")) {
192 # {{{
193 while (<$curr_fp>) {
194 $data_line = $_;
195 %Dat = (
196 'year' => '', 'month' => '', 'day' => '',
197 'hour' => '', 'min' => '', 'sec' => '',
198 'epoch' => '',
199 'date-format' => '',
200 'lat' => '', 'lon' => '',
201 'ele' => '',
202 'desc' => '',
203 'error' => "",
204 'what' => 'tp',
205 'curr_file' => $curr_file,
208 $Opt{'epoch'} && ($Dat{'date-format'} = "epoch");
209 $Opt{'short-date'} && ($Dat{'date-format'} = "short");
211 if ($Opt{'save-to-file'} ne "\n") {
212 push(@first_lines, $_);
214 s/^# error // && ($Dat{'error'} = "error");
215 s/^# ?// && ($Dat{'error'} = "desc");
216 $xml_data = "";
217 if (m#^<(e?tp)\b(.*?)>(.*?)</(e?tp)>\s*$#) {
218 # gpsml — The main storage format {{{
219 my ($Elem, $Props, $Data) =
220 ( $1, $2, $3);
221 my $err_str = ($Props =~ /\berr="(.*?)"/) ? $1 : "error";
222 $Elem eq "etp" && ($Dat{'error'} = $err_str);
223 my $Time = "";
224 $Data =~ m#<time>(.*?)</time># && ($Time = $1);
225 $Time =~ s{
226 (\d\d\d\d)-?(\d\d)-?(\d\d)[T ](\d\d):?(\d\d):?([\d\.]+?)Z
228 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
229 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
230 ( $1, $2, $3,
231 $4, $5, $6);
233 }ex;
234 $Data =~ m#<lat>($DIGIT*?)</lat># && ($Dat{'lat'} = $1);
235 $Data =~ m#<lon>($DIGIT*?)</lon># && ($Dat{'lon'} = $1);
236 $Data =~ m#<ele>($DIGIT*?)</ele># && ($Dat{'ele'} = $1);
237 $Data =~ m#<desc>(.*?)</desc># && ($Dat{'desc'} = $1);
238 print_entry(%Dat);
239 # }}}
240 } elsif (m#^<break\b.*?/>#) {
241 $found_move = 1;
242 } elsif (m#^<(title|pause)\b.*?>(.*?)</(title|pause)>#) {
243 $Dat{'what'} = $1;
244 $Dat{$1} = $2;
245 print_entry(%Dat);
246 } elsif (m#^<desc\b.*?>(.*$)#s) {
247 $Dat{'what'} = "desc";
248 my $Txt = $1;
249 until ($Txt =~ m#</desc>#s) {
250 $Txt .= <$curr_fp>;
252 $Txt =~ s#^(.*)(</desc>.*$)#$1#s;
253 $Dat{'desc'} = $Txt;
254 print_entry(%Dat);
255 } elsif (/<gpx\b/) {
256 $xml_data = $_;
257 $xml_data .= join("", <$curr_fp>);
258 if (!length($Opt{'output-format'})) {
259 $Opt{'output-format'} = "gpx";
260 print_header(*STDOUT);
262 read_xmlfile($xml_data);
263 last;
264 } elsif (/^move$/) {
265 $found_move = 1;
266 } elsif (m#^(\d+)\t($DIGIT+)\t($DIGIT+)\t($DIGIT)#) {
267 # CSV format, epoch style {{{
268 my ($ep_time, $lon_val, $lat_val, $Alt) =
269 ( $1, $2, $3, $4);
270 $Dat{'epoch'} = $ep_time;
271 ($Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
272 $Dat{'day'}, $Dat{'month'}, $Dat{'year'},
273 $Dat{'wday'}, $Dat{'yday'}) = gmtime($ep_time);
274 $Dat{'month'}++; # Urgh Ⅰ
275 $Dat{'year'} += 1900; # Urgh Ⅱ
276 print_entry(%Dat);
277 # }}}
278 } elsif (
280 (\d\d\d\d)-?(\d\d)-?(\d\d)[T\ ](\d\d):?(\d\d):?(\d\d)Z?\t
281 ($DIGIT+)\t($DIGIT+)\t($DIGIT)
284 # CSV format, human-readable date format {{{
285 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
286 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'},
287 $Dat{'lon'}, $Dat{'lat'}, $Dat{'ele'}) =
288 ($1, $2, $3,
289 $4, $5, $6,
290 $7, $8, $9);
291 print_entry(%Dat);
292 # }}}
293 } elsif (/^Trackpoint\t/) {
294 # 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 {{{
296 # Trackpoint\t
297 # N60.41630 E5.31675\t
298 # 09.02.2006 20:24:37 (UTC)\t
299 # 13.6 m\t
300 # \t
301 # 93.9 m\t
302 # 00:00:06\t
303 # 56 kph\t
304 # 123° true
305 my $Orig = $_;
306 $Orig =~ s/[\r\n]+$//;
307 my ($Marker_f, $Position_f, $Time_f, $Alt_f, $Depth_f,
308 $Leglength_f, $Legtime_f, $Legspeed_f, $Legcourse_f) =
309 split(/\t/, $Orig .
310 # Nødløsning for å unngå at variabler
311 # blir udefinert.
312 "\t\t\t\t\t\t\t\t\t\t"
314 # D(join("",
315 # "Position_f=\"$Position_f\" \x7B\x7B\x7B\n",
316 # "Time_f=\"$Time_f\"\n",
317 # "Alt_f=\"$Alt_f\"\n",
318 # "Depth_f=\"$Depth_f\"\n",
319 # "Leglength_f=\"$Leglength_f\"\n",
320 # "Legtime_f=\"$Legtime_f\"\n",
321 # "Legspeed_f=\"$Legspeed_f\"\n",
322 # "Legcourse_f=\"$Legcourse_f\" \x7D\x7D\x7D\n",
323 # ));
324 my ($NS, $WE,
325 $Alt_unit,
326 $Leglength,
327 $Legtime_hour, $Legtime_min, $Legtime_sec,
328 $Legspeed, $Legspeed_unit,
329 $Legcourse
330 ) = ("", "", "", "", "", "", "", "", "", "", "", "", "",
331 "", "", "", "", "", "", "", "", "", "");
332 ($Position_f =~ /^(N|S)([\d\.]+) (W|E)([\d\.]+)/) &&
333 ($NS = $1, $Dat{'lat'} = $2, $WE = $3, $Dat{'lon'} = $4);
334 ($Time_f =~ /^(\d+)\.(\d+)\.(\d+) (\d+):(\d+):(\d+) \((.+?)\)/) &&
335 ($Dat{'day'} = $1, $Dat{'month'} = $2, $Dat{'year'} = $3,
336 $Dat{'hour'} = $4, $Dat{'min'} = $5, $Dat{'sec'} = $6);
337 ($Alt_f =~ /^($DIGIT+) (.*?)/) &&
338 ($Dat{'ele'} = $1, $Alt_unit = $2);
339 # D("ele = \"$Dat{'ele'}\"");
340 ($NS eq "S") && ($Dat{'lat'} = 0-$Dat{'lat'});
341 ($WE eq "W") && ($Dat{'lon'} = 0-$Dat{'lon'});
342 # MapSource in win xp writes YYYY, but YY in win98se.
344 defined($Dat{'year'})
345 && $Dat{'year'} =~ /\d/
346 && $Dat{'year'} < 1900
347 ) && ($Dat{'year'} += 2000);
348 print_entry(%Dat);
349 # }}}
350 } elsif (/^Track\t(.*?)\t/) {
351 $Dat{'title'} = txt_to_xml($1);
352 $Dat{'what'} = "title";
353 $found_move = 1;
354 print_entry(%Dat);
355 } elsif (
358 (\d\d)/(\d\d)/(\d\d\d\d)\ (\d\d):(\d\d):(\d\d)\t
359 (.+)\xB0(.+)'(.+)"\t
360 (.+)\xB0(.+)'(.+)"
363 # T 09/01/2002 11:51:26 60°23'36.3" 5°19'35.9" {{{
364 my ($lat_d, $lat_m, $lat_s, $lon_d, $lon_m, $lon_s);
365 ($Dat{'month'}, $Dat{'day'}, $Dat{'year'},
366 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'},
367 $lat_d, $lat_m, $lat_s,
368 $lon_d, $lon_m, $lon_s) =
369 ($1, $2, $3,
370 $4, $5, $6,
371 $7, $8, $9,
372 $10, $11, $12);
373 $Dat{'lat'} = 1.0*($lat_d+($lat_m/60)+($lat_s/3600));
374 $Dat{'lon'} = 1.0*$lon_d+($lon_m/60)+($lon_s/3600);
375 print_entry(%Dat);
376 # }}}
377 } elsif (
379 1\ (\S+)\ (\S+)\ (\S+)\ (\S+)\x20
380 (\d\d)/(\d\d)/(\d\d\d\d)\ (\d\d):(\d\d):(\d\d)
383 # 1 60.3938222 5.3238754 17.3 0 09/01/2002 14:18:23 {{{
384 ($Dat{'lat'}, $Dat{'lon'}, $Dat{'speed'},
385 $Dat{'unkn'},
386 $Dat{'month'}, $Dat{'day'}, $Dat{'year'},
387 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
388 ($1, $2, $3,
390 $5, $6, $7,
391 $8, $9, $10);
392 print_entry(%Dat);
393 # }}}
394 } elsif (/^
395 # @020721221336N6048353E00701826S015-00001E4859N1673U0000 {{{
396 # Regexp {{{
397 (@) # @
398 (\d\d) # Year
399 (\d\d) # Month
400 (\d\d) # Day
401 (\d\d) # Hours
402 (\d\d) # Minutes
403 (\d\d) # Seconds
404 ([NS]) # N|S
405 (\d\d) # Latitude degree
406 (\d\d) # Latitude minute
407 (\d\d\d) # Latitude minute decimals
408 ([EW]) # E|W
409 (\d\d\d) # Longitude degree
410 (\d\d) # Longitude minute
411 (\d\d\d) # Longitude minute degree
412 (....) # Accurancy
413 (......) # Elevation
414 (...............)
415 # }}}
416 /x) {
417 my ($NS, $EW, $lat_deg, $lat_degmin, $lat_mindec, $lon_deg,
418 $lon_degmin, $lon_mindec);
419 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'}, $Dat{'hour'},
420 $Dat{'min'}, $Dat{'sec'}, $NS, $lat_deg,
421 $lat_degmin, $lat_mindec, $EW,
422 $lon_deg, $lon_degmin, $lon_mindec,
423 $Dat{'accur'}, $Dat{'ele'}, $Dat{'unknown'}) =
424 ($2+2000, $3, $4, $5,
425 $6, $7, $8, $9,
426 $10, $11, $12,
427 $13, $14, $15,
428 $16, $17, $18);
429 my $ep_time = timegm_nocheck(
430 $Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
431 $Dat{'day'}, $Dat{'month'}-1, $Dat{'year'}
433 $last_time = $ep_time;
434 my $tmp_lon = $lon_deg + $lon_degmin/60 + $lon_mindec/60000;
435 my $tmp_lat = $lat_deg + $lat_degmin/60 + $lat_mindec/60000;
436 ($NS eq "S") && ($tmp_lat = 0-$tmp_lat);
437 ($EW eq "W") && ($tmp_lon = 0-$tmp_lon);
438 $Dat{'lat'} = $tmp_lat;
439 $Dat{'lon'} = $tmp_lon;
440 print_entry(%Dat);
441 # }}}
442 } elsif (/^(@)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(_{42})/) {
443 # @020721221336__________________________________________ {{{
444 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
445 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}, $Dat{'rest'}) =
446 ($2+2000, $3, $4,
447 $5, $6, $7, $8);
448 $Dat{'error'} = "nosignal";
449 print_entry(%Dat);
450 # }}}
451 } elsif (/^xmaplog /) {
452 # NOP
453 } elsif (/^$/) {
454 ($Opt{'output-format'} eq "csv")
455 && ($Opt{'save-to-file'} eq "\n")
456 && print("\n");
457 } elsif (/^Pause: /) {
458 # NOP, is here to cope with old files I’ve lying around.
459 } elsif ($Dat{'error'} eq "desc") {
460 my $Comment = $_;
461 if (defined($Comment)) {
462 $Comment =~ s/^\s*(.*?)\s*$/$1/;
463 if ($Opt{'output-format'} eq "gpsml") {
464 $Dat{'desc'} = txt_to_xml($Comment);
465 $Dat{'what'} = "desc";
466 print_entry(%Dat);
469 } else {
470 $Opt{'verbose'} && warn("Line $.: Unknown: \"$_\"\n");
473 # }}}
474 } else {
475 warn("$progname: $curr_file: Cannot open file for read: $!\n");
477 # }}}
480 print_footer(*STDOUT);
482 exit(0);
484 sub read_xmlfile {
485 # {{{
486 my $Txt = join("", @_);
487 $Txt =~ s/<!--(.*?)-->//gs;
488 $Txt =~ s#(<gpx\b.*?>.*?</gpx>)#print_gpx($1)#gse;
489 # }}}
490 } # read_xmlfile()
492 sub print_gpx {
493 # {{{
494 my $Orig = shift;
495 my $Str = $Orig;
496 # D("print_xml_gps(\"$Orig\")\n");
497 $Str =~ s/<!--(.*?)-->//gs;
498 my $fromdate_str = "";
499 if ($Opt{'from-date'}) {
500 $fromdate_str = "date >= '$Opt{'from-date'}' AND ";
502 if ($Opt{'output-format'} =~ /^(pgwtab|pgwupd)$/) {
503 # {{{
504 $Str =~
506 <wpt\b(.*?)>(.*?)</wpt>
509 my $attr_wpt = $1;
510 my $el_wpt = $2;
511 my ($Lat, $Lon, $Name, $Ele, $Type, $Time, $Cmt, $Desc, $Src, $Sym) =
512 ('\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N', '\N');
514 $attr_wpt =~ /.*lat="($DIGIT+?)"/s &&
515 ($Lat = postgresql_copy_safe($1));
516 $attr_wpt =~ /.*lon="($DIGIT+?)"/s &&
517 ($Lon = postgresql_copy_safe($1));
518 $el_wpt =~ /.*<name\b(.*?)>(.*?)<\/name>/s &&
519 ($Name = postgresql_copy_safe(xml_to_txt($2)));
520 $el_wpt =~ /.*<ele\b(.*?)>(.*?)<\/ele>/s &&
521 ($Ele = postgresql_copy_safe(xml_to_txt($2)));
522 $el_wpt =~ /.*<type\b(.*?)>(.*?)<\/type>/s &&
523 ($Type = postgresql_copy_safe(xml_to_txt($2)));
524 $el_wpt =~ /.*<time\b(.*?)>(.*?)<\/time>/s &&
525 ($Time = postgresql_copy_safe(xml_to_txt($2)));
526 $el_wpt =~ /.*<cmt\b(.*?)>(.*?)<\/cmt>/s &&
527 ($Cmt = postgresql_copy_safe(xml_to_txt($2)));
528 $el_wpt =~ /.*<desc\b(.*?)>(.*?)<\/desc>/s &&
529 ($Desc = postgresql_copy_safe(xml_to_txt($2)));
530 $el_wpt =~ /.*<src\b(.*?)>(.*?)<\/src>/s &&
531 ($Src = postgresql_copy_safe(xml_to_txt($2)));
532 $el_wpt =~ /.*<sym\b(.*?)>(.*?)<\/sym>/s &&
533 ($Sym = postgresql_copy_safe(xml_to_txt($2)));
535 if (length($Opt{'round'})) {
536 $Round{'lat'} && ($Lat = int($Lat * (10**$Round{'lat'})) / 10**$Round{'lat'});
537 $Round{'lon'} && ($Lon = int($Lon * (10**$Round{'lon'})) / 10**$Round{'lon'});
538 $Round{'ele'} && $Ele ne '\N' && ($Ele = int($Ele * (10**$Round{'ele'})) / 10**$Round{'ele'});
541 if ($Opt{'output-format'} eq "pgwtab") {
542 print(
543 join("\t",
544 "($Lat,$Lon)",
545 $Name,
546 $Ele,
547 $Type,
548 $Time,
549 $Cmt,
550 $Desc,
551 $Src,
552 $Sym
553 ) . "\n"
555 } elsif ($Opt{'output-format'} eq "pgwupd") {
556 $Name =~ s/'/''/gs;
557 print(join("\n",
558 "BEGIN;",
559 "$Spc${Spc}UPDATE logg SET name = clname(coor) " .
560 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
561 "$Spc${Spc}UPDATE logg SET dist = cldist(coor) " .
562 "WHERE $fromdate_str(point($Lat,$Lon) <-> coor) < 0.05;",
563 "COMMIT;"
564 ) . "\n");
567 }gsex;
568 # }}}
569 } else {
570 # {{{
571 $Str =~
573 <trk\b(.*?)>(.*?)</trk>
576 my $el_trk = $2;
577 $el_trk =~
579 <name\b(.*?)>(.*?)</name>
581 my %tmp_dat = ();
582 $tmp_dat{'title'} = $2;
583 $tmp_dat{'what'} = "title";
584 $tmp_dat{'error'} = "";
585 print_entry(%tmp_dat);
587 }sex;
588 $el_trk =~
590 <trkseg\b(.*?)>(.*?)</trkseg>
593 my $el_trkseg = $2;
594 $el_trkseg =~
596 <trkpt\b(.*?)>(.*?)</trkpt>
599 my ($attr_trkpt, $el_trkpt) =
600 ( $1, $2);
601 %Dat = (
602 'year' => '', 'month' => '', 'day' => '',
603 'hour' => '', 'min' => '', 'sec' => '',
604 'epoch' => '',
605 'date-format' => '',
606 'lat' => '', 'lon' => '',
607 'ele' => '',
608 'desc' => '',
609 'error' => "",
610 'what' => 'tp',
612 ($attr_trkpt =~ /\blon="(.*?)"/) && ($Dat{'lon'} = $1);
613 ($attr_trkpt =~ /\blat="(.*?)"/) && ($Dat{'lat'} = $1);
614 ($el_trkpt =~ m#<ele\b.*?>(.*?)</ele>#) && ($Dat{'ele'} = $1);
615 if (
616 $el_trkpt =~
618 <time>(\d\d\d\d)-?(\d\d)-?(\d\d)T
619 (\d\d):?(\d\d):?([\d\.]+)Z</time>
622 ($Dat{'year'}, $Dat{'month'}, $Dat{'day'},
623 $Dat{'hour'}, $Dat{'min'}, $Dat{'sec'}) =
624 ($1, $2, $3, $4, $5, $6);
626 print_entry(%Dat);
628 }gsex;
629 $found_move = 1;
630 }gsex;
631 $found_move = 1;
632 }gsex;
633 # }}}
635 # }}}
636 } # print_gpx()
638 sub print_header {
639 # {{{
640 my $out_fp = shift;
641 if ($Opt{'output-format'} eq "gpsml") {
642 print($out_fp join("",
643 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
644 "<gpsml>\n",
645 "<track>\n",
647 } elsif ($Opt{'output-format'} eq "gpstrans") {
648 print($out_fp "Format: DMS UTC Offset: 0.00 hrs " .
649 "Datum[100]: WGS 84\n");
650 } elsif ($Opt{'output-format'} eq "gpx") {
651 print($out_fp join("",
652 qq{<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n},
653 qq{<gpx\n},
654 qq{$Spc${Spc}version="1.1"\n},
655 qq{$Spc${Spc}creator="gpst - http://sunny256.github.com/gpstools/"\n},
656 qq{$Spc${Spc}xmlns="http://www.topografix.com/GPX/1/1"\n},
657 qq{$Spc${Spc}xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n},
658 qq{$Spc${Spc}xsi:schemaLocation="http://www.topografix.com/GPX/1/1 },
659 qq{http://www.topografix.com/GPX/1/1/gpx.xsd"\n},
660 qq{>\n},
661 qq{$Spc$Spc<trk>\n},
662 qq{$Spc$Spc$Spc$Spc<trkseg>\n},
664 } elsif ($Opt{'output-format'} eq "ps") {
665 print($out_fp ps_header(532, 6034, 533, 6040));
666 print($out_fp "*u\n");
667 } elsif ($Opt{'output-format'} eq "svg") {
668 print($out_fp join("",
669 "<?xml version=\"1.0\" standalone=\"no\"?>\n",
670 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
671 "$Spc$Spc\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
672 "<svg height=\"1000\" width=\"1000\" viewBox=\"23 70 2 2\"\n",
673 "$Spc${Spc}xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">\n",
674 "$Spc$Spc<title></title>\n",
675 "$Spc$Spc<desc></desc>\n",
678 # }}}
679 } # print_header()
681 sub print_footer {
682 # Print footer {{{
683 my $out_fp = shift;
684 if ($Opt{'output-format'} eq "gpsml") {
685 print($out_fp join("",
686 "</track>\n",
687 "</gpsml>\n",
689 } elsif ($Opt{'output-format'} eq "gpx") {
690 print($out_fp join("",
691 "$Spc$Spc$Spc$Spc</trkseg>\n",
692 "$Spc$Spc</trk>\n",
693 "</gpx>\n",
695 } elsif ($Opt{'output-format'} eq "poscount") {
696 while (my ($l_name, $l_val) = each %Poscount) {
697 $l_name =~ /^(.+?),(.+?)$/
698 && print($out_fp "$1\t$2\t$l_val\n");
700 } elsif ($Opt{'output-format'} eq "ps") {
701 print($out_fp join("",
702 "*U\n",
703 "%%Trailer\n",
704 "%%EOF\n",
706 } elsif ($Opt{'output-format'} eq "svg") {
707 print($out_fp "\"/>\n</svg>\n");
709 # }}}
710 } # print_footer()
712 sub print_entry {
713 # Print a GPS entry with time, latitude, longitude and elevation in
714 # various formats
715 # {{{
716 my %Dat = @_;
717 defined($Dat{'desc'}) || ($Dat{'desc'} = "");
718 defined($Dat{'ele'}) || ($Dat{'ele'} = "");
719 defined($Dat{'lat'}) || ($Dat{'lat'} = "");
720 defined($Dat{'lon'}) || ($Dat{'lon'} = "");
721 defined($Dat{'year'}) || ($Dat{'year'} = "");
722 my $print_time = length($Dat{'year'}) ? 1 : 0;
723 my $print_pos;
724 if (!$Req{'position'} && $Opt{'output-format'} eq "gpsml") {
725 $print_pos = (length($Dat{'lat'}) || length($Dat{'lon'})) ? 1 : 0;
726 } else {
727 $print_pos = (length($Dat{'lat'}) && length($Dat{'lon'})) ? 1 : 0;
729 if (!$print_pos) {
730 $Dat{'lat'} = $Dat{'lon'} = "";
732 my $print_ele = length($Dat{'ele'}) ? 1 : 0;
733 my $print_desc = length($Dat{'desc'}) ? 1 : 0;
734 my $Line = "";
735 # D("print_entry(\"" . join("\", \"", @_) . "\");");
736 my $ep_time;
738 if (length($Opt{'round'})) {
739 for my $Tmp (qw{ lat lon ele }) {
740 if (defined($Round{$Tmp}) && length($Dat{$Tmp})) {
741 # D("Tmp = '$Tmp'");
742 $Dat{$Tmp} = num_expand(int($Dat{$Tmp} * (10**$Round{$Tmp})) / 10**$Round{$Tmp});
747 if ($Opt{'output-format'} eq "poscount") {
748 # FIXME: Sort output in some way
749 if (!length($Dat{'error'})) {
750 $Dat{'lat'} = num_expand($Dat{'lat'});
751 $Dat{'lon'} = num_expand($Dat{'lon'});
752 my $Name = "$Dat{'lon'},$Dat{'lat'}";
753 defined($Poscount{$Name}) || ($Poscount{$Name} = 0);
754 $Poscount{$Name}++;
756 return;
759 if ($print_time) {
760 $ep_time = timegm_nocheck(
761 $Dat{'sec'}, $Dat{'min'}, $Dat{'hour'},
762 $Dat{'day'}, $Dat{'month'} - 1, $Dat{'year'}
764 if ($Opt{'time-shift'}) {
765 # D("ep_time før: '$ep_time'");
766 $ep_time += $Opt{'time-shift'};
767 # D("ep_time etter: '$ep_time'");
768 ($Dat{'sec'}, $Dat{'min'},$Dat{'hour'}, $Dat{'day'},
769 $Dat{'month'}, $Dat{'year'}) = gmtime($ep_time);
770 $Dat{'year'} += 1900;
771 $Dat{'month'}++;
773 $Dat{'epoch'} = $ep_time;
774 $Dat{'year'} = sprintf("%04u", $Dat{'year'});
775 $Dat{'month'} = sprintf("%02u", $Dat{'month'});
776 $Dat{'day'} = sprintf("%02u", $Dat{'day'});
777 $Dat{'hour'} = sprintf("%02u", $Dat{'hour'});
778 $Dat{'min'} = sprintf("%02u", $Dat{'min'});
779 $Dat{'sec'} = sprintf("%02u", $Dat{'sec'});
780 if ($Opt{'chronology'}) {
781 if ($last_time > $ep_time && !length($Dat{'error'})) {
782 warn(sprintf(
783 "%s: $Dat{'curr_file'}: \"%sZ\": Next date is %s in the past (%sZ)\n",
784 $progname, sec_to_string($last_time, "T"),
785 sec_to_readable($last_time-$ep_time),
786 sec_to_string($ep_time, "T")
788 # FIXME: Make --fix work with gpx.
789 if ($Opt{'fix'} && ($Opt{'output-format'} !~ /^gpx$/)) {
790 $Dat{'error'} = "chrono";
792 } elsif ($last_time == $ep_time && !length($Dat{'error'})) {
793 warn(sprintf(
794 "%s: $Dat{'curr_file'}: \"%sZ\": Duplicated time\n",
795 $progname, sec_to_string($last_time, "T")
797 # FIXME: Make --fix work with gpx.
798 if ($Opt{'fix'} && ($Opt{'output-format'} !~ /^gpx$/)) {
799 $Dat{'error'} = "duptime";
803 } else {
804 $ep_time = 0;
805 $Dat{'year'} = 0;
806 $Dat{'month'} = 0;
807 $Dat{'day'} = 0;
808 $Dat{'hour'} = 0;
809 $Dat{'min'} = 0;
810 $Dat{'sec'} = 0;
813 if ($Opt{'save-to-file'} ne "\n") {
814 # {{{
815 $print_time || return;
816 my $base_name = "$Dat{'year'}$Dat{'month'}$Dat{'day'}T" .
817 "$Dat{'hour'}$Dat{'min'}$Dat{'sec'}Z" .
818 "$Opt{'save-to-file'}";
819 my $file_name = $base_name;
820 if (-e $file_name) {
821 for (my $a = 1; (-e $file_name) && ($a < 1000); $a++) {
822 $file_name = "$base_name.dup_$a";
824 if (-e $file_name) {
825 die("$progname: $base_name: File already exists, and ran " .
826 "out of attempts to create unique file name\n");
828 if ($Opt{'verbose'}) {
829 warn("$progname: $base_name: File already exists, using " .
830 "unique name \"$file_name\" instead\n");
833 if (open(my $to_fp, ">", $file_name)) {
834 print_header(*$to_fp);
835 print($to_fp (
836 $from_stdin
837 ? @first_lines
838 : ()),
839 (length($xml_data)
840 ? $xml_data
841 : <>)
842 ) || die("$progname: $file_name: Cannot write to file: $!\n");
843 print_footer(*$to_fp);
844 close($to_fp);
845 if ($Opt{'output-format'} eq "gpsml") {
846 printf("<include>%s</include>\n",
847 txt_to_xml($file_name));
848 } elsif ($Opt{'output-format'} eq "gpx") {
849 printf("<!-- Saved unconverted data to \"%s\" -->\n",
850 txt_to_xml($file_name));
851 } else {
852 print("$progname: Saved unconverted data to \"$file_name\"\n");
854 exit 0;
855 } else {
856 die("$progname: $file_name: Cannot create file: $!\n");
858 # }}}
861 my $pause_len = 0;
862 my $do_print = 1;
864 if ($Dat{'what'} eq "tp") {
865 # {{{
866 if ($Opt{'require'}) {
867 $Req{'time'} && !$print_time && return;
868 $Req{'position'} && !$print_pos && return;
869 $Req{'ele'} && !$print_ele && return;
872 if ($Opt{'inside'} || $Opt{'outside'}) {
873 if (
874 ($Dat{'lat'} < $lat1) ||
875 ($Dat{'lat'} > $lat2) ||
876 ($Dat{'lon'} < $lon1) ||
877 ($Dat{'lon'} > $lon2)
879 $Opt{'inside'} && return;
880 } else {
881 $Opt{'outside'} && return;
885 if ($Opt{'output-format'} eq "ps") {
886 $Dat{'lon'} *= 100;
887 $Dat{'lat'} *= 100;
890 if (
891 $Opt{'skip-dups'}
892 && ($Dat{'lon'} eq $last_lon)
893 && ($Dat{'lat'} eq $last_lat)
895 if ($Opt{'output-format'} eq 'gpsml') {
896 $Dat{'error'} = "dup";
897 } else {
898 $do_print = 0;
900 } else {
901 $do_print = 1;
904 if (
905 $Opt{'create-breaks'}
906 && $ep_time-$last_time > $PAUSE_LIMIT
907 && $last_time
909 $pause_len = $ep_time-$last_time;
910 # D("pause_len set to '$pause_len'");
913 $Line .= pause_entry($pause_len, $ep_time, $last_time);
914 # }}}
917 if ($do_print) {
918 # Valid data was found, send to stdout {{{
919 unless ($first_time) {
920 $first_time = $ep_time;
922 $Line .= gen_entry($print_pos, $pause_len, $print_time, $ep_time, $print_ele, %Dat);
923 # }}}
926 if (!$last_time && $Opt{'output-format'} eq "ps") {
927 $Line .= "$Dat{'lon'} $Dat{'lat'} m\n";
930 if ($do_print) {
931 if ($found_move) {
932 if ($Opt{'output-format'} eq "gpsml") {
933 $Line = "<break/>\n$Line";
935 (!$pause_len && ($Opt{'output-format'} eq "xgraph"))
936 && ($Line .= "move $Line");
937 ($Opt{'output-format'} eq "clean") && ($Line .= "\n");
938 if ($Opt{'output-format'} eq "gpx") {
939 $Line .= "$Spc$Spc$Spc$Spc</trkseg>\n" .
940 "$Spc$Spc$Spc$Spc<trkseg>\n";
942 $found_move = 0;
944 print($Line);
946 $print_time && ($last_time = $ep_time);
947 if ($print_pos) {
948 $last_lon = $Dat{'lon'};
949 $last_lat = $Dat{'lat'};
951 $last_line = $data_line;
952 $svg_start_thing = "\"/>\n";
953 # }}}
954 } # print_entry()
956 sub gen_entry {
957 # Generate trackpoint entry, calls trackpoint() {{{
958 my ($print_pos, $pause_len, $print_time, $ep_time, $print_ele, %Dat) = @_;
959 my $Line = "";
960 if ($Opt{'double-y-scale'} && length($Dat{'lat'})) {
961 $Dat{'lat'} *= 2;
963 if ($Opt{'output-format'} eq "gpsml") {
964 if ($Dat{'what'} eq "tp") {
965 $Dat{'format'} = "gpsml";
966 $Line .= trackpoint(%Dat);
967 } elsif ($Dat{'what'} =~ /^(pause|desc|title)$/) {
968 $Line .= sprintf("<%s>%s</%s>\n",
970 $Dat{$1},
971 $1);
973 } elsif ($Opt{'output-format'} eq "pgtab") {
974 if ($Dat{'what'} eq "tp" && !length($Dat{'error'})) {
975 $Dat{'format'} = "pgtab";
976 $Line .= trackpoint(%Dat);
978 } elsif ($Opt{'output-format'} eq "xgraph") {
979 if ($print_pos && !length($Dat{'error'})) {
980 $Dat{'format'} = "xgraph";
981 $Line .= trackpoint(%Dat);
983 } elsif($Opt{'output-format'} eq "gpstrans") {
984 if ($print_pos && !length($Dat{'error'})) {
985 $Dat{'format'} = "gpstrans";
986 $Line .= trackpoint(%Dat);
988 } elsif($Opt{'output-format'} eq "gpx") {
989 if ($Dat{'what'} eq "tp") {
990 $Dat{'format'} = "gpx";
991 $Line .= trackpoint(%Dat);
993 } elsif ($Opt{'output-format'} eq "clean") {
994 if ($Dat{'what'} eq "tp" && !length($Dat{'error'})) {
995 $Dat{'format'} = "clean";
996 $Line .= trackpoint(%Dat);
998 } elsif ($Opt{'output-format'} eq "ps") {
999 $Line .= (
1000 $pause_len
1001 ? "f\n$Dat{'lon'} $Dat{'lat'} m\n"
1002 : "$Dat{'lon'} $Dat{'lat'} l\n"
1004 } elsif ($Opt{'output-format'} eq "svg") {
1005 $Line .= (
1006 ($last_lon == 1000) || $pause_len
1007 ? join("",
1008 "$svg_start_thing<path\n",
1009 " stroke=\"blue\"\n",
1010 " stroke-width=\"0.001\"\n",
1011 " fill=\"none\"\n",
1012 " d=\"\n",
1013 "M $Dat{'lon'} $Dat{'lat'}\n")
1014 : "L $Dat{'lon'} $Dat{'lat'}\n"
1016 } elsif ($Opt{'output-format'} eq "ygraph") {
1017 if (!length($Dat{'error'})) {
1018 $Dat{'lat'} = num_expand($Dat{'lat'});
1019 $Dat{'lon'} = num_expand($Dat{'lon'});
1020 $Dat{'ele'} = num_expand($Dat{'ele'});
1021 my $Time = $print_time ? ($ep_time - $first_time) * 1 : 0;
1022 $Line .= "\"Time = $Time.0\n$Dat{'lon'} $Dat{'lat'}\n\n";
1024 } elsif ($Opt{'output-format'} eq "csv") {
1025 # {{{
1026 if (!length($Dat{'error'})) {
1027 $Dat{'format'} = "csv";
1028 $Dat{'lat'} = num_expand($Dat{'lat'});
1029 $Dat{'lon'} = num_expand($Dat{'lon'});
1030 $Dat{'ele'} = num_expand($Dat{'ele'});
1031 $Line .= join("\t",
1032 $print_time
1033 ? $Opt{'epoch'}
1034 ? $ep_time
1035 : $Opt{'short-date'}
1036 ? "$Dat{'year'}$Dat{'month'}$Dat{'day'}T" .
1037 "$Dat{'hour'}$Dat{'min'}$Dat{'sec'}Z"
1038 : "$Dat{'year'}-$Dat{'month'}-$Dat{'day'}T" .
1039 "$Dat{'hour'}:$Dat{'min'}:$Dat{'sec'}Z"
1040 : "",
1041 $Dat{'lon'},
1042 $Dat{'lat'},
1043 $print_ele ? $Dat{'ele'} : "", # Elevation
1044 "\n"
1047 # }}}
1048 } elsif ($Opt{'output-format'} eq "pgwtab") {
1049 # FIXME: NOP at the moment.
1050 } else {
1051 die("$progname: \"$Opt{'output-format'}\": " .
1052 "Unknown output format\n");
1054 return($Line);
1055 # }}}
1056 } # gen_entry()
1058 sub pause_entry {
1059 # {{{
1060 my ($pause_len, $ep_time, $last_time) = @_;
1061 my $Line = "";
1062 if ($pause_len) {
1063 if ($Opt{'output-format'} eq "gpsml") {
1064 $Line .= sprintf("<pause>%s</pause>\n",
1065 sec_to_readable($ep_time-$last_time));
1066 } elsif ($Opt{'output-format'} eq "clean") {
1067 $pause_len && ($Line .= "\n");
1068 } elsif ($Opt{'output-format'} eq "csv") {
1069 $Line .= sprintf("# Pause: %s\n# move\n",
1070 sec_to_readable($ep_time-$last_time));
1071 } elsif ($Opt{'output-format'} eq "xgraph") {
1072 $pause_len && ($Line .= "move ");
1075 return($Line);
1076 # }}}
1077 } # pause_entry()
1079 sub ps_header {
1080 # Send a Postscript header to stdout {{{
1081 my ($bl_lon, $bl_lat, $br_lon, $br_lat) = @_;
1082 my $Date = sec_to_string(time);
1083 return(join("",
1084 "%!PS-Adobe-3.0 EPSF-3.0\n",
1085 "%%Creator: gpst\n",
1086 "%%Title:\n",
1087 "%%CreationDate: $Date\n",
1088 "%%BoundingBox: $bl_lon $bl_lat $br_lon $br_lat\n",
1089 "%%DocumentData: Clean7Bit\n",
1090 "%%EndComments\n",
1091 "%%BeginProlog\n",
1092 "/bd { bind def } bind def\n",
1093 "/incompound false def\n",
1094 "/m { moveto } bd\n",
1095 "/l { lineto } bd\n",
1096 "/c { curveto } bd\n",
1097 "/F { incompound not {fill} if } bd\n",
1098 "/f { closepath F } bd\n",
1099 "/S { stroke } bd\n",
1100 "/*u { /incompound true def } bd\n",
1101 "/*U { /incompound false def f} bd\n",
1102 "/k { setcmykcolor } bd\n",
1103 "/K { k } bd\n",
1104 "%%EndProlog\n",
1105 "%%BeginSetup\n",
1106 "%%EndSetup\n",
1108 # }}}
1109 } # ps_header()
1111 sub print_version {
1112 # Print program version {{{
1113 print("$progname v$VERSION\n");
1114 # }}}
1115 } # print_version()
1117 sub usage {
1118 # Send the help message to stdout {{{
1119 my $Retval = shift;
1121 if ($Opt{'verbose'}) {
1122 print("\n");
1123 print_version();
1125 print(<<END);
1127 Converts between various GPS formats.
1129 Usage: $progname [options] [file [files [...]]]
1130 $progname -S [file [files [...]]]
1131 $progname -u [file [files [...]]]
1133 Options:
1135 --chronology
1136 Check for broken chronology, warn about entries with an old
1137 timestamp.
1138 -d, --skip-dups
1139 Skip duplicated coordinates.
1140 -e, --epoch
1141 Use seconds since 1970-01-01 00:00:00 GMT as date format.
1142 --fix
1143 Comment out entries which is obviously wrong. Use together with
1144 --chronology to fix those kind of errors. Does not work with GPX
1145 output yet.
1146 --from-date x
1147 Used by the pgwupd format. Specifies from which date waypoints
1148 should be updated. No checks for valid date format here, let
1149 PostgreSQL take care of that. All variants it understands can be
1150 used here.
1151 -h, --help
1152 Show this help.
1153 --inside
1154 Print only trackpoints inside a rectangle specified by --pos1 and
1155 --pos2.
1156 -n, --undefined x
1157 Use x as undefined value. Default: "$Udef".
1158 -o, --output-format x
1159 Use output format x:
1160 clean
1162 gpsml (Default)
1163 gpstrans
1164 gpx (Not complete)
1165 pgtab
1166 pgwtab
1167 pgwupd
1168 poscount
1169 ps (Unfinished)
1170 svg (Unfinished)
1171 xgraph
1172 ygraph
1173 --outside
1174 Print only trackpoints outside a rectangle specified by --pos1 and
1175 --pos2.
1176 --pos1 x
1177 --pos2 x
1178 Specifies one corner where x is in "lat,lon" format (decimal
1179 degrees, negative for west or south) of area rectangle used by the
1180 --inside and --outside options.
1181 -r, --require x
1182 Specify requirements for trackpoints to be written. x is a string
1183 with the following flags:
1185 Print only waypoints which have an elevation.
1187 Print only waypoints which have a position.
1189 Print only waypoints which have a timestamp.
1190 -R, --round x=y[,x2=y2[...]]
1191 Round trackpoint element x to y decimals. Example:
1192 --round lat=4,lon=5,ele=1
1193 Note that this "rounding" merely truncates the number to not mess up
1194 any substring searches through the GPS logs. I.e.:
1195 -R lat=3,lon=3
1196 5.9999,6.4001 = 5.999,6.4
1197 -R lat=0,lon=2
1198 5.7,4.999 = 5,4.99
1199 -s, --short-date
1200 Use short date format.
1201 -S, --save-to-file x
1202 Save the unconverted data to a file with a filename starting with
1203 the timestamp of the first trackpoint. The parameter string x is
1204 added at the end of the filename. For the time being this option
1205 will ignore all other options. Note: If several files are specified
1206 on the command line, all data will be saved into only one file. This
1207 behaviour may change in the future.
1208 -t, --create-breaks
1209 Create breaks in track between points with a difference more than
1210 $PAUSE_LIMIT seconds.
1211 -T x, --time-shift x
1212 Move time of trackpoint x seconds forwards or backwards. x can be a
1213 positive or negative integer.
1214 -v, --verbose
1215 Increase level of verbosity. Can be repeated.
1216 --version
1217 Print version information.
1218 -w, --strip-whitespace
1219 Strip all unnecessary whitespace.
1220 -y, --double-y-scale
1221 Double Y scale (latitude) to get it right in gnuplot.
1222 --debug
1223 Print debugging messages.
1226 exit($Retval);
1227 # }}}
1228 } # usage()
1230 sub msg {
1231 # Print a status message to stderr based on verbosity level {{{
1232 my ($verbose_level, $Txt) = @_;
1234 if ($Opt{'verbose'} >= $verbose_level) {
1235 print(STDERR "$progname: $Txt\n");
1237 # }}}
1238 } # msg()
1240 __END__
1242 # Law talk {{{
1243 # Copyleft © Øyvind A. Holm <sunny@sunbase.org>
1245 This program is free software: you can redistribute it and/or modify it
1246 under the terms of the GNU General Public License as published by the
1247 Free Software Foundation, either version 3 of the License, or (at your
1248 option) any later version.
1250 This program is distributed in the hope that it will be useful, but
1251 WITHOUT ANY WARRANTY; without even the implied warranty of
1252 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
1253 See the GNU General Public License for more details.
1255 You should have received a copy of the GNU General Public License along
1256 with this program.
1257 If not, see L<http://www.gnu.org/licenses/>.
1258 # }}}
1260 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :